From 61be1c662a47b5fe03ad3effec74dc48ec8d2a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=A2=A8?= Date: Thu, 7 May 2026 14:20:06 +0000 Subject: [PATCH] feat(cli): help --skill command for agent-consumable docs (#69) --- packages/cli-workflow/__tests__/help.test.ts | 84 +++++++++++++++++++ packages/cli-workflow/src/cli-dispatch.ts | 13 +++ packages/cli-workflow/src/cmd-help.ts | 86 ++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 packages/cli-workflow/__tests__/help.test.ts create mode 100644 packages/cli-workflow/src/cmd-help.ts diff --git a/packages/cli-workflow/__tests__/help.test.ts b/packages/cli-workflow/__tests__/help.test.ts new file mode 100644 index 0000000..f8841aa --- /dev/null +++ b/packages/cli-workflow/__tests__/help.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, test } from "bun:test"; +import { runCli } from "../src/cli-dispatch.js"; +import { formatSkillDoc } from "../src/cmd-help.js"; + +const STORAGE_ROOT = "/tmp/help-test-storage"; + +describe("help command", () => { + test("help returns 0", async () => { + const code = await runCli(STORAGE_ROOT, ["help"]); + expect(code).toBe(0); + }); + + test("help --skill returns 0", async () => { + const code = await runCli(STORAGE_ROOT, ["help", "--skill"]); + expect(code).toBe(0); + }); +}); + +describe("formatSkillDoc", () => { + const doc = formatSkillDoc(); + + test("contains title", () => { + expect(doc).toContain("# uncaged-workflow CLI Reference"); + }); + + test("contains all command group headers", () => { + expect(doc).toContain("### workflow"); + expect(doc).toContain("### thread"); + expect(doc).toContain("### cas"); + expect(doc).toContain("### init"); + expect(doc).toContain("### Top-level shortcuts"); + }); + + test("contains core concepts", () => { + expect(doc).toContain("## Core Concepts"); + expect(doc).toContain("Workflow"); + expect(doc).toContain("Bundle"); + expect(doc).toContain("Thread"); + expect(doc).toContain("CAS"); + expect(doc).toContain("Registry"); + }); + + test("mentions all workflow subcommands", () => { + for (const sub of ["add", "list", "show", "rm", "history", "rollback"]) { + expect(doc).toContain(`workflow ${sub}`); + } + }); + + test("mentions all thread subcommands", () => { + for (const sub of [ + "run", + "list", + "show", + "rm", + "fork", + "ps", + "kill", + "live", + "pause", + "resume", + ]) { + expect(doc).toContain(`thread ${sub}`); + } + }); + + test("mentions all cas subcommands", () => { + for (const sub of ["get", "put", "list", "rm", "gc"]) { + expect(doc).toContain(`cas ${sub}`); + } + }); + + test("contains exit codes section", () => { + expect(doc).toContain("## Exit Codes"); + }); + + test("contains environment variables section", () => { + expect(doc).toContain("## Environment Variables"); + expect(doc).toContain("UNCAGED_WORKFLOW_STORAGE_ROOT"); + }); + + test("contains typical workflow section", () => { + expect(doc).toContain("## Typical Workflow"); + }); +}); diff --git a/packages/cli-workflow/src/cli-dispatch.ts b/packages/cli-workflow/src/cli-dispatch.ts index d6db616..44ae43c 100644 --- a/packages/cli-workflow/src/cli-dispatch.ts +++ b/packages/cli-workflow/src/cli-dispatch.ts @@ -3,6 +3,7 @@ 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"; @@ -501,6 +502,17 @@ async function dispatchThread(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 = { @@ -509,6 +521,7 @@ const COMMAND_TABLE: Record = { thread: dispatchThread, cas: dispatchCas, init: dispatchInit, + help: dispatchHelp, // Top-level shortcuts (no deprecation) run: dispatchRun, diff --git a/packages/cli-workflow/src/cmd-help.ts b/packages/cli-workflow/src/cmd-help.ts new file mode 100644 index 0000000..db85e4f --- /dev/null +++ b/packages/cli-workflow/src/cmd-help.ts @@ -0,0 +1,86 @@ +export function formatSkillDoc(): string { + return `# uncaged-workflow CLI Reference + +## Core Concepts + +| Concept | Description | +|---------|-------------| +| **Workflow** | A single-file ESM bundle (\`.esm.js\`) that exports \`run\` and \`descriptor\`. Identified by name and XXH64 hash. | +| **Bundle** | The physical \`.esm.js\` file stored in the bundles directory. Immutable once written. | +| **Thread** | A single execution of a workflow, identified by a ULID. Persists state as JSONL files. | +| **CAS** | Content-Addressable Storage. Per-thread key-value store keyed by content hash. | +| **Registry** | \`workflow.yaml\` — maps workflow names to their current and historical bundle hashes. | + +## Commands + +### workflow + +| Command | Args | Description | +|---------|------|-------------| +| \`workflow add\` | \` [--types ]\` | Register a workflow bundle in the registry | +| \`workflow list\` | (none) | List all registered workflows | +| \`workflow show\` | \`\` | Show details of a registered workflow | +| \`workflow rm\` | \`\` | Remove a workflow from the registry | +| \`workflow history\` | \`\` | Show version history of a workflow | +| \`workflow rollback\` | \` [hash]\` | Rollback a workflow to a previous version | + +### thread + +| Command | Args | Description | +|---------|------|-------------| +| \`thread run\` | \` [--prompt ] [--max-rounds N]\` | Start a new thread executing a workflow | +| \`thread list\` | \`[name]\` | List threads, optionally filtered by workflow name | +| \`thread show\` | \`\` | Show thread details and state | +| \`thread rm\` | \`\` | Remove a thread | +| \`thread fork\` | \` [--from-role ]\` | Fork a thread, optionally from a specific role | +| \`thread ps\` | (none) | List running threads | +| \`thread kill\` | \`\` | Kill a running thread | +| \`thread live\` | \` [--debug] [--role ]\` or \`--latest [--debug] [--role ]\` | Attach to a thread and stream output live | +| \`thread pause\` | \`\` | Pause a running thread | +| \`thread resume\` | \`\` | Resume a paused thread | + +### cas + +| Command | Args | Description | +|---------|------|-------------| +| \`cas get\` | \` \` | Retrieve content by hash from a thread's CAS | +| \`cas put\` | \` \` | Store content in a thread's CAS, returns hash | +| \`cas list\` | \`\` | List all CAS entries for a thread | +| \`cas rm\` | \` \` | Remove a CAS entry | +| \`cas gc\` | (none) | Garbage-collect unreferenced CAS entries | + +### init + +| Command | Args | Description | +|---------|------|-------------| +| \`init workspace\` | \`\` | Initialize a new workflow workspace | +| \`init template\` | \`\` | Initialize a new workflow template | + +### Top-level shortcuts + +| Command | Equivalent | Description | +|---------|------------|-------------| +| \`run\` | \`thread run\` | Shortcut to start a thread | +| \`live\` | \`thread live\` | Shortcut to attach to a thread | + +## Typical Workflow + +1. \`uncaged-workflow workflow add my-wf ./my-wf.esm.js\` — register a workflow +2. \`uncaged-workflow run my-wf --prompt "do the thing"\` — start a thread +3. \`uncaged-workflow live --latest\` — attach and watch output +4. \`uncaged-workflow thread show \` — inspect completed thread + +## Exit Codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | Error | + +## Environment Variables + +| Variable | Description | +|----------|-------------| +| \`UNCAGED_WORKFLOW_STORAGE_ROOT\` | Override the default storage directory for all workflow data | +`; +}