#!/usr/bin/env node import minimist from "minimist"; import { fileURLToPath } from "node:url"; import { getAuthStatus, runAuthorizationFlow } from "./auth.js"; import { runImportCommand } from "./importers/index.js"; import { runAddToPlaylistCommand, runCreatePlaylistCommand, runRemoveFromPlaylistCommand, runSearchAndAddCommand } from "./playlists.js"; import { runListPlaylistsCommand, runSearchCommand } from "./search.js"; export interface CliDeps { stdout: Pick; stderr: Pick; } export interface ParsedCli { command: string; positional: string[]; json: boolean; public?: boolean; limit?: string; offset?: string; description?: string; playlist?: string; playlistId?: string; } export type CommandHandler = (args: ParsedCli, deps: CliDeps) => Promise | number; export type CommandHandlers = Record; function notImplemented(command: string): CommandHandler { return (args, deps) => { const payload = { ok: false, error: `Command not implemented yet: ${command}` }; if (args.json) { deps.stdout.write(`${JSON.stringify(payload, null, 2)}\n`); } else { deps.stderr.write(`${payload.error}\n`); } return 2; }; } export function createDefaultHandlers(): CommandHandlers { return { auth: async (_args, deps) => { await runAuthorizationFlow(); deps.stdout.write("Spotify authorization complete.\n"); return 0; }, status: async (args, deps) => { const status = await getAuthStatus(); if (args.json) { deps.stdout.write(`${JSON.stringify(status, null, 2)}\n`); } else { deps.stdout.write(`Spotify config: ${status.configFound ? "found" : "missing"}\n`); deps.stdout.write(`Spotify token: ${status.tokenFound ? "found" : "missing"}\n`); if (status.tokenFound) { deps.stdout.write(`Spotify token expired: ${status.tokenExpired ? "yes" : "no"}\n`); } } return status.configFound && status.tokenFound ? 0 : 1; }, search: runSearchCommand, "list-playlists": runListPlaylistsCommand, "create-playlist": runCreatePlaylistCommand, "add-to-playlist": runAddToPlaylistCommand, "remove-from-playlist": runRemoveFromPlaylistCommand, "search-and-add": runSearchAndAddCommand, import: runImportCommand }; } export function usage(): string { return `spotify Commands: auth status [--json] search [--limit N] [--json] list-playlists [--limit N] [--offset N] [--json] create-playlist [--description TEXT] [--public] [--json] add-to-playlist [more uris...] [--json] remove-from-playlist [more uris...] [--json] search-and-add [more queries...] [--json] import [--playlist NAME | --playlist-id ID] [--public] [--json] `; } export async function runCli( argv: string[], deps: CliDeps = { stdout: process.stdout, stderr: process.stderr }, handlers: CommandHandlers = createDefaultHandlers() ): Promise { const args = minimist(argv, { boolean: ["help", "json", "public"], string: ["limit", "offset", "description", "playlist", "playlist-id"], alias: { h: "help" } }); const [command] = args._; if (!command || args.help) { deps.stdout.write(usage()); return 0; } const handler = handlers[command]; if (!handler) { deps.stderr.write(`Unknown command: ${command}\n\n${usage()}`); return 1; } return handler( { command, positional: args._.slice(1).map(String), json: Boolean(args.json), public: Boolean(args.public), limit: args.limit, offset: args.offset, description: args.description, playlist: args.playlist, playlistId: args["playlist-id"] }, deps ); } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { runCli(process.argv.slice(2)).then((code) => { process.exitCode = code; }).catch((error: unknown) => { const message = error instanceof Error ? error.message : String(error); process.stderr.write(`${message}\n`); process.exitCode = 1; }); }