@united-workforce/cli 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +32 -5
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/__tests__/broker-step-active-turns.test.d.ts +20 -0
  4. package/dist/__tests__/broker-step-active-turns.test.d.ts.map +1 -0
  5. package/dist/__tests__/broker-step-active-turns.test.js +428 -0
  6. package/dist/__tests__/broker-step-active-turns.test.js.map +1 -0
  7. package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts +13 -0
  8. package/dist/__tests__/broker-step-turn-chain-phase2.test.d.ts.map +1 -0
  9. package/dist/__tests__/broker-step-turn-chain-phase2.test.js +429 -0
  10. package/dist/__tests__/broker-step-turn-chain-phase2.test.js.map +1 -0
  11. package/dist/__tests__/e2e-broker-step-suspend.test.d.ts +18 -0
  12. package/dist/__tests__/e2e-broker-step-suspend.test.d.ts.map +1 -0
  13. package/dist/__tests__/e2e-broker-step-suspend.test.js +313 -0
  14. package/dist/__tests__/e2e-broker-step-suspend.test.js.map +1 -0
  15. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts +28 -0
  16. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.d.ts.map +1 -0
  17. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js +322 -0
  18. package/dist/__tests__/e2e-thread-resume-timeout-suspend.test.js.map +1 -0
  19. package/dist/__tests__/log-tag-validity.test.d.ts +2 -0
  20. package/dist/__tests__/log-tag-validity.test.d.ts.map +1 -0
  21. package/dist/__tests__/log-tag-validity.test.js +110 -0
  22. package/dist/__tests__/log-tag-validity.test.js.map +1 -0
  23. package/dist/__tests__/setup-agent-discovery.test.js +23 -23
  24. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
  25. package/dist/__tests__/step-show-json.test.js +5 -5
  26. package/dist/__tests__/step-show-json.test.js.map +1 -1
  27. package/dist/__tests__/step-show-text.test.d.ts +2 -0
  28. package/dist/__tests__/step-show-text.test.d.ts.map +1 -0
  29. package/dist/__tests__/step-show-text.test.js +192 -0
  30. package/dist/__tests__/step-show-text.test.js.map +1 -0
  31. package/dist/__tests__/step-turns-cli-subprocess.test.d.ts +21 -0
  32. package/dist/__tests__/step-turns-cli-subprocess.test.d.ts.map +1 -0
  33. package/dist/__tests__/step-turns-cli-subprocess.test.js +356 -0
  34. package/dist/__tests__/step-turns-cli-subprocess.test.js.map +1 -0
  35. package/dist/__tests__/step-turns-panorama-phase3.test.d.ts +21 -0
  36. package/dist/__tests__/step-turns-panorama-phase3.test.d.ts.map +1 -0
  37. package/dist/__tests__/step-turns-panorama-phase3.test.js +476 -0
  38. package/dist/__tests__/step-turns-panorama-phase3.test.js.map +1 -0
  39. package/dist/__tests__/step-turns.test.d.ts +24 -0
  40. package/dist/__tests__/step-turns.test.d.ts.map +1 -0
  41. package/dist/__tests__/step-turns.test.js +646 -0
  42. package/dist/__tests__/step-turns.test.js.map +1 -0
  43. package/dist/__tests__/store-turn-chain.test.d.ts +2 -0
  44. package/dist/__tests__/store-turn-chain.test.d.ts.map +1 -0
  45. package/dist/__tests__/store-turn-chain.test.js +341 -0
  46. package/dist/__tests__/store-turn-chain.test.js.map +1 -0
  47. package/dist/__tests__/thread-list-limit-offset.test.d.ts +24 -0
  48. package/dist/__tests__/thread-list-limit-offset.test.d.ts.map +1 -0
  49. package/dist/__tests__/thread-list-limit-offset.test.js +254 -0
  50. package/dist/__tests__/thread-list-limit-offset.test.js.map +1 -0
  51. package/dist/__tests__/thread-list-template-ms-date.test.js +7 -2
  52. package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -1
  53. package/dist/__tests__/thread.test.js +28 -14
  54. package/dist/__tests__/thread.test.js.map +1 -1
  55. package/dist/cli.js +910 -344
  56. package/dist/cli.js.map +1 -1
  57. package/dist/commands/broker-step.d.ts +10 -3
  58. package/dist/commands/broker-step.d.ts.map +1 -1
  59. package/dist/commands/broker-step.js +231 -27
  60. package/dist/commands/broker-step.js.map +1 -1
  61. package/dist/commands/prompt.d.ts.map +1 -1
  62. package/dist/commands/prompt.js +42 -50
  63. package/dist/commands/prompt.js.map +1 -1
  64. package/dist/commands/setup.d.ts +6 -4
  65. package/dist/commands/setup.d.ts.map +1 -1
  66. package/dist/commands/setup.js +16 -26
  67. package/dist/commands/setup.js.map +1 -1
  68. package/dist/commands/step.d.ts +48 -1
  69. package/dist/commands/step.d.ts.map +1 -1
  70. package/dist/commands/step.js +496 -3
  71. package/dist/commands/step.js.map +1 -1
  72. package/dist/output-mappers.d.ts +8 -0
  73. package/dist/output-mappers.d.ts.map +1 -1
  74. package/dist/output-mappers.js +72 -18
  75. package/dist/output-mappers.js.map +1 -1
  76. package/dist/schemas.d.ts +3 -0
  77. package/dist/schemas.d.ts.map +1 -1
  78. package/dist/schemas.js +17 -3
  79. package/dist/schemas.js.map +1 -1
  80. package/dist/store.d.ts +147 -1
  81. package/dist/store.d.ts.map +1 -1
  82. package/dist/store.js +254 -1
  83. package/dist/store.js.map +1 -1
  84. package/dist/text-renderers.d.ts.map +1 -1
  85. package/dist/text-renderers.js +27 -2
  86. package/dist/text-renderers.js.map +1 -1
  87. package/package.json +7 -6
  88. package/src/__tests__/broker-step-active-turns.test.ts +509 -0
  89. package/src/__tests__/broker-step-turn-chain-phase2.test.ts +525 -0
  90. package/src/__tests__/e2e-broker-step-suspend.test.ts +351 -0
  91. package/src/__tests__/e2e-thread-resume-timeout-suspend.test.ts +360 -0
  92. package/src/__tests__/log-tag-validity.test.ts +124 -0
  93. package/src/__tests__/setup-agent-discovery.test.ts +23 -23
  94. package/src/__tests__/step-show-json.test.ts +5 -5
  95. package/src/__tests__/step-show-text.test.ts +236 -0
  96. package/src/__tests__/step-turns-cli-subprocess.test.ts +411 -0
  97. package/src/__tests__/step-turns-panorama-phase3.test.ts +579 -0
  98. package/src/__tests__/step-turns.test.ts +734 -0
  99. package/src/__tests__/store-turn-chain.test.ts +386 -0
  100. package/src/__tests__/thread-list-limit-offset.test.ts +305 -0
  101. package/src/__tests__/thread-list-template-ms-date.test.ts +7 -2
  102. package/src/__tests__/thread.test.ts +29 -15
  103. package/src/cli.ts +1056 -483
  104. package/src/commands/broker-step.ts +315 -38
  105. package/src/commands/prompt.ts +42 -50
  106. package/src/commands/setup.ts +16 -28
  107. package/src/commands/step.ts +655 -3
  108. package/src/output-mappers.ts +99 -21
  109. package/src/schemas.ts +32 -2
  110. package/src/store.ts +297 -2
  111. package/src/text-renderers.ts +35 -2
package/src/cli.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
2
 
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createCLI, type ParsedFlags } from "@ocas/cli-kit";
3
7
  import type { CasRef, OutputSchemaName, ThreadId, ThreadStatus } from "@united-workforce/protocol";
4
- import { Command } from "commander";
8
+ import { z } from "zod";
5
9
  import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js";
6
10
  import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
7
11
  import {
@@ -12,7 +16,14 @@ import {
12
16
  cmdPromptWorkflowAuthoring,
13
17
  } from "./commands/prompt.js";
14
18
  import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
15
- import { cmdStepAsk, cmdStepFork, cmdStepList, cmdStepRead, cmdStepShow } from "./commands/step.js";
19
+ import {
20
+ cmdStepAsk,
21
+ cmdStepFork,
22
+ cmdStepList,
23
+ cmdStepRead,
24
+ cmdStepShow,
25
+ cmdStepTurns,
26
+ } from "./commands/step.js";
16
27
  import {
17
28
  cmdThreadCancel,
18
29
  cmdThreadExec,
@@ -54,8 +65,22 @@ import {
54
65
  } from "./output-mappers.js";
55
66
  import { createUwfStore, resolveStorageRoot } from "./store.js";
56
67
 
68
+ // --- Package version (readFileSync replaces dynamic import) ---
69
+
70
+ const __dirname = dirname(fileURLToPath(import.meta.url));
71
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")) as {
72
+ version: string;
73
+ };
74
+ const VERSION = pkg.version;
75
+
76
+ // --- Module-level state (cli-kit v0.2.1 workaround: no global --format) ---
77
+ // Parsed from argv early, before cli-kit sees it.
78
+ let formatOverride: string | null = null;
79
+ // Parsed from argv early — cli-kit rejects values starting with `-`.
80
+ let countOverride: string | null = null;
81
+
57
82
  function getFormat(): OutputFormat {
58
- const raw = program.opts().format as string;
83
+ const raw = formatOverride ?? "text";
59
84
  if (!isOutputFormat(raw)) {
60
85
  process.stderr.write(
61
86
  `Invalid --format: ${raw}. Must be one of: ${SUPPORTED_FORMATS.join(", ")}\n`,
@@ -85,164 +110,21 @@ async function writeOutput(
85
110
  * (the default) it renders via the per-command registry when available
86
111
  * and falls back to JSON.
87
112
  */
88
- function writeRawOutput(data: unknown, commandPath?: string): void {
113
+ function writeRawOutput(data: unknown, commandPath: string | null = null): void {
89
114
  const fmt = getFormat();
90
- process.stdout.write(`${formatOutput(data, fmt, commandPath)}\n`);
115
+ process.stdout.write(`${formatOutput(data, fmt, commandPath ?? undefined)}\n`);
91
116
  }
92
117
 
93
- function runAction(action: () => Promise<void>): void {
94
- action().catch((e: unknown) => {
118
+ function runAction(action: () => Promise<void>): Promise<void> {
119
+ return action().catch((e: unknown) => {
95
120
  const message = e instanceof Error ? e.message : String(e);
96
121
  process.stderr.write(`${message}\n`);
97
122
  process.exit(1);
98
123
  });
99
124
  }
100
125
 
101
- const program = new Command();
102
-
103
- // eslint-disable-next-line -- dynamic import for version
104
- const pkg = await import("../package.json", { with: { type: "json" } });
105
- program
106
- .name("uwf")
107
- .description(
108
- "Stateless workflow CLI\n\n" +
109
- "Four-layer architecture:\n" +
110
- " workflow → thread → step → turn",
111
- )
112
- .version(pkg.default.version, "-V, --version");
113
- program.option(
114
- "--format <fmt>",
115
- "Output format: text (default), json, yaml, raw-json, raw-yaml",
116
- "text",
117
- );
118
-
119
- const workflow = program
120
- .command("workflow")
121
- .description("Workflow definitions (layer 1: templates)");
126
+ // --- Helper functions for thread list / step turns parsing (unchanged) ---
122
127
 
123
- workflow
124
- .command("add")
125
- .description("Register a workflow from YAML")
126
- .argument("<file>", "Workflow YAML file")
127
- .action((file: string) => {
128
- const storageRoot = resolveStorageRoot();
129
- runAction(async () => {
130
- const result = await cmdWorkflowAdd(storageRoot, file);
131
- await writeOutput(toWorkflowAddPayload(result), "workflow-add", storageRoot);
132
- });
133
- });
134
-
135
- workflow
136
- .command("validate")
137
- .description("Validate a workflow YAML without registering it (CI-friendly)")
138
- .argument("<file>", "Workflow YAML file")
139
- .action((file: string) => {
140
- const storageRoot = resolveStorageRoot();
141
- runAction(async () => {
142
- const errors = await cmdWorkflowValidate(file);
143
- await writeOutput(toValidateResultPayload(errors), "validate-result", storageRoot);
144
- if (errors.length > 0) {
145
- process.exit(1);
146
- }
147
- });
148
- });
149
-
150
- workflow
151
- .command("show")
152
- .description("Show a workflow by name or CAS hash")
153
- .argument("<id>", "Workflow name or hash")
154
- .action((id: string) => {
155
- const storageRoot = resolveStorageRoot();
156
- runAction(async () => {
157
- const result = await cmdWorkflowShow(storageRoot, id, process.cwd());
158
- await writeOutput(toWorkflowDetailPayload(result), "workflow-detail", storageRoot);
159
- });
160
- });
161
-
162
- workflow
163
- .command("list")
164
- .description("List registered workflows")
165
- .action(() => {
166
- const storageRoot = resolveStorageRoot();
167
- runAction(async () => {
168
- const result = await cmdWorkflowList(storageRoot, process.cwd());
169
- await writeOutput(toWorkflowListPayload(result), "workflow-list", storageRoot);
170
- });
171
- });
172
-
173
- const thread = program.command("thread").description("Thread execution (layer 2: instances)");
174
-
175
- thread
176
- .command("start")
177
- .description("Create a thread without executing")
178
- .argument("<workflow>", "Workflow name or hash")
179
- .requiredOption("-p, --prompt <text>", "User prompt")
180
- .option("--cwd <path>", "Working directory for thread execution (default: process.cwd())")
181
- .action((workflow: string, opts: { prompt: string; cwd: string | undefined }) => {
182
- const storageRoot = resolveStorageRoot();
183
- runAction(async () => {
184
- const result = await cmdThreadStart(
185
- storageRoot,
186
- workflow,
187
- opts.prompt,
188
- process.cwd(),
189
- opts.cwd ?? process.cwd(),
190
- );
191
- await writeOutput(toThreadStartPayload(result), "thread-start", storageRoot);
192
- });
193
- });
194
-
195
- thread
196
- .command("exec")
197
- .description("Execute one or more steps")
198
- .argument("<thread-id>", "Thread ULID")
199
- .option("--agent <cmd>", "Override agent command")
200
- .option("-c, --count <number>", "Number of steps to run (default: 1)")
201
- .option("--background", "Run in background and return immediately")
202
- .option("--_background-worker", "Internal flag for background worker process", false)
203
- .action(
204
- (
205
- threadId: string,
206
- opts: {
207
- agent: string | undefined;
208
- count: string | undefined;
209
- background: boolean;
210
- _backgroundWorker: boolean;
211
- },
212
- ) => {
213
- const storageRoot = resolveStorageRoot();
214
- runAction(async () => {
215
- const agentOverride = opts.agent ?? null;
216
- const count = opts.count !== undefined ? Number(opts.count) : 1;
217
- const background = opts.background ?? false;
218
- const backgroundWorker = opts._backgroundWorker ?? false;
219
-
220
- const results = await cmdThreadExec(
221
- storageRoot,
222
- threadId,
223
- agentOverride,
224
- count,
225
- background,
226
- backgroundWorker,
227
- );
228
- await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
229
- });
230
- },
231
- );
232
-
233
- thread
234
- .command("show")
235
- .description("Show thread head pointer")
236
- .argument("<thread-id>", "Thread ULID")
237
- .action((threadId: string) => {
238
- const storageRoot = resolveStorageRoot();
239
- runAction(async () => {
240
- const result = await cmdThreadShow(storageRoot, threadId);
241
- await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
242
- });
243
- });
244
-
245
- // Helper functions for thread list command parsing
246
128
  function parseStatusFilter(status: string | undefined): ThreadStatus[] | null {
247
129
  if (status === undefined) return null;
248
130
  const raw = status.trim();
@@ -308,123 +190,850 @@ function parsePaginationOptions(
308
190
  return { skip: skipVal, take: takeVal };
309
191
  }
310
192
 
311
- thread
193
+ /**
194
+ * Parse a `step turns` `--limit`/`--offset` value into a non-negative integer, or
195
+ * `null` when the flag is absent (the OCAS `ListOptions` "no limit" / offset-0
196
+ * convention). `--limit 0` is a legal value (renders no turns); negative or
197
+ * non-numeric values are a CLI usage error (exit non-zero). The `flag` label is
198
+ * used verbatim in the error message.
199
+ */
200
+ function parseTurnsPageOption(flag: string, value: string | undefined): number | null {
201
+ if (value === undefined) {
202
+ return null;
203
+ }
204
+ const trimmed = value.trim();
205
+ if (!/^\d+$/.test(trimmed)) {
206
+ process.stderr.write(`${flag} must be a non-negative integer\n`);
207
+ process.exit(1);
208
+ }
209
+ return Number.parseInt(trimmed, 10);
210
+ }
211
+
212
+ /**
213
+ * Resolve `thread list` pagination from both the canonical repo-wide
214
+ * `ListOptions` vocabulary (`--limit`/`--offset`, as used by `step turns`) and
215
+ * the backward-compatible legacy aliases (`--skip`/`--take`). The canonical
216
+ * flags map onto the existing `cmdThreadList` parameters: `--limit` → `take`
217
+ * (max items), `--offset` → `skip` (items skipped from the front of the
218
+ * newest-first list). When both a canonical flag and its legacy alias are
219
+ * supplied, the canonical flag wins and the alias is the fallback.
220
+ * `--limit`/`--offset` are validated via `parseTurnsPageOption` (same
221
+ * non-negative-integer rule and flag-named error as `step turns`); `--limit 0`
222
+ * is legal and yields no items (the `ListOptions` "no limit" convention treats
223
+ * an absent flag, not 0, as "all items").
224
+ */
225
+ function resolveThreadListPagination(flags: {
226
+ skip: string | undefined;
227
+ take: string | undefined;
228
+ limit: string | undefined;
229
+ offset: string | undefined;
230
+ }): { skip: number | null; take: number | null } {
231
+ const legacy = parsePaginationOptions(flags.skip, flags.take);
232
+ const limit = parseTurnsPageOption("--limit", flags.limit);
233
+ const offset = parseTurnsPageOption("--offset", flags.offset);
234
+ return {
235
+ skip: offset ?? legacy.skip,
236
+ take: limit ?? legacy.take,
237
+ };
238
+ }
239
+
240
+ // --- Positional arg helper (cli-kit workaround: custom missing-arg message) ---
241
+ // cli-kit emits "Missing positional arguments" as NDJSON; tests expect a
242
+ // plain "missing required argument" message on stderr. We don't declare
243
+ // .arg() on commands and read positionals from the _positionals field that
244
+ // cli-kit injects into the flags object.
245
+
246
+ function getPositionals(flags: ParsedFlags): string[] {
247
+ const p = flags._positionals;
248
+ return Array.isArray(p) ? (p as string[]) : [];
249
+ }
250
+
251
+ function requirePositional(flags: ParsedFlags, index: number, name: string): string {
252
+ const positionals = getPositionals(flags);
253
+ const value = positionals[index];
254
+ if (value === undefined) {
255
+ process.stderr.write(`Error: missing required argument: ${name}\n`);
256
+ process.exit(1);
257
+ }
258
+ return value;
259
+ }
260
+
261
+ // --- Help text (cli-kit v0.2.1 workaround: no per-command --help) ---
262
+
263
+ const TOP_LEVEL_HELP = `Usage: uwf <command> [options]
264
+
265
+ Stateless workflow CLI
266
+
267
+ Four-layer architecture:
268
+ workflow → thread → step → turn
269
+
270
+ Commands:
271
+ workflow Workflow definitions (layer 1: templates)
272
+ thread Thread execution (layer 2: instances)
273
+ step Step results (layer 3: single cycle)
274
+ prompt Built-in prompt references for agents
275
+ setup Configure the default agent
276
+ log Process-level debug logs
277
+ config Configuration management
278
+
279
+ Standard flags:
280
+ --format <fmt> Output format: text (default), json, yaml, raw-json, raw-yaml
281
+ -V, --version Show version
282
+ -h, --help Show this help
283
+ `;
284
+
285
+ const PROMPT_HELP = `Usage: uwf prompt <command>
286
+
287
+ Built-in prompt references for agents
288
+
289
+ Commands:
290
+ usage Print the usage reference (CLI guide + typical workflows)
291
+ bootstrap Print setup instructions for installing uwf skills
292
+ workflow-authoring Print the workflow authoring reference (YAML design guide)
293
+ adapter-developing Print the adapter developing reference (building agent adapters)
294
+ list List all available prompt names
295
+ `;
296
+
297
+ const WORKFLOW_HELP = `Usage: uwf workflow <command>
298
+
299
+ Workflow definitions (layer 1: templates)
300
+
301
+ Commands:
302
+ add Register a workflow from YAML
303
+ validate Validate a workflow YAML without registering it (CI-friendly)
304
+ show Show a workflow by name or CAS hash
305
+ list List registered workflows
306
+ `;
307
+
308
+ const WORKFLOW_ADD_HELP = `Usage: uwf workflow add <file>
309
+
310
+ Register a workflow from YAML
311
+
312
+ Arguments:
313
+ file Workflow YAML file
314
+ `;
315
+
316
+ const WORKFLOW_VALIDATE_HELP = `Usage: uwf workflow validate <file>
317
+
318
+ Validate a workflow YAML without registering it (CI-friendly)
319
+
320
+ Arguments:
321
+ file Workflow YAML file
322
+ `;
323
+
324
+ const WORKFLOW_SHOW_HELP = `Usage: uwf workflow show <id>
325
+
326
+ Show a workflow by name or CAS hash
327
+
328
+ Arguments:
329
+ id Workflow name or hash
330
+ `;
331
+
332
+ const WORKFLOW_LIST_HELP = `Usage: uwf workflow list
333
+
334
+ List registered workflows
335
+ `;
336
+
337
+ const THREAD_HELP = `Usage: uwf thread <command>
338
+
339
+ Thread execution (layer 2: instances)
340
+
341
+ Commands:
342
+ start Create a thread without executing
343
+ exec Execute one or more steps
344
+ show Show thread head pointer
345
+ list List threads (defaults to active: idle + running + corrupt)
346
+ resume Resume a suspended thread and re-run the suspended role
347
+ poke Re-run the head step's agent with a supplementary prompt
348
+ stop Stop background execution of a thread (keep thread active)
349
+ cancel Cancel a thread (stop execution and move to history)
350
+ join Block until a running thread finishes, then return the final result
351
+ read Read thread context as human-readable markdown
352
+ `;
353
+
354
+ const THREAD_START_HELP = `Usage: uwf thread start <workflow> [options]
355
+
356
+ Create a thread without executing
357
+
358
+ Arguments:
359
+ workflow Workflow name or hash
360
+
361
+ Options:
362
+ -p, --prompt <text> User prompt (required)
363
+ --cwd <path> Working directory for thread execution (default: process.cwd())
364
+ `;
365
+
366
+ const THREAD_EXEC_HELP = `Usage: uwf thread exec <thread-id> [options]
367
+
368
+ Execute one or more steps
369
+
370
+ Arguments:
371
+ thread-id Thread ULID
372
+
373
+ Options:
374
+ --agent <cmd> Override agent command
375
+ -c, --count <number> Number of steps to run (default: 1)
376
+ --background Run in background and return immediately
377
+ `;
378
+
379
+ const THREAD_SHOW_HELP = `Usage: uwf thread show <thread-id>
380
+
381
+ Show thread head pointer
382
+
383
+ Arguments:
384
+ thread-id Thread ULID
385
+ `;
386
+
387
+ const THREAD_LIST_HELP = `Usage: uwf thread list [options]
388
+
389
+ List threads (defaults to active: idle + running + corrupt)
390
+
391
+ Options:
392
+ --status <status> Filter by status: idle, running, end, cancelled, active, or comma-separated
393
+ --all Show all threads regardless of status
394
+ --after <date> Filter threads created after this date
395
+ --before <date> Filter threads created before this date
396
+ --limit <n> Return at most n threads (newest first)
397
+ --offset <m> Skip the first m threads (newest first)
398
+ --take <n> Alias for --limit
399
+ --skip <n> Alias for --offset
400
+ `;
401
+
402
+ const THREAD_RESUME_HELP = `Usage: uwf thread resume <thread-id> [options]
403
+
404
+ Resume a suspended thread and re-run the suspended role
405
+
406
+ Arguments:
407
+ thread-id Thread ULID
408
+
409
+ Options:
410
+ -p, --prompt <text> Supplementary info to append to the resume prompt
411
+ --agent <cmd> Override agent command
412
+ `;
413
+
414
+ const THREAD_POKE_HELP = `Usage: uwf thread poke <thread-id> [options]
415
+
416
+ Re-run the head step's agent with a supplementary prompt (replaces head step)
417
+
418
+ Arguments:
419
+ thread-id Thread ULID
420
+
421
+ Options:
422
+ -p, --prompt <text> Supplementary prompt for the agent (required)
423
+ --agent <cmd> Override agent command (defaults to head step's agent)
424
+ `;
425
+
426
+ const THREAD_STOP_HELP = `Usage: uwf thread stop <thread-id>
427
+
428
+ Stop background execution of a thread (keep thread active)
429
+
430
+ Arguments:
431
+ thread-id Thread ULID
432
+ `;
433
+
434
+ const THREAD_CANCEL_HELP = `Usage: uwf thread cancel <thread-id>
435
+
436
+ Cancel a thread (stop execution and move to history)
437
+
438
+ Arguments:
439
+ thread-id Thread ULID
440
+ `;
441
+
442
+ const THREAD_JOIN_HELP = `Usage: uwf thread join <thread-id> [options]
443
+
444
+ Block until a running thread finishes, then return the final result
445
+
446
+ Arguments:
447
+ thread-id Thread ULID
448
+
449
+ Options:
450
+ --timeout <seconds> Max seconds to wait before giving up
451
+ `;
452
+
453
+ const THREAD_READ_HELP = `Usage: uwf thread read <thread-id> [options]
454
+
455
+ Read thread context as human-readable markdown
456
+
457
+ Arguments:
458
+ thread-id Thread ULID
459
+
460
+ Options:
461
+ --quota <chars> Max output characters
462
+ --before <step-hash> Load steps before this hash (exclusive)
463
+ --start Include start step in output
464
+ `;
465
+
466
+ const STEP_HELP = `Usage: uwf step <command>
467
+
468
+ Step results (layer 3: single cycle)
469
+
470
+ Commands:
471
+ list List all steps in a thread
472
+ show Show details of a specific step
473
+ ask Ask a follow-up question to a historical step's agent
474
+ read Read a step's turns as human-readable markdown
475
+ turns Show all turns across a thread's steps
476
+ fork Fork a thread from a specific step
477
+ `;
478
+
479
+ const STEP_LIST_HELP = `Usage: uwf step list <thread-id>
480
+
481
+ List all steps in a thread
482
+
483
+ Arguments:
484
+ thread-id Thread ULID
485
+ `;
486
+
487
+ const STEP_SHOW_HELP = `Usage: uwf step show <step-hash>
488
+
489
+ Show details of a specific step
490
+
491
+ Arguments:
492
+ step-hash CAS hash of the StepNode
493
+ `;
494
+
495
+ const STEP_ASK_HELP = `Usage: uwf step ask <step-hash> [options]
496
+
497
+ Ask a follow-up question to a historical step's agent (read-only; no thread mutation)
498
+
499
+ Arguments:
500
+ step-hash CAS hash of the StepNode to query
501
+
502
+ Options:
503
+ -p, --prompt <text> Question to ask the step's agent (required)
504
+ --agent <cmd> Override agent command
505
+ --no-fork Skip session-fork; spawn fresh ask session
506
+ `;
507
+
508
+ const STEP_READ_HELP = `Usage: uwf step read <step-hash> [options]
509
+
510
+ Read a step's turns as human-readable markdown
511
+
512
+ Arguments:
513
+ step-hash CAS hash of the StepNode
514
+
515
+ Options:
516
+ --quota <chars> Max output characters (default: 4000)
517
+ --prompt Show the assembled prompt sent to the agent
518
+ `;
519
+
520
+ const STEP_TURNS_HELP = `Usage: uwf step turns <thread-id> [options]
521
+
522
+ Show all turns across a thread's steps (the whole-chain panorama)
523
+
524
+ Arguments:
525
+ thread-id Thread ULID
526
+
527
+ Options:
528
+ --role <role> Filter to one role's steps across the whole chain
529
+ --live Follow the in-flight step's turns
530
+ --limit <n> Max turns to show from the flattened cross-step sequence
531
+ --offset <n> Skip the first N turns of the flattened cross-step sequence
532
+ `;
533
+
534
+ const STEP_FORK_HELP = `Usage: uwf step fork <step-hash>
535
+
536
+ Fork a thread from a specific step
537
+
538
+ Arguments:
539
+ step-hash CAS hash of the StartNode or StepNode to fork from
540
+ `;
541
+
542
+ const SETUP_HELP = `Usage: uwf setup [options]
543
+
544
+ Configure the default agent. Run without --agent for interactive wizard.
545
+
546
+ Options:
547
+ --agent <name> Default agent adapter (e.g. builtin, or a Sumeru gateway alias)
548
+ `;
549
+
550
+ const LOG_HELP = `Usage: uwf log <command>
551
+
552
+ Process-level debug logs
553
+
554
+ Commands:
555
+ list List log files with sizes
556
+ show Show and filter log entries
557
+ clean Delete log files older than given date
558
+ `;
559
+
560
+ const LOG_LIST_HELP = `Usage: uwf log list
561
+
562
+ List log files with sizes
563
+ `;
564
+
565
+ const LOG_SHOW_HELP = `Usage: uwf log show [options]
566
+
567
+ Show and filter log entries
568
+
569
+ Options:
570
+ --thread <thread-id> Filter by thread ID
571
+ --process <pid> Filter by process ID
572
+ --date <date> Filter by date (YYYY-MM-DD)
573
+ `;
574
+
575
+ const LOG_CLEAN_HELP = `Usage: uwf log clean --before <date>
576
+
577
+ Delete log files older than given date
578
+
579
+ Options:
580
+ --before <date> Delete files before this date (YYYY-MM-DD) (required)
581
+ `;
582
+
583
+ const CONFIG_HELP = `Usage: uwf config <command>
584
+
585
+ Configuration management
586
+
587
+ Commands:
588
+ list Display all configuration values (masks API keys)
589
+ get Get a specific configuration value
590
+ set Set a specific configuration value
591
+ `;
592
+
593
+ const CONFIG_LIST_HELP = `Usage: uwf config list
594
+
595
+ Display all configuration values (masks API keys)
596
+ `;
597
+
598
+ const CONFIG_GET_HELP = `Usage: uwf config get <key>
599
+
600
+ Get a specific configuration value
601
+
602
+ Arguments:
603
+ key Dot-notation path to config value
604
+ `;
605
+
606
+ const CONFIG_SET_HELP = `Usage: uwf config set <key> <value>
607
+
608
+ Set a specific configuration value
609
+
610
+ Arguments:
611
+ key Dot-notation path to config value
612
+ value New value (use JSON array for 'args' key)
613
+ `;
614
+
615
+ const HELP_MAP: Record<string, string> = {
616
+ "": TOP_LEVEL_HELP,
617
+ prompt: PROMPT_HELP,
618
+ workflow: WORKFLOW_HELP,
619
+ "workflow add": WORKFLOW_ADD_HELP,
620
+ "workflow validate": WORKFLOW_VALIDATE_HELP,
621
+ "workflow show": WORKFLOW_SHOW_HELP,
622
+ "workflow list": WORKFLOW_LIST_HELP,
623
+ thread: THREAD_HELP,
624
+ "thread start": THREAD_START_HELP,
625
+ "thread exec": THREAD_EXEC_HELP,
626
+ "thread show": THREAD_SHOW_HELP,
627
+ "thread list": THREAD_LIST_HELP,
628
+ "thread resume": THREAD_RESUME_HELP,
629
+ "thread poke": THREAD_POKE_HELP,
630
+ "thread stop": THREAD_STOP_HELP,
631
+ "thread cancel": THREAD_CANCEL_HELP,
632
+ "thread join": THREAD_JOIN_HELP,
633
+ "thread read": THREAD_READ_HELP,
634
+ step: STEP_HELP,
635
+ "step list": STEP_LIST_HELP,
636
+ "step show": STEP_SHOW_HELP,
637
+ "step ask": STEP_ASK_HELP,
638
+ "step read": STEP_READ_HELP,
639
+ "step turns": STEP_TURNS_HELP,
640
+ "step fork": STEP_FORK_HELP,
641
+ setup: SETUP_HELP,
642
+ log: LOG_HELP,
643
+ "log list": LOG_LIST_HELP,
644
+ "log show": LOG_SHOW_HELP,
645
+ "log clean": LOG_CLEAN_HELP,
646
+ config: CONFIG_HELP,
647
+ "config list": CONFIG_LIST_HELP,
648
+ "config get": CONFIG_GET_HELP,
649
+ "config set": CONFIG_SET_HELP,
650
+ };
651
+
652
+ // --- Early intercepts (cli-kit v0.2.1 workarounds) ---
653
+
654
+ /** Print help text for the command path extracted from argv, then exit. */
655
+ function printHelp(argv: string[]): void {
656
+ const helpIdx = argv.findIndex((t) => t === "--help" || t === "-h");
657
+ const tokens = helpIdx >= 0 ? argv.slice(0, helpIdx).filter((t) => !t.startsWith("-")) : [];
658
+ for (let len = tokens.length; len >= 0; len--) {
659
+ const path = tokens.slice(0, len).join(" ");
660
+ const text = HELP_MAP[path];
661
+ if (text !== undefined) {
662
+ process.stdout.write(text);
663
+ process.exit(0);
664
+ }
665
+ }
666
+ process.stdout.write(TOP_LEVEL_HELP);
667
+ process.exit(0);
668
+ }
669
+
670
+ /** Intercept deprecated commands before cli-kit parsing. Prints message + exit(1). */
671
+ function handleDeprecated(argv: string[]): void {
672
+ const [cmd, sub] = argv;
673
+ if (cmd === "workflow" && sub === "put") {
674
+ process.stderr.write(`Error: Command 'workflow put' has been removed.
675
+ Use 'workflow add' instead.
676
+
677
+ For more information, see: uwf help workflow add
678
+ `);
679
+ process.exit(1);
680
+ }
681
+ if (cmd === "thread") {
682
+ if (sub === "step") {
683
+ process.stderr.write(`Error: Command 'thread step' has been removed.
684
+ Use 'thread exec' instead.
685
+
686
+ For more information, see: uwf help thread exec
687
+ `);
688
+ process.exit(1);
689
+ }
690
+ if (sub === "steps") {
691
+ process.stderr.write(`Error: Command 'thread steps' has been removed.
692
+ Use 'step list' instead.
693
+
694
+ For more information, see: uwf help step list
695
+ `);
696
+ process.exit(1);
697
+ }
698
+ if (sub === "step-details") {
699
+ process.stderr.write(`Error: Command 'thread step-details' has been removed.
700
+ Use 'step show' instead.
701
+
702
+ For more information, see: uwf help step show
703
+ `);
704
+ process.exit(1);
705
+ }
706
+ if (sub === "fork") {
707
+ process.stderr.write(`Error: Command 'thread fork' has been removed.
708
+ Use 'step fork' instead.
709
+
710
+ For more information, see: uwf help step fork
711
+ `);
712
+ process.exit(1);
713
+ }
714
+ if (sub === "kill") {
715
+ process.stderr.write(`Error: Command 'thread kill' has been removed.
716
+ Use 'thread stop' to stop background execution (keep thread active),
717
+ or 'thread cancel' to cancel and archive the thread.
718
+
719
+ For more information, see:
720
+ uwf help thread stop
721
+ uwf help thread cancel
722
+ `);
723
+ process.exit(1);
724
+ }
725
+ if (sub === "running") {
726
+ process.stderr.write(`Error: Command 'thread running' has been removed.
727
+ Use 'thread list --status running' instead.
728
+
729
+ For more information, see: uwf help thread list
730
+ `);
731
+ process.exit(1);
732
+ }
733
+ }
734
+ }
735
+
736
+ /** Strip `--format <value>` / `--format=value` from argv, returning cleaned argv. */
737
+ function stripFormatFlag(argv: string[]): string[] {
738
+ const out: string[] = [];
739
+ for (let i = 0; i < argv.length; i++) {
740
+ const token = argv[i];
741
+ if (token === "--format") {
742
+ const value = argv[i + 1];
743
+ if (value !== undefined) {
744
+ formatOverride = value;
745
+ i++;
746
+ }
747
+ continue;
748
+ }
749
+ if (token.startsWith("--format=")) {
750
+ formatOverride = token.slice("--format=".length);
751
+ continue;
752
+ }
753
+ out.push(token);
754
+ }
755
+ return out;
756
+ }
757
+
758
+ /** Strip `--count <value>` / `--count=value` / `-c <value>` from argv. */
759
+ function stripCountFlag(argv: string[]): string[] {
760
+ const out: string[] = [];
761
+ for (let i = 0; i < argv.length; i++) {
762
+ const token = argv[i];
763
+ if (token === "--count" || token === "-c") {
764
+ const value = argv[i + 1];
765
+ if (value !== undefined) {
766
+ countOverride = value;
767
+ i++;
768
+ }
769
+ continue;
770
+ }
771
+ if (token.startsWith("--count=")) {
772
+ countOverride = token.slice("--count=".length);
773
+ continue;
774
+ }
775
+ out.push(token);
776
+ }
777
+ return out;
778
+ }
779
+
780
+ // --- Build CLI with @ocas/cli-kit ---
781
+
782
+ const cli = createCLI({
783
+ name: "uwf",
784
+ version: VERSION,
785
+ });
786
+
787
+ const unknownSchema = z.unknown();
788
+
789
+ // ── workflow group ───────────────────────────────────────────────────────────
790
+
791
+ const workflow = cli.command("workflow");
792
+
793
+ workflow
794
+ .command("add")
795
+ .returns(unknownSchema, "")
796
+ .action(async (_args, flags) => {
797
+ const file = requirePositional(flags, 0, "file");
798
+ const storageRoot = resolveStorageRoot();
799
+ await runAction(async () => {
800
+ const result = await cmdWorkflowAdd(storageRoot, file);
801
+ await writeOutput(toWorkflowAddPayload(result), "workflow-add", storageRoot);
802
+ });
803
+ return undefined;
804
+ });
805
+
806
+ workflow
807
+ .command("validate")
808
+ .returns(unknownSchema, "")
809
+ .action(async (_args, flags) => {
810
+ const file = requirePositional(flags, 0, "file");
811
+ const storageRoot = resolveStorageRoot();
812
+ await runAction(async () => {
813
+ const errors = await cmdWorkflowValidate(file);
814
+ await writeOutput(toValidateResultPayload(errors), "validate-result", storageRoot);
815
+ if (errors.length > 0) {
816
+ process.exit(1);
817
+ }
818
+ });
819
+ return undefined;
820
+ });
821
+
822
+ workflow
823
+ .command("show")
824
+ .returns(unknownSchema, "")
825
+ .action(async (_args, flags) => {
826
+ const id = requirePositional(flags, 0, "id");
827
+ const storageRoot = resolveStorageRoot();
828
+ await runAction(async () => {
829
+ const result = await cmdWorkflowShow(storageRoot, id, process.cwd());
830
+ await writeOutput(toWorkflowDetailPayload(result), "workflow-detail", storageRoot);
831
+ });
832
+ return undefined;
833
+ });
834
+
835
+ workflow
312
836
  .command("list")
313
- .description("List threads (defaults to active: idle + running + corrupt)")
314
- .option(
315
- "--status <status>",
316
- "Filter by status: idle, running, end, cancelled, active (idle+running), or comma-separated values",
317
- )
318
- .option("--all", "Show all threads regardless of status (overrides default active-only filter)")
319
- .option("--after <date>", "Filter threads created after this date (ISO or relative like '7d')")
320
- .option("--before <date>", "Filter threads created before this date (ISO or relative like '7d')")
321
- .option("--skip <n>", "Skip first n threads")
322
- .option("--take <n>", "Return at most n threads")
323
- .action(
324
- (opts: {
325
- status: string | undefined;
326
- all: boolean | undefined;
327
- after: string | undefined;
328
- before: string | undefined;
329
- skip: string | undefined;
330
- take: string | undefined;
331
- }) => {
332
- const storageRoot = resolveStorageRoot();
333
- runAction(async () => {
334
- const statusFilter = parseStatusFilter(opts.status);
335
- const nowMs = Date.now();
336
- const { afterMs, beforeMs } = parseTimeFilters(opts.after, opts.before, nowMs);
337
- const { skip, take } = parsePaginationOptions(opts.skip, opts.take);
338
- const showAll = opts.all === true;
339
-
340
- const result = await cmdThreadList(
341
- storageRoot,
342
- statusFilter,
343
- afterMs,
344
- beforeMs,
345
- skip,
346
- take,
347
- showAll,
348
- );
349
- await writeOutput(toThreadListPayload(result), "thread-list", storageRoot);
350
- });
351
- },
352
- );
837
+ .returns(unknownSchema, "")
838
+ .action(async () => {
839
+ const storageRoot = resolveStorageRoot();
840
+ await runAction(async () => {
841
+ const result = await cmdWorkflowList(storageRoot, process.cwd());
842
+ await writeOutput(toWorkflowListPayload(result), "workflow-list", storageRoot);
843
+ });
844
+ return undefined;
845
+ });
846
+
847
+ // ── thread group ─────────────────────────────────────────────────────────────
848
+
849
+ const thread = cli.command("thread");
353
850
 
354
851
  thread
355
- .command("resume")
356
- .description("Resume a suspended thread and re-run the suspended role")
357
- .argument("<thread-id>", "Thread ULID")
358
- .option("-p, --prompt <text>", "Supplementary info to append to the resume prompt")
359
- .option("--agent <cmd>", "Override agent command")
360
- .action((threadId: string, opts: { prompt: string | undefined; agent: string | undefined }) => {
852
+ .command("start")
853
+ .flag("prompt", { type: "string" })
854
+ .flag("p", { type: "string" })
855
+ .flag("cwd", { type: "string" })
856
+ .returns(unknownSchema, "")
857
+ .action(async (_args, flags) => {
858
+ const workflowName = requirePositional(flags, 0, "workflow");
859
+ const prompt = (flags.prompt as string | undefined) ?? (flags.p as string | undefined);
860
+ if (prompt === undefined) {
861
+ process.stderr.write("Error: missing required option: -p, --prompt <text>\n");
862
+ process.exit(1);
863
+ }
864
+ const storageRoot = resolveStorageRoot();
865
+ await runAction(async () => {
866
+ const result = await cmdThreadStart(
867
+ storageRoot,
868
+ workflowName,
869
+ prompt,
870
+ process.cwd(),
871
+ (flags.cwd as string | undefined) ?? process.cwd(),
872
+ );
873
+ await writeOutput(toThreadStartPayload(result), "thread-start", storageRoot);
874
+ });
875
+ return undefined;
876
+ });
877
+
878
+ thread
879
+ .command("exec")
880
+ .flag("agent", { type: "string" })
881
+ .flag("background", { type: "boolean", default: false })
882
+ .flag("_background-worker", { type: "boolean", default: false })
883
+ .returns(unknownSchema, "")
884
+ .action(async (_args, flags) => {
885
+ const threadId = requirePositional(flags, 0, "thread-id");
361
886
  const storageRoot = resolveStorageRoot();
362
- runAction(async () => {
363
- const supplement = opts.prompt ?? null;
364
- const agentOverride = opts.agent ?? null;
365
- const result = await cmdThreadResume(
887
+ await runAction(async () => {
888
+ const agentOverride = (flags.agent as string | undefined) ?? null;
889
+ const count = countOverride !== null ? Number(countOverride) : 1;
890
+ const background = flags.background as boolean;
891
+ const backgroundWorker = (flags["_background-worker"] as boolean) ?? false;
892
+ const results = await cmdThreadExec(
366
893
  storageRoot,
367
- threadId as ThreadId,
368
- supplement,
894
+ threadId,
369
895
  agentOverride,
896
+ count,
897
+ background,
898
+ backgroundWorker,
370
899
  );
900
+ await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
901
+ });
902
+ return undefined;
903
+ });
904
+
905
+ thread
906
+ .command("show")
907
+ .returns(unknownSchema, "")
908
+ .action(async (_args, flags) => {
909
+ const threadId = requirePositional(flags, 0, "thread-id");
910
+ const storageRoot = resolveStorageRoot();
911
+ await runAction(async () => {
912
+ const result = await cmdThreadShow(storageRoot, threadId);
371
913
  await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
372
914
  });
915
+ return undefined;
373
916
  });
374
917
 
375
918
  thread
376
- .command("poke")
377
- .description("Re-run the head step's agent with a supplementary prompt (replaces head step)")
378
- .argument("<thread-id>", "Thread ULID")
379
- .requiredOption("-p, --prompt <text>", "Supplementary prompt for the agent")
380
- .option("--agent <cmd>", "Override agent command (defaults to head step's agent)")
381
- .action((threadId: string, opts: { prompt: string; agent: string | undefined }) => {
919
+ .command("list")
920
+ .flag("status", { type: "string" })
921
+ .flag("all", { type: "boolean", default: false })
922
+ .flag("after", { type: "string" })
923
+ .flag("before", { type: "string" })
924
+ .flag("skip", { type: "string" })
925
+ .flag("take", { type: "string" })
926
+ .flag("limit", { type: "string" })
927
+ .flag("offset", { type: "string" })
928
+ .returns(unknownSchema, "")
929
+ .action(async (_args, flags) => {
382
930
  const storageRoot = resolveStorageRoot();
383
- runAction(async () => {
384
- const agentOverride = opts.agent ?? null;
385
- const result = await cmdThreadPoke(
931
+ await runAction(async () => {
932
+ const statusFilter = parseStatusFilter(flags.status as string | undefined);
933
+ const nowMs = Date.now();
934
+ const { afterMs, beforeMs } = parseTimeFilters(
935
+ flags.after as string | undefined,
936
+ flags.before as string | undefined,
937
+ nowMs,
938
+ );
939
+ const { skip, take } = resolveThreadListPagination({
940
+ skip: flags.skip as string | undefined,
941
+ take: flags.take as string | undefined,
942
+ limit: flags.limit as string | undefined,
943
+ offset: flags.offset as string | undefined,
944
+ });
945
+ const showAll = flags.all === true;
946
+ const result = await cmdThreadList(
386
947
  storageRoot,
387
- threadId as ThreadId,
388
- opts.prompt,
389
- agentOverride,
948
+ statusFilter,
949
+ afterMs,
950
+ beforeMs,
951
+ skip,
952
+ take,
953
+ showAll,
390
954
  );
955
+ await writeOutput(toThreadListPayload(result), "thread-list", storageRoot);
956
+ });
957
+ return undefined;
958
+ });
959
+
960
+ thread
961
+ .command("resume")
962
+ .flag("prompt", { type: "string" })
963
+ .flag("p", { type: "string" })
964
+ .flag("agent", { type: "string" })
965
+ .returns(unknownSchema, "")
966
+ .action(async (_args, flags) => {
967
+ const threadId = requirePositional(flags, 0, "thread-id") as ThreadId;
968
+ const storageRoot = resolveStorageRoot();
969
+ const prompt = (flags.prompt as string | undefined) ?? (flags.p as string | undefined);
970
+ await runAction(async () => {
971
+ const supplement = prompt ?? null;
972
+ const agentOverride = (flags.agent as string | undefined) ?? null;
973
+ const result = await cmdThreadResume(storageRoot, threadId, supplement, agentOverride);
391
974
  await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
392
975
  });
976
+ return undefined;
977
+ });
978
+
979
+ thread
980
+ .command("poke")
981
+ .flag("prompt", { type: "string" })
982
+ .flag("p", { type: "string" })
983
+ .flag("agent", { type: "string" })
984
+ .returns(unknownSchema, "")
985
+ .action(async (_args, flags) => {
986
+ const threadId = requirePositional(flags, 0, "thread-id") as ThreadId;
987
+ const prompt = (flags.prompt as string | undefined) ?? (flags.p as string | undefined);
988
+ if (prompt === undefined) {
989
+ process.stderr.write("Error: missing required option: -p, --prompt <text>\n");
990
+ process.exit(1);
991
+ }
992
+ const storageRoot = resolveStorageRoot();
993
+ await runAction(async () => {
994
+ const agentOverride = (flags.agent as string | undefined) ?? null;
995
+ const result = await cmdThreadPoke(storageRoot, threadId, prompt, agentOverride);
996
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
997
+ });
998
+ return undefined;
393
999
  });
394
1000
 
395
1001
  thread
396
1002
  .command("stop")
397
- .description("Stop background execution of a thread (keep thread active)")
398
- .argument("<thread-id>", "Thread ULID")
399
- .action((threadId: string) => {
1003
+ .returns(unknownSchema, "")
1004
+ .action(async (_args, flags) => {
1005
+ const threadId = requirePositional(flags, 0, "thread-id");
400
1006
  const storageRoot = resolveStorageRoot();
401
- runAction(async () => {
1007
+ await runAction(async () => {
402
1008
  const result = await cmdThreadStop(storageRoot, threadId);
403
1009
  writeRawOutput(result, "thread stop");
404
1010
  });
1011
+ return undefined;
405
1012
  });
406
1013
 
407
1014
  thread
408
1015
  .command("cancel")
409
- .description("Cancel a thread (stop execution and move to history)")
410
- .argument("<thread-id>", "Thread ULID")
411
- .action((threadId: string) => {
1016
+ .returns(unknownSchema, "")
1017
+ .action(async (_args, flags) => {
1018
+ const threadId = requirePositional(flags, 0, "thread-id");
412
1019
  const storageRoot = resolveStorageRoot();
413
- runAction(async () => {
1020
+ await runAction(async () => {
414
1021
  const result = await cmdThreadCancel(storageRoot, threadId);
415
1022
  writeRawOutput(result, "thread cancel");
416
1023
  });
1024
+ return undefined;
417
1025
  });
418
1026
 
419
1027
  thread
420
1028
  .command("join")
421
- .description("Block until a running thread finishes, then return the final result")
422
- .argument("<thread-id>", "Thread ULID")
423
- .option("--timeout <seconds>", "Max seconds to wait before giving up")
424
- .action((threadId: string, opts: { timeout: string | undefined }) => {
1029
+ .flag("timeout", { type: "string" })
1030
+ .returns(unknownSchema, "")
1031
+ .action(async (_args, flags) => {
1032
+ const threadId = requirePositional(flags, 0, "thread-id");
425
1033
  const storageRoot = resolveStorageRoot();
426
- runAction(async () => {
427
- const timeoutMs = opts.timeout !== undefined ? Number(opts.timeout) * 1000 : null;
1034
+ await runAction(async () => {
1035
+ const timeoutRaw = flags.timeout as string | undefined;
1036
+ const timeoutMs = timeoutRaw !== undefined ? Number(timeoutRaw) * 1000 : null;
428
1037
  if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
429
1038
  process.stderr.write("invalid --timeout: must be a positive number\n");
430
1039
  process.exit(1);
@@ -432,375 +1041,339 @@ thread
432
1041
  const results = await cmdThreadJoin(storageRoot, threadId, timeoutMs);
433
1042
  await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
434
1043
  });
1044
+ return undefined;
435
1045
  });
436
1046
 
437
1047
  thread
438
1048
  .command("read")
439
- .description("Read thread context as human-readable markdown")
440
- .argument("<thread-id>", "Thread ULID")
441
- .option("--quota <chars>", "Max output characters", String(THREAD_READ_DEFAULT_QUOTA))
442
- .option("--before <step-hash>", "Load steps before this hash (exclusive)")
443
- .option("--start", "Include start step in output")
444
- .action(
445
- (threadId: string, opts: { quota: string; before: string | undefined; start: boolean }) => {
446
- const storageRoot = resolveStorageRoot();
447
- runAction(async () => {
448
- const quota = Number.parseInt(opts.quota, 10);
449
- if (!Number.isFinite(quota) || quota < 1) {
450
- process.stderr.write("invalid --quota: must be a positive integer\n");
451
- process.exit(1);
452
- }
453
- const before = opts.before ?? null;
454
- const markdown = await cmdThreadRead(
455
- storageRoot,
456
- threadId as ThreadId,
457
- quota,
458
- before,
459
- opts.start ?? false,
460
- );
461
- process.stdout.write(markdown.endsWith("\n") ? markdown : `${markdown}\n`);
462
- });
463
- },
464
- );
1049
+ .flag("quota", { type: "string", default: String(THREAD_READ_DEFAULT_QUOTA) })
1050
+ .flag("before", { type: "string" })
1051
+ .flag("start", { type: "boolean", default: false })
1052
+ .returns(unknownSchema, "")
1053
+ .action(async (_args, flags) => {
1054
+ const threadId = requirePositional(flags, 0, "thread-id") as ThreadId;
1055
+ const storageRoot = resolveStorageRoot();
1056
+ await runAction(async () => {
1057
+ const quota = Number.parseInt(flags.quota as string, 10);
1058
+ if (!Number.isFinite(quota) || quota < 1) {
1059
+ process.stderr.write("invalid --quota: must be a positive integer\n");
1060
+ process.exit(1);
1061
+ }
1062
+ const before = (flags.before as string | undefined) ?? null;
1063
+ const markdown = await cmdThreadRead(
1064
+ storageRoot,
1065
+ threadId,
1066
+ quota,
1067
+ before,
1068
+ (flags.start as boolean) ?? false,
1069
+ );
1070
+ process.stdout.write(markdown.endsWith("\n") ? markdown : `${markdown}\n`);
1071
+ });
1072
+ return undefined;
1073
+ });
1074
+
1075
+ // ── step group ───────────────────────────────────────────────────────────────
465
1076
 
466
- const step = program.command("step").description("Step results (layer 3: single cycle)");
1077
+ const step = cli.command("step");
467
1078
 
468
1079
  step
469
1080
  .command("list")
470
- .description("List all steps in a thread")
471
- .argument("<thread-id>", "Thread ULID")
472
- .action((threadId: string) => {
1081
+ .returns(unknownSchema, "")
1082
+ .action(async (_args, flags) => {
1083
+ const threadId = requirePositional(flags, 0, "thread-id");
473
1084
  const storageRoot = resolveStorageRoot();
474
- runAction(async () => {
1085
+ await runAction(async () => {
475
1086
  const result = await cmdStepList(storageRoot, threadId);
476
1087
  await writeOutput(toStepListPayload(result), "step-list", storageRoot);
477
1088
  });
1089
+ return undefined;
478
1090
  });
479
1091
 
480
1092
  step
481
1093
  .command("show")
482
- .description("Show details of a specific step")
483
- .argument("<step-hash>", "CAS hash of the StepNode")
484
- .action((stepHash: string) => {
1094
+ .returns(unknownSchema, "")
1095
+ .action(async (_args, flags) => {
1096
+ const stepHash = requirePositional(flags, 0, "step-hash") as CasRef;
485
1097
  const storageRoot = resolveStorageRoot();
486
- runAction(async () => {
487
- const detail = await cmdStepShow(storageRoot, stepHash as CasRef);
488
- await writeOutput(
489
- toStepDetailPayload(stepHash as CasRef, detail),
490
- "step-detail",
491
- storageRoot,
492
- );
1098
+ await runAction(async () => {
1099
+ const detail = await cmdStepShow(storageRoot, stepHash);
1100
+ await writeOutput(toStepDetailPayload(stepHash, detail), "step-detail", storageRoot);
493
1101
  });
1102
+ return undefined;
494
1103
  });
495
1104
 
496
1105
  step
497
1106
  .command("ask")
498
- .description(
499
- "Ask a follow-up question to a historical step's agent (read-only; no thread mutation)",
500
- )
501
- .argument("<step-hash>", "CAS hash of the StepNode to query")
502
- .requiredOption("-p, --prompt <text>", "Question to ask the step's agent")
503
- .option("--agent <cmd>", "Override agent command (defaults to the step's recorded agent)")
504
- .option(
505
- "--no-fork",
506
- "Skip session-fork; spawn the agent in a fresh ask session and inject the step's detail ref for context",
507
- )
508
- .action(
509
- (stepHash: string, opts: { prompt: string; agent: string | undefined; fork: boolean }) => {
510
- const storageRoot = resolveStorageRoot();
511
- runAction(async () => {
512
- const stdout = await cmdStepAsk(storageRoot, stepHash as CasRef, {
513
- prompt: opts.prompt,
514
- agentOverride: opts.agent ?? null,
515
- fork: opts.fork,
516
- });
517
- process.stdout.write(stdout.endsWith("\n") ? stdout : `${stdout}\n`);
1107
+ .flag("prompt", { type: "string" })
1108
+ .flag("p", { type: "string" })
1109
+ .flag("agent", { type: "string" })
1110
+ .flag("no-fork", { type: "boolean", default: false })
1111
+ .returns(unknownSchema, "")
1112
+ .action(async (_args, flags) => {
1113
+ const stepHash = requirePositional(flags, 0, "step-hash") as CasRef;
1114
+ const prompt = (flags.prompt as string | undefined) ?? (flags.p as string | undefined);
1115
+ if (prompt === undefined) {
1116
+ process.stderr.write("Error: missing required option: -p, --prompt <text>\n");
1117
+ process.exit(1);
1118
+ }
1119
+ const storageRoot = resolveStorageRoot();
1120
+ await runAction(async () => {
1121
+ const stdout = await cmdStepAsk(storageRoot, stepHash, {
1122
+ prompt,
1123
+ agentOverride: (flags.agent as string | undefined) ?? null,
1124
+ fork: !(flags["no-fork"] as boolean),
518
1125
  });
519
- },
520
- );
1126
+ process.stdout.write(stdout.endsWith("\n") ? stdout : `${stdout}\n`);
1127
+ });
1128
+ return undefined;
1129
+ });
521
1130
 
522
1131
  step
523
1132
  .command("read")
524
- .description("Read a step's turns as human-readable markdown")
525
- .argument("<step-hash>", "CAS hash of the StepNode")
526
- .option("--quota <chars>", "Max output characters", "4000")
527
- .option("--prompt", "Show the assembled prompt sent to the agent instead of turns")
528
- .action((stepHash: string, opts: { quota: string; prompt: boolean }) => {
1133
+ .flag("quota", { type: "string", default: "4000" })
1134
+ .flag("prompt", { type: "boolean", default: false })
1135
+ .returns(unknownSchema, "")
1136
+ .action(async (_args, flags) => {
1137
+ const stepHash = requirePositional(flags, 0, "step-hash") as CasRef;
529
1138
  const storageRoot = resolveStorageRoot();
530
- runAction(async () => {
531
- const quota = Number.parseInt(opts.quota, 10);
1139
+ await runAction(async () => {
1140
+ const quota = Number.parseInt(flags.quota as string, 10);
532
1141
  if (!Number.isFinite(quota) || quota < 1) {
533
1142
  process.stderr.write("invalid --quota: must be a positive integer\n");
534
1143
  process.exit(1);
535
1144
  }
536
- const markdown = await cmdStepRead(
537
- storageRoot,
538
- stepHash as CasRef,
539
- quota,
540
- opts.prompt === true,
541
- );
1145
+ const markdown = await cmdStepRead(storageRoot, stepHash, quota, flags.prompt === true);
542
1146
  process.stdout.write(markdown.endsWith("\n") ? markdown : `${markdown}\n`);
543
1147
  });
1148
+ return undefined;
544
1149
  });
545
1150
 
546
1151
  step
547
- .command("fork")
548
- .description("Fork a thread from a specific step")
549
- .argument("<step-hash>", "CAS hash of the StartNode or StepNode to fork from")
550
- .action((stepHash: string) => {
1152
+ .command("turns")
1153
+ .flag("role", { type: "string" })
1154
+ .flag("live", { type: "boolean", default: false })
1155
+ .flag("limit", { type: "string" })
1156
+ .flag("offset", { type: "string" })
1157
+ .returns(unknownSchema, "")
1158
+ .action(async (_args, flags) => {
1159
+ const threadId = requirePositional(flags, 0, "thread-id") as ThreadId;
551
1160
  const storageRoot = resolveStorageRoot();
552
- runAction(async () => {
553
- const result = await cmdStepFork(storageRoot, stepHash as CasRef);
554
- writeRawOutput(result);
1161
+ await runAction(async () => {
1162
+ const limit = parseTurnsPageOption("--limit", flags.limit as string | undefined);
1163
+ const offset = parseTurnsPageOption("--offset", flags.offset as string | undefined) ?? 0;
1164
+ const markdown = await cmdStepTurns(storageRoot, threadId, {
1165
+ role: (flags.role as string | undefined) ?? null,
1166
+ live: flags.live === true,
1167
+ limit,
1168
+ offset,
1169
+ });
1170
+ if (markdown !== "") {
1171
+ process.stdout.write(markdown.endsWith("\n") ? markdown : `${markdown}\n`);
1172
+ }
555
1173
  });
1174
+ return undefined;
556
1175
  });
557
1176
 
558
- // ── Deprecation Handlers ──────────────────────────────────────────────────────
559
- // These commands have been removed. Show helpful error messages.
560
-
561
- workflow
562
- .command("put")
563
- .description("[DEPRECATED] Use 'workflow add' instead")
564
- .argument("<file>", "Workflow YAML file")
565
- .action(() => {
566
- process.stderr.write(`Error: Command 'workflow put' has been removed.
567
- Use 'workflow add' instead.
568
-
569
- For more information, see: uwf help workflow add
570
- `);
571
- process.exit(1);
572
- });
573
-
574
- thread
575
- .command("step")
576
- .description("[DEPRECATED] Use 'thread exec' instead")
577
- .argument("<thread-id>", "Thread ULID")
578
- .allowUnknownOption()
579
- .action(() => {
580
- process.stderr.write(`Error: Command 'thread step' has been removed.
581
- Use 'thread exec' instead.
582
-
583
- For more information, see: uwf help thread exec
584
- `);
585
- process.exit(1);
586
- });
587
-
588
- thread
589
- .command("steps")
590
- .description("[DEPRECATED] Use 'step list' instead")
591
- .argument("<thread-id>", "Thread ULID")
592
- .action(() => {
593
- process.stderr.write(`Error: Command 'thread steps' has been removed.
594
- Use 'step list' instead.
595
-
596
- For more information, see: uwf help step list
597
- `);
598
- process.exit(1);
599
- });
600
-
601
- thread
602
- .command("step-details")
603
- .description("[DEPRECATED] Use 'step show' instead")
604
- .argument("<step-hash>", "Step hash")
605
- .action(() => {
606
- process.stderr.write(`Error: Command 'thread step-details' has been removed.
607
- Use 'step show' instead.
608
-
609
- For more information, see: uwf help step show
610
- `);
611
- process.exit(1);
612
- });
613
-
614
- thread
1177
+ step
615
1178
  .command("fork")
616
- .description("[DEPRECATED] Use 'step fork' instead")
617
- .argument("<step-hash>", "Step hash")
618
- .action(() => {
619
- process.stderr.write(`Error: Command 'thread fork' has been removed.
620
- Use 'step fork' instead.
621
-
622
- For more information, see: uwf help step fork
623
- `);
624
- process.exit(1);
625
- });
626
-
627
- thread
628
- .command("kill")
629
- .description("[DEPRECATED] Use 'thread stop' or 'thread cancel' instead")
630
- .argument("<thread-id>", "Thread ULID")
631
- .action(() => {
632
- process.stderr.write(`Error: Command 'thread kill' has been removed.
633
- Use 'thread stop' to stop background execution (keep thread active),
634
- or 'thread cancel' to cancel and archive the thread.
635
-
636
- For more information, see:
637
- uwf help thread stop
638
- uwf help thread cancel
639
- `);
640
- process.exit(1);
1179
+ .returns(unknownSchema, "")
1180
+ .action(async (_args, flags) => {
1181
+ const stepHash = requirePositional(flags, 0, "step-hash") as CasRef;
1182
+ const storageRoot = resolveStorageRoot();
1183
+ await runAction(async () => {
1184
+ const result = await cmdStepFork(storageRoot, stepHash);
1185
+ writeRawOutput(result);
1186
+ });
1187
+ return undefined;
641
1188
  });
642
1189
 
643
- thread
644
- .command("running")
645
- .description("[DEPRECATED] Use 'thread list --status running' instead")
646
- .action(() => {
647
- process.stderr.write(`Error: Command 'thread running' has been removed.
648
- Use 'thread list --status running' instead.
649
-
650
- For more information, see: uwf help thread list
651
- `);
652
- process.exit(1);
653
- });
1190
+ // ── prompt group ─────────────────────────────────────────────────────────────
654
1191
 
655
- const prompt = program.command("prompt").description("Built-in prompt references for agents");
656
- prompt.addHelpCommand(false);
1192
+ const promptGroup = cli.command("prompt");
657
1193
 
658
- prompt
1194
+ promptGroup
659
1195
  .command("usage")
660
- .description("Print the usage reference (CLI guide + typical workflows)")
661
- .action(() => {
1196
+ .returns(unknownSchema, "")
1197
+ .action(async () => {
662
1198
  console.log(cmdPromptUsage());
1199
+ return undefined;
663
1200
  });
664
1201
 
665
- prompt
1202
+ promptGroup
666
1203
  .command("bootstrap")
667
- .description("Print setup instructions for installing uwf skills")
668
- .action(() => {
1204
+ .returns(unknownSchema, "")
1205
+ .action(async () => {
669
1206
  console.log(cmdPromptBootstrap());
1207
+ return undefined;
670
1208
  });
671
1209
 
672
- prompt
1210
+ promptGroup
673
1211
  .command("workflow-authoring")
674
- .description("Print the workflow authoring reference (YAML design guide)")
675
- .action(() => {
1212
+ .returns(unknownSchema, "")
1213
+ .action(async () => {
676
1214
  console.log(cmdPromptWorkflowAuthoring());
1215
+ return undefined;
677
1216
  });
678
1217
 
679
- prompt
1218
+ promptGroup
680
1219
  .command("adapter-developing")
681
- .description("Print the adapter developing reference (building agent adapters)")
682
- .action(() => {
1220
+ .returns(unknownSchema, "")
1221
+ .action(async () => {
683
1222
  console.log(cmdPromptAdapterDeveloping());
1223
+ return undefined;
684
1224
  });
685
1225
 
686
- prompt
1226
+ promptGroup
687
1227
  .command("list")
688
- .description("List all available prompt names")
689
- .action(() => {
1228
+ .returns(unknownSchema, "")
1229
+ .action(async () => {
690
1230
  console.log(cmdPromptList().join("\n"));
1231
+ return undefined;
691
1232
  });
692
1233
 
693
- program
1234
+ // ── setup (top-level) ────────────────────────────────────────────────────────
1235
+
1236
+ cli
694
1237
  .command("setup")
695
- .description(
696
- "Configure the default agent. Run without --agent for interactive wizard.\n" +
697
- "Each adapter owns its own LLM configuration — the engine config is LLM-free.",
698
- )
699
- .option("--agent <name>", "Default agent adapter (e.g. hermes → uwf-hermes)")
700
- .action((opts: { agent?: string }) => {
1238
+ .flag("agent", { type: "string" })
1239
+ .returns(unknownSchema, "")
1240
+ .action(async (_args, flags) => {
701
1241
  const storageRoot = resolveStorageRoot();
702
- runAction(async () => {
703
- if (opts.agent !== undefined && opts.agent !== "") {
704
- const result = await cmdSetup({ agent: opts.agent, storageRoot });
1242
+ const agent = flags.agent as string | undefined;
1243
+ await runAction(async () => {
1244
+ if (agent !== undefined && agent !== "") {
1245
+ const result = await cmdSetup({ agent, storageRoot });
705
1246
  writeRawOutput(result);
706
1247
  } else {
707
1248
  await cmdSetupInteractive(storageRoot);
708
1249
  }
709
1250
  });
1251
+ return undefined;
710
1252
  });
711
1253
 
712
- const log = program.command("log").description("Process-level debug logs");
1254
+ // ── log group ────────────────────────────────────────────────────────────────
1255
+
1256
+ const log = cli.command("log");
713
1257
 
714
1258
  log
715
1259
  .command("list")
716
- .description("List log files with sizes")
717
- .action(() => {
1260
+ .returns(unknownSchema, "")
1261
+ .action(async () => {
718
1262
  const storageRoot = resolveStorageRoot();
719
- runAction(async () => {
1263
+ await runAction(async () => {
720
1264
  const result = await cmdLogList(storageRoot);
721
1265
  writeRawOutput(result, "log list");
722
1266
  });
1267
+ return undefined;
723
1268
  });
724
1269
 
725
1270
  log
726
1271
  .command("show")
727
- .description("Show and filter log entries")
728
- .option("--thread <thread-id>", "Filter by thread ID")
729
- .option("--process <pid>", "Filter by process ID")
730
- .option("--date <date>", "Filter by date (YYYY-MM-DD)")
731
- .action(
732
- (opts: {
733
- thread: string | undefined;
734
- process: string | undefined;
735
- date: string | undefined;
736
- }) => {
737
- const storageRoot = resolveStorageRoot();
738
- runAction(async () => {
739
- const result = await cmdLogShow(storageRoot, {
740
- thread: opts.thread ?? null,
741
- process: opts.process ?? null,
742
- date: opts.date ?? null,
743
- });
744
- writeRawOutput(result, "log show");
1272
+ .flag("thread", { type: "string" })
1273
+ .flag("process", { type: "string" })
1274
+ .flag("date", { type: "string" })
1275
+ .returns(unknownSchema, "")
1276
+ .action(async (_args, flags) => {
1277
+ const storageRoot = resolveStorageRoot();
1278
+ await runAction(async () => {
1279
+ const result = await cmdLogShow(storageRoot, {
1280
+ thread: (flags.thread as string | undefined) ?? null,
1281
+ process: (flags.process as string | undefined) ?? null,
1282
+ date: (flags.date as string | undefined) ?? null,
745
1283
  });
746
- },
747
- );
1284
+ writeRawOutput(result, "log show");
1285
+ });
1286
+ return undefined;
1287
+ });
748
1288
 
749
1289
  log
750
1290
  .command("clean")
751
- .description("Delete log files older than given date")
752
- .requiredOption("--before <date>", "Delete files before this date (YYYY-MM-DD)")
753
- .action((opts: { before: string }) => {
1291
+ .flag("before", { type: "string" })
1292
+ .returns(unknownSchema, "")
1293
+ .action(async (_args, flags) => {
1294
+ const before = flags.before as string | undefined;
1295
+ if (before === undefined) {
1296
+ process.stderr.write("Error: missing required option: --before <date>\n");
1297
+ process.exit(1);
1298
+ }
754
1299
  const storageRoot = resolveStorageRoot();
755
- runAction(async () => {
756
- const result = await cmdLogClean(storageRoot, opts.before);
1300
+ await runAction(async () => {
1301
+ const result = await cmdLogClean(storageRoot, before);
757
1302
  writeRawOutput(result);
758
1303
  });
1304
+ return undefined;
759
1305
  });
760
1306
 
761
- const config = program.command("config").description("Configuration management");
1307
+ // ── config group ─────────────────────────────────────────────────────────────
1308
+
1309
+ const config = cli.command("config");
762
1310
 
763
1311
  config
764
1312
  .command("list")
765
- .description("Display all configuration values (masks API keys)")
766
- .action(() => {
1313
+ .returns(unknownSchema, "")
1314
+ .action(async () => {
767
1315
  const storageRoot = resolveStorageRoot();
768
- runAction(async () => {
1316
+ await runAction(async () => {
769
1317
  const result = await cmdConfigList(storageRoot);
770
1318
  writeRawOutput(result, "config list");
771
1319
  });
1320
+ return undefined;
772
1321
  });
773
1322
 
774
1323
  config
775
1324
  .command("get")
776
- .description("Get a specific configuration value")
777
- .argument(
778
- "<key>",
779
- "Dot-notation path to config value (e.g., defaultAgent, providers.dashscope.baseUrl)",
780
- )
781
- .action((key: string) => {
1325
+ .returns(unknownSchema, "")
1326
+ .action(async (_args, flags) => {
1327
+ const key = requirePositional(flags, 0, "key");
782
1328
  const storageRoot = resolveStorageRoot();
783
- runAction(async () => {
1329
+ await runAction(async () => {
784
1330
  const result = await cmdConfigGet(storageRoot, key);
785
1331
  writeRawOutput({ value: result }, "config get");
786
1332
  });
1333
+ return undefined;
787
1334
  });
788
1335
 
789
1336
  config
790
1337
  .command("set")
791
- .description("Set a specific configuration value")
792
- .argument("<key>", "Dot-notation path to config value")
793
- .argument("<value>", "New value (use JSON array for 'args' key, e.g., '[\"--flag\"]')")
794
- .action((key: string, value: string) => {
1338
+ .returns(unknownSchema, "")
1339
+ .action(async (_args, flags) => {
1340
+ const key = requirePositional(flags, 0, "key");
1341
+ const value = requirePositional(flags, 1, "value");
795
1342
  const storageRoot = resolveStorageRoot();
796
- runAction(async () => {
1343
+ await runAction(async () => {
797
1344
  const result = await cmdConfigSet(storageRoot, key, value);
798
1345
  writeRawOutput(result, "config set");
799
1346
  });
1347
+ return undefined;
800
1348
  });
801
1349
 
802
- program.parseAsync(process.argv).catch((e: unknown) => {
803
- const message = e instanceof Error ? e.message : String(e);
804
- process.stderr.write(`${message}\n`);
805
- process.exit(1);
806
- });
1350
+ // --- Main execution: early intercepts + cli.run() ---
1351
+
1352
+ const rawArgv = process.argv.slice(2);
1353
+
1354
+ // 1. --version / -V (cli-kit doesn't handle these)
1355
+ const firstToken = rawArgv[0];
1356
+ if (firstToken === "--version" || firstToken === "-V") {
1357
+ process.stdout.write(`${VERSION}\n`);
1358
+ process.exit(0);
1359
+ }
1360
+
1361
+ // 2. Strip --format (cli-kit has no global --format; parse into module-level var)
1362
+ const argvNoFormat = stripFormatFlag(rawArgv);
1363
+
1364
+ // 3. --help / -h / no args (cli-kit has no per-command --help)
1365
+ if (argvNoFormat.length === 0 || argvNoFormat.includes("--help") || argvNoFormat.includes("-h")) {
1366
+ printHelp(argvNoFormat);
1367
+ }
1368
+
1369
+ // 4. Deprecated commands (intercept before cli-kit parsing)
1370
+ handleDeprecated(argvNoFormat);
1371
+
1372
+ // 5. Strip --count / -c (cli-kit rejects values starting with `-`)
1373
+ const argvNoCount = stripCountFlag(argvNoFormat);
1374
+
1375
+ // 6. Run cli-kit
1376
+ const exitCode = await cli.run({ argv: argvNoCount });
1377
+ if (exitCode !== 0) {
1378
+ process.exit(exitCode);
1379
+ }