@united-workforce/cli 0.4.0 → 0.6.0

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 (223) hide show
  1. package/README.md +30 -3
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.js +16 -6
  4. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
  5. package/dist/__tests__/concurrency.test.d.ts +2 -0
  6. package/dist/__tests__/concurrency.test.d.ts.map +1 -0
  7. package/dist/__tests__/concurrency.test.js +196 -0
  8. package/dist/__tests__/concurrency.test.js.map +1 -0
  9. package/dist/__tests__/config-text-renderer.test.d.ts +2 -0
  10. package/dist/__tests__/config-text-renderer.test.d.ts.map +1 -0
  11. package/dist/__tests__/config-text-renderer.test.js +137 -0
  12. package/dist/__tests__/config-text-renderer.test.js.map +1 -0
  13. package/dist/__tests__/e2e-mock-agent.test.js +23 -7
  14. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  15. package/dist/__tests__/format-text-default.test.d.ts +2 -0
  16. package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
  17. package/dist/__tests__/format-text-default.test.js +43 -0
  18. package/dist/__tests__/format-text-default.test.js.map +1 -0
  19. package/dist/__tests__/format-text-registry.test.d.ts +2 -0
  20. package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
  21. package/dist/__tests__/format-text-registry.test.js +158 -0
  22. package/dist/__tests__/format-text-registry.test.js.map +1 -0
  23. package/dist/__tests__/issue-180-workflow-ref-removed.test.js +1 -1
  24. package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
  25. package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
  26. package/dist/__tests__/log-text-renderer.test.js +265 -0
  27. package/dist/__tests__/log-text-renderer.test.js.map +1 -0
  28. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
  29. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
  30. package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
  31. package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
  32. package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
  33. package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
  34. package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
  35. package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
  36. package/dist/__tests__/pid-recycling.test.js +9 -7
  37. package/dist/__tests__/pid-recycling.test.js.map +1 -1
  38. package/dist/__tests__/prompt.test.js +46 -4
  39. package/dist/__tests__/prompt.test.js.map +1 -1
  40. package/dist/__tests__/resolve-head-hash.test.js +8 -0
  41. package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
  42. package/dist/__tests__/solve-issue-tea-worktree.test.js +3 -1
  43. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
  44. package/dist/__tests__/step-ask.test.js +9 -1
  45. package/dist/__tests__/step-ask.test.js.map +1 -1
  46. package/dist/__tests__/store-unified-threads.test.js +19 -17
  47. package/dist/__tests__/store-unified-threads.test.js.map +1 -1
  48. package/dist/__tests__/thread-agent-failure-suspended.test.d.ts +2 -0
  49. package/dist/__tests__/thread-agent-failure-suspended.test.d.ts.map +1 -0
  50. package/dist/__tests__/thread-agent-failure-suspended.test.js +332 -0
  51. package/dist/__tests__/thread-agent-failure-suspended.test.js.map +1 -0
  52. package/dist/__tests__/thread-cancel-status.test.js +19 -13
  53. package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
  54. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
  55. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
  56. package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
  57. package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
  58. package/dist/__tests__/thread-join.test.d.ts +2 -0
  59. package/dist/__tests__/thread-join.test.d.ts.map +1 -0
  60. package/dist/__tests__/thread-join.test.js +77 -0
  61. package/dist/__tests__/thread-join.test.js.map +1 -0
  62. package/dist/__tests__/thread-list-filters.test.js +10 -8
  63. package/dist/__tests__/thread-list-filters.test.js.map +1 -1
  64. package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
  65. package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
  66. package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
  67. package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
  68. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
  69. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
  70. package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
  71. package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
  72. package/dist/__tests__/thread-poke.test.js +15 -2
  73. package/dist/__tests__/thread-poke.test.js.map +1 -1
  74. package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
  75. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
  76. package/dist/__tests__/thread-resume.test.js +11 -1
  77. package/dist/__tests__/thread-resume.test.js.map +1 -1
  78. package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
  79. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
  80. package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
  81. package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
  82. package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
  83. package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
  84. package/dist/__tests__/thread-suspend-step.test.js +5 -2
  85. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  86. package/dist/__tests__/thread-test-helpers.d.ts +7 -0
  87. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
  88. package/dist/__tests__/thread-test-helpers.js +13 -0
  89. package/dist/__tests__/thread-test-helpers.js.map +1 -1
  90. package/dist/__tests__/thread.test.js +11 -9
  91. package/dist/__tests__/thread.test.js.map +1 -1
  92. package/dist/__tests__/validate-semantic.test.js +56 -2
  93. package/dist/__tests__/validate-semantic.test.js.map +1 -1
  94. package/dist/__tests__/workflow-list-recursive.test.js +10 -7
  95. package/dist/__tests__/workflow-list-recursive.test.js.map +1 -1
  96. package/dist/__tests__/workflow-paths.test.d.ts +2 -0
  97. package/dist/__tests__/workflow-paths.test.d.ts.map +1 -0
  98. package/dist/__tests__/workflow-paths.test.js +261 -0
  99. package/dist/__tests__/workflow-paths.test.js.map +1 -0
  100. package/dist/__tests__/workflow-resolution.test.js +10 -7
  101. package/dist/__tests__/workflow-resolution.test.js.map +1 -1
  102. package/dist/__tests__/workflow-show-resolution.test.js +10 -7
  103. package/dist/__tests__/workflow-show-resolution.test.js.map +1 -1
  104. package/dist/__tests__/workflow-validate.test.js +75 -55
  105. package/dist/__tests__/workflow-validate.test.js.map +1 -1
  106. package/dist/__tests__/write-envelope.test.d.ts +2 -0
  107. package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
  108. package/dist/__tests__/write-envelope.test.js +201 -0
  109. package/dist/__tests__/write-envelope.test.js.map +1 -0
  110. package/dist/cli.js +76 -36
  111. package/dist/cli.js.map +1 -1
  112. package/dist/commands/config.d.ts +5 -0
  113. package/dist/commands/config.d.ts.map +1 -1
  114. package/dist/commands/config.js +81 -3
  115. package/dist/commands/config.js.map +1 -1
  116. package/dist/commands/prompt.d.ts.map +1 -1
  117. package/dist/commands/prompt.js +42 -29
  118. package/dist/commands/prompt.js.map +1 -1
  119. package/dist/commands/setup.d.ts +9 -4
  120. package/dist/commands/setup.d.ts.map +1 -1
  121. package/dist/commands/setup.js +51 -7
  122. package/dist/commands/setup.js.map +1 -1
  123. package/dist/commands/thread.d.ts +12 -0
  124. package/dist/commands/thread.d.ts.map +1 -1
  125. package/dist/commands/thread.js +226 -9
  126. package/dist/commands/thread.js.map +1 -1
  127. package/dist/commands/workflow.d.ts +2 -2
  128. package/dist/commands/workflow.d.ts.map +1 -1
  129. package/dist/commands/workflow.js +26 -10
  130. package/dist/commands/workflow.js.map +1 -1
  131. package/dist/concurrency/concurrency.d.ts +34 -0
  132. package/dist/concurrency/concurrency.d.ts.map +1 -0
  133. package/dist/concurrency/concurrency.js +216 -0
  134. package/dist/concurrency/concurrency.js.map +1 -0
  135. package/dist/concurrency/index.d.ts +3 -0
  136. package/dist/concurrency/index.d.ts.map +1 -0
  137. package/dist/concurrency/index.js +2 -0
  138. package/dist/concurrency/index.js.map +1 -0
  139. package/dist/concurrency/types.d.ts +19 -0
  140. package/dist/concurrency/types.d.ts.map +1 -0
  141. package/dist/concurrency/types.js +2 -0
  142. package/dist/concurrency/types.js.map +1 -0
  143. package/dist/format.d.ts +69 -2
  144. package/dist/format.d.ts.map +1 -1
  145. package/dist/format.js +198 -1
  146. package/dist/format.js.map +1 -1
  147. package/dist/output-mappers.d.ts +122 -0
  148. package/dist/output-mappers.d.ts.map +1 -0
  149. package/dist/output-mappers.js +134 -0
  150. package/dist/output-mappers.js.map +1 -0
  151. package/dist/schemas.d.ts +4 -1
  152. package/dist/schemas.d.ts.map +1 -1
  153. package/dist/schemas.js +31 -4
  154. package/dist/schemas.js.map +1 -1
  155. package/dist/store.d.ts +11 -0
  156. package/dist/store.d.ts.map +1 -1
  157. package/dist/store.js +20 -1
  158. package/dist/store.js.map +1 -1
  159. package/dist/text-renderers.d.ts +30 -0
  160. package/dist/text-renderers.d.ts.map +1 -0
  161. package/dist/text-renderers.js +251 -0
  162. package/dist/text-renderers.js.map +1 -0
  163. package/dist/validate-semantic.d.ts.map +1 -1
  164. package/dist/validate-semantic.js +28 -11
  165. package/dist/validate-semantic.js.map +1 -1
  166. package/examples/brainstorm.yaml +130 -0
  167. package/examples/debate.yaml +169 -0
  168. package/examples/socratic-questioning.yaml +112 -0
  169. package/package.json +12 -11
  170. package/src/__tests__/adapter-json-roundtrip.test.ts +15 -6
  171. package/src/__tests__/concurrency.test.ts +266 -0
  172. package/src/__tests__/config-text-renderer.test.ts +156 -0
  173. package/src/__tests__/e2e-mock-agent.test.ts +45 -7
  174. package/src/__tests__/format-text-default.test.ts +49 -0
  175. package/src/__tests__/format-text-registry.test.ts +173 -0
  176. package/src/__tests__/issue-180-workflow-ref-removed.test.ts +1 -1
  177. package/src/__tests__/log-text-renderer.test.ts +294 -0
  178. package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
  179. package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
  180. package/src/__tests__/pid-recycling.test.ts +9 -8
  181. package/src/__tests__/prompt.test.ts +48 -4
  182. package/src/__tests__/resolve-head-hash.test.ts +7 -0
  183. package/src/__tests__/solve-issue-tea-worktree.test.ts +3 -1
  184. package/src/__tests__/step-ask.test.ts +8 -1
  185. package/src/__tests__/store-unified-threads.test.ts +21 -18
  186. package/src/__tests__/thread-agent-failure-suspended.test.ts +406 -0
  187. package/src/__tests__/thread-cancel-status.test.ts +21 -14
  188. package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
  189. package/src/__tests__/thread-join.test.ts +103 -0
  190. package/src/__tests__/thread-list-filters.test.ts +9 -9
  191. package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
  192. package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
  193. package/src/__tests__/thread-poke.test.ts +14 -2
  194. package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
  195. package/src/__tests__/thread-resume.test.ts +10 -1
  196. package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
  197. package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
  198. package/src/__tests__/thread-suspend-step.test.ts +5 -2
  199. package/src/__tests__/thread-test-helpers.ts +15 -1
  200. package/src/__tests__/thread.test.ts +10 -10
  201. package/src/__tests__/validate-semantic.test.ts +59 -2
  202. package/src/__tests__/workflow-list-recursive.test.ts +9 -9
  203. package/src/__tests__/workflow-paths.test.ts +337 -0
  204. package/src/__tests__/workflow-resolution.test.ts +9 -8
  205. package/src/__tests__/workflow-show-resolution.test.ts +9 -8
  206. package/src/__tests__/workflow-validate.test.ts +78 -56
  207. package/src/__tests__/write-envelope.test.ts +257 -0
  208. package/src/cli.ts +111 -35
  209. package/src/commands/config.ts +85 -3
  210. package/src/commands/prompt.ts +42 -29
  211. package/src/commands/setup.ts +57 -7
  212. package/src/commands/thread.ts +280 -9
  213. package/src/commands/workflow.ts +32 -11
  214. package/src/concurrency/concurrency.ts +245 -0
  215. package/src/concurrency/index.ts +10 -0
  216. package/src/concurrency/types.ts +19 -0
  217. package/src/format.ts +282 -2
  218. package/src/output-mappers.ts +255 -0
  219. package/src/schemas.ts +39 -3
  220. package/src/store.ts +25 -1
  221. package/src/text-renderers.ts +355 -0
  222. package/src/validate-semantic.ts +33 -12
  223. package/LICENSE +0 -21
package/src/cli.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
2
 
3
- import type { CasRef, ThreadId, ThreadStatus } from "@united-workforce/protocol";
3
+ import type { CasRef, OutputSchemaName, ThreadId, ThreadStatus } from "@united-workforce/protocol";
4
4
  import { Command } from "commander";
5
5
  import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js";
6
6
  import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
@@ -16,6 +16,7 @@ import { cmdStepAsk, cmdStepFork, cmdStepList, cmdStepRead, cmdStepShow } from "
16
16
  import {
17
17
  cmdThreadCancel,
18
18
  cmdThreadExec,
19
+ cmdThreadJoin,
19
20
  cmdThreadList,
20
21
  cmdThreadPoke,
21
22
  cmdThreadRead,
@@ -32,12 +33,61 @@ import {
32
33
  cmdWorkflowShow,
33
34
  cmdWorkflowValidate,
34
35
  } from "./commands/workflow.js";
35
- import { formatOutput, type OutputFormat } from "./format.js";
36
- import { resolveStorageRoot } from "./store.js";
36
+ import {
37
+ formatOutput,
38
+ isOutputFormat,
39
+ type OutputFormat,
40
+ SUPPORTED_FORMATS,
41
+ writeEnvelope,
42
+ } from "./format.js";
43
+ import {
44
+ toStepDetailPayload,
45
+ toStepListPayload,
46
+ toThreadExecPayload,
47
+ toThreadListPayload,
48
+ toThreadStartPayload,
49
+ toThreadStatusPayload,
50
+ toValidateResultPayload,
51
+ toWorkflowAddPayload,
52
+ toWorkflowDetailPayload,
53
+ toWorkflowListPayload,
54
+ } from "./output-mappers.js";
55
+ import { createUwfStore, resolveStorageRoot } from "./store.js";
56
+
57
+ function getFormat(): OutputFormat {
58
+ const raw = program.opts().format as string;
59
+ if (!isOutputFormat(raw)) {
60
+ process.stderr.write(
61
+ `Invalid --format: ${raw}. Must be one of: ${SUPPORTED_FORMATS.join(", ")}\n`,
62
+ );
63
+ process.exit(1);
64
+ }
65
+ return raw;
66
+ }
67
+
68
+ async function writeOutput(
69
+ payload: unknown,
70
+ schemaName: OutputSchemaName,
71
+ storageRoot: string,
72
+ ): Promise<void> {
73
+ const fmt = getFormat();
74
+ const uwf = await createUwfStore(storageRoot);
75
+ await writeEnvelope(payload, schemaName, {
76
+ format: fmt,
77
+ store: uwf.store,
78
+ schemas: uwf.schemas,
79
+ });
80
+ }
37
81
 
38
- function writeOutput(data: unknown): void {
39
- const fmt = program.opts().format as OutputFormat;
40
- process.stdout.write(`${formatOutput(data, fmt)}\n`);
82
+ /**
83
+ * Legacy raw output for commands without an output schema (log/config/setup).
84
+ * Always emits text/JSON/YAML based on the active --format. For `text`
85
+ * (the default) it renders via the per-command registry when available
86
+ * and falls back to JSON.
87
+ */
88
+ function writeRawOutput(data: unknown, commandPath?: string): void {
89
+ const fmt = getFormat();
90
+ process.stdout.write(`${formatOutput(data, fmt, commandPath)}\n`);
41
91
  }
42
92
 
43
93
  function runAction(action: () => Promise<void>): void {
@@ -60,7 +110,11 @@ program
60
110
  " workflow → thread → step → turn",
61
111
  )
62
112
  .version(pkg.default.version, "-V, --version");
63
- program.option("--format <fmt>", "Output format: json or yaml", "json");
113
+ program.option(
114
+ "--format <fmt>",
115
+ "Output format: text (default), json, yaml, raw-json, raw-yaml",
116
+ "text",
117
+ );
64
118
 
65
119
  const workflow = program
66
120
  .command("workflow")
@@ -74,7 +128,7 @@ workflow
74
128
  const storageRoot = resolveStorageRoot();
75
129
  runAction(async () => {
76
130
  const result = await cmdWorkflowAdd(storageRoot, file);
77
- writeOutput(result);
131
+ await writeOutput(toWorkflowAddPayload(result), "workflow-add", storageRoot);
78
132
  });
79
133
  });
80
134
 
@@ -83,9 +137,13 @@ workflow
83
137
  .description("Validate a workflow YAML without registering it (CI-friendly)")
84
138
  .argument("<file>", "Workflow YAML file")
85
139
  .action((file: string) => {
140
+ const storageRoot = resolveStorageRoot();
86
141
  runAction(async () => {
87
- await cmdWorkflowValidate(file);
88
- // silent on success — do not call writeOutput
142
+ const errors = await cmdWorkflowValidate(file);
143
+ await writeOutput(toValidateResultPayload(errors), "validate-result", storageRoot);
144
+ if (errors.length > 0) {
145
+ process.exit(1);
146
+ }
89
147
  });
90
148
  });
91
149
 
@@ -97,7 +155,7 @@ workflow
97
155
  const storageRoot = resolveStorageRoot();
98
156
  runAction(async () => {
99
157
  const result = await cmdWorkflowShow(storageRoot, id, process.cwd());
100
- writeOutput(result);
158
+ await writeOutput(toWorkflowDetailPayload(result), "workflow-detail", storageRoot);
101
159
  });
102
160
  });
103
161
 
@@ -108,7 +166,7 @@ workflow
108
166
  const storageRoot = resolveStorageRoot();
109
167
  runAction(async () => {
110
168
  const result = await cmdWorkflowList(storageRoot, process.cwd());
111
- writeOutput(result);
169
+ await writeOutput(toWorkflowListPayload(result), "workflow-list", storageRoot);
112
170
  });
113
171
  });
114
172
 
@@ -130,7 +188,7 @@ thread
130
188
  process.cwd(),
131
189
  opts.cwd ?? process.cwd(),
132
190
  );
133
- writeOutput(result);
191
+ await writeOutput(toThreadStartPayload(result), "thread-start", storageRoot);
134
192
  });
135
193
  });
136
194
 
@@ -167,11 +225,7 @@ thread
167
225
  background,
168
226
  backgroundWorker,
169
227
  );
170
- if (results.length === 1) {
171
- writeOutput(results[0]);
172
- } else {
173
- writeOutput(results);
174
- }
228
+ await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
175
229
  });
176
230
  },
177
231
  );
@@ -184,7 +238,7 @@ thread
184
238
  const storageRoot = resolveStorageRoot();
185
239
  runAction(async () => {
186
240
  const result = await cmdThreadShow(storageRoot, threadId);
187
- writeOutput(result);
241
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
188
242
  });
189
243
  });
190
244
 
@@ -292,7 +346,7 @@ thread
292
346
  take,
293
347
  showAll,
294
348
  );
295
- writeOutput(result);
349
+ await writeOutput(toThreadListPayload(result), "thread-list", storageRoot);
296
350
  });
297
351
  },
298
352
  );
@@ -314,7 +368,7 @@ thread
314
368
  supplement,
315
369
  agentOverride,
316
370
  );
317
- writeOutput(result);
371
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
318
372
  });
319
373
  });
320
374
 
@@ -334,7 +388,7 @@ thread
334
388
  opts.prompt,
335
389
  agentOverride,
336
390
  );
337
- writeOutput(result);
391
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
338
392
  });
339
393
  });
340
394
 
@@ -346,7 +400,7 @@ thread
346
400
  const storageRoot = resolveStorageRoot();
347
401
  runAction(async () => {
348
402
  const result = await cmdThreadStop(storageRoot, threadId);
349
- writeOutput(result);
403
+ writeRawOutput(result, "thread stop");
350
404
  });
351
405
  });
352
406
 
@@ -358,7 +412,25 @@ thread
358
412
  const storageRoot = resolveStorageRoot();
359
413
  runAction(async () => {
360
414
  const result = await cmdThreadCancel(storageRoot, threadId);
361
- writeOutput(result);
415
+ writeRawOutput(result, "thread cancel");
416
+ });
417
+ });
418
+
419
+ thread
420
+ .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 }) => {
425
+ const storageRoot = resolveStorageRoot();
426
+ runAction(async () => {
427
+ const timeoutMs = opts.timeout !== undefined ? Number(opts.timeout) * 1000 : null;
428
+ if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
429
+ process.stderr.write("invalid --timeout: must be a positive number\n");
430
+ process.exit(1);
431
+ }
432
+ const results = await cmdThreadJoin(storageRoot, threadId, timeoutMs);
433
+ await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
362
434
  });
363
435
  });
364
436
 
@@ -401,7 +473,7 @@ step
401
473
  const storageRoot = resolveStorageRoot();
402
474
  runAction(async () => {
403
475
  const result = await cmdStepList(storageRoot, threadId);
404
- writeOutput(result);
476
+ await writeOutput(toStepListPayload(result), "step-list", storageRoot);
405
477
  });
406
478
  });
407
479
 
@@ -413,7 +485,11 @@ step
413
485
  const storageRoot = resolveStorageRoot();
414
486
  runAction(async () => {
415
487
  const detail = await cmdStepShow(storageRoot, stepHash as CasRef);
416
- writeOutput(detail);
488
+ await writeOutput(
489
+ toStepDetailPayload(stepHash as CasRef, detail),
490
+ "step-detail",
491
+ storageRoot,
492
+ );
417
493
  });
418
494
  });
419
495
 
@@ -475,7 +551,7 @@ step
475
551
  const storageRoot = resolveStorageRoot();
476
552
  runAction(async () => {
477
553
  const result = await cmdStepFork(storageRoot, stepHash as CasRef);
478
- writeOutput(result);
554
+ writeRawOutput(result);
479
555
  });
480
556
  });
481
557
 
@@ -618,7 +694,7 @@ program
618
694
  .command("setup")
619
695
  .description(
620
696
  "Configure the default agent. Run without --agent for interactive wizard.\n" +
621
- "LLM provider/model configuration lives in <storage>/config.yaml under providers and models.",
697
+ "Each adapter owns its own LLM configuration the engine config is LLM-free.",
622
698
  )
623
699
  .option("--agent <name>", "Default agent adapter (e.g. hermes → uwf-hermes)")
624
700
  .action((opts: { agent?: string }) => {
@@ -626,7 +702,7 @@ program
626
702
  runAction(async () => {
627
703
  if (opts.agent !== undefined && opts.agent !== "") {
628
704
  const result = await cmdSetup({ agent: opts.agent, storageRoot });
629
- writeOutput(result);
705
+ writeRawOutput(result);
630
706
  } else {
631
707
  await cmdSetupInteractive(storageRoot);
632
708
  }
@@ -642,7 +718,7 @@ log
642
718
  const storageRoot = resolveStorageRoot();
643
719
  runAction(async () => {
644
720
  const result = await cmdLogList(storageRoot);
645
- writeOutput(result);
721
+ writeRawOutput(result, "log list");
646
722
  });
647
723
  });
648
724
 
@@ -665,7 +741,7 @@ log
665
741
  process: opts.process ?? null,
666
742
  date: opts.date ?? null,
667
743
  });
668
- writeOutput(result);
744
+ writeRawOutput(result, "log show");
669
745
  });
670
746
  },
671
747
  );
@@ -678,7 +754,7 @@ log
678
754
  const storageRoot = resolveStorageRoot();
679
755
  runAction(async () => {
680
756
  const result = await cmdLogClean(storageRoot, opts.before);
681
- writeOutput(result);
757
+ writeRawOutput(result);
682
758
  });
683
759
  });
684
760
 
@@ -691,7 +767,7 @@ config
691
767
  const storageRoot = resolveStorageRoot();
692
768
  runAction(async () => {
693
769
  const result = await cmdConfigList(storageRoot);
694
- writeOutput(result);
770
+ writeRawOutput(result, "config list");
695
771
  });
696
772
  });
697
773
 
@@ -706,7 +782,7 @@ config
706
782
  const storageRoot = resolveStorageRoot();
707
783
  runAction(async () => {
708
784
  const result = await cmdConfigGet(storageRoot, key);
709
- writeOutput({ value: result });
785
+ writeRawOutput({ value: result }, "config get");
710
786
  });
711
787
  });
712
788
 
@@ -719,7 +795,7 @@ config
719
795
  const storageRoot = resolveStorageRoot();
720
796
  runAction(async () => {
721
797
  const result = await cmdConfigSet(storageRoot, key, value);
722
- writeOutput(result);
798
+ writeRawOutput(result, "config set");
723
799
  });
724
800
  });
725
801
 
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve as resolvePath } from "node:path";
3
4
  import { parse, stringify } from "yaml";
4
5
 
5
6
  /**
@@ -21,6 +22,12 @@ const VALID_CONFIG_KEYS: Record<
21
22
  // No knownFields — workflow/role names are user-defined
22
23
  },
23
24
  defaultAgent: { nested: false },
25
+ concurrency: {
26
+ nested: true,
27
+ knownFields: ["maxRunning"],
28
+ minDepth: 2,
29
+ },
30
+ workflowPaths: { nested: false },
24
31
  };
25
32
 
26
33
  /**
@@ -216,6 +223,31 @@ function parseArgsValue(value: string): unknown {
216
223
  throw new Error("Value for 'args' key must be a JSON array starting with '['");
217
224
  }
218
225
 
226
+ /**
227
+ * Parse value for a top-level string array key (must be JSON array of strings).
228
+ */
229
+ function parseStringArrayValue(value: string, keyName: string): unknown {
230
+ if (value.startsWith("[")) {
231
+ try {
232
+ const parsed = JSON.parse(value);
233
+ if (!Array.isArray(parsed)) {
234
+ throw new Error("Value must be an array");
235
+ }
236
+ for (const item of parsed) {
237
+ if (typeof item !== "string") {
238
+ throw new Error(`All items must be strings, got ${typeof item}`);
239
+ }
240
+ }
241
+ return parsed;
242
+ } catch (error) {
243
+ throw new Error(
244
+ `Invalid JSON array for ${keyName}: ${error instanceof Error ? error.message : String(error)}`,
245
+ );
246
+ }
247
+ }
248
+ throw new Error(`Value for '${keyName}' must be a JSON array starting with '['`);
249
+ }
250
+
219
251
  /**
220
252
  * Validate that we're not setting a property on a non-object
221
253
  */
@@ -260,10 +292,18 @@ export async function cmdConfigSet(
260
292
 
261
293
  const lastSegment = path[path.length - 1];
262
294
 
263
- // Parse value if it's for an array key (args)
295
+ // Parse value if it's for an array key (args, workflowPaths)
264
296
  let parsedValue: unknown = value;
265
- if (lastSegment === "args") {
297
+ if (path[0] === "workflowPaths") {
298
+ parsedValue = parseStringArrayValue(value, "workflowPaths");
299
+ } else if (lastSegment === "args") {
266
300
  parsedValue = parseArgsValue(value);
301
+ } else if (lastSegment === "maxRunning") {
302
+ const num = Number(value);
303
+ if (!Number.isInteger(num) || num < 1) {
304
+ throw new Error("Value for 'maxRunning' must be a positive integer");
305
+ }
306
+ parsedValue = num;
267
307
  }
268
308
 
269
309
  // Validate we're not setting a property on a non-object
@@ -274,3 +314,45 @@ export async function cmdConfigSet(
274
314
 
275
315
  return { key, value: parsedValue };
276
316
  }
317
+
318
+ /**
319
+ * Expand leading `~/` in a path to the user's home directory.
320
+ */
321
+ function expandTilde(p: string): string {
322
+ if (p.startsWith("~/") || p === "~") {
323
+ return join(homedir(), p.slice(1));
324
+ }
325
+ return p;
326
+ }
327
+
328
+ /**
329
+ * Load workflowPaths from config and resolve to absolute paths.
330
+ * Returns empty array if config doesn't exist or key is missing.
331
+ */
332
+ export function loadWorkflowPaths(storageRoot: string): string[] {
333
+ const configPath = getConfigPath(storageRoot);
334
+ if (!existsSync(configPath)) {
335
+ return [];
336
+ }
337
+
338
+ let config: Record<string, unknown>;
339
+ try {
340
+ config = loadConfig(configPath);
341
+ } catch {
342
+ return [];
343
+ }
344
+
345
+ const raw = config.workflowPaths;
346
+ if (!Array.isArray(raw)) {
347
+ return [];
348
+ }
349
+
350
+ const result: string[] = [];
351
+ for (const item of raw) {
352
+ if (typeof item === "string" && item.trim() !== "") {
353
+ const expanded = expandTilde(item.trim());
354
+ result.push(resolvePath(expanded));
355
+ }
356
+ }
357
+ return result;
358
+ }
@@ -148,44 +148,36 @@ pipx install 'hermes-agent[acp]'
148
148
  pip install -e '.[acp]'
149
149
  \`\`\`
150
150
 
151
- ### Step 2 — Configure provider and model
151
+ ### Step 2 — Configure default agent
152
152
 
153
- uwf needs an LLM provider to run agents. **Ask the user** for their provider, API key, and model, then run:
153
+ Run the interactive wizard:
154
154
 
155
155
  \`\`\`bash
156
- uwf setup --provider <name> --api-key <key> --model <model> --agent <adapter-command>
156
+ uwf setup
157
157
  \`\`\`
158
158
 
159
- **Note:** \`--agent\` takes the adapter **command name** (e.g. \`uwf-hermes\`), not the npm package name.
160
-
161
- **Preset providers** — when using a preset name, \`--base-url\` is auto-filled and can be omitted:
162
-
163
- | Provider | Name | Default base URL |
164
- |----------|------|-----------------|
165
- | OpenAI | \`openai\` | https://api.openai.com/v1 |
166
- | xAI | \`xai\` | https://api.x.ai/v1 |
167
- | OpenRouter | \`openrouter\` | https://openrouter.ai/api/v1 |
168
- | Venice | \`venice\` | https://api.venice.ai/api/v1 |
169
- | Dashscope | \`dashscope\` | https://dashscope.aliyuncs.com/compatible-mode/v1 |
170
- | DeepSeek | \`deepseek\` | https://api.deepseek.com/v1 |
171
- | SiliconFlow | \`siliconflow\` | https://api.siliconflow.cn/v1 |
172
- | VolcEngine | \`volcengine\` | https://ark.cn-beijing.volces.com/api/v3 |
173
- | Kimi (Moonshot) | \`kimi\` | https://api.moonshot.cn/v1 |
174
- | GLM (Zhipu AI) | \`glm\` | https://open.bigmodel.cn/api/paas/v4 |
175
- | StepFun | \`stepfun\` | https://api.stepfun.com/v1 |
176
- | MiniMax | \`minimax\` | https://api.minimax.io/v1 |
177
- | Ollama (local) | \`ollama\` | http://localhost:11434/v1 |
178
-
179
- For **non-preset providers**, you must specify \`--base-url\` manually.
180
-
181
- Example:
159
+ Or configure non-interactively:
160
+
182
161
  \`\`\`bash
183
- uwf setup --provider openrouter --api-key sk-or-... --model anthropic/claude-sonnet-4 --agent uwf-hermes
162
+ uwf setup --agent <adapter-command>
163
+ \`\`\`
164
+
165
+ **Note:** \`--agent\` takes the adapter **command name** (e.g. \`uwf-hermes\`, \`uwf-claude-code\`), not the npm package name.
166
+
167
+ Config is saved to \`~/.uwf/config.yaml\`:
168
+
169
+ \`\`\`yaml
170
+ agents:
171
+ hermes:
172
+ command: uwf-hermes
173
+ args: []
174
+ defaultAgent: hermes
175
+ agentOverrides: {}
184
176
  \`\`\`
185
177
 
186
- If the user doesn't know what to choose, suggest \`openrouter\` with \`anthropic/claude-sonnet-4\` as a sensible default.
178
+ **LLM configuration** is per-adapter each adapter manages its own provider, model, and API key settings independently (typically via environment variables like \`CLAUDE_MODEL\`, \`ANTHROPIC_API_KEY\`, etc.). The engine config (\`~/.uwf/config.yaml\`) is LLM-free.
187
179
 
188
- Config is saved to \`~/.uwf/config.yaml\`. Verify with \`cat ~/.uwf/config.yaml\`.
180
+ Verify with \`cat ~/.uwf/config.yaml\`.
189
181
 
190
182
  ### Step 3 — Install skills
191
183
 
@@ -339,5 +331,26 @@ uwf prompt workflow-authoring # workflow YAML design guide
339
331
  uwf prompt adapter-developing # building agent adapters
340
332
  uwf prompt bootstrap # this guide
341
333
  \`\`\`
334
+
335
+ ## What's next — introduce uwf to the user
336
+
337
+ After setup is complete, give the user a brief introduction to what uwf can do
338
+ and invite them to try it. Cover these three things:
339
+
340
+ 1. **Run workflows** — execute pre-built workflows to automate multi-step tasks.
341
+ Setup auto-registers example workflows, ready to use immediately.
342
+ 2. **Create workflows** — design custom YAML workflows for their own recurring tasks
343
+ (code review, issue triage, release pipelines, etc.). Use \`uwf prompt workflow-authoring\`
344
+ for the authoring guide.
345
+ 3. **Debug & improve workflows** — iterate on workflow definitions, inspect thread state
346
+ with \`uwf thread show\`, replay failed steps with \`uwf thread poke\`, and refine
347
+ role procedures based on real execution results.
348
+
349
+ **Discover & try built-in examples:**
350
+
351
+ Run \`uwf workflow list\` to see which workflows are registered (setup auto-registers
352
+ several built-in examples). Show the user what's available, then invite them to try
353
+ one — for instance, suggest a fun debate topic like "AI 是否会抢了人类的工作?" and
354
+ offer to kick it off for them.
342
355
  `;
343
356
  }
@@ -1,9 +1,11 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
4
4
  import { stdin as input, stdout as output } from "node:process";
5
5
  import { createInterface } from "node:readline/promises";
6
+ import { fileURLToPath } from "node:url";
6
7
  import { parse, stringify } from "yaml";
8
+ import { cmdWorkflowAdd } from "./workflow.js";
7
9
 
8
10
  export type SetupArgs = {
9
11
  agent: string;
@@ -261,10 +263,56 @@ export function _checkAdapterAvailability(agentName: string): string[] {
261
263
  return warnings;
262
264
  }
263
265
 
266
+ // ──────────────────────────────────────────────────────────────────────────────
267
+ // Bundled example workflows
268
+ // ──────────────────────────────────────────────────────────────────────────────
269
+
270
+ /** Resolve the examples/ directory bundled with the CLI package. */
271
+ function _findExamplesDir(): string | null {
272
+ // Walk up from this file (src/commands/ or dist/commands/) to the package root
273
+ let dir = dirname(fileURLToPath(import.meta.url));
274
+ for (let i = 0; i < 5; i++) {
275
+ const candidate = join(dir, "examples");
276
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
277
+ return candidate;
278
+ }
279
+ dir = dirname(dir);
280
+ }
281
+ return null;
282
+ }
283
+
284
+ /**
285
+ * Register bundled example workflows. Non-destructive — silently skips
286
+ * any that fail (e.g. already registered with same hash).
287
+ * Returns list of successfully registered workflow names.
288
+ */
289
+ export async function _registerBundledExamples(storageRoot: string): Promise<string[]> {
290
+ const examplesDir = _findExamplesDir();
291
+ if (examplesDir === null) return [];
292
+
293
+ const registered: string[] = [];
294
+ const files = readdirSync(examplesDir)
295
+ .filter((f) => f.endsWith(".yaml"))
296
+ .sort();
297
+
298
+ for (const file of files) {
299
+ try {
300
+ const result = await cmdWorkflowAdd(storageRoot, join(examplesDir, file));
301
+ registered.push(result.name);
302
+ console.error(` ✓ ${result.name}`);
303
+ } catch {
304
+ // Skip silently — workflow may already exist or be invalid
305
+ }
306
+ }
307
+
308
+ return registered;
309
+ }
310
+
311
+ // ──────────────────────────────────────────────────────────────────────────────
312
+
264
313
  /**
265
314
  * Non-interactive setup. Engine config is LLM-free — only writes
266
- * agents + defaultAgent. LLM provider/model configuration lives in
267
- * config.yaml under `providers` and `models`.
315
+ * agents + defaultAgent. Each adapter owns its own LLM configuration.
268
316
  */
269
317
  export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>> {
270
318
  const { storageRoot } = args;
@@ -287,16 +335,20 @@ export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>
287
335
  console.error(`⚠ ${w}`);
288
336
  }
289
337
 
338
+ // Auto-register bundled example workflows
339
+ const registeredExamples = await _registerBundledExamples(storageRoot);
340
+
290
341
  return {
291
342
  configPath,
292
343
  defaultAgent: merged.defaultAgent,
293
344
  adapterWarnings,
345
+ registeredExamples,
294
346
  };
295
347
  }
296
348
 
297
349
  /**
298
- * Interactive setup — prompts the user only for the default agent. LLM
299
- * configuration lives in config.yaml under `providers` and `models`.
350
+ * Interactive setup — prompts the user only for the default agent.
351
+ * Each adapter owns its own LLM configuration.
300
352
  */
301
353
  export async function cmdSetupInteractive(storageRoot: string): Promise<Record<string, unknown>> {
302
354
  const rl = createInterface({ input, output });
@@ -313,8 +365,6 @@ export async function cmdSetupInteractive(storageRoot: string): Promise<Record<s
313
365
  console.log(' uwf thread start <name> -p "..." Start a thread');
314
366
  console.log(" uwf thread exec <thread-id> Execute next step");
315
367
  console.log("");
316
- console.log("LLM config: edit ~/.uwf/config.yaml (providers + models sections).");
317
- console.log("");
318
368
 
319
369
  return null as unknown as Record<string, unknown>;
320
370
  } finally {