import { printCliError, printCliLine, printCliWarn } from "./cli-output.js"; import { cmdAdd, formatAddSuccess, parseAddArgv } from "./cmd-add.js"; import { cmdCasGet, cmdCasList, cmdCasPut, cmdCasRm } from "./cmd-cas.js"; import { cmdFork, parseForkArgv } from "./cmd-fork.js"; import { cmdGc } from "./cmd-gc.js"; import { formatSkillDoc } from "./cmd-help.js"; import { cmdHistory } from "./cmd-history.js"; import { cmdInitTemplate, cmdInitWorkspace } from "./cmd-init.js"; import { cmdKill } from "./cmd-kill.js"; import { cmdList, formatListLines } from "./cmd-list.js"; import { cmdLive } from "./cmd-live.js"; import { cmdPause } from "./cmd-pause.js"; import { cmdPs } from "./cmd-ps.js"; import { cmdRemove } from "./cmd-remove.js"; import { cmdResume } from "./cmd-resume.js"; import { cmdRollback } from "./cmd-rollback.js"; import { cmdRun } from "./cmd-run.js"; import { cmdShow, formatShowYaml } from "./cmd-show.js"; import { cmdThreadRemove, cmdThreadShow } from "./cmd-thread.js"; import { cmdThreads } from "./cmd-threads.js"; import { parseLiveArgv } from "./live-argv.js"; import { parseRunArgv } from "./run-argv.js"; export function formatCliUsage(): string { return [ "Usage:", " uncaged-workflow workflow add [--types ]", " uncaged-workflow workflow list", " uncaged-workflow workflow show ", " uncaged-workflow workflow rm ", " uncaged-workflow workflow history ", " uncaged-workflow workflow rollback [hash]", "", " uncaged-workflow thread run [--prompt ] [--max-rounds N]", " uncaged-workflow thread list [name]", " uncaged-workflow thread show ", " uncaged-workflow thread rm ", " uncaged-workflow thread ps", " uncaged-workflow thread kill ", " uncaged-workflow thread live [--debug] [--role ]", " uncaged-workflow thread live --latest [--debug] [--role ]", " uncaged-workflow thread pause ", " uncaged-workflow thread resume ", " uncaged-workflow thread fork [--from-role ]", "", " uncaged-workflow cas get ", " uncaged-workflow cas put ", " uncaged-workflow cas list ", " uncaged-workflow cas rm ", " uncaged-workflow cas gc", "", " uncaged-workflow init workspace ", " uncaged-workflow init template ", "", " uncaged-workflow run [...] (shortcut for thread run)", " uncaged-workflow live [...] (shortcut for thread live)", "", "Environment variables:", " WORKFLOW_STORAGE_ROOT Override storage directory (default: ~/.uncaged/workflow)", " UNCAGED_WORKFLOW_STORAGE_ROOT Internal override (takes priority over WORKFLOW_STORAGE_ROOT)", ].join("\n"); } function printDeprecation(oldCmd: string, newCmd: string): void { printCliWarn(`⚠ "${oldCmd}" is deprecated, use "${newCmd}" instead`); } type DispatchFn = (storageRoot: string, argv: string[]) => Promise; // ── Individual dispatch functions ────────────────────────────────────── async function dispatchInit(_storageRoot: string, argv: string[]): Promise { const sub = argv[0]; const name = argv[1]; if (sub === undefined || name === undefined || argv.length > 2) { printCliError(`${formatCliUsage()}\n\nerror: init requires workspace|template `); return 1; } if (sub === "workspace") { const result = await cmdInitWorkspace(process.cwd(), name); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`initialized workflow workspace at ${result.value.rootPath}`); return 0; } if (sub === "template") { const result = await cmdInitTemplate(process.cwd(), name); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`initialized template at ${result.value.templatePath}`); return 0; } printCliError(`${formatCliUsage()}\n\nerror: unknown init subcommand: ${sub}`); return 1; } async function dispatchAdd(storageRoot: string, argv: string[]): Promise { const parsed = parseAddArgv(argv); if (!parsed.ok) { printCliError(`${formatCliUsage()}\n\nerror: ${parsed.error}`); return 1; } const result = await cmdAdd(storageRoot, parsed.value); if (!result.ok) { printCliError(result.error); return 1; } for (const w of result.value.warnings) { printCliWarn(w); } printCliLine(formatAddSuccess(parsed.value.name, parsed.value.filePath, result.value.hash)); return 0; } async function dispatchList(storageRoot: string, argv: string[]): Promise { if (argv.length > 0) { printCliError(`${formatCliUsage()}\n\nerror: list takes no arguments`); return 1; } const result = await cmdList(storageRoot); if (!result.ok) { printCliError(result.error); return 1; } for (const line of formatListLines(result.value)) { printCliLine(line); } return 0; } async function dispatchShow(storageRoot: string, argv: string[]): Promise { const name = argv[0]; if (name === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: show requires `); return 1; } const result = await cmdShow(storageRoot, name); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(formatShowYaml(name, result.value)); return 0; } async function dispatchRemove(storageRoot: string, argv: string[]): Promise { const name = argv[0]; if (name === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: remove requires `); return 1; } const result = await cmdRemove(storageRoot, name); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`removed workflow "${name}" from registry`); return 0; } async function dispatchRun(storageRoot: string, argv: string[]): Promise { const parsed = parseRunArgv(argv); if (!parsed.ok) { printCliError(`${formatCliUsage()}\n\nerror: ${parsed.error}`); return 1; } const result = await cmdRun( storageRoot, parsed.value.name, parsed.value.prompt, parsed.value.maxRounds, ); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(result.value.threadId); return 0; } async function dispatchPs(storageRoot: string, argv: string[]): Promise { if (argv.length > 0) { printCliError(`${formatCliUsage()}\n\nerror: ps takes no arguments`); return 1; } for (const line of await cmdPs(storageRoot)) { printCliLine(line); } return 0; } async function dispatchKill(storageRoot: string, argv: string[]): Promise { const threadId = argv[0]; if (threadId === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: kill requires `); return 1; } const result = await cmdKill(storageRoot, threadId); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`kill sent for thread ${threadId}`); return 0; } async function dispatchLive(storageRoot: string, argv: string[]): Promise { const parsed = parseLiveArgv(argv); if (!parsed.ok) { printCliError(`${formatCliUsage()}\n\nerror: ${parsed.error}`); return 1; } return cmdLive(storageRoot, parsed.value); } async function dispatchHistory(storageRoot: string, argv: string[]): Promise { const name = argv[0]; if (name === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: history requires `); return 1; } const result = await cmdHistory(storageRoot, name); if (!result.ok) { printCliError(result.error); return 1; } for (const line of result.value) { printCliLine(line); } return 0; } async function dispatchRollback(storageRoot: string, argv: string[]): Promise { const name = argv[0]; if (name === undefined || argv.length > 2) { printCliError(`${formatCliUsage()}\n\nerror: rollback requires [hash]`); return 1; } const hashArg = argv[1]; const result = await cmdRollback(storageRoot, name, hashArg === undefined ? null : hashArg); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`rolled back workflow "${name}"`); return 0; } async function dispatchPause(storageRoot: string, argv: string[]): Promise { const threadId = argv[0]; if (threadId === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: pause requires `); return 1; } const result = await cmdPause(storageRoot, threadId); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`pause sent for thread ${threadId}`); return 0; } async function dispatchResume(storageRoot: string, argv: string[]): Promise { const threadId = argv[0]; if (threadId === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: resume requires `); return 1; } const result = await cmdResume(storageRoot, threadId); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`resume sent for thread ${threadId}`); return 0; } async function dispatchThreadList(storageRoot: string, argv: string[]): Promise { const result = await cmdThreads(storageRoot, argv); if (!result.ok) { printCliError(result.error); return 1; } for (const line of result.value) { printCliLine(line); } return 0; } async function dispatchThreadShow(storageRoot: string, argv: string[]): Promise { const id = argv[0]; if (id === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: thread show requires `); return 1; } const result = await cmdThreadShow(storageRoot, id); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(result.value); return 0; } async function dispatchThreadRm(storageRoot: string, argv: string[]): Promise { const id = argv[0]; if (id === undefined || argv.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: thread rm requires `); return 1; } const result = await cmdThreadRemove(storageRoot, id); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`removed thread ${id}`); return 0; } async function dispatchGc(storageRoot: string, argv: string[]): Promise { if (argv.length > 0) { printCliError(`${formatCliUsage()}\n\nerror: gc takes no arguments`); return 1; } const result = await cmdGc(storageRoot); if (!result.ok) { printCliError(result.error); return 1; } const stats = result.value; printCliLine( `scanned ${stats.scannedThreads} threads, ${stats.activeRefs} active refs, deleted ${stats.deletedEntries} entries`, ); return 0; } async function dispatchFork(storageRoot: string, argv: string[]): Promise { const parsed = parseForkArgv(argv); if (!parsed.ok) { printCliError(`${formatCliUsage()}\n\nerror: ${parsed.error}`); return 1; } const result = await cmdFork(storageRoot, parsed.value.threadId, parsed.value.fromRole); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(result.value.threadId); return 0; } // ── CAS subcommand table ─────────────────────────────────────────────── async function dispatchCasGet(storageRoot: string, rest: string[]): Promise { const threadId = rest[0]; const hash = rest[1]; if (threadId === undefined || hash === undefined || rest.length > 2) { printCliError(`${formatCliUsage()}\n\nerror: cas get requires `); return 1; } const result = await cmdCasGet(storageRoot, threadId, hash); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(result.value); return 0; } async function dispatchCasPut(storageRoot: string, rest: string[]): Promise { const threadId = rest[0]; const content = rest[1]; if (threadId === undefined || content === undefined || rest.length > 2) { printCliError(`${formatCliUsage()}\n\nerror: cas put requires `); return 1; } const result = await cmdCasPut(storageRoot, threadId, content); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(result.value); return 0; } async function dispatchCasList(storageRoot: string, rest: string[]): Promise { const threadId = rest[0]; if (threadId === undefined || rest.length > 1) { printCliError(`${formatCliUsage()}\n\nerror: cas list requires `); return 1; } const result = await cmdCasList(storageRoot, threadId); if (!result.ok) { printCliError(result.error); return 1; } for (const hash of result.value) { printCliLine(hash); } return 0; } async function dispatchCasRm(storageRoot: string, rest: string[]): Promise { const threadId = rest[0]; const hash = rest[1]; if (threadId === undefined || hash === undefined || rest.length > 2) { printCliError(`${formatCliUsage()}\n\nerror: cas rm requires `); return 1; } const result = await cmdCasRm(storageRoot, threadId, hash); if (!result.ok) { printCliError(result.error); return 1; } printCliLine(`removed cas entry ${hash}`); return 0; } const CAS_SUBCOMMAND_TABLE: Record = { get: dispatchCasGet, put: dispatchCasPut, list: dispatchCasList, rm: dispatchCasRm, gc: dispatchGc, }; async function dispatchCas(storageRoot: string, argv: string[]): Promise { const sub = argv[0]; if (sub === undefined) { printCliError(`${formatCliUsage()}\n\nerror: unknown cas subcommand: (none)`); return 1; } const handler = CAS_SUBCOMMAND_TABLE[sub]; if (handler === undefined) { printCliError(`${formatCliUsage()}\n\nerror: unknown cas subcommand: ${sub}`); return 1; } return handler(storageRoot, argv.slice(1)); } // ── Workflow subcommand table (Phase 1) ──────────────────────────────── const WORKFLOW_SUBCOMMAND_TABLE: Record = { add: dispatchAdd, list: dispatchList, show: dispatchShow, rm: dispatchRemove, history: dispatchHistory, rollback: dispatchRollback, }; async function dispatchWorkflow(storageRoot: string, argv: string[]): Promise { const sub = argv[0]; if (sub === undefined) { printCliError(`${formatCliUsage()}\n\nerror: unknown workflow subcommand: (none)`); return 1; } const handler = WORKFLOW_SUBCOMMAND_TABLE[sub]; if (handler !== undefined) { return handler(storageRoot, argv.slice(1)); } if (sub === "remove") { printDeprecation("workflow remove", "workflow rm"); return dispatchRemove(storageRoot, argv.slice(1)); } printCliError(`${formatCliUsage()}\n\nerror: unknown workflow subcommand: ${sub}`); return 1; } // ── Thread subcommand table (Phase 2) ────────────────────────────────── const THREAD_SUBCOMMAND_TABLE: Record = { run: dispatchRun, list: dispatchThreadList, show: dispatchThreadShow, rm: dispatchThreadRm, fork: dispatchFork, ps: dispatchPs, kill: dispatchKill, live: dispatchLive, pause: dispatchPause, resume: dispatchResume, }; async function dispatchThread(storageRoot: string, argv: string[]): Promise { const sub = argv[0]; if (sub === undefined) { printCliError(`${formatCliUsage()}\n\nerror: unknown thread subcommand: (none)`); return 1; } const handler = THREAD_SUBCOMMAND_TABLE[sub]; if (handler === undefined) { printCliError(`${formatCliUsage()}\n\nerror: unknown thread subcommand: ${sub}`); return 1; } return handler(storageRoot, argv.slice(1)); } // ── Help ──────────────────────────────────────────────────────────────── async function dispatchHelp(_storageRoot: string, argv: string[]): Promise { if (argv.includes("--skill")) { printCliLine(formatSkillDoc()); } else { printCliLine(formatCliUsage()); } return 0; } // ── Top-level command table (Phase 3) ────────────────────────────────── const COMMAND_TABLE: Record = { // Grouped commands (primary) workflow: dispatchWorkflow, thread: dispatchThread, cas: dispatchCas, init: dispatchInit, help: dispatchHelp, // Top-level shortcuts (no deprecation) run: dispatchRun, live: dispatchLive, }; // Deprecated flat commands that delegate to grouped commands const DEPRECATED_ALIASES: Record = { add: { newCmd: "workflow add", handler: dispatchAdd }, list: { newCmd: "workflow list", handler: dispatchList }, show: { newCmd: "workflow show", handler: dispatchShow }, remove: { newCmd: "workflow rm", handler: dispatchRemove }, ps: { newCmd: "thread ps", handler: dispatchPs }, kill: { newCmd: "thread kill", handler: dispatchKill }, pause: { newCmd: "thread pause", handler: dispatchPause }, resume: { newCmd: "thread resume", handler: dispatchResume }, threads: { newCmd: "thread list", handler: dispatchThreadList }, fork: { newCmd: "thread fork", handler: dispatchFork }, gc: { newCmd: "cas gc", handler: dispatchGc }, history: { newCmd: "workflow history", handler: dispatchHistory }, rollback: { newCmd: "workflow rollback", handler: dispatchRollback }, }; export async function runCli(storageRoot: string, argv: string[]): Promise { if (argv.length === 0) { printCliError(formatCliUsage()); return 1; } const command = argv[0]; if (command === undefined) { printCliError(formatCliUsage()); return 1; } const rest = argv.slice(1); const dispatch = COMMAND_TABLE[command]; if (dispatch !== undefined) { return dispatch(storageRoot, rest); } const deprecated = DEPRECATED_ALIASES[command]; if (deprecated !== undefined) { printDeprecation(command, deprecated.newCmd); return deprecated.handler(storageRoot, rest); } printCliError(`${formatCliUsage()}\n\nerror: unknown command ${command}`); return 1; }