import minimist from "minimist"; import { detectClients as realDetectClients } from "./detect.js"; import { executePrompt as realExecutePrompt } from "./execute.js"; import { resolveClient as realResolveClient } from "./dispatch.js"; import { resolveConfig as realResolveConfig } from "./config.js"; import { CLIENT_NAMES } from "./constants.js"; import { type ClientName, type ClientInfo, type ExecResult, ClientNotFoundError, } from "./types.js"; export interface CliDeps { detectClients?: () => ClientInfo[]; executePrompt?: ( client: ClientName, prompt: string ) => Promise; resolveClient?: ( prompt: string, config?: { client?: ClientName; defaultClient?: ClientName } ) => ClientName | null; resolveConfig?: () => { paths: Partial>; defaultClient?: ClientName; timeout?: number }; stdoutWrite?: (chunk: string) => void; stderrWrite?: (chunk: string) => void; } function printHelp(): void { console.log(`AI CLI Dispatch Usage: ai-cli-dispatch list [--json|--text] ai-cli-dispatch run --client --prompt [--json|--text] ai-cli-dispatch dispatch [--client ] [--json|--text] ai-cli-dispatch --help Clients: codex, claude, opencode`); } export async function main( argv: string[], deps: CliDeps = {} ): Promise { const detectClients = deps.detectClients ?? realDetectClients; const executePrompt = deps.executePrompt ?? realExecutePrompt; const resolveClient = deps.resolveClient ?? realResolveClient; const resolveConfig = deps.resolveConfig ?? realResolveConfig; const stdoutWrite = deps.stdoutWrite ?? ((c: string) => process.stdout.write(c)); const stderrWrite = deps.stderrWrite ?? ((c: string) => process.stderr.write(c)); const rawArgs = argv.slice(2); const parseArgs = rawArgs[0]?.includes("cli.ts") ? rawArgs.slice(1) : rawArgs; const args = minimist(parseArgs, { string: ["client", "prompt", "timeout"], boolean: ["json", "text", "help", "debug"], alias: { h: "help" }, }); const jsonMode = !args.text; if (args.help) { printHelp(); return 0; } const command = args._[0]; if (!command) { printHelp(); return 1; } if (command === "list") { const clients = detectClients(); if (jsonMode) { console.log(JSON.stringify(clients, null, 2)); } else { for (const c of clients) { const status = c.found ? `✓ ${c.version ?? "unknown version"}` : "✗ not found"; console.log(`${c.name}: ${status}`); } } return 0; } if (command === "run") { const client = args.client as ClientName | undefined; const prompt = args.prompt as string | undefined; if (!client || !CLIENT_NAMES.includes(client)) { const message = !client ? "--client is required" : `Unknown client: ${client}`; if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(`Error: ${message}`); } return 1; } if (!prompt) { const message = "--prompt is required"; if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(`Error: ${message}`); } return 1; } const config = resolveConfig(); const timeoutMs = typeof args.timeout === "string" ? Number(args.timeout) : config.timeout; try { const result = await executePrompt(client, prompt, { timeoutMs }); if (jsonMode) { console.log(JSON.stringify(result, null, 2)); } else { if (result.stdout) stdoutWrite(result.stdout); if (result.stderr) stderrWrite(result.stderr); } return 0; } catch (err) { const message = err instanceof Error ? err.message : String(err); if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(message); } return 1; } } if (command === "dispatch") { let prompt = args.prompt as string | undefined; if (!prompt && args._.length > 1) { prompt = args._.slice(1).join(" "); } if (!prompt) { const message = "prompt is required"; if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(`Error: ${message}`); } return 1; } const config = resolveConfig(); const explicitClient = args.client as ClientName | undefined; const client = resolveClient(prompt, { client: explicitClient, defaultClient: config.defaultClient, }); if (!client) { const message = "Could not resolve client from prompt"; if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(`Error: ${message}`); } return 1; } const timeoutMs = typeof args.timeout === "string" ? Number(args.timeout) : config.timeout; try { const result = await executePrompt(client, prompt, { timeoutMs }); if (jsonMode) { console.log(JSON.stringify(result, null, 2)); } else { if (result.stdout) stdoutWrite(result.stdout); if (result.stderr) stderrWrite(result.stderr); } return 0; } catch (err) { const message = err instanceof Error ? err.message : String(err); if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(message); } return 1; } } const message = `Unknown command: ${command}`; if (jsonMode) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(message); } return 1; } const isMain = import.meta.url.startsWith("file://") && !!process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/")); if (isMain) { main(process.argv).then((code) => process.exit(code)); }