@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,685 @@
1
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, test } from "vitest";
5
+ import { cmdConfigGet, cmdConfigList, cmdConfigSet, getConfigPath, getNestedValue, maskApiKeys, parseDotPath, setNestedValue, } from "../commands/config.js";
6
+ describe("config command", () => {
7
+ // Helper function to create a test config
8
+ function createTestConfig(tempDir, content) {
9
+ const configPath = getConfigPath(tempDir);
10
+ writeFileSync(configPath, content, "utf8");
11
+ return configPath;
12
+ }
13
+ // Sample test config
14
+ const sampleConfig = `providers:
15
+ dashscope:
16
+ baseUrl: https://dashscope.aliyuncs.com/compatible-mode/v1
17
+ apiKey: sk-test-dashscope-key
18
+ openai:
19
+ baseUrl: https://api.openai.com/v1
20
+ apiKey: sk-test-openai-key
21
+ models:
22
+ default:
23
+ provider: dashscope
24
+ name: qwen-max
25
+ gpt4:
26
+ provider: openai
27
+ name: gpt-4
28
+ agents:
29
+ hermes:
30
+ command: uwf-hermes
31
+ args:
32
+ - --provider
33
+ - dashscope
34
+ claude-code:
35
+ command: claude-code
36
+ args:
37
+ - --profile
38
+ - work
39
+ defaultAgent: hermes
40
+ defaultModel: default
41
+ `;
42
+ describe("helper functions", () => {
43
+ describe("parseDotPath", () => {
44
+ test("splits dot notation correctly", () => {
45
+ expect(parseDotPath("a.b.c")).toEqual(["a", "b", "c"]);
46
+ expect(parseDotPath("defaultAgent")).toEqual(["defaultAgent"]);
47
+ expect(parseDotPath("providers.dashscope.baseUrl")).toEqual([
48
+ "providers",
49
+ "dashscope",
50
+ "baseUrl",
51
+ ]);
52
+ });
53
+ });
54
+ describe("getNestedValue", () => {
55
+ test("traverses nested objects", () => {
56
+ const obj = {
57
+ a: { b: { c: "value" } },
58
+ x: "simple",
59
+ };
60
+ expect(getNestedValue(obj, ["a", "b", "c"])).toBe("value");
61
+ expect(getNestedValue(obj, ["x"])).toBe("simple");
62
+ });
63
+ test("returns undefined for non-existent paths", () => {
64
+ const obj = { a: { b: "value" } };
65
+ expect(getNestedValue(obj, ["a", "c"])).toBeUndefined();
66
+ expect(getNestedValue(obj, ["x", "y"])).toBeUndefined();
67
+ });
68
+ });
69
+ describe("setNestedValue", () => {
70
+ test("creates intermediate objects and sets value", () => {
71
+ const obj = {};
72
+ setNestedValue(obj, ["a", "b", "c"], "value");
73
+ expect(obj).toEqual({ a: { b: { c: "value" } } });
74
+ });
75
+ test("preserves existing values", () => {
76
+ const obj = { a: { x: "keep" } };
77
+ setNestedValue(obj, ["a", "b"], "new");
78
+ expect(obj).toEqual({ a: { x: "keep", b: "new" } });
79
+ });
80
+ test("overwrites existing value at path", () => {
81
+ const obj = { a: { b: "old" } };
82
+ setNestedValue(obj, ["a", "b"], "new");
83
+ expect(obj).toEqual({ a: { b: "new" } });
84
+ });
85
+ });
86
+ describe("maskApiKeys", () => {
87
+ test("deep clones and masks all apiKey values in providers", () => {
88
+ const config = {
89
+ providers: {
90
+ dashscope: {
91
+ baseUrl: "https://example.com",
92
+ apiKey: "sk-test-key-12345",
93
+ },
94
+ openai: {
95
+ baseUrl: "https://api.openai.com",
96
+ apiKey: "sk-another-secret",
97
+ },
98
+ },
99
+ models: {
100
+ default: { provider: "dashscope" },
101
+ },
102
+ };
103
+ const masked = maskApiKeys(config);
104
+ expect(masked).toEqual({
105
+ providers: {
106
+ dashscope: {
107
+ baseUrl: "https://example.com",
108
+ apiKey: "***MASKED***",
109
+ },
110
+ openai: {
111
+ baseUrl: "https://api.openai.com",
112
+ apiKey: "***MASKED***",
113
+ },
114
+ },
115
+ models: {
116
+ default: { provider: "dashscope" },
117
+ },
118
+ });
119
+ // Ensure it's a deep clone
120
+ expect(masked).not.toBe(config);
121
+ });
122
+ test("handles config without providers", () => {
123
+ const config = { models: { default: { provider: "test" } } };
124
+ const masked = maskApiKeys(config);
125
+ expect(masked).toEqual(config);
126
+ });
127
+ test("does not mask non-provider apiKey fields", () => {
128
+ const config = {
129
+ apiKey: "root-level-key",
130
+ providers: {
131
+ dashscope: { apiKey: "sk-secret" },
132
+ },
133
+ models: {
134
+ default: { provider: "dashscope" },
135
+ },
136
+ };
137
+ const masked = maskApiKeys(config);
138
+ // Root-level apiKey should NOT be masked
139
+ expect(masked.apiKey).toBe("root-level-key");
140
+ // Provider apiKey SHOULD be masked
141
+ const providers = masked.providers;
142
+ expect(providers.dashscope.apiKey).toBe("***MASKED***");
143
+ });
144
+ test("handles empty provider object", () => {
145
+ const config = {
146
+ providers: { dashscope: {} },
147
+ };
148
+ const masked = maskApiKeys(config);
149
+ expect(masked).toEqual({ providers: { dashscope: {} } });
150
+ });
151
+ test("handles provider with null apiKey", () => {
152
+ const config = {
153
+ providers: {
154
+ dashscope: { apiKey: null, baseUrl: "https://example.com" },
155
+ },
156
+ };
157
+ const masked = maskApiKeys(config);
158
+ const providers = masked.providers;
159
+ expect(providers.dashscope.apiKey).toBe("***MASKED***");
160
+ expect(providers.dashscope.baseUrl).toBe("https://example.com");
161
+ });
162
+ });
163
+ });
164
+ describe("cmdConfigList", () => {
165
+ test("returns full config when file exists", async () => {
166
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
167
+ try {
168
+ createTestConfig(tempDir, sampleConfig);
169
+ const result = await cmdConfigList(tempDir);
170
+ expect(result).toBeDefined();
171
+ expect(typeof result).toBe("object");
172
+ expect(result).toHaveProperty("providers");
173
+ expect(result).toHaveProperty("models");
174
+ expect(result).toHaveProperty("agents");
175
+ expect(result).toHaveProperty("defaultAgent");
176
+ expect(result).toHaveProperty("defaultModel");
177
+ }
178
+ finally {
179
+ rmSync(tempDir, { recursive: true, force: true });
180
+ }
181
+ });
182
+ test("masks all apiKey values in providers section", async () => {
183
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
184
+ try {
185
+ createTestConfig(tempDir, sampleConfig);
186
+ const result = (await cmdConfigList(tempDir));
187
+ const providers = result.providers;
188
+ const dashscope = providers.dashscope;
189
+ const openai = providers.openai;
190
+ expect(dashscope.apiKey).toBe("***MASKED***");
191
+ expect(openai.apiKey).toBe("***MASKED***");
192
+ }
193
+ finally {
194
+ rmSync(tempDir, { recursive: true, force: true });
195
+ }
196
+ });
197
+ test("throws error when config file doesn't exist", async () => {
198
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
199
+ try {
200
+ await expect(cmdConfigList(tempDir)).rejects.toThrow();
201
+ }
202
+ finally {
203
+ rmSync(tempDir, { recursive: true, force: true });
204
+ }
205
+ });
206
+ test("returns empty object when config file is empty", async () => {
207
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
208
+ try {
209
+ createTestConfig(tempDir, "");
210
+ const result = await cmdConfigList(tempDir);
211
+ expect(result).toEqual({});
212
+ }
213
+ finally {
214
+ rmSync(tempDir, { recursive: true, force: true });
215
+ }
216
+ });
217
+ test("throws error when config file is invalid YAML", async () => {
218
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
219
+ try {
220
+ createTestConfig(tempDir, "invalid: yaml: [broken");
221
+ await expect(cmdConfigList(tempDir)).rejects.toThrow();
222
+ }
223
+ finally {
224
+ rmSync(tempDir, { recursive: true, force: true });
225
+ }
226
+ });
227
+ });
228
+ describe("cmdConfigGet", () => {
229
+ test("retrieves top-level string value (defaultAgent)", async () => {
230
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
231
+ try {
232
+ createTestConfig(tempDir, sampleConfig);
233
+ const result = await cmdConfigGet(tempDir, "defaultAgent");
234
+ expect(result).toBe("hermes");
235
+ }
236
+ finally {
237
+ rmSync(tempDir, { recursive: true, force: true });
238
+ }
239
+ });
240
+ test("retrieves top-level string value (defaultModel)", async () => {
241
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
242
+ try {
243
+ createTestConfig(tempDir, sampleConfig);
244
+ const result = await cmdConfigGet(tempDir, "defaultModel");
245
+ expect(result).toBe("default");
246
+ }
247
+ finally {
248
+ rmSync(tempDir, { recursive: true, force: true });
249
+ }
250
+ });
251
+ test("retrieves nested object (providers.dashscope)", async () => {
252
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
253
+ try {
254
+ createTestConfig(tempDir, sampleConfig);
255
+ const result = await cmdConfigGet(tempDir, "providers.dashscope");
256
+ expect(result).toEqual({
257
+ baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
258
+ apiKey: "sk-test-dashscope-key",
259
+ });
260
+ }
261
+ finally {
262
+ rmSync(tempDir, { recursive: true, force: true });
263
+ }
264
+ });
265
+ test("retrieves deeply nested string (providers.dashscope.baseUrl)", async () => {
266
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
267
+ try {
268
+ createTestConfig(tempDir, sampleConfig);
269
+ const result = await cmdConfigGet(tempDir, "providers.dashscope.baseUrl");
270
+ expect(result).toBe("https://dashscope.aliyuncs.com/compatible-mode/v1");
271
+ }
272
+ finally {
273
+ rmSync(tempDir, { recursive: true, force: true });
274
+ }
275
+ });
276
+ test("retrieves nested string in models (models.default.provider)", async () => {
277
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
278
+ try {
279
+ createTestConfig(tempDir, sampleConfig);
280
+ const result = await cmdConfigGet(tempDir, "models.default.provider");
281
+ expect(result).toBe("dashscope");
282
+ }
283
+ finally {
284
+ rmSync(tempDir, { recursive: true, force: true });
285
+ }
286
+ });
287
+ test("retrieves array value (agents.hermes.args)", async () => {
288
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
289
+ try {
290
+ createTestConfig(tempDir, sampleConfig);
291
+ const result = await cmdConfigGet(tempDir, "agents.hermes.args");
292
+ expect(result).toEqual(["--provider", "dashscope"]);
293
+ }
294
+ finally {
295
+ rmSync(tempDir, { recursive: true, force: true });
296
+ }
297
+ });
298
+ test("throws error when key doesn't exist", async () => {
299
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
300
+ try {
301
+ createTestConfig(tempDir, sampleConfig);
302
+ await expect(cmdConfigGet(tempDir, "nonexistent.key")).rejects.toThrow(/Key not found/);
303
+ }
304
+ finally {
305
+ rmSync(tempDir, { recursive: true, force: true });
306
+ }
307
+ });
308
+ test("throws error when config file doesn't exist", async () => {
309
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
310
+ try {
311
+ await expect(cmdConfigGet(tempDir, "defaultAgent")).rejects.toThrow();
312
+ }
313
+ finally {
314
+ rmSync(tempDir, { recursive: true, force: true });
315
+ }
316
+ });
317
+ test("throws error when accessing property on non-object", async () => {
318
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
319
+ try {
320
+ createTestConfig(tempDir, sampleConfig);
321
+ await expect(cmdConfigGet(tempDir, "defaultAgent.foo")).rejects.toThrow();
322
+ }
323
+ finally {
324
+ rmSync(tempDir, { recursive: true, force: true });
325
+ }
326
+ });
327
+ });
328
+ describe("cmdConfigSet", () => {
329
+ test("sets top-level string value (defaultAgent)", async () => {
330
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
331
+ try {
332
+ createTestConfig(tempDir, sampleConfig);
333
+ const result = await cmdConfigSet(tempDir, "defaultAgent", "claude-code");
334
+ expect(result).toEqual({ key: "defaultAgent", value: "claude-code" });
335
+ // Verify it was written
336
+ const updated = await cmdConfigGet(tempDir, "defaultAgent");
337
+ expect(updated).toBe("claude-code");
338
+ }
339
+ finally {
340
+ rmSync(tempDir, { recursive: true, force: true });
341
+ }
342
+ });
343
+ test("sets nested string value (providers.dashscope.baseUrl)", async () => {
344
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
345
+ try {
346
+ createTestConfig(tempDir, sampleConfig);
347
+ const newUrl = "https://new-api.example.com/v1";
348
+ const result = await cmdConfigSet(tempDir, "providers.dashscope.baseUrl", newUrl);
349
+ expect(result).toEqual({
350
+ key: "providers.dashscope.baseUrl",
351
+ value: newUrl,
352
+ });
353
+ // Verify it was written
354
+ const updated = await cmdConfigGet(tempDir, "providers.dashscope.baseUrl");
355
+ expect(updated).toBe(newUrl);
356
+ }
357
+ finally {
358
+ rmSync(tempDir, { recursive: true, force: true });
359
+ }
360
+ });
361
+ test("creates new nested path (providers.newprovider.baseUrl)", async () => {
362
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
363
+ try {
364
+ createTestConfig(tempDir, sampleConfig);
365
+ const newUrl = "https://new-provider.com/v1";
366
+ const result = await cmdConfigSet(tempDir, "providers.newprovider.baseUrl", newUrl);
367
+ expect(result).toEqual({
368
+ key: "providers.newprovider.baseUrl",
369
+ value: newUrl,
370
+ });
371
+ // Verify it was created
372
+ const updated = await cmdConfigGet(tempDir, "providers.newprovider.baseUrl");
373
+ expect(updated).toBe(newUrl);
374
+ }
375
+ finally {
376
+ rmSync(tempDir, { recursive: true, force: true });
377
+ }
378
+ });
379
+ test("sets array value for args key with valid JSON array", async () => {
380
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
381
+ try {
382
+ createTestConfig(tempDir, sampleConfig);
383
+ const newArgs = '["--new", "--flags"]';
384
+ const result = await cmdConfigSet(tempDir, "agents.hermes.args", newArgs);
385
+ expect(result).toEqual({
386
+ key: "agents.hermes.args",
387
+ value: ["--new", "--flags"],
388
+ });
389
+ // Verify it was written
390
+ const updated = await cmdConfigGet(tempDir, "agents.hermes.args");
391
+ expect(updated).toEqual(["--new", "--flags"]);
392
+ }
393
+ finally {
394
+ rmSync(tempDir, { recursive: true, force: true });
395
+ }
396
+ });
397
+ test("preserves existing config values when updating one key", async () => {
398
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
399
+ try {
400
+ createTestConfig(tempDir, sampleConfig);
401
+ await cmdConfigSet(tempDir, "defaultAgent", "claude-code");
402
+ // Verify other values are preserved
403
+ const defaultModel = await cmdConfigGet(tempDir, "defaultModel");
404
+ expect(defaultModel).toBe("default");
405
+ const dashscopeUrl = await cmdConfigGet(tempDir, "providers.dashscope.baseUrl");
406
+ expect(dashscopeUrl).toBe("https://dashscope.aliyuncs.com/compatible-mode/v1");
407
+ }
408
+ finally {
409
+ rmSync(tempDir, { recursive: true, force: true });
410
+ }
411
+ });
412
+ test("creates config file if it doesn't exist", async () => {
413
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
414
+ try {
415
+ const result = await cmdConfigSet(tempDir, "defaultAgent", "hermes");
416
+ expect(result).toEqual({ key: "defaultAgent", value: "hermes" });
417
+ // Verify file was created
418
+ const configPath = getConfigPath(tempDir);
419
+ const content = readFileSync(configPath, "utf8");
420
+ expect(content).toContain("defaultAgent: hermes");
421
+ }
422
+ finally {
423
+ rmSync(tempDir, { recursive: true, force: true });
424
+ }
425
+ });
426
+ test("throws error when setting property on non-object", async () => {
427
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
428
+ try {
429
+ createTestConfig(tempDir, sampleConfig);
430
+ await expect(cmdConfigSet(tempDir, "defaultAgent.foo", "bar")).rejects.toThrow();
431
+ }
432
+ finally {
433
+ rmSync(tempDir, { recursive: true, force: true });
434
+ }
435
+ });
436
+ test("throws error when array value is invalid JSON for args key", async () => {
437
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
438
+ try {
439
+ createTestConfig(tempDir, sampleConfig);
440
+ await expect(cmdConfigSet(tempDir, "agents.hermes.args", "[invalid json")).rejects.toThrow();
441
+ }
442
+ finally {
443
+ rmSync(tempDir, { recursive: true, force: true });
444
+ }
445
+ });
446
+ test("sets deeply nested model config (models.gpt4.provider)", async () => {
447
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
448
+ try {
449
+ createTestConfig(tempDir, sampleConfig);
450
+ const result = await cmdConfigSet(tempDir, "models.gpt4.provider", "new-provider");
451
+ expect(result).toEqual({
452
+ key: "models.gpt4.provider",
453
+ value: "new-provider",
454
+ });
455
+ // Verify it was written
456
+ const updated = await cmdConfigGet(tempDir, "models.gpt4.provider");
457
+ expect(updated).toBe("new-provider");
458
+ }
459
+ finally {
460
+ rmSync(tempDir, { recursive: true, force: true });
461
+ }
462
+ });
463
+ test("sets agent command (agents.claude-code.command)", async () => {
464
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
465
+ try {
466
+ createTestConfig(tempDir, sampleConfig);
467
+ const result = await cmdConfigSet(tempDir, "agents.claude-code.command", "new-command");
468
+ expect(result).toEqual({
469
+ key: "agents.claude-code.command",
470
+ value: "new-command",
471
+ });
472
+ // Verify it was written
473
+ const updated = await cmdConfigGet(tempDir, "agents.claude-code.command");
474
+ expect(updated).toBe("new-command");
475
+ }
476
+ finally {
477
+ rmSync(tempDir, { recursive: true, force: true });
478
+ }
479
+ });
480
+ });
481
+ describe("cmdConfigSet validation", () => {
482
+ test("rejects unknown top-level key", async () => {
483
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
484
+ try {
485
+ createTestConfig(tempDir, sampleConfig);
486
+ await expect(cmdConfigSet(tempDir, "unknownKey", "value")).rejects.toThrow(/Unknown config key.*unknownKey/);
487
+ }
488
+ finally {
489
+ rmSync(tempDir, { recursive: true, force: true });
490
+ }
491
+ });
492
+ test("rejects unknown nested key in providers", async () => {
493
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
494
+ try {
495
+ createTestConfig(tempDir, sampleConfig);
496
+ await expect(cmdConfigSet(tempDir, "providers.myProvider.unknownField", "value")).rejects.toThrow(/Unknown field.*unknownField.*providers/);
497
+ }
498
+ finally {
499
+ rmSync(tempDir, { recursive: true, force: true });
500
+ }
501
+ });
502
+ test("rejects unknown nested key in models", async () => {
503
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
504
+ try {
505
+ createTestConfig(tempDir, sampleConfig);
506
+ await expect(cmdConfigSet(tempDir, "models.default.invalidField", "value")).rejects.toThrow(/Unknown field.*invalidField.*models/);
507
+ }
508
+ finally {
509
+ rmSync(tempDir, { recursive: true, force: true });
510
+ }
511
+ });
512
+ test("rejects unknown nested key in agents", async () => {
513
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
514
+ try {
515
+ createTestConfig(tempDir, sampleConfig);
516
+ await expect(cmdConfigSet(tempDir, "agents.hermes.badField", "value")).rejects.toThrow(/Unknown field.*badField.*agents/);
517
+ }
518
+ finally {
519
+ rmSync(tempDir, { recursive: true, force: true });
520
+ }
521
+ });
522
+ test("rejects nested path on scalar key (defaultAgent)", async () => {
523
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
524
+ try {
525
+ createTestConfig(tempDir, sampleConfig);
526
+ await expect(cmdConfigSet(tempDir, "defaultAgent.foo", "value")).rejects.toThrow(/defaultAgent.*scalar|Cannot set property/i);
527
+ }
528
+ finally {
529
+ rmSync(tempDir, { recursive: true, force: true });
530
+ }
531
+ });
532
+ test("rejects nested path on scalar key (defaultModel)", async () => {
533
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
534
+ try {
535
+ createTestConfig(tempDir, sampleConfig);
536
+ await expect(cmdConfigSet(tempDir, "defaultModel.bar", "value")).rejects.toThrow(/defaultModel.*scalar|Cannot set property/i);
537
+ }
538
+ finally {
539
+ rmSync(tempDir, { recursive: true, force: true });
540
+ }
541
+ });
542
+ test("rejects incomplete nested path (providers without field)", async () => {
543
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
544
+ try {
545
+ createTestConfig(tempDir, sampleConfig);
546
+ await expect(cmdConfigSet(tempDir, "providers.myProvider", "value")).rejects.toThrow(/incomplete path|must specify a field/i);
547
+ }
548
+ finally {
549
+ rmSync(tempDir, { recursive: true, force: true });
550
+ }
551
+ });
552
+ test("rejects incomplete nested path (models without field)", async () => {
553
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
554
+ try {
555
+ createTestConfig(tempDir, sampleConfig);
556
+ await expect(cmdConfigSet(tempDir, "models.myModel", "value")).rejects.toThrow(/incomplete path|must specify a field/i);
557
+ }
558
+ finally {
559
+ rmSync(tempDir, { recursive: true, force: true });
560
+ }
561
+ });
562
+ test("rejects incomplete nested path (agents without field)", async () => {
563
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
564
+ try {
565
+ createTestConfig(tempDir, sampleConfig);
566
+ await expect(cmdConfigSet(tempDir, "agents.myAgent", "value")).rejects.toThrow(/incomplete path|must specify a field/i);
567
+ }
568
+ finally {
569
+ rmSync(tempDir, { recursive: true, force: true });
570
+ }
571
+ });
572
+ test("allows valid nested keys in providers", async () => {
573
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
574
+ try {
575
+ createTestConfig(tempDir, sampleConfig);
576
+ await cmdConfigSet(tempDir, "providers.newprovider.baseUrl", "https://example.com");
577
+ await cmdConfigSet(tempDir, "providers.newprovider.apiKey", "sk-test");
578
+ const baseUrl = await cmdConfigGet(tempDir, "providers.newprovider.baseUrl");
579
+ const apiKey = await cmdConfigGet(tempDir, "providers.newprovider.apiKey");
580
+ expect(baseUrl).toBe("https://example.com");
581
+ expect(apiKey).toBe("sk-test");
582
+ }
583
+ finally {
584
+ rmSync(tempDir, { recursive: true, force: true });
585
+ }
586
+ });
587
+ test("allows valid nested keys in models", async () => {
588
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
589
+ try {
590
+ createTestConfig(tempDir, sampleConfig);
591
+ await cmdConfigSet(tempDir, "models.gpt4.provider", "openai");
592
+ await cmdConfigSet(tempDir, "models.gpt4.name", "gpt-4o");
593
+ const provider = await cmdConfigGet(tempDir, "models.gpt4.provider");
594
+ const name = await cmdConfigGet(tempDir, "models.gpt4.name");
595
+ expect(provider).toBe("openai");
596
+ expect(name).toBe("gpt-4o");
597
+ }
598
+ finally {
599
+ rmSync(tempDir, { recursive: true, force: true });
600
+ }
601
+ });
602
+ test("allows valid nested keys in agents", async () => {
603
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
604
+ try {
605
+ createTestConfig(tempDir, sampleConfig);
606
+ await cmdConfigSet(tempDir, "agents.hermes.command", "uwf-hermes");
607
+ await cmdConfigSet(tempDir, "agents.hermes.args", '["--flag"]');
608
+ const command = await cmdConfigGet(tempDir, "agents.hermes.command");
609
+ const args = await cmdConfigGet(tempDir, "agents.hermes.args");
610
+ expect(command).toBe("uwf-hermes");
611
+ expect(args).toEqual(["--flag"]);
612
+ }
613
+ finally {
614
+ rmSync(tempDir, { recursive: true, force: true });
615
+ }
616
+ });
617
+ test("agentOverrides — accepts valid 3-segment path", async () => {
618
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
619
+ try {
620
+ createTestConfig(tempDir, sampleConfig);
621
+ await cmdConfigSet(tempDir, "agentOverrides.solve-issue.planner", "claude-code");
622
+ const value = await cmdConfigGet(tempDir, "agentOverrides.solve-issue.planner");
623
+ expect(value).toBe("claude-code");
624
+ }
625
+ finally {
626
+ rmSync(tempDir, { recursive: true, force: true });
627
+ }
628
+ });
629
+ test("agentOverrides — rejects incomplete path (2 segments)", async () => {
630
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
631
+ try {
632
+ createTestConfig(tempDir, sampleConfig);
633
+ await expect(cmdConfigSet(tempDir, "agentOverrides.solve-issue", "hermes")).rejects.toThrow(/incomplete path|must specify a field/i);
634
+ }
635
+ finally {
636
+ rmSync(tempDir, { recursive: true, force: true });
637
+ }
638
+ });
639
+ test("modelOverrides — accepts valid 2-segment path", async () => {
640
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
641
+ try {
642
+ createTestConfig(tempDir, sampleConfig);
643
+ await cmdConfigSet(tempDir, "modelOverrides.extract", "gpt4");
644
+ const value = await cmdConfigGet(tempDir, "modelOverrides.extract");
645
+ expect(value).toBe("gpt4");
646
+ }
647
+ finally {
648
+ rmSync(tempDir, { recursive: true, force: true });
649
+ }
650
+ });
651
+ test("modelOverrides — rejects incomplete path (1 segment only)", async () => {
652
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
653
+ try {
654
+ createTestConfig(tempDir, sampleConfig);
655
+ await expect(cmdConfigSet(tempDir, "modelOverrides", "gpt4")).rejects.toThrow(/incomplete path|must specify a field/i);
656
+ }
657
+ finally {
658
+ rmSync(tempDir, { recursive: true, force: true });
659
+ }
660
+ });
661
+ test("rejects unknown top-level key (regression)", async () => {
662
+ const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
663
+ try {
664
+ createTestConfig(tempDir, sampleConfig);
665
+ await expect(cmdConfigSet(tempDir, "randomKey", "value")).rejects.toThrow(/Unknown config key/);
666
+ }
667
+ finally {
668
+ rmSync(tempDir, { recursive: true, force: true });
669
+ }
670
+ });
671
+ });
672
+ describe("no legacy apiKeyEnv references", () => {
673
+ test("config.ts has no references to apiKeyEnv", () => {
674
+ const configSource = readFileSync(join(__dirname, "..", "..", "src", "commands", "config.ts"), "utf8");
675
+ expect(configSource).not.toContain("apiKeyEnv");
676
+ });
677
+ test("config.test.ts has no references to apiKeyEnv (except this test)", () => {
678
+ const testSource = readFileSync(__filename, "utf8");
679
+ // Remove this test block's own mentions before checking
680
+ const withoutThisTest = testSource.replace(/describe\("no legacy apiKeyEnv references"[\s\S]*$/, "");
681
+ expect(withoutThisTest).not.toContain("apiKeyEnv");
682
+ });
683
+ });
684
+ });
685
+ //# sourceMappingURL=config.test.js.map