@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,304 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse, stringify } from "yaml";
4
+
5
+ /**
6
+ * Valid configuration key schema
7
+ */
8
+ const VALID_CONFIG_KEYS: Record<
9
+ string,
10
+ { nested: boolean; knownFields?: string[]; minDepth?: number }
11
+ > = {
12
+ providers: {
13
+ nested: true,
14
+ knownFields: ["baseUrl", "apiKey"],
15
+ },
16
+ models: {
17
+ nested: true,
18
+ knownFields: ["provider", "name"],
19
+ },
20
+ agents: {
21
+ nested: true,
22
+ knownFields: ["command", "args"],
23
+ },
24
+ agentOverrides: {
25
+ nested: true,
26
+ // agentOverrides.<workflowName>.<roleName> = agentAlias (string value)
27
+ // No knownFields — workflow/role names are user-defined
28
+ },
29
+ modelOverrides: {
30
+ nested: true,
31
+ minDepth: 2,
32
+ // modelOverrides.<scenario> = modelAlias (string value)
33
+ // No knownFields — scenarios are user-defined
34
+ },
35
+ defaultAgent: { nested: false },
36
+ defaultModel: { nested: false },
37
+ };
38
+
39
+ /**
40
+ * Validate a config key path against the known schema
41
+ */
42
+ function validateConfigKey(path: string[]): void {
43
+ if (path.length === 0) {
44
+ throw new Error("Path cannot be empty");
45
+ }
46
+
47
+ const topLevel = path[0];
48
+ const schema = VALID_CONFIG_KEYS[topLevel];
49
+
50
+ if (!schema) {
51
+ const validKeys = Object.keys(VALID_CONFIG_KEYS).join(", ");
52
+ throw new Error(`Unknown config key: ${topLevel}. Valid top-level keys are: ${validKeys}`);
53
+ }
54
+
55
+ // Scalar keys cannot have nested paths
56
+ if (!schema.nested && path.length > 1) {
57
+ throw new Error(`${topLevel} is a scalar key and cannot have nested properties`);
58
+ }
59
+
60
+ // Nested keys must have at least minDepth segments (default 3)
61
+ const minDepth = schema.minDepth ?? 3;
62
+ if (schema.nested && path.length < minDepth) {
63
+ const fields = schema.knownFields?.join(", ") ?? "";
64
+ throw new Error(
65
+ `Incomplete path for ${topLevel}. Must specify a field (e.g., ${topLevel}.<name>.<field>). Valid fields: ${fields}`,
66
+ );
67
+ }
68
+
69
+ // Validate the field name for nested keys
70
+ if (schema.nested && path.length >= 3 && schema.knownFields) {
71
+ const field = path[path.length - 1];
72
+ if (!schema.knownFields.includes(field)) {
73
+ throw new Error(
74
+ `Unknown field '${field}' in ${topLevel}. Valid fields are: ${schema.knownFields.join(", ")}`,
75
+ );
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Returns the path to the config.yaml file
82
+ */
83
+ export function getConfigPath(storageRoot: string): string {
84
+ return join(storageRoot, "config.yaml");
85
+ }
86
+
87
+ /**
88
+ * Load and parse YAML config file
89
+ */
90
+ export function loadConfig(configPath: string): Record<string, unknown> {
91
+ if (!existsSync(configPath)) {
92
+ throw new Error(`Config file not found: ${configPath}`);
93
+ }
94
+ const content = readFileSync(configPath, "utf8");
95
+ if (!content.trim()) {
96
+ return {};
97
+ }
98
+ try {
99
+ const parsed = parse(content);
100
+ return (parsed ?? {}) as Record<string, unknown>;
101
+ } catch (error) {
102
+ throw new Error(
103
+ `Invalid YAML in config file: ${error instanceof Error ? error.message : String(error)}`,
104
+ );
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Save config as YAML
110
+ */
111
+ export function saveConfig(configPath: string, config: Record<string, unknown>): void {
112
+ const dir = join(configPath, "..");
113
+ if (!existsSync(dir)) {
114
+ mkdirSync(dir, { recursive: true });
115
+ }
116
+ const yaml = stringify(config);
117
+ writeFileSync(configPath, yaml, "utf8");
118
+ }
119
+
120
+ /**
121
+ * Parse dot-notation key into path segments
122
+ */
123
+ export function parseDotPath(key: string): string[] {
124
+ return key.split(".");
125
+ }
126
+
127
+ /**
128
+ * Get nested value from object using path array
129
+ */
130
+ export function getNestedValue(obj: Record<string, unknown>, path: string[]): unknown {
131
+ let current: unknown = obj;
132
+ for (const segment of path) {
133
+ if (current === null || current === undefined || typeof current !== "object") {
134
+ return undefined;
135
+ }
136
+ current = (current as Record<string, unknown>)[segment];
137
+ }
138
+ return current;
139
+ }
140
+
141
+ /**
142
+ * Set nested value in object using path array (mutates obj)
143
+ */
144
+ export function setNestedValue(obj: Record<string, unknown>, path: string[], value: unknown): void {
145
+ if (path.length === 0) {
146
+ throw new Error("Path cannot be empty");
147
+ }
148
+
149
+ let current: Record<string, unknown> = obj;
150
+
151
+ // Navigate/create to the parent of the target
152
+ for (let i = 0; i < path.length - 1; i++) {
153
+ const segment = path[i];
154
+ const next = current[segment];
155
+
156
+ if (next === null || next === undefined) {
157
+ // Create intermediate object
158
+ const newObj: Record<string, unknown> = {};
159
+ current[segment] = newObj;
160
+ current = newObj;
161
+ } else if (typeof next === "object" && !Array.isArray(next)) {
162
+ // Navigate into existing object
163
+ current = next as Record<string, unknown>;
164
+ } else {
165
+ // Cannot navigate into non-object
166
+ throw new Error(
167
+ `Cannot set property '${path[i + 1]}' on non-object at path '${path.slice(0, i + 1).join(".")}'`,
168
+ );
169
+ }
170
+ }
171
+
172
+ // Set the final value
173
+ const lastSegment = path[path.length - 1];
174
+ current[lastSegment] = value;
175
+ }
176
+
177
+ /**
178
+ * Deep clone and mask all apiKey values in providers section
179
+ */
180
+ export function maskApiKeys(config: Record<string, unknown>): Record<string, unknown> {
181
+ // Deep clone
182
+ const cloned = JSON.parse(JSON.stringify(config)) as Record<string, unknown>;
183
+
184
+ // Mask apiKey values in providers
185
+ if (cloned.providers && typeof cloned.providers === "object") {
186
+ const providers = cloned.providers as Record<string, unknown>;
187
+ for (const providerName of Object.keys(providers)) {
188
+ const provider = providers[providerName];
189
+ if (provider && typeof provider === "object") {
190
+ const providerObj = provider as Record<string, unknown>;
191
+ if ("apiKey" in providerObj) {
192
+ providerObj.apiKey = "***MASKED***";
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ return cloned;
199
+ }
200
+
201
+ /**
202
+ * List all configuration values (masks API keys)
203
+ */
204
+ export async function cmdConfigList(storageRoot: string): Promise<unknown> {
205
+ const configPath = getConfigPath(storageRoot);
206
+ const config = loadConfig(configPath);
207
+ const masked = maskApiKeys(config);
208
+ return masked;
209
+ }
210
+
211
+ /**
212
+ * Get a specific configuration value
213
+ */
214
+ export async function cmdConfigGet(storageRoot: string, key: string): Promise<unknown> {
215
+ const configPath = getConfigPath(storageRoot);
216
+ const config = loadConfig(configPath);
217
+ const path = parseDotPath(key);
218
+ const value = getNestedValue(config, path);
219
+
220
+ if (value === undefined) {
221
+ throw new Error(`Key not found: ${key}`);
222
+ }
223
+
224
+ return value;
225
+ }
226
+
227
+ /**
228
+ * Parse value for args key (must be JSON array)
229
+ */
230
+ function parseArgsValue(value: string): unknown {
231
+ if (value.startsWith("[")) {
232
+ try {
233
+ const parsed = JSON.parse(value);
234
+ if (!Array.isArray(parsed)) {
235
+ throw new Error("Value must be an array");
236
+ }
237
+ return parsed;
238
+ } catch (error) {
239
+ throw new Error(
240
+ `Invalid JSON array for args key: ${error instanceof Error ? error.message : String(error)}`,
241
+ );
242
+ }
243
+ }
244
+ throw new Error("Value for 'args' key must be a JSON array starting with '['");
245
+ }
246
+
247
+ /**
248
+ * Validate that we're not setting a property on a non-object
249
+ */
250
+ function validateParentPath(
251
+ config: Record<string, unknown>,
252
+ path: string[],
253
+ lastSegment: string,
254
+ ): void {
255
+ if (path.length > 1) {
256
+ const parentPath = path.slice(0, -1);
257
+ const parent = getNestedValue(config, parentPath);
258
+ if (parent !== null && parent !== undefined && typeof parent !== "object") {
259
+ throw new Error(
260
+ `Cannot set property '${lastSegment}' on non-object at path '${parentPath.join(".")}'`,
261
+ );
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Set a specific configuration value
268
+ */
269
+ export async function cmdConfigSet(
270
+ storageRoot: string,
271
+ key: string,
272
+ value: string,
273
+ ): Promise<unknown> {
274
+ const configPath = getConfigPath(storageRoot);
275
+
276
+ // Load existing config or create empty one
277
+ let config: Record<string, unknown>;
278
+ if (existsSync(configPath)) {
279
+ config = loadConfig(configPath);
280
+ } else {
281
+ config = {};
282
+ }
283
+
284
+ const path = parseDotPath(key);
285
+
286
+ // Validate the key path
287
+ validateConfigKey(path);
288
+
289
+ const lastSegment = path[path.length - 1];
290
+
291
+ // Parse value if it's for an array key (args)
292
+ let parsedValue: unknown = value;
293
+ if (lastSegment === "args") {
294
+ parsedValue = parseArgsValue(value);
295
+ }
296
+
297
+ // Validate we're not setting a property on a non-object
298
+ validateParentPath(config, path, lastSegment);
299
+
300
+ setNestedValue(config, path, parsedValue);
301
+ saveConfig(configPath, config);
302
+
303
+ return { key, value: parsedValue };
304
+ }
@@ -0,0 +1,116 @@
1
+ import { readdir, readFile, stat, unlink } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ type LogListItem = {
5
+ name: string;
6
+ size: number;
7
+ date: string;
8
+ };
9
+
10
+ type LogShowFilter = {
11
+ thread: string | null;
12
+ process: string | null;
13
+ date: string | null;
14
+ };
15
+
16
+ type LogEntry = {
17
+ ts: string;
18
+ pid: string;
19
+ tag: string;
20
+ msg: string;
21
+ thread: string | null;
22
+ workflow: string | null;
23
+ };
24
+
25
+ type LogCleanResult = {
26
+ deleted: number;
27
+ };
28
+
29
+ function logsDir(storageRoot: string): string {
30
+ return join(storageRoot, "logs");
31
+ }
32
+
33
+ async function listLogFiles(dir: string): Promise<Array<string>> {
34
+ try {
35
+ const files = await readdir(dir);
36
+ return files.filter((f) => f.endsWith(".jsonl")).sort();
37
+ } catch {
38
+ return [];
39
+ }
40
+ }
41
+
42
+ function dateFromFilename(name: string): string {
43
+ return name.replace(".jsonl", "");
44
+ }
45
+
46
+ async function parseJsonlFile(path: string): Promise<Array<LogEntry>> {
47
+ const content = await readFile(path, "utf-8");
48
+ const lines = content
49
+ .trim()
50
+ .split("\n")
51
+ .filter((l) => l.length > 0);
52
+ return lines.map((line) => JSON.parse(line) as LogEntry);
53
+ }
54
+
55
+ export async function cmdLogList(storageRoot: string): Promise<Array<LogListItem>> {
56
+ const dir = logsDir(storageRoot);
57
+ const files = await listLogFiles(dir);
58
+ const items: Array<LogListItem> = [];
59
+ for (const name of files) {
60
+ const s = await stat(join(dir, name));
61
+ items.push({ name, size: s.size, date: dateFromFilename(name) });
62
+ }
63
+ // sort by date descending
64
+ items.sort((a, b) => (a.date > b.date ? -1 : a.date < b.date ? 1 : 0));
65
+ return items;
66
+ }
67
+
68
+ export async function cmdLogShow(
69
+ storageRoot: string,
70
+ filter: LogShowFilter,
71
+ ): Promise<Array<LogEntry>> {
72
+ const dir = logsDir(storageRoot);
73
+ let files: Array<string>;
74
+
75
+ if (filter.date !== null) {
76
+ files = [`${filter.date}.jsonl`];
77
+ } else {
78
+ files = await listLogFiles(dir);
79
+ }
80
+
81
+ let entries: Array<LogEntry> = [];
82
+ for (const file of files) {
83
+ try {
84
+ const parsed = await parseJsonlFile(join(dir, file));
85
+ entries = entries.concat(parsed);
86
+ } catch {
87
+ // file doesn't exist or is unreadable, skip
88
+ }
89
+ }
90
+
91
+ if (filter.thread !== null) {
92
+ entries = entries.filter((e) => e.thread === filter.thread);
93
+ }
94
+ if (filter.process !== null) {
95
+ entries = entries.filter((e) => e.pid === filter.process);
96
+ }
97
+
98
+ entries.sort((a, b) => (a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0));
99
+ return entries;
100
+ }
101
+
102
+ export async function cmdLogClean(storageRoot: string, before: string): Promise<LogCleanResult> {
103
+ const dir = logsDir(storageRoot);
104
+ const files = await listLogFiles(dir);
105
+ let deleted = 0;
106
+
107
+ for (const name of files) {
108
+ const date = dateFromFilename(name);
109
+ if (date < before) {
110
+ await unlink(join(dir, name));
111
+ deleted++;
112
+ }
113
+ }
114
+
115
+ return { deleted };
116
+ }
@@ -0,0 +1,81 @@
1
+ import {
2
+ generateAdapterDevelopingReference,
3
+ generateBootstrapReference,
4
+ generateUsageReference,
5
+ generateWorkflowAuthoringReference,
6
+ } from "@united-workforce/util";
7
+
8
+ export {
9
+ generateAdapterDevelopingReference as cmdPromptAdapterDeveloping,
10
+ generateBootstrapReference as cmdPromptBootstrap,
11
+ generateUsageReference as cmdPromptUsageReference,
12
+ generateWorkflowAuthoringReference as cmdPromptWorkflowAuthoring,
13
+ };
14
+
15
+ const PROMPT_ENTRIES: ReadonlyArray<{ name: string; generate: () => string }> = [
16
+ { name: "usage", generate: generateUsageReference },
17
+ { name: "workflow-authoring", generate: generateWorkflowAuthoringReference },
18
+ { name: "adapter-developing", generate: generateAdapterDevelopingReference },
19
+ { name: "bootstrap", generate: generateBootstrapReference },
20
+ ];
21
+
22
+ export function cmdPromptList(): ReadonlyArray<string> {
23
+ return PROMPT_ENTRIES.map((e) => e.name);
24
+ }
25
+
26
+ export function cmdPromptUsage(): string {
27
+ return PROMPT_ENTRIES.filter((e) => e.name !== "bootstrap")
28
+ .map((e) => e.generate())
29
+ .join("\n\n---\n\n");
30
+ }
31
+
32
+ export function cmdPromptSetup(): string {
33
+ return `# uwf Skill Setup
34
+
35
+ You are being asked to install or update the uwf (United Workforce) skill
36
+ so that you know how to use the \`uwf\` CLI for workflow orchestration.
37
+
38
+ ## Steps
39
+
40
+ 1. **Check if uwf CLI is installed:**
41
+ \`\`\`bash
42
+ uwf --help
43
+ \`\`\`
44
+ If not installed: \`npm install -g @united-workforce/cli\`
45
+
46
+ 2. **Clean up old versions of the skill** (if any exist):
47
+ - Look for any existing uwf/workflow skill files in your skill directories
48
+ (e.g. \`~/.hermes/skills/uwf/SKILL.md\` or similar paths).
49
+ - If a skill exists, read its \`version\` from the YAML frontmatter.
50
+ - If the version matches the current CLI version (\`uwf --version\`), you're done — no update needed.
51
+ - If the version is older, **delete the old skill file** before proceeding.
52
+
53
+ 3. **Install the bootstrap skill:**
54
+ \`\`\`bash
55
+ uwf prompt bootstrap > ~/.hermes/skills/uwf/SKILL.md
56
+ \`\`\`
57
+ The bootstrap prompt already includes complete YAML frontmatter — no editing needed.
58
+
59
+ 4. **Verify** the skill is loadable by your agent framework.
60
+
61
+ ## Individual prompts
62
+
63
+ Each prompt outputs a complete SKILL.md with frontmatter — pipe directly to a file:
64
+
65
+ \`\`\`bash
66
+ uwf prompt list # list available prompt names
67
+ uwf prompt usage > ~/.hermes/skills/uwf-usage/SKILL.md # CLI usage guide
68
+ uwf prompt workflow-authoring > ~/.hermes/skills/uwf-workflow-authoring/SKILL.md
69
+ uwf prompt adapter-developing > ~/.hermes/skills/uwf-adapter-developing/SKILL.md
70
+ uwf prompt bootstrap > ~/.hermes/skills/uwf/SKILL.md # bootstrap skill
71
+ \`\`\`
72
+
73
+ ## Notes
74
+
75
+ - The skill content is bundled with the CLI and versioned with it — always use
76
+ \`uwf prompt usage\` to get the content matching your installed version.
77
+ - Do NOT hand-edit the skill body. If the CLI is updated, re-run \`uwf prompt setup\`
78
+ and follow the steps again.
79
+ - When upgrading, always delete the old skill first to avoid stale instructions.
80
+ `;
81
+ }