@united-workforce/cli 0.3.0 → 0.4.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 (219) hide show
  1. package/README.md +15 -8
  2. package/dist/__tests__/adapter-json-roundtrip.test.js +1 -1
  3. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
  4. package/dist/__tests__/agent-resolution-llm-free.test.d.ts +2 -0
  5. package/dist/__tests__/agent-resolution-llm-free.test.d.ts.map +1 -0
  6. package/dist/__tests__/agent-resolution-llm-free.test.js +30 -0
  7. package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -0
  8. package/dist/__tests__/build-step-entry.test.d.ts +2 -0
  9. package/dist/__tests__/build-step-entry.test.d.ts.map +1 -0
  10. package/dist/__tests__/build-step-entry.test.js +173 -0
  11. package/dist/__tests__/build-step-entry.test.js.map +1 -0
  12. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts +2 -0
  13. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts.map +1 -0
  14. package/dist/__tests__/clear-thread-failed-attempts.test.js +93 -0
  15. package/dist/__tests__/clear-thread-failed-attempts.test.js.map +1 -0
  16. package/dist/__tests__/config.test.js +26 -302
  17. package/dist/__tests__/config.test.js.map +1 -1
  18. package/dist/__tests__/current-role.test.js +7 -6
  19. package/dist/__tests__/current-role.test.js.map +1 -1
  20. package/dist/__tests__/e2e-mock-agent.test.js +20 -23
  21. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  22. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts +2 -0
  23. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts.map +1 -0
  24. package/dist/__tests__/issue-180-workflow-ref-removed.test.js +40 -0
  25. package/dist/__tests__/issue-180-workflow-ref-removed.test.js.map +1 -0
  26. package/dist/__tests__/moderator-evaluate.test.js +9 -50
  27. package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
  28. package/dist/__tests__/pid-recycling.test.d.ts +2 -0
  29. package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
  30. package/dist/__tests__/pid-recycling.test.js +271 -0
  31. package/dist/__tests__/pid-recycling.test.js.map +1 -0
  32. package/dist/__tests__/prompt.test.js +321 -0
  33. package/dist/__tests__/prompt.test.js.map +1 -1
  34. package/dist/__tests__/resolve-head-hash.test.js +4 -4
  35. package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
  36. package/dist/__tests__/setup-agent-discovery.test.js +21 -30
  37. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
  38. package/dist/__tests__/setup-complexity.test.js +2 -168
  39. package/dist/__tests__/setup-complexity.test.js.map +1 -1
  40. package/dist/__tests__/setup-no-llm.test.d.ts +2 -0
  41. package/dist/__tests__/setup-no-llm.test.d.ts.map +1 -0
  42. package/dist/__tests__/setup-no-llm.test.js +52 -0
  43. package/dist/__tests__/setup-no-llm.test.js.map +1 -0
  44. package/dist/__tests__/solve-issue-tea-worktree.test.js +24 -27
  45. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
  46. package/dist/__tests__/step-ask.test.d.ts +2 -0
  47. package/dist/__tests__/step-ask.test.d.ts.map +1 -0
  48. package/dist/__tests__/step-ask.test.js +499 -0
  49. package/dist/__tests__/step-ask.test.js.map +1 -0
  50. package/dist/__tests__/step-show-json.test.js +1 -0
  51. package/dist/__tests__/step-show-json.test.js.map +1 -1
  52. package/dist/__tests__/step-timing.test.js +2 -0
  53. package/dist/__tests__/step-timing.test.js.map +1 -1
  54. package/dist/__tests__/store-global-cas.test.js +2 -2
  55. package/dist/__tests__/store-global-cas.test.js.map +1 -1
  56. package/dist/__tests__/store-unified-threads.test.js +9 -9
  57. package/dist/__tests__/store-unified-threads.test.js.map +1 -1
  58. package/dist/__tests__/thread-cancel-status.test.js +6 -6
  59. package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
  60. package/dist/__tests__/thread-list-filters.test.js +344 -9
  61. package/dist/__tests__/thread-list-filters.test.js.map +1 -1
  62. package/dist/__tests__/thread-poke.test.d.ts +2 -0
  63. package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
  64. package/dist/__tests__/thread-poke.test.js +412 -0
  65. package/dist/__tests__/thread-poke.test.js.map +1 -0
  66. package/dist/__tests__/thread-resume.test.js +10 -14
  67. package/dist/__tests__/thread-resume.test.js.map +1 -1
  68. package/dist/__tests__/thread-show-status.test.js +17 -28
  69. package/dist/__tests__/thread-show-status.test.js.map +1 -1
  70. package/dist/__tests__/thread-suspend-step.test.js +8 -14
  71. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  72. package/dist/__tests__/thread-suspended-display.test.js +10 -22
  73. package/dist/__tests__/thread-suspended-display.test.js.map +1 -1
  74. package/dist/__tests__/thread.test.js +4 -4
  75. package/dist/__tests__/thread.test.js.map +1 -1
  76. package/dist/__tests__/validate-semantic.test.js +49 -21
  77. package/dist/__tests__/validate-semantic.test.js.map +1 -1
  78. package/dist/__tests__/workflow-list-recursive.test.d.ts +2 -0
  79. package/dist/__tests__/workflow-list-recursive.test.d.ts.map +1 -0
  80. package/dist/__tests__/workflow-list-recursive.test.js +283 -0
  81. package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
  82. package/dist/__tests__/workflow-resolution.test.js +36 -21
  83. package/dist/__tests__/workflow-resolution.test.js.map +1 -1
  84. package/dist/__tests__/workflow-show-resolution.test.d.ts +2 -0
  85. package/dist/__tests__/workflow-show-resolution.test.d.ts.map +1 -0
  86. package/dist/__tests__/workflow-show-resolution.test.js +210 -0
  87. package/dist/__tests__/workflow-show-resolution.test.js.map +1 -0
  88. package/dist/__tests__/workflow-validate.test.d.ts +2 -0
  89. package/dist/__tests__/workflow-validate.test.d.ts.map +1 -0
  90. package/dist/__tests__/workflow-validate.test.js +687 -0
  91. package/dist/__tests__/workflow-validate.test.js.map +1 -0
  92. package/dist/background/background.d.ts +22 -1
  93. package/dist/background/background.d.ts.map +1 -1
  94. package/dist/background/background.js +83 -6
  95. package/dist/background/background.js.map +1 -1
  96. package/dist/background/index.d.ts +1 -1
  97. package/dist/background/index.d.ts.map +1 -1
  98. package/dist/background/index.js +1 -1
  99. package/dist/background/index.js.map +1 -1
  100. package/dist/background/types.d.ts +1 -0
  101. package/dist/background/types.d.ts.map +1 -1
  102. package/dist/cli.js +66 -31
  103. package/dist/cli.js.map +1 -1
  104. package/dist/commands/config.d.ts +3 -1
  105. package/dist/commands/config.d.ts.map +1 -1
  106. package/dist/commands/config.js +7 -33
  107. package/dist/commands/config.js.map +1 -1
  108. package/dist/commands/prompt.d.ts.map +1 -1
  109. package/dist/commands/prompt.js +15 -2
  110. package/dist/commands/prompt.js.map +1 -1
  111. package/dist/commands/setup.d.ts +7 -39
  112. package/dist/commands/setup.d.ts.map +1 -1
  113. package/dist/commands/setup.js +27 -302
  114. package/dist/commands/setup.js.map +1 -1
  115. package/dist/commands/step.d.ts +44 -1
  116. package/dist/commands/step.d.ts.map +1 -1
  117. package/dist/commands/step.js +255 -11
  118. package/dist/commands/step.js.map +1 -1
  119. package/dist/commands/thread.d.ts +16 -3
  120. package/dist/commands/thread.d.ts.map +1 -1
  121. package/dist/commands/thread.js +379 -140
  122. package/dist/commands/thread.js.map +1 -1
  123. package/dist/commands/workflow.d.ts +9 -1
  124. package/dist/commands/workflow.d.ts.map +1 -1
  125. package/dist/commands/workflow.js +130 -6
  126. package/dist/commands/workflow.js.map +1 -1
  127. package/dist/moderator/__tests__/evaluate.test.js +31 -17
  128. package/dist/moderator/__tests__/evaluate.test.js.map +1 -1
  129. package/dist/moderator/evaluate.d.ts.map +1 -1
  130. package/dist/moderator/evaluate.js +4 -16
  131. package/dist/moderator/evaluate.js.map +1 -1
  132. package/dist/moderator/index.d.ts +1 -2
  133. package/dist/moderator/index.d.ts.map +1 -1
  134. package/dist/moderator/index.js +0 -1
  135. package/dist/moderator/index.js.map +1 -1
  136. package/dist/moderator/types.d.ts +6 -10
  137. package/dist/moderator/types.d.ts.map +1 -1
  138. package/dist/moderator/types.js +1 -3
  139. package/dist/moderator/types.js.map +1 -1
  140. package/dist/schemas.d.ts +2 -0
  141. package/dist/schemas.d.ts.map +1 -1
  142. package/dist/schemas.js +5 -3
  143. package/dist/schemas.js.map +1 -1
  144. package/dist/store.d.ts +28 -9
  145. package/dist/store.d.ts.map +1 -1
  146. package/dist/store.js +75 -16
  147. package/dist/store.js.map +1 -1
  148. package/dist/validate-semantic.d.ts.map +1 -1
  149. package/dist/validate-semantic.js +83 -66
  150. package/dist/validate-semantic.js.map +1 -1
  151. package/dist/validate.d.ts +6 -0
  152. package/dist/validate.d.ts.map +1 -1
  153. package/dist/validate.js +24 -0
  154. package/dist/validate.js.map +1 -1
  155. package/package.json +8 -10
  156. package/src/__tests__/adapter-json-roundtrip.test.ts +1 -1
  157. package/src/__tests__/agent-resolution-llm-free.test.ts +39 -0
  158. package/src/__tests__/build-step-entry.test.ts +203 -0
  159. package/src/__tests__/clear-thread-failed-attempts.test.ts +122 -0
  160. package/src/__tests__/config.test.ts +33 -321
  161. package/src/__tests__/current-role.test.ts +7 -6
  162. package/src/__tests__/e2e-mock-agent.test.ts +20 -23
  163. package/src/__tests__/fixtures/e2e-count.workflow.yaml +1 -0
  164. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +1 -0
  165. package/src/__tests__/fixtures/{e2e-mustache.workflow.yaml → e2e-liquid.workflow.yaml} +3 -2
  166. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +1 -0
  167. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +2 -2
  168. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +6 -10
  169. package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
  170. package/src/__tests__/moderator-evaluate.test.ts +9 -52
  171. package/src/__tests__/pid-recycling.test.ts +328 -0
  172. package/src/__tests__/prompt.test.ts +397 -0
  173. package/src/__tests__/resolve-head-hash.test.ts +4 -4
  174. package/src/__tests__/setup-agent-discovery.test.ts +26 -51
  175. package/src/__tests__/setup-complexity.test.ts +1 -203
  176. package/src/__tests__/setup-no-llm.test.ts +68 -0
  177. package/src/__tests__/solve-issue-tea-worktree.test.ts +24 -30
  178. package/src/__tests__/step-ask.test.ts +670 -0
  179. package/src/__tests__/step-show-json.test.ts +1 -0
  180. package/src/__tests__/step-timing.test.ts +2 -0
  181. package/src/__tests__/store-global-cas.test.ts +2 -2
  182. package/src/__tests__/store-unified-threads.test.ts +9 -9
  183. package/src/__tests__/thread-cancel-status.test.ts +6 -6
  184. package/src/__tests__/thread-list-filters.test.ts +434 -8
  185. package/src/__tests__/thread-poke.test.ts +545 -0
  186. package/src/__tests__/thread-resume.test.ts +10 -14
  187. package/src/__tests__/thread-show-status.test.ts +17 -29
  188. package/src/__tests__/thread-suspend-step.test.ts +8 -14
  189. package/src/__tests__/thread-suspended-display.test.ts +10 -22
  190. package/src/__tests__/thread.test.ts +4 -4
  191. package/src/__tests__/validate-semantic.test.ts +59 -31
  192. package/src/__tests__/workflow-list-recursive.test.ts +370 -0
  193. package/src/__tests__/workflow-resolution.test.ts +39 -21
  194. package/src/__tests__/workflow-show-resolution.test.ts +285 -0
  195. package/src/__tests__/workflow-validate.test.ts +806 -0
  196. package/src/background/background.ts +88 -6
  197. package/src/background/index.ts +2 -0
  198. package/src/background/types.ts +1 -0
  199. package/src/cli.ts +97 -47
  200. package/src/commands/config.ts +7 -35
  201. package/src/commands/prompt.ts +15 -2
  202. package/src/commands/setup.ts +29 -357
  203. package/src/commands/step.ts +339 -12
  204. package/src/commands/thread.ts +463 -169
  205. package/src/commands/workflow.ts +159 -4
  206. package/src/moderator/__tests__/evaluate.test.ts +34 -17
  207. package/src/moderator/evaluate.ts +5 -17
  208. package/src/moderator/index.ts +1 -6
  209. package/src/moderator/types.ts +6 -14
  210. package/src/schemas.ts +13 -3
  211. package/src/store.ts +86 -20
  212. package/src/validate-semantic.ts +109 -78
  213. package/src/validate.ts +27 -0
  214. package/dist/__tests__/setup-validate.test.d.ts +0 -2
  215. package/dist/__tests__/setup-validate.test.d.ts.map +0 -1
  216. package/dist/__tests__/setup-validate.test.js +0 -108
  217. package/dist/__tests__/setup-validate.test.js.map +0 -1
  218. package/src/__tests__/setup-validate.test.ts +0 -148
  219. /package/src/__tests__/fixtures/{e2e-mustache.mock.yaml → e2e-liquid.mock.yaml} +0 -0
@@ -3,88 +3,10 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSy
3
3
  import { 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 type { Result } from "@united-workforce/util";
7
6
  import { parse, stringify } from "yaml";
8
7
 
9
- /**
10
- * Send a minimal chat completion request to verify the model is reachable.
11
- * Returns ok on 2xx, error with reason string otherwise.
12
- */
13
- export async function validateModel(
14
- baseUrl: string,
15
- apiKey: string,
16
- model: string,
17
- ): Promise<Result<void, string>> {
18
- try {
19
- const url = `${baseUrl.replace(/\/+$/, "")}/chat/completions`;
20
- const res = await fetch(url, {
21
- method: "POST",
22
- headers: {
23
- Authorization: `Bearer ${apiKey}`,
24
- "Content-Type": "application/json",
25
- },
26
- body: JSON.stringify({
27
- model,
28
- messages: [{ role: "user", content: "hi" }],
29
- max_tokens: 1,
30
- }),
31
- signal: AbortSignal.timeout(15_000),
32
- });
33
- if (!res.ok) {
34
- return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };
35
- }
36
- return { ok: true, value: undefined };
37
- } catch (err: unknown) {
38
- if (err instanceof DOMException && err.name === "AbortError") {
39
- return { ok: false, error: "Request timed out — model endpoint unreachable" };
40
- }
41
- return { ok: false, error: `Network error — could not reach endpoint (${String(err)})` };
42
- }
43
- }
44
-
45
- /**
46
- * Preset provider list — embedded to avoid runtime YAML loading dependency.
47
- * Keep in sync with providers.yaml in cli.
48
- */
49
- const PRESET_PROVIDERS = [
50
- // International
51
- { name: "openai", label: "OpenAI", baseUrl: "https://api.openai.com/v1" },
52
- { name: "xai", label: "xAI", baseUrl: "https://api.x.ai/v1" },
53
- { name: "openrouter", label: "OpenRouter", baseUrl: "https://openrouter.ai/api/v1" },
54
- { name: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1" },
55
- // China
56
- {
57
- name: "dashscope",
58
- label: "DashScope (Alibaba)",
59
- baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
60
- },
61
- { name: "deepseek", label: "DeepSeek", baseUrl: "https://api.deepseek.com/v1" },
62
- { name: "siliconflow", label: "SiliconFlow", baseUrl: "https://api.siliconflow.cn/v1" },
63
- {
64
- name: "volcengine",
65
- label: "Volcengine (ByteDance)",
66
- baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
67
- },
68
- { name: "kimi", label: "Kimi (Moonshot)", baseUrl: "https://api.moonshot.cn/v1" },
69
- { name: "glm", label: "GLM (Zhipu AI)", baseUrl: "https://open.bigmodel.cn/api/paas/v4" },
70
- { name: "stepfun", label: "StepFun", baseUrl: "https://api.stepfun.com/v1" },
71
- { name: "minimax", label: "MiniMax", baseUrl: "https://api.minimax.io/v1" },
72
- // Local
73
- { name: "ollama", label: "Ollama (local)", baseUrl: "http://localhost:11434/v1" },
74
- ] as const;
75
-
76
- /** Look up the base URL for a preset provider name. Returns null if not a preset. */
77
- export function resolvePresetBaseUrl(providerName: string): string | null {
78
- const preset = PRESET_PROVIDERS.find((p) => p.name === providerName);
79
- return preset !== undefined ? preset.baseUrl : null;
80
- }
81
-
82
- type SetupArgs = {
83
- provider: string;
84
- baseUrl: string;
85
- apiKey: string;
86
- model: string;
87
- agent?: string | undefined;
8
+ export type SetupArgs = {
9
+ agent: string;
88
10
  storageRoot: string;
89
11
  };
90
12
 
@@ -193,77 +115,17 @@ async function _tryWhichDiscovery(): Promise<string[] | null> {
193
115
  }
194
116
 
195
117
  // ──────────────────────────────────────────────────────────────────────────────
196
- // Extracted helpers onData closure (promptSecret)
118
+ // Terminator/backspace helpers (kept for reuse + test coverage)
197
119
  // ──────────────────────────────────────────────────────────────────────────────
198
120
 
199
121
  /** Returns true for newline, carriage return, or EOF (EOT). */
200
122
  export function _isTerminator(c: string): boolean {
201
- return c === "\n" || c === "\r" || c === "";
123
+ return c === "\n" || c === "\r" || c === "\u0004";
202
124
  }
203
125
 
204
126
  /** Returns true for DEL or backspace. */
205
127
  export function _isBackspace(c: string): boolean {
206
- return c === "" || c === "\b";
207
- }
208
-
209
- // ──────────────────────────────────────────────────────────────────────────────
210
- // Extracted helpers — cmdSetupInteractive
211
- // ──────────────────────────────────────────────────────────────────────────────
212
-
213
- type ProviderEntry = { name: string; label: string; baseUrl: string };
214
-
215
- /** Prints the numbered provider list and custom option to stdout. */
216
- export function _printProviderMenu(providers: readonly ProviderEntry[]): void {
217
- const numWidth = String(providers.length + 1).length;
218
- for (let i = 0; i < providers.length; i++) {
219
- const p = providers[i];
220
- if (!p) continue;
221
- const num = String(i + 1).padStart(numWidth);
222
- console.log(` ${num}) ${p.label.padEnd(28)} ${p.baseUrl}`);
223
- }
224
- const customNum = String(providers.length + 1).padStart(numWidth);
225
- console.log(` ${customNum}) Custom (enter name and URL manually)\n`);
226
- }
227
-
228
- /** Resolves a numeric choice string to a preset provider, or null for custom/invalid. */
229
- export function _resolveProviderChoice(
230
- choice: string,
231
- providers: readonly ProviderEntry[],
232
- ): { providerName: string; baseUrl: string } | null {
233
- const n = Number.parseInt(choice, 10);
234
- if (Number.isNaN(n) || n < 1 || n > providers.length) return null;
235
- const p = providers[n - 1];
236
- if (!p) return null;
237
- return { providerName: p.name, baseUrl: p.baseUrl };
238
- }
239
-
240
- /** Resolves numeric index or literal model name to a model string. */
241
- export function _resolveModelChoice(input: string, models: string[]): string {
242
- const n = Number.parseInt(input, 10);
243
- if (!Number.isNaN(n) && n >= 1 && n <= models.length) {
244
- return models[n - 1] ?? input;
245
- }
246
- return input;
247
- }
248
-
249
- /** Prints the multi-column model list to stdout. */
250
- export function _printModelMenu(models: string[], termCols: number): void {
251
- const nw = String(models.length).length;
252
- const maxLen = models.reduce((m, s) => Math.max(m, s.length), 0);
253
- const colWidth = nw + 2 + maxLen + 4;
254
- const cols = Math.max(1, Math.floor(termCols / colWidth));
255
- const rows = Math.ceil(models.length / cols);
256
- for (let r = 0; r < rows; r++) {
257
- let line = "";
258
- for (let c = 0; c < cols; c++) {
259
- const idx = c * rows + r;
260
- if (idx >= models.length) break;
261
- const num = String(idx + 1).padStart(nw);
262
- const name = (models[idx] ?? "").padEnd(maxLen);
263
- line += ` ${num}) ${name} `;
264
- }
265
- console.log(line.trimEnd());
266
- }
128
+ return c === "\u007f" || c === "\b";
267
129
  }
268
130
 
269
131
  // ──────────────────────────────────────────────────────────────────────────────
@@ -323,7 +185,6 @@ export async function _promptAgentSelection(
323
185
  console.log(` Found 1 agent: ${label} — auto-selected.\n`);
324
186
  return name;
325
187
  }
326
-
327
188
  console.log(` Found ${agents.length} agents:\n`);
328
189
  _printAgentMenu(agents);
329
190
  const choice = (await rl.question(`Choose default agent [1-${agents.length}]: `)).trim();
@@ -340,61 +201,33 @@ export async function _promptAgentSelection(
340
201
  return name;
341
202
  }
342
203
 
343
- type ValidationResult = { ok: boolean; error: string | null };
344
-
345
- /** Prints the model validation result to stdout. */
346
- export function _printValidationResult(validation: ValidationResult): void {
347
- if (validation.ok) {
348
- console.log("✓ Model verified — connection successful.\n");
349
- } else {
350
- console.log(`\n⚠ Warning: Could not reach model — ${validation.error}`);
351
- console.log(
352
- " Config saved, but you may want to try a different model or check your API key.\n",
353
- );
354
- }
355
- }
356
-
357
204
  // ──────────────────────────────────────────────────────────────────────────────
358
205
 
359
206
  /**
360
- * Merge setup args into config.yaml structure. Non-destructive — preserves existing entries.
207
+ * Merge setup args into config.yaml structure. Non-destructive — preserves
208
+ * existing entries (including agentOverrides). Engine config is LLM-free, so
209
+ * legacy provider/model fields are dropped on rewrite.
361
210
  */
362
211
  function mergeConfig(existing: Record<string, unknown>, args: SetupArgs): Record<string, unknown> {
363
- const providers = (
364
- typeof existing.providers === "object" && existing.providers !== null
365
- ? { ...(existing.providers as Record<string, unknown>) }
366
- : {}
367
- ) as Record<string, unknown>;
368
-
369
- providers[args.provider] = { baseUrl: args.baseUrl, apiKey: args.apiKey };
370
-
371
- const models = (
372
- typeof existing.models === "object" && existing.models !== null
373
- ? { ...(existing.models as Record<string, unknown>) }
374
- : {}
375
- ) as Record<string, unknown>;
376
- models.default = { provider: args.provider, name: args.model };
377
-
378
212
  const agents = (
379
213
  typeof existing.agents === "object" && existing.agents !== null
380
214
  ? { ...(existing.agents as Record<string, unknown>) }
381
215
  : {}
382
216
  ) as Record<string, unknown>;
383
217
 
384
- const agentName = _agentNameFromBinary(args.agent ?? "hermes");
385
- // Ensure the selected agent has an entry
218
+ const agentName = _agentNameFromBinary(args.agent);
386
219
  if (!agents[agentName]) {
387
220
  agents[agentName] = { command: `uwf-${agentName}`, args: [] };
388
221
  }
389
222
 
390
- return {
391
- ...existing,
392
- providers,
393
- models,
223
+ const merged: Record<string, unknown> = {
394
224
  agents,
395
225
  defaultAgent: agentName,
396
- defaultModel: existing.defaultModel ?? "default",
397
226
  };
227
+ if (existing.agentOverrides !== undefined) {
228
+ merged.agentOverrides = existing.agentOverrides;
229
+ }
230
+ return merged;
398
231
  }
399
232
 
400
233
  /**
@@ -429,7 +262,9 @@ export function _checkAdapterAvailability(agentName: string): string[] {
429
262
  }
430
263
 
431
264
  /**
432
- * Non-interactive setup. All required args provided via CLI flags.
265
+ * 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`.
433
268
  */
434
269
  export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>> {
435
270
  const { storageRoot } = args;
@@ -445,11 +280,8 @@ export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>
445
280
  // Print config path to stderr (stdout is reserved for JSON output)
446
281
  console.error(`Config saved to ${configPath} ✓`);
447
282
 
448
- // Validate model connectivity
449
- const validation = await validateModel(args.baseUrl, args.apiKey, args.model);
450
-
451
283
  // Check adapter availability
452
- const agentName = _agentNameFromBinary(args.agent ?? "hermes");
284
+ const agentName = _agentNameFromBinary(args.agent);
453
285
  const adapterWarnings = _checkAdapterAvailability(agentName);
454
286
  for (const w of adapterWarnings) {
455
287
  console.error(`⚠ ${w}`);
@@ -457,191 +289,31 @@ export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>
457
289
 
458
290
  return {
459
291
  configPath,
460
- provider: args.provider,
461
- model: args.model,
462
292
  defaultAgent: merged.defaultAgent,
463
- validation,
464
293
  adapterWarnings,
465
294
  };
466
295
  }
467
296
 
468
- type SecretState = {
469
- buf: string;
470
- rawWasSet: boolean;
471
- resolve: (value: string) => void;
472
- onData: (chunk: string) => void;
473
- };
474
-
475
- function _handleSecretTerminator(state: SecretState): void {
476
- if (process.stdin.isTTY) process.stdin.setRawMode(state.rawWasSet);
477
- process.stdin.pause();
478
- process.stdin.removeListener("data", state.onData);
479
- process.stdout.write("\n");
480
- state.resolve(state.buf.trim());
481
- }
482
-
483
- function _handleSecretBackspace(state: SecretState): void {
484
- if (state.buf.length > 0) {
485
- state.buf = state.buf.slice(0, -1);
486
- process.stdout.write("\b \b");
487
- }
488
- }
489
-
490
- function _handleSecretChar(c: string, state: SecretState): boolean {
491
- if (_isTerminator(c)) {
492
- _handleSecretTerminator(state);
493
- return true;
494
- }
495
- if (_isBackspace(c)) {
496
- _handleSecretBackspace(state);
497
- return false;
498
- }
499
- if (c === "") {
500
- if (process.stdin.isTTY) process.stdin.setRawMode(state.rawWasSet);
501
- process.exit(130);
502
- }
503
- state.buf += c;
504
- process.stdout.write("*");
505
- return false;
506
- }
507
-
508
- /** Read a line with terminal echo disabled (for secrets). */
509
- async function promptSecret(label: string): Promise<string> {
510
- process.stdout.write(label);
511
- return new Promise((resolve) => {
512
- const rawWasSet = process.stdin.isRaw;
513
- if (process.stdin.isTTY) {
514
- process.stdin.setRawMode(true);
515
- }
516
- process.stdin.resume();
517
- process.stdin.setEncoding("utf8");
518
-
519
- const state: SecretState = { buf: "", rawWasSet, resolve, onData: () => {} };
520
- state.onData = (chunk: string) => {
521
- for (const c of chunk.toString()) {
522
- if (_handleSecretChar(c, state)) return;
523
- }
524
- };
525
- process.stdin.on("data", state.onData);
526
- });
527
- }
528
-
529
- /** Fetch available models from an OpenAI-compatible /models endpoint. */
530
- async function fetchModels(baseUrl: string, apiKey: string): Promise<string[]> {
531
- try {
532
- const url = `${baseUrl.replace(/\/+$/, "")}/models`;
533
- const res = await fetch(url, {
534
- headers: { Authorization: `Bearer ${apiKey}` },
535
- signal: AbortSignal.timeout(10_000),
536
- });
537
- if (!res.ok) return [];
538
- const body = (await res.json()) as { data?: { id: string }[] };
539
- if (!Array.isArray(body.data)) return [];
540
- const NON_CHAT =
541
- /speech|embed|image|video|audio|ocr|rerank|tts|asr|paraformer|sambert|cosyvoice|wordart|wanx|wan2|flux|stable-diffusion|gui-/i;
542
- return body.data
543
- .map((m) => m.id)
544
- .filter((id) => !NON_CHAT.test(id))
545
- .sort();
546
- } catch {
547
- return [];
548
- }
549
- }
550
-
551
- async function _promptProviderSelection(
552
- rl: ReturnType<typeof createInterface>,
553
- ): Promise<{ providerName: string; baseUrl: string }> {
554
- console.log("Select a provider:\n");
555
- _printProviderMenu(PRESET_PROVIDERS);
556
-
557
- const choice = (await rl.question(`Choose [1-${PRESET_PROVIDERS.length + 1}]: `)).trim();
558
- const choiceNum = Number.parseInt(choice, 10);
559
- if (Number.isNaN(choiceNum) || choiceNum < 1 || choiceNum > PRESET_PROVIDERS.length + 1) {
560
- throw new Error(`Invalid choice: ${choice}`);
561
- }
562
-
563
- const preset = _resolveProviderChoice(choice, PRESET_PROVIDERS);
564
- if (preset) {
565
- const selected = PRESET_PROVIDERS[choiceNum - 1];
566
- if (selected) {
567
- console.log(`\n → ${selected.label} (${selected.baseUrl})\n`);
568
- }
569
- return preset;
570
- }
571
-
572
- const providerName = (await rl.question("Provider name (e.g. my-proxy): ")).trim();
573
- if (!providerName) throw new Error("Provider name required");
574
- const baseUrl = (await rl.question("OpenAI-compatible API base URL: ")).trim();
575
- if (!baseUrl) throw new Error("Base URL required");
576
- return { providerName, baseUrl };
577
- }
578
-
579
- async function _promptModelSelection(
580
- rl: ReturnType<typeof createInterface>,
581
- baseUrl: string,
582
- apiKey: string,
583
- ): Promise<string> {
584
- console.log("\nFetching available models...");
585
- const models = await fetchModels(baseUrl, apiKey);
586
-
587
- if (models.length === 0) {
588
- console.log("Could not fetch models. Enter model name manually.");
589
- const model = (await rl.question("Default model (e.g. qwen-plus, gpt-4o): ")).trim();
590
- if (!model) throw new Error("Model required");
591
- return model;
592
- }
593
- console.log(`\nAvailable models (${models.length}):\n`);
594
- _printModelMenu(models, process.stdout.columns || 100);
595
- console.log(`\nChoose a number, or type a model name directly.`);
596
- const modelInput = (await rl.question(`Default model [1-${models.length}]: `)).trim();
597
- if (!modelInput) throw new Error("Model required");
598
- return _resolveModelChoice(modelInput, models);
599
- }
600
-
601
297
  /**
602
- * Interactive setup — prompts user for provider, API key, model.
298
+ * Interactive setup — prompts the user only for the default agent. LLM
299
+ * configuration lives in config.yaml under `providers` and `models`.
603
300
  */
604
301
  export async function cmdSetupInteractive(storageRoot: string): Promise<Record<string, unknown>> {
605
302
  const rl = createInterface({ input, output });
606
-
607
303
  try {
608
- console.log("Configure LLM provider for uwf workflow agents.\n");
609
-
610
- const { providerName, baseUrl } = await _promptProviderSelection(rl);
304
+ console.log("Configure default agent for uwf workflow.\n");
611
305
 
612
- // 2. API key
306
+ const agentName = await _promptAgentSelection(rl);
613
307
  rl.close();
614
- const apiKey = await promptSecret("API key: ");
615
- if (!apiKey) throw new Error("API key required");
616
-
617
- // 3. Model selection
618
- const rl2 = createInterface({ input, output });
619
- const model = await _promptModelSelection(rl2, baseUrl, apiKey);
620
- rl2.close();
621
- console.log(` → ${providerName}/${model}\n`);
622
-
623
- // 4. Agent discovery & selection
624
- const rl3 = createInterface({ input, output });
625
- const agentName = await _promptAgentSelection(rl3);
626
- rl3.close();
627
-
628
- const setupResult = await cmdSetup({
629
- provider: providerName,
630
- baseUrl,
631
- apiKey,
632
- model,
633
- agent: agentName,
634
- storageRoot,
635
- });
636
308
 
637
- // Show validation result
638
- if (setupResult.validation && typeof setupResult.validation === "object") {
639
- _printValidationResult(setupResult.validation as ValidationResult);
640
- }
309
+ await cmdSetup({ agent: agentName, storageRoot });
641
310
  console.log("Setup complete! Get started:\n");
642
- console.log(" uwf workflow put <workflow.yaml> Register a workflow");
643
- console.log(' uwf thread start <name> -p "..." Start a thread');
644
- console.log(" uwf thread step <thread-id> Execute next step");
311
+ console.log(" uwf workflow list List available workflows");
312
+ console.log(" uwf workflow add <workflow.yaml> Register a workflow");
313
+ console.log(' uwf thread start <name> -p "..." Start a thread');
314
+ console.log(" uwf thread exec <thread-id> Execute next step");
315
+ console.log("");
316
+ console.log("LLM config: edit ~/.uwf/config.yaml (providers + models sections).");
645
317
  console.log("");
646
318
 
647
319
  return null as unknown as Record<string, unknown>;