@united-workforce/cli 0.1.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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/adapter-json-roundtrip.test.js +147 -0
  6. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +2 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +685 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/current-role.test.d.ts +2 -0
  12. package/dist/__tests__/current-role.test.d.ts.map +1 -0
  13. package/dist/__tests__/current-role.test.js +401 -0
  14. package/dist/__tests__/current-role.test.js.map +1 -0
  15. package/dist/__tests__/e2e-mock-agent.test.d.ts +2 -0
  16. package/dist/__tests__/e2e-mock-agent.test.d.ts.map +1 -0
  17. package/dist/__tests__/e2e-mock-agent.test.js +401 -0
  18. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -0
  19. package/dist/__tests__/include-tag.test.d.ts +2 -0
  20. package/dist/__tests__/include-tag.test.d.ts.map +1 -0
  21. package/dist/__tests__/include-tag.test.js +69 -0
  22. package/dist/__tests__/include-tag.test.js.map +1 -0
  23. package/dist/__tests__/log.test.d.ts +2 -0
  24. package/dist/__tests__/log.test.d.ts.map +1 -0
  25. package/dist/__tests__/log.test.js +161 -0
  26. package/dist/__tests__/log.test.js.map +1 -0
  27. package/dist/__tests__/moderator-evaluate.test.d.ts +2 -0
  28. package/dist/__tests__/moderator-evaluate.test.d.ts.map +1 -0
  29. package/dist/__tests__/moderator-evaluate.test.js +170 -0
  30. package/dist/__tests__/moderator-evaluate.test.js.map +1 -0
  31. package/dist/__tests__/preload.d.ts +3 -0
  32. package/dist/__tests__/preload.d.ts.map +1 -0
  33. package/dist/__tests__/preload.js +6 -0
  34. package/dist/__tests__/preload.js.map +1 -0
  35. package/dist/__tests__/prompt.test.d.ts +2 -0
  36. package/dist/__tests__/prompt.test.d.ts.map +1 -0
  37. package/dist/__tests__/prompt.test.js +111 -0
  38. package/dist/__tests__/prompt.test.js.map +1 -0
  39. package/dist/__tests__/resolve-head-hash.test.d.ts +2 -0
  40. package/dist/__tests__/resolve-head-hash.test.d.ts.map +1 -0
  41. package/dist/__tests__/resolve-head-hash.test.js +66 -0
  42. package/dist/__tests__/resolve-head-hash.test.js.map +1 -0
  43. package/dist/__tests__/setup-agent-discovery.test.d.ts +2 -0
  44. package/dist/__tests__/setup-agent-discovery.test.d.ts.map +1 -0
  45. package/dist/__tests__/setup-agent-discovery.test.js +119 -0
  46. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -0
  47. package/dist/__tests__/setup-complexity.test.d.ts +2 -0
  48. package/dist/__tests__/setup-complexity.test.d.ts.map +1 -0
  49. package/dist/__tests__/setup-complexity.test.js +314 -0
  50. package/dist/__tests__/setup-complexity.test.js.map +1 -0
  51. package/dist/__tests__/setup-validate.test.d.ts +2 -0
  52. package/dist/__tests__/setup-validate.test.d.ts.map +1 -0
  53. package/dist/__tests__/setup-validate.test.js +108 -0
  54. package/dist/__tests__/setup-validate.test.js.map +1 -0
  55. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts +2 -0
  56. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts.map +1 -0
  57. package/dist/__tests__/solve-issue-tea-worktree.test.js +107 -0
  58. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -0
  59. package/dist/__tests__/spawn-agent-json.test.d.ts +2 -0
  60. package/dist/__tests__/spawn-agent-json.test.d.ts.map +1 -0
  61. package/dist/__tests__/spawn-agent-json.test.js +79 -0
  62. package/dist/__tests__/spawn-agent-json.test.js.map +1 -0
  63. package/dist/__tests__/step-read.test.d.ts +2 -0
  64. package/dist/__tests__/step-read.test.d.ts.map +1 -0
  65. package/dist/__tests__/step-read.test.js +561 -0
  66. package/dist/__tests__/step-read.test.js.map +1 -0
  67. package/dist/__tests__/step-show-json.test.d.ts +2 -0
  68. package/dist/__tests__/step-show-json.test.d.ts.map +1 -0
  69. package/dist/__tests__/step-show-json.test.js +311 -0
  70. package/dist/__tests__/step-show-json.test.js.map +1 -0
  71. package/dist/__tests__/step-timing.test.d.ts +2 -0
  72. package/dist/__tests__/step-timing.test.d.ts.map +1 -0
  73. package/dist/__tests__/step-timing.test.js +345 -0
  74. package/dist/__tests__/step-timing.test.js.map +1 -0
  75. package/dist/__tests__/store-global-cas.test.d.ts +2 -0
  76. package/dist/__tests__/store-global-cas.test.d.ts.map +1 -0
  77. package/dist/__tests__/store-global-cas.test.js +235 -0
  78. package/dist/__tests__/store-global-cas.test.js.map +1 -0
  79. package/dist/__tests__/store-storage-root.test.d.ts +2 -0
  80. package/dist/__tests__/store-storage-root.test.d.ts.map +1 -0
  81. package/dist/__tests__/store-storage-root.test.js +43 -0
  82. package/dist/__tests__/store-storage-root.test.js.map +1 -0
  83. package/dist/__tests__/store-unified-threads.test.d.ts +2 -0
  84. package/dist/__tests__/store-unified-threads.test.d.ts.map +1 -0
  85. package/dist/__tests__/store-unified-threads.test.js +189 -0
  86. package/dist/__tests__/store-unified-threads.test.js.map +1 -0
  87. package/dist/__tests__/thread-cancel-status.test.d.ts +2 -0
  88. package/dist/__tests__/thread-cancel-status.test.d.ts.map +1 -0
  89. package/dist/__tests__/thread-cancel-status.test.js +111 -0
  90. package/dist/__tests__/thread-cancel-status.test.js.map +1 -0
  91. package/dist/__tests__/thread-list-filters.test.d.ts +2 -0
  92. package/dist/__tests__/thread-list-filters.test.d.ts.map +1 -0
  93. package/dist/__tests__/thread-list-filters.test.js +442 -0
  94. package/dist/__tests__/thread-list-filters.test.js.map +1 -0
  95. package/dist/__tests__/thread-location.test.d.ts +2 -0
  96. package/dist/__tests__/thread-location.test.d.ts.map +1 -0
  97. package/dist/__tests__/thread-location.test.js +159 -0
  98. package/dist/__tests__/thread-location.test.js.map +1 -0
  99. package/dist/__tests__/thread-read-quota.test.d.ts +2 -0
  100. package/dist/__tests__/thread-read-quota.test.d.ts.map +1 -0
  101. package/dist/__tests__/thread-read-quota.test.js +546 -0
  102. package/dist/__tests__/thread-read-quota.test.js.map +1 -0
  103. package/dist/__tests__/thread-read-xml-tags.test.d.ts +2 -0
  104. package/dist/__tests__/thread-read-xml-tags.test.d.ts.map +1 -0
  105. package/dist/__tests__/thread-read-xml-tags.test.js +610 -0
  106. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -0
  107. package/dist/__tests__/thread-resume.test.d.ts +2 -0
  108. package/dist/__tests__/thread-resume.test.d.ts.map +1 -0
  109. package/dist/__tests__/thread-resume.test.js +592 -0
  110. package/dist/__tests__/thread-resume.test.js.map +1 -0
  111. package/dist/__tests__/thread-show-status.test.d.ts +2 -0
  112. package/dist/__tests__/thread-show-status.test.d.ts.map +1 -0
  113. package/dist/__tests__/thread-show-status.test.js +267 -0
  114. package/dist/__tests__/thread-show-status.test.js.map +1 -0
  115. package/dist/__tests__/thread-start-cwd-cli.test.d.ts +2 -0
  116. package/dist/__tests__/thread-start-cwd-cli.test.d.ts.map +1 -0
  117. package/dist/__tests__/thread-start-cwd-cli.test.js +130 -0
  118. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -0
  119. package/dist/__tests__/thread-step-count.test.d.ts +2 -0
  120. package/dist/__tests__/thread-step-count.test.d.ts.map +1 -0
  121. package/dist/__tests__/thread-step-count.test.js +55 -0
  122. package/dist/__tests__/thread-step-count.test.js.map +1 -0
  123. package/dist/__tests__/thread-suspend-step.test.d.ts +2 -0
  124. package/dist/__tests__/thread-suspend-step.test.d.ts.map +1 -0
  125. package/dist/__tests__/thread-suspend-step.test.js +155 -0
  126. package/dist/__tests__/thread-suspend-step.test.js.map +1 -0
  127. package/dist/__tests__/thread-suspended-display.test.d.ts +2 -0
  128. package/dist/__tests__/thread-suspended-display.test.d.ts.map +1 -0
  129. package/dist/__tests__/thread-suspended-display.test.js +247 -0
  130. package/dist/__tests__/thread-suspended-display.test.js.map +1 -0
  131. package/dist/__tests__/thread-test-helpers.d.ts +4 -0
  132. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -0
  133. package/dist/__tests__/thread-test-helpers.js +23 -0
  134. package/dist/__tests__/thread-test-helpers.js.map +1 -0
  135. package/dist/__tests__/thread.test.d.ts +2 -0
  136. package/dist/__tests__/thread.test.d.ts.map +1 -0
  137. package/dist/__tests__/thread.test.js +883 -0
  138. package/dist/__tests__/thread.test.js.map +1 -0
  139. package/dist/__tests__/validate-semantic.test.d.ts +2 -0
  140. package/dist/__tests__/validate-semantic.test.d.ts.map +1 -0
  141. package/dist/__tests__/validate-semantic.test.js +408 -0
  142. package/dist/__tests__/validate-semantic.test.js.map +1 -0
  143. package/dist/__tests__/workflow-resolution.test.d.ts +2 -0
  144. package/dist/__tests__/workflow-resolution.test.d.ts.map +1 -0
  145. package/dist/__tests__/workflow-resolution.test.js +308 -0
  146. package/dist/__tests__/workflow-resolution.test.js.map +1 -0
  147. package/dist/background/background.d.ts +38 -0
  148. package/dist/background/background.d.ts.map +1 -0
  149. package/dist/background/background.js +123 -0
  150. package/dist/background/background.js.map +1 -0
  151. package/dist/background/index.d.ts +3 -0
  152. package/dist/background/index.d.ts.map +1 -0
  153. package/dist/background/index.js +2 -0
  154. package/dist/background/index.js.map +1 -0
  155. package/dist/background/types.d.ts +9 -0
  156. package/dist/background/types.d.ts.map +1 -0
  157. package/dist/background/types.js +2 -0
  158. package/dist/background/types.js.map +1 -0
  159. package/dist/cli.d.ts +3 -0
  160. package/dist/cli.d.ts.map +1 -0
  161. package/dist/cli.js +535 -0
  162. package/dist/cli.js.map +1 -0
  163. package/dist/commands/config.d.ts +41 -0
  164. package/dist/commands/config.d.ts.map +1 -0
  165. package/dist/commands/config.js +252 -0
  166. package/dist/commands/config.js.map +1 -0
  167. package/dist/commands/log.d.ts +26 -0
  168. package/dist/commands/log.d.ts.map +1 -0
  169. package/dist/commands/log.js +79 -0
  170. package/dist/commands/log.js.map +1 -0
  171. package/dist/commands/prompt.d.ts +6 -0
  172. package/dist/commands/prompt.d.ts.map +1 -0
  173. package/dist/commands/prompt.js +67 -0
  174. package/dist/commands/prompt.js.map +1 -0
  175. package/dist/commands/setup.d.ts +73 -0
  176. package/dist/commands/setup.d.ts.map +1 -0
  177. package/dist/commands/setup.js +522 -0
  178. package/dist/commands/setup.js.map +1 -0
  179. package/dist/commands/shared.d.ts +31 -0
  180. package/dist/commands/shared.d.ts.map +1 -0
  181. package/dist/commands/shared.js +154 -0
  182. package/dist/commands/shared.js.map +1 -0
  183. package/dist/commands/step.d.ts +18 -0
  184. package/dist/commands/step.d.ts.map +1 -0
  185. package/dist/commands/step.js +257 -0
  186. package/dist/commands/step.js.map +1 -0
  187. package/dist/commands/thread-time-parser.d.ts +6 -0
  188. package/dist/commands/thread-time-parser.d.ts.map +1 -0
  189. package/dist/commands/thread-time-parser.js +22 -0
  190. package/dist/commands/thread-time-parser.js.map +1 -0
  191. package/dist/commands/thread.d.ts +38 -0
  192. package/dist/commands/thread.d.ts.map +1 -0
  193. package/dist/commands/thread.js +1087 -0
  194. package/dist/commands/thread.js.map +1 -0
  195. package/dist/commands/workflow.d.ts +24 -0
  196. package/dist/commands/workflow.d.ts.map +1 -0
  197. package/dist/commands/workflow.js +138 -0
  198. package/dist/commands/workflow.js.map +1 -0
  199. package/dist/format.d.ts +3 -0
  200. package/dist/format.d.ts.map +1 -0
  201. package/dist/format.js +10 -0
  202. package/dist/format.js.map +1 -0
  203. package/dist/include.d.ts +12 -0
  204. package/dist/include.d.ts.map +1 -0
  205. package/dist/include.js +35 -0
  206. package/dist/include.js.map +1 -0
  207. package/dist/moderator/__tests__/evaluate.test.d.ts +2 -0
  208. package/dist/moderator/__tests__/evaluate.test.d.ts.map +1 -0
  209. package/dist/moderator/__tests__/evaluate.test.js +167 -0
  210. package/dist/moderator/__tests__/evaluate.test.js.map +1 -0
  211. package/dist/moderator/evaluate.d.ts +6 -0
  212. package/dist/moderator/evaluate.d.ts.map +1 -0
  213. package/dist/moderator/evaluate.js +65 -0
  214. package/dist/moderator/evaluate.js.map +1 -0
  215. package/dist/moderator/index.d.ts +4 -0
  216. package/dist/moderator/index.d.ts.map +1 -0
  217. package/dist/moderator/index.js +3 -0
  218. package/dist/moderator/index.js.map +1 -0
  219. package/dist/moderator/types.d.ts +25 -0
  220. package/dist/moderator/types.d.ts.map +1 -0
  221. package/dist/moderator/types.js +4 -0
  222. package/dist/moderator/types.js.map +1 -0
  223. package/dist/schemas.d.ts +16 -0
  224. package/dist/schemas.d.ts.map +1 -0
  225. package/dist/schemas.js +17 -0
  226. package/dist/schemas.js.map +1 -0
  227. package/dist/store.d.ts +77 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +392 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/validate-semantic.d.ts +7 -0
  232. package/dist/validate-semantic.d.ts.map +1 -0
  233. package/dist/validate-semantic.js +263 -0
  234. package/dist/validate-semantic.js.map +1 -0
  235. package/dist/validate.d.ts +16 -0
  236. package/dist/validate.d.ts.map +1 -0
  237. package/dist/validate.js +115 -0
  238. package/dist/validate.js.map +1 -0
  239. package/package.json +44 -0
  240. package/src/__tests__/adapter-json-roundtrip.test.ts +181 -0
  241. package/src/__tests__/config.test.ts +740 -0
  242. package/src/__tests__/current-role.test.ts +438 -0
  243. package/src/__tests__/e2e-mock-agent.test.ts +498 -0
  244. package/src/__tests__/fixtures/e2e-completed-resume.mock.yaml +15 -0
  245. package/src/__tests__/fixtures/e2e-count.mock.yaml +19 -0
  246. package/src/__tests__/fixtures/e2e-count.workflow.yaml +45 -0
  247. package/src/__tests__/fixtures/e2e-linear.mock.yaml +13 -0
  248. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +32 -0
  249. package/src/__tests__/fixtures/e2e-loop.mock.yaml +25 -0
  250. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +36 -0
  251. package/src/__tests__/fixtures/e2e-mismatch.mock.yaml +16 -0
  252. package/src/__tests__/fixtures/e2e-mustache.mock.yaml +15 -0
  253. package/src/__tests__/fixtures/e2e-mustache.workflow.yaml +34 -0
  254. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +14 -0
  255. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +24 -0
  256. package/src/__tests__/include-tag.test.ts +84 -0
  257. package/src/__tests__/log.test.ts +181 -0
  258. package/src/__tests__/moderator-evaluate.test.ts +186 -0
  259. package/src/__tests__/preload.ts +7 -0
  260. package/src/__tests__/prompt.test.ts +129 -0
  261. package/src/__tests__/resolve-head-hash.test.ts +86 -0
  262. package/src/__tests__/setup-agent-discovery.test.ts +167 -0
  263. package/src/__tests__/setup-complexity.test.ts +381 -0
  264. package/src/__tests__/setup-validate.test.ts +148 -0
  265. package/src/__tests__/solve-issue-tea-worktree.test.ts +144 -0
  266. package/src/__tests__/spawn-agent-json.test.ts +100 -0
  267. package/src/__tests__/step-read.test.ts +632 -0
  268. package/src/__tests__/step-show-json.test.ts +373 -0
  269. package/src/__tests__/step-timing.test.ts +392 -0
  270. package/src/__tests__/store-global-cas.test.ts +308 -0
  271. package/src/__tests__/store-storage-root.test.ts +49 -0
  272. package/src/__tests__/store-unified-threads.test.ts +235 -0
  273. package/src/__tests__/thread-cancel-status.test.ts +138 -0
  274. package/src/__tests__/thread-list-filters.test.ts +572 -0
  275. package/src/__tests__/thread-location.test.ts +186 -0
  276. package/src/__tests__/thread-read-quota.test.ts +613 -0
  277. package/src/__tests__/thread-read-xml-tags.test.ts +717 -0
  278. package/src/__tests__/thread-resume.test.ts +710 -0
  279. package/src/__tests__/thread-show-status.test.ts +317 -0
  280. package/src/__tests__/thread-start-cwd-cli.test.ts +164 -0
  281. package/src/__tests__/thread-step-count.test.ts +70 -0
  282. package/src/__tests__/thread-suspend-step.test.ts +181 -0
  283. package/src/__tests__/thread-suspended-display.test.ts +287 -0
  284. package/src/__tests__/thread-test-helpers.ts +37 -0
  285. package/src/__tests__/thread.test.ts +1025 -0
  286. package/src/__tests__/validate-semantic.test.ts +474 -0
  287. package/src/__tests__/workflow-resolution.test.ts +421 -0
  288. package/src/background/background.ts +147 -0
  289. package/src/background/index.ts +11 -0
  290. package/src/background/types.ts +9 -0
  291. package/src/cli.ts +692 -0
  292. package/src/commands/config.ts +304 -0
  293. package/src/commands/log.ts +116 -0
  294. package/src/commands/prompt.ts +81 -0
  295. package/src/commands/setup.ts +603 -0
  296. package/src/commands/shared.ts +227 -0
  297. package/src/commands/step.ts +343 -0
  298. package/src/commands/thread-time-parser.ts +23 -0
  299. package/src/commands/thread.ts +1575 -0
  300. package/src/commands/workflow.ts +213 -0
  301. package/src/format.ts +12 -0
  302. package/src/include.ts +37 -0
  303. package/src/moderator/__tests__/evaluate.test.ts +199 -0
  304. package/src/moderator/evaluate.ts +80 -0
  305. package/src/moderator/index.ts +7 -0
  306. package/src/moderator/types.ts +24 -0
  307. package/src/schemas.ts +26 -0
  308. package/src/store.ts +479 -0
  309. package/src/validate-semantic.ts +304 -0
  310. package/src/validate.ts +137 -0
@@ -0,0 +1,603 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { stdin as input, stdout as output } from "node:process";
4
+ import { createInterface } from "node:readline/promises";
5
+ import type { Result } from "@united-workforce/util";
6
+ import { parse, stringify } from "yaml";
7
+
8
+ /**
9
+ * Send a minimal chat completion request to verify the model is reachable.
10
+ * Returns ok on 2xx, error with reason string otherwise.
11
+ */
12
+ export async function validateModel(
13
+ baseUrl: string,
14
+ apiKey: string,
15
+ model: string,
16
+ ): Promise<Result<void, string>> {
17
+ try {
18
+ const url = `${baseUrl.replace(/\/+$/, "")}/chat/completions`;
19
+ const res = await fetch(url, {
20
+ method: "POST",
21
+ headers: {
22
+ Authorization: `Bearer ${apiKey}`,
23
+ "Content-Type": "application/json",
24
+ },
25
+ body: JSON.stringify({
26
+ model,
27
+ messages: [{ role: "user", content: "hi" }],
28
+ max_tokens: 1,
29
+ }),
30
+ signal: AbortSignal.timeout(15_000),
31
+ });
32
+ if (!res.ok) {
33
+ return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };
34
+ }
35
+ return { ok: true, value: undefined };
36
+ } catch (err: unknown) {
37
+ if (err instanceof DOMException && err.name === "AbortError") {
38
+ return { ok: false, error: "Request timed out — model endpoint unreachable" };
39
+ }
40
+ return { ok: false, error: `Network error — could not reach endpoint (${String(err)})` };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Preset provider list — embedded to avoid runtime YAML loading dependency.
46
+ * Keep in sync with providers.yaml in cli.
47
+ */
48
+ const PRESET_PROVIDERS = [
49
+ // International
50
+ { name: "openai", label: "OpenAI", baseUrl: "https://api.openai.com/v1" },
51
+ { name: "xai", label: "xAI", baseUrl: "https://api.x.ai/v1" },
52
+ { name: "openrouter", label: "OpenRouter", baseUrl: "https://openrouter.ai/api/v1" },
53
+ { name: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1" },
54
+ // China
55
+ {
56
+ name: "dashscope",
57
+ label: "DashScope (Alibaba)",
58
+ baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
59
+ },
60
+ { name: "deepseek", label: "DeepSeek", baseUrl: "https://api.deepseek.com/v1" },
61
+ { name: "siliconflow", label: "SiliconFlow", baseUrl: "https://api.siliconflow.cn/v1" },
62
+ {
63
+ name: "volcengine",
64
+ label: "Volcengine (ByteDance)",
65
+ baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
66
+ },
67
+ { name: "kimi", label: "Kimi (Moonshot)", baseUrl: "https://api.moonshot.cn/v1" },
68
+ { name: "glm", label: "GLM (Zhipu AI)", baseUrl: "https://open.bigmodel.cn/api/paas/v4" },
69
+ { name: "stepfun", label: "StepFun", baseUrl: "https://api.stepfun.com/v1" },
70
+ { name: "minimax", label: "MiniMax", baseUrl: "https://api.minimax.io/v1" },
71
+ // Local
72
+ { name: "ollama", label: "Ollama (local)", baseUrl: "http://localhost:11434/v1" },
73
+ ] as const;
74
+
75
+ type SetupArgs = {
76
+ provider: string;
77
+ baseUrl: string;
78
+ apiKey: string;
79
+ model: string;
80
+ agent?: string | undefined;
81
+ storageRoot: string;
82
+ };
83
+
84
+ function getConfigPath(root: string): string {
85
+ return join(root, "config.yaml");
86
+ }
87
+
88
+ /**
89
+ * Load existing config.yaml or return empty structure.
90
+ */
91
+ function loadExistingConfig(configPath: string): Record<string, unknown> {
92
+ try {
93
+ if (existsSync(configPath)) {
94
+ const raw = parse(readFileSync(configPath, "utf8")) as unknown;
95
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
96
+ return raw as Record<string, unknown>;
97
+ }
98
+ }
99
+ } catch {
100
+ // ignore parse errors, start fresh
101
+ }
102
+ return {};
103
+ }
104
+
105
+ // ──────────────────────────────────────────────────────────────────────────────
106
+ // Extracted helpers — _discoverAgents
107
+ // ──────────────────────────────────────────────────────────────────────────────
108
+
109
+ /**
110
+ * Scans directories from a PATH string for uwf-* executables.
111
+ */
112
+ export async function _searchPathDirs(pathEnv: string): Promise<string[]> {
113
+ if (!pathEnv) return [];
114
+ const dirs = pathEnv.split(":").filter((d) => d.length > 0);
115
+ const agents = new Set<string>();
116
+ for (const dir of dirs) {
117
+ _scanDirForAgents(dir, agents);
118
+ }
119
+ return Array.from(agents).sort();
120
+ }
121
+
122
+ function _scanDirForAgents(dir: string, agents: Set<string>): void {
123
+ try {
124
+ if (!existsSync(dir)) return;
125
+ const entries = readdirSync(dir);
126
+ for (const entry of entries) {
127
+ if (!entry.startsWith("uwf-") || entry === "uwf") continue;
128
+ if (_isExecutableFile(join(dir, entry))) {
129
+ agents.add(entry);
130
+ }
131
+ }
132
+ } catch {
133
+ // Skip inaccessible directories
134
+ }
135
+ }
136
+
137
+ function _isExecutableFile(fullPath: string): boolean {
138
+ try {
139
+ const s = statSync(fullPath);
140
+ return s.isFile() && (s.mode & 0o111) !== 0;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Parses the stdout of `which -a` into sorted unique basenames.
148
+ */
149
+ export function _parseWhichOutput(text: string): string[] {
150
+ if (!text) return [];
151
+ const agents = new Set<string>();
152
+ for (const line of text.trim().split("\n")) {
153
+ if (!line) continue;
154
+ const basename = line.split("/").pop() ?? "";
155
+ if (basename.startsWith("uwf-") && basename !== "uwf") {
156
+ agents.add(basename);
157
+ }
158
+ }
159
+ return Array.from(agents).sort();
160
+ }
161
+
162
+ /**
163
+ * Discover uwf-* agent binaries in PATH.
164
+ * Returns sorted list of binary names (e.g., ["uwf-hermes", "uwf-claude-code"]).
165
+ */
166
+ export async function _discoverAgents(): Promise<string[]> {
167
+ try {
168
+ const agents = await _tryWhichDiscovery();
169
+ if (agents !== null) return agents;
170
+ return await _searchPathDirs(process.env.PATH ?? "");
171
+ } catch {
172
+ return [];
173
+ }
174
+ }
175
+
176
+ async function _tryWhichDiscovery(): Promise<string[] | null> {
177
+ try {
178
+ const { execFileSync } = await import("node:child_process");
179
+ const text = execFileSync("which", ["-a", "uwf-hermes", "uwf-claude-code", "uwf-cursor"], {
180
+ encoding: "utf-8",
181
+ stdio: ["pipe", "pipe", "pipe"],
182
+ });
183
+ return _parseWhichOutput(text);
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+
189
+ // ──────────────────────────────────────────────────────────────────────────────
190
+ // Extracted helpers — onData closure (promptSecret)
191
+ // ──────────────────────────────────────────────────────────────────────────────
192
+
193
+ /** Returns true for newline, carriage return, or EOF (EOT). */
194
+ export function _isTerminator(c: string): boolean {
195
+ return c === "\n" || c === "\r" || c === "";
196
+ }
197
+
198
+ /** Returns true for DEL or backspace. */
199
+ export function _isBackspace(c: string): boolean {
200
+ return c === "" || c === "\b";
201
+ }
202
+
203
+ // ──────────────────────────────────────────────────────────────────────────────
204
+ // Extracted helpers — cmdSetupInteractive
205
+ // ──────────────────────────────────────────────────────────────────────────────
206
+
207
+ type ProviderEntry = { name: string; label: string; baseUrl: string };
208
+
209
+ /** Prints the numbered provider list and custom option to stdout. */
210
+ export function _printProviderMenu(providers: readonly ProviderEntry[]): void {
211
+ const numWidth = String(providers.length + 1).length;
212
+ for (let i = 0; i < providers.length; i++) {
213
+ const p = providers[i];
214
+ if (!p) continue;
215
+ const num = String(i + 1).padStart(numWidth);
216
+ console.log(` ${num}) ${p.label.padEnd(28)} ${p.baseUrl}`);
217
+ }
218
+ const customNum = String(providers.length + 1).padStart(numWidth);
219
+ console.log(` ${customNum}) Custom (enter name and URL manually)\n`);
220
+ }
221
+
222
+ /** Resolves a numeric choice string to a preset provider, or null for custom/invalid. */
223
+ export function _resolveProviderChoice(
224
+ choice: string,
225
+ providers: readonly ProviderEntry[],
226
+ ): { providerName: string; baseUrl: string } | null {
227
+ const n = Number.parseInt(choice, 10);
228
+ if (Number.isNaN(n) || n < 1 || n > providers.length) return null;
229
+ const p = providers[n - 1];
230
+ if (!p) return null;
231
+ return { providerName: p.name, baseUrl: p.baseUrl };
232
+ }
233
+
234
+ /** Resolves numeric index or literal model name to a model string. */
235
+ export function _resolveModelChoice(input: string, models: string[]): string {
236
+ const n = Number.parseInt(input, 10);
237
+ if (!Number.isNaN(n) && n >= 1 && n <= models.length) {
238
+ return models[n - 1] ?? input;
239
+ }
240
+ return input;
241
+ }
242
+
243
+ /** Prints the multi-column model list to stdout. */
244
+ export function _printModelMenu(models: string[], termCols: number): void {
245
+ const nw = String(models.length).length;
246
+ const maxLen = models.reduce((m, s) => Math.max(m, s.length), 0);
247
+ const colWidth = nw + 2 + maxLen + 4;
248
+ const cols = Math.max(1, Math.floor(termCols / colWidth));
249
+ const rows = Math.ceil(models.length / cols);
250
+ for (let r = 0; r < rows; r++) {
251
+ let line = "";
252
+ for (let c = 0; c < cols; c++) {
253
+ const idx = c * rows + r;
254
+ if (idx >= models.length) break;
255
+ const num = String(idx + 1).padStart(nw);
256
+ const name = (models[idx] ?? "").padEnd(maxLen);
257
+ line += ` ${num}) ${name} `;
258
+ }
259
+ console.log(line.trimEnd());
260
+ }
261
+ }
262
+
263
+ // ──────────────────────────────────────────────────────────────────────────────
264
+ // Agent selection prompt
265
+ // ──────────────────────────────────────────────────────────────────────────────
266
+
267
+ /** Known agent binary → display label mapping. */
268
+ const KNOWN_AGENTS: Record<string, string> = {
269
+ "uwf-hermes": "Hermes (hermes-agent)",
270
+ "uwf-claude-code": "Claude Code",
271
+ "uwf-cursor": "Cursor",
272
+ "uwf-builtin": "Built-in (lightweight, no external agent)",
273
+ };
274
+
275
+ /** Extract short agent name from binary name: uwf-claude-code → claude-code */
276
+ export function _agentNameFromBinary(binary: string): string {
277
+ return binary.replace(/^uwf-/, "");
278
+ }
279
+
280
+ /** Prints numbered agent list to stdout. */
281
+ export function _printAgentMenu(agents: string[]): void {
282
+ const numWidth = String(agents.length).length;
283
+ for (let i = 0; i < agents.length; i++) {
284
+ const bin = agents[i] ?? "";
285
+ const label = KNOWN_AGENTS[bin] ?? bin;
286
+ const num = String(i + 1).padStart(numWidth);
287
+ console.log(` ${num}) ${label} (${bin})`);
288
+ }
289
+ console.log("");
290
+ }
291
+
292
+ /**
293
+ * Interactive agent selection. Discovers uwf-* binaries, lets user pick default.
294
+ * Returns short agent name (e.g. "hermes", "claude-code").
295
+ */
296
+ export async function _promptAgentSelection(
297
+ rl: ReturnType<typeof createInterface>,
298
+ ): Promise<string> {
299
+ console.log("Discovering installed agents...\n");
300
+ const agents = await _discoverAgents();
301
+
302
+ if (agents.length === 0) {
303
+ console.log(" No uwf-* agent binaries found in PATH.\n");
304
+ console.log(" Install one first, for example:");
305
+ console.log(" npm i -g @united-workforce/agent-hermes");
306
+ console.log(" npm i -g @united-workforce/agent-claude-code\n");
307
+ const manual = (
308
+ await rl.question("Agent binary name (e.g. uwf-hermes), or press Enter to skip: ")
309
+ ).trim();
310
+ if (!manual) return "hermes";
311
+ return _agentNameFromBinary(manual.startsWith("uwf-") ? manual : `uwf-${manual}`);
312
+ }
313
+
314
+ if (agents.length === 1) {
315
+ const name = _agentNameFromBinary(agents[0] ?? "uwf-hermes");
316
+ const label = KNOWN_AGENTS[agents[0] ?? ""] ?? agents[0];
317
+ console.log(` Found 1 agent: ${label} — auto-selected.\n`);
318
+ return name;
319
+ }
320
+
321
+ console.log(` Found ${agents.length} agents:\n`);
322
+ _printAgentMenu(agents);
323
+ const choice = (await rl.question(`Choose default agent [1-${agents.length}]: `)).trim();
324
+ const n = Number.parseInt(choice, 10);
325
+ if (!Number.isNaN(n) && n >= 1 && n <= agents.length) {
326
+ const selected = agents[n - 1] ?? "uwf-hermes";
327
+ const name = _agentNameFromBinary(selected);
328
+ console.log(` → ${name}\n`);
329
+ return name;
330
+ }
331
+ // Treat as literal name
332
+ const name = _agentNameFromBinary(choice.startsWith("uwf-") ? choice : `uwf-${choice}`);
333
+ console.log(` → ${name}\n`);
334
+ return name;
335
+ }
336
+
337
+ type ValidationResult = { ok: boolean; error: string | null };
338
+
339
+ /** Prints the model validation result to stdout. */
340
+ export function _printValidationResult(validation: ValidationResult): void {
341
+ if (validation.ok) {
342
+ console.log("✓ Model verified — connection successful.\n");
343
+ } else {
344
+ console.log(`\n⚠ Warning: Could not reach model — ${validation.error}`);
345
+ console.log(
346
+ " Config saved, but you may want to try a different model or check your API key.\n",
347
+ );
348
+ }
349
+ }
350
+
351
+ // ──────────────────────────────────────────────────────────────────────────────
352
+
353
+ /**
354
+ * Merge setup args into config.yaml structure. Non-destructive — preserves existing entries.
355
+ */
356
+ function mergeConfig(existing: Record<string, unknown>, args: SetupArgs): Record<string, unknown> {
357
+ const providers = (
358
+ typeof existing.providers === "object" && existing.providers !== null
359
+ ? { ...(existing.providers as Record<string, unknown>) }
360
+ : {}
361
+ ) as Record<string, unknown>;
362
+
363
+ providers[args.provider] = { baseUrl: args.baseUrl, apiKey: args.apiKey };
364
+
365
+ const models = (
366
+ typeof existing.models === "object" && existing.models !== null
367
+ ? { ...(existing.models as Record<string, unknown>) }
368
+ : {}
369
+ ) as Record<string, unknown>;
370
+ models.default = { provider: args.provider, name: args.model };
371
+
372
+ const agents = (
373
+ typeof existing.agents === "object" && existing.agents !== null
374
+ ? { ...(existing.agents as Record<string, unknown>) }
375
+ : {}
376
+ ) as Record<string, unknown>;
377
+
378
+ const agentName = _agentNameFromBinary(args.agent ?? "hermes");
379
+ // Ensure the selected agent has an entry
380
+ if (!agents[agentName]) {
381
+ agents[agentName] = { command: `uwf-${agentName}`, args: [] };
382
+ }
383
+
384
+ return {
385
+ ...existing,
386
+ providers,
387
+ models,
388
+ agents,
389
+ defaultAgent: agentName,
390
+ defaultModel: existing.defaultModel ?? "default",
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Non-interactive setup. All required args provided via CLI flags.
396
+ */
397
+ export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>> {
398
+ const { storageRoot } = args;
399
+ mkdirSync(storageRoot, { recursive: true });
400
+
401
+ const configPath = getConfigPath(storageRoot);
402
+
403
+ const existing = loadExistingConfig(configPath);
404
+ const merged = mergeConfig(existing, args);
405
+
406
+ writeFileSync(configPath, stringify(merged, { indent: 2 }), "utf8");
407
+
408
+ // Validate model connectivity
409
+ const validation = await validateModel(args.baseUrl, args.apiKey, args.model);
410
+
411
+ return {
412
+ configPath,
413
+ provider: args.provider,
414
+ model: args.model,
415
+ defaultAgent: merged.defaultAgent,
416
+ validation,
417
+ };
418
+ }
419
+
420
+ type SecretState = {
421
+ buf: string;
422
+ rawWasSet: boolean;
423
+ resolve: (value: string) => void;
424
+ onData: (chunk: string) => void;
425
+ };
426
+
427
+ function _handleSecretTerminator(state: SecretState): void {
428
+ if (process.stdin.isTTY) process.stdin.setRawMode(state.rawWasSet);
429
+ process.stdin.pause();
430
+ process.stdin.removeListener("data", state.onData);
431
+ process.stdout.write("\n");
432
+ state.resolve(state.buf.trim());
433
+ }
434
+
435
+ function _handleSecretBackspace(state: SecretState): void {
436
+ if (state.buf.length > 0) {
437
+ state.buf = state.buf.slice(0, -1);
438
+ process.stdout.write("\b \b");
439
+ }
440
+ }
441
+
442
+ function _handleSecretChar(c: string, state: SecretState): boolean {
443
+ if (_isTerminator(c)) {
444
+ _handleSecretTerminator(state);
445
+ return true;
446
+ }
447
+ if (_isBackspace(c)) {
448
+ _handleSecretBackspace(state);
449
+ return false;
450
+ }
451
+ if (c === "") {
452
+ if (process.stdin.isTTY) process.stdin.setRawMode(state.rawWasSet);
453
+ process.exit(130);
454
+ }
455
+ state.buf += c;
456
+ process.stdout.write("*");
457
+ return false;
458
+ }
459
+
460
+ /** Read a line with terminal echo disabled (for secrets). */
461
+ async function promptSecret(label: string): Promise<string> {
462
+ process.stdout.write(label);
463
+ return new Promise((resolve) => {
464
+ const rawWasSet = process.stdin.isRaw;
465
+ if (process.stdin.isTTY) {
466
+ process.stdin.setRawMode(true);
467
+ }
468
+ process.stdin.resume();
469
+ process.stdin.setEncoding("utf8");
470
+
471
+ const state: SecretState = { buf: "", rawWasSet, resolve, onData: () => {} };
472
+ state.onData = (chunk: string) => {
473
+ for (const c of chunk.toString()) {
474
+ if (_handleSecretChar(c, state)) return;
475
+ }
476
+ };
477
+ process.stdin.on("data", state.onData);
478
+ });
479
+ }
480
+
481
+ /** Fetch available models from an OpenAI-compatible /models endpoint. */
482
+ async function fetchModels(baseUrl: string, apiKey: string): Promise<string[]> {
483
+ try {
484
+ const url = `${baseUrl.replace(/\/+$/, "")}/models`;
485
+ const res = await fetch(url, {
486
+ headers: { Authorization: `Bearer ${apiKey}` },
487
+ signal: AbortSignal.timeout(10_000),
488
+ });
489
+ if (!res.ok) return [];
490
+ const body = (await res.json()) as { data?: { id: string }[] };
491
+ if (!Array.isArray(body.data)) return [];
492
+ const NON_CHAT =
493
+ /speech|embed|image|video|audio|ocr|rerank|tts|asr|paraformer|sambert|cosyvoice|wordart|wanx|wan2|flux|stable-diffusion|gui-/i;
494
+ return body.data
495
+ .map((m) => m.id)
496
+ .filter((id) => !NON_CHAT.test(id))
497
+ .sort();
498
+ } catch {
499
+ return [];
500
+ }
501
+ }
502
+
503
+ async function _promptProviderSelection(
504
+ rl: ReturnType<typeof createInterface>,
505
+ ): Promise<{ providerName: string; baseUrl: string }> {
506
+ console.log("Select a provider:\n");
507
+ _printProviderMenu(PRESET_PROVIDERS);
508
+
509
+ const choice = (await rl.question(`Choose [1-${PRESET_PROVIDERS.length + 1}]: `)).trim();
510
+ const choiceNum = Number.parseInt(choice, 10);
511
+ if (Number.isNaN(choiceNum) || choiceNum < 1 || choiceNum > PRESET_PROVIDERS.length + 1) {
512
+ throw new Error(`Invalid choice: ${choice}`);
513
+ }
514
+
515
+ const preset = _resolveProviderChoice(choice, PRESET_PROVIDERS);
516
+ if (preset) {
517
+ const selected = PRESET_PROVIDERS[choiceNum - 1];
518
+ if (selected) {
519
+ console.log(`\n → ${selected.label} (${selected.baseUrl})\n`);
520
+ }
521
+ return preset;
522
+ }
523
+
524
+ const providerName = (await rl.question("Provider name (e.g. my-proxy): ")).trim();
525
+ if (!providerName) throw new Error("Provider name required");
526
+ const baseUrl = (await rl.question("OpenAI-compatible API base URL: ")).trim();
527
+ if (!baseUrl) throw new Error("Base URL required");
528
+ return { providerName, baseUrl };
529
+ }
530
+
531
+ async function _promptModelSelection(
532
+ rl: ReturnType<typeof createInterface>,
533
+ baseUrl: string,
534
+ apiKey: string,
535
+ ): Promise<string> {
536
+ console.log("\nFetching available models...");
537
+ const models = await fetchModels(baseUrl, apiKey);
538
+
539
+ if (models.length === 0) {
540
+ console.log("Could not fetch models. Enter model name manually.");
541
+ const model = (await rl.question("Default model (e.g. qwen-plus, gpt-4o): ")).trim();
542
+ if (!model) throw new Error("Model required");
543
+ return model;
544
+ }
545
+ console.log(`\nAvailable models (${models.length}):\n`);
546
+ _printModelMenu(models, process.stdout.columns || 100);
547
+ console.log(`\nChoose a number, or type a model name directly.`);
548
+ const modelInput = (await rl.question(`Default model [1-${models.length}]: `)).trim();
549
+ if (!modelInput) throw new Error("Model required");
550
+ return _resolveModelChoice(modelInput, models);
551
+ }
552
+
553
+ /**
554
+ * Interactive setup — prompts user for provider, API key, model.
555
+ */
556
+ export async function cmdSetupInteractive(storageRoot: string): Promise<Record<string, unknown>> {
557
+ const rl = createInterface({ input, output });
558
+
559
+ try {
560
+ console.log("Configure LLM provider for uwf workflow agents.\n");
561
+
562
+ const { providerName, baseUrl } = await _promptProviderSelection(rl);
563
+
564
+ // 2. API key
565
+ rl.close();
566
+ const apiKey = await promptSecret("API key: ");
567
+ if (!apiKey) throw new Error("API key required");
568
+
569
+ // 3. Model selection
570
+ const rl2 = createInterface({ input, output });
571
+ const model = await _promptModelSelection(rl2, baseUrl, apiKey);
572
+ rl2.close();
573
+ console.log(` → ${providerName}/${model}\n`);
574
+
575
+ // 4. Agent discovery & selection
576
+ const rl3 = createInterface({ input, output });
577
+ const agentName = await _promptAgentSelection(rl3);
578
+ rl3.close();
579
+
580
+ const setupResult = await cmdSetup({
581
+ provider: providerName,
582
+ baseUrl,
583
+ apiKey,
584
+ model,
585
+ agent: agentName,
586
+ storageRoot,
587
+ });
588
+
589
+ // Show validation result
590
+ if (setupResult.validation && typeof setupResult.validation === "object") {
591
+ _printValidationResult(setupResult.validation as ValidationResult);
592
+ }
593
+ console.log("Setup complete! Get started:\n");
594
+ console.log(" uwf workflow put <workflow.yaml> Register a workflow");
595
+ console.log(' uwf thread start <name> -p "..." Start a thread');
596
+ console.log(" uwf thread step <thread-id> Execute next step");
597
+ console.log("");
598
+
599
+ return null as unknown as Record<string, unknown>;
600
+ } finally {
601
+ rl.close();
602
+ }
603
+ }