diff --git a/packages/cli-workflow/__tests__/gc-cli.test.ts b/packages/cli-workflow/__tests__/gc-cli.test.ts index 1d7a19b..78c9765 100644 --- a/packages/cli-workflow/__tests__/gc-cli.test.ts +++ b/packages/cli-workflow/__tests__/gc-cli.test.ts @@ -129,7 +129,10 @@ describe("gc cli and garbageCollectCas", () => { }); const env = { ...process.env, UNCAGED_WORKFLOW_STORAGE_ROOT: storageRoot }; - const proc = spawnSync(process.execPath, [cliEntryPath, "gc"], { env, encoding: "utf8" }); + const proc = spawnSync(process.execPath, [cliEntryPath, "cas", "gc"], { + env, + encoding: "utf8", + }); expect(proc.status).toBe(0); expect(String(proc.stdout).trim()).toBe("scanned 1 threads, 2 active refs, deleted 1 entries"); }); diff --git a/packages/cli-workflow/__tests__/thread-cli.test.ts b/packages/cli-workflow/__tests__/thread-cli.test.ts index 779a205..a8a770b 100644 --- a/packages/cli-workflow/__tests__/thread-cli.test.ts +++ b/packages/cli-workflow/__tests__/thread-cli.test.ts @@ -250,13 +250,16 @@ describe("cli thread commands", () => { test("cli entrypoint dispatches threads / ps (spawn)", () => { const env = { ...process.env, UNCAGED_WORKFLOW_STORAGE_ROOT: storageRoot }; - const threads = spawnSync(process.execPath, [cliEntryPath, "threads"], { + const threads = spawnSync(process.execPath, [cliEntryPath, "thread", "list"], { env, encoding: "utf8", }); expect(threads.status).toBe(0); - const ps = spawnSync(process.execPath, [cliEntryPath, "ps"], { env, encoding: "utf8" }); + const ps = spawnSync(process.execPath, [cliEntryPath, "thread", "ps"], { + env, + encoding: "utf8", + }); expect(ps.status).toBe(0); }); diff --git a/packages/cli-workflow/src/cli-dispatch.ts b/packages/cli-workflow/src/cli-dispatch.ts index 7f676c2..0dc8245 100644 --- a/packages/cli-workflow/src/cli-dispatch.ts +++ b/packages/cli-workflow/src/cli-dispatch.ts @@ -23,33 +23,47 @@ import { parseRunArgv } from "./run-argv.js"; export function formatCliUsage(): string { return [ "Usage:", - " uncaged-workflow add [--types ]", - " uncaged-workflow list", - " uncaged-workflow show ", - " uncaged-workflow remove ", - " uncaged-workflow run [--prompt ] [--max-rounds N]", - " uncaged-workflow ps", - " uncaged-workflow kill ", - " uncaged-workflow live [--debug] [--role ]", - " uncaged-workflow live --latest [--debug] [--role ]", - " uncaged-workflow history ", - " uncaged-workflow rollback [hash]", - " uncaged-workflow pause ", - " uncaged-workflow resume ", - " uncaged-workflow threads [name]", - " uncaged-workflow thread ", + " 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 fork [--from-role ]", - " uncaged-workflow gc", + " 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)", ].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]; @@ -266,7 +280,7 @@ async function dispatchResume(storageRoot: string, argv: string[]): Promise { +async function dispatchThreadList(storageRoot: string, argv: string[]): Promise { const result = await cmdThreads(storageRoot, argv); if (!result.ok) { printCliError(result.error); @@ -278,10 +292,10 @@ async function dispatchThreads(storageRoot: string, argv: string[]): Promise { +async function dispatchThreadShow(storageRoot: string, argv: string[]): Promise { const id = argv[0]; if (id === undefined || argv.length > 1) { - printCliError(`${formatCliUsage()}\n\nerror: thread requires `); + printCliError(`${formatCliUsage()}\n\nerror: thread show requires `); return 1; } const result = await cmdThreadShow(storageRoot, id); @@ -308,14 +322,6 @@ async function dispatchThreadRm(storageRoot: string, argv: string[]): Promise { - const sub = rest[0]; - if (sub === "rm") { - return dispatchThreadRm(storageRoot, rest.slice(1)); - } - return dispatchThread(storageRoot, rest); -} - async function dispatchGc(storageRoot: string, argv: string[]): Promise { if (argv.length > 0) { printCliError(`${formatCliUsage()}\n\nerror: gc takes no arguments`); @@ -348,6 +354,8 @@ async function dispatchFork(storageRoot: string, argv: string[]): Promise { const threadId = rest[0]; const hash = rest[1]; @@ -413,14 +421,12 @@ async function dispatchCasRm(storageRoot: string, rest: string[]): Promise Promise -> = { +const CAS_SUBCOMMAND_TABLE: Record = { get: dispatchCasGet, put: dispatchCasPut, list: dispatchCasList, rm: dispatchCasRm, + gc: dispatchGc, }; async function dispatchCas(storageRoot: string, argv: string[]): Promise { @@ -437,27 +443,90 @@ async function dispatchCas(storageRoot: string, argv: string[]): Promise return handler(storageRoot, argv.slice(1)); } -type DispatchFn = (storageRoot: string, argv: string[]) => Promise; +// ── Workflow subcommand table (Phase 1) ──────────────────────────────── -const COMMAND_TABLE: Record = { +const WORKFLOW_SUBCOMMAND_TABLE: Record = { add: dispatchAdd, - init: dispatchInit, list: dispatchList, show: dispatchShow, + rm: dispatchRemove, remove: 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) { + printCliError(`${formatCliUsage()}\n\nerror: unknown workflow subcommand: ${sub}`); + return 1; + } + return handler(storageRoot, argv.slice(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, - history: dispatchHistory, - rollback: dispatchRollback, pause: dispatchPause, resume: dispatchResume, - threads: dispatchThreads, - thread: dispatchThreadBranch, - fork: dispatchFork, - gc: dispatchGc, +}; + +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)); +} + +// ── Top-level command table (Phase 3) ────────────────────────────────── + +const COMMAND_TABLE: Record = { + // Grouped commands (primary) + workflow: dispatchWorkflow, + thread: dispatchThread, cas: dispatchCas, + init: dispatchInit, + + // 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 { @@ -471,10 +540,18 @@ export async function runCli(storageRoot: string, argv: string[]): Promise