@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.
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +147 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +685 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/current-role.test.d.ts +2 -0
- package/dist/__tests__/current-role.test.d.ts.map +1 -0
- package/dist/__tests__/current-role.test.js +401 -0
- package/dist/__tests__/current-role.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.d.ts +2 -0
- package/dist/__tests__/e2e-mock-agent.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +401 -0
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -0
- package/dist/__tests__/include-tag.test.d.ts +2 -0
- package/dist/__tests__/include-tag.test.d.ts.map +1 -0
- package/dist/__tests__/include-tag.test.js +69 -0
- package/dist/__tests__/include-tag.test.js.map +1 -0
- package/dist/__tests__/log.test.d.ts +2 -0
- package/dist/__tests__/log.test.d.ts.map +1 -0
- package/dist/__tests__/log.test.js +161 -0
- package/dist/__tests__/log.test.js.map +1 -0
- package/dist/__tests__/moderator-evaluate.test.d.ts +2 -0
- package/dist/__tests__/moderator-evaluate.test.d.ts.map +1 -0
- package/dist/__tests__/moderator-evaluate.test.js +170 -0
- package/dist/__tests__/moderator-evaluate.test.js.map +1 -0
- package/dist/__tests__/preload.d.ts +3 -0
- package/dist/__tests__/preload.d.ts.map +1 -0
- package/dist/__tests__/preload.js +6 -0
- package/dist/__tests__/preload.js.map +1 -0
- package/dist/__tests__/prompt.test.d.ts +2 -0
- package/dist/__tests__/prompt.test.d.ts.map +1 -0
- package/dist/__tests__/prompt.test.js +111 -0
- package/dist/__tests__/prompt.test.js.map +1 -0
- package/dist/__tests__/resolve-head-hash.test.d.ts +2 -0
- package/dist/__tests__/resolve-head-hash.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-head-hash.test.js +66 -0
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.d.ts +2 -0
- package/dist/__tests__/setup-agent-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/setup-agent-discovery.test.js +119 -0
- package/dist/__tests__/setup-agent-discovery.test.js.map +1 -0
- package/dist/__tests__/setup-complexity.test.d.ts +2 -0
- package/dist/__tests__/setup-complexity.test.d.ts.map +1 -0
- package/dist/__tests__/setup-complexity.test.js +314 -0
- package/dist/__tests__/setup-complexity.test.js.map +1 -0
- package/dist/__tests__/setup-validate.test.d.ts +2 -0
- package/dist/__tests__/setup-validate.test.d.ts.map +1 -0
- package/dist/__tests__/setup-validate.test.js +108 -0
- package/dist/__tests__/setup-validate.test.js.map +1 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.d.ts +2 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.d.ts.map +1 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.js +107 -0
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -0
- package/dist/__tests__/spawn-agent-json.test.d.ts +2 -0
- package/dist/__tests__/spawn-agent-json.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-agent-json.test.js +79 -0
- package/dist/__tests__/spawn-agent-json.test.js.map +1 -0
- package/dist/__tests__/step-read.test.d.ts +2 -0
- package/dist/__tests__/step-read.test.d.ts.map +1 -0
- package/dist/__tests__/step-read.test.js +561 -0
- package/dist/__tests__/step-read.test.js.map +1 -0
- package/dist/__tests__/step-show-json.test.d.ts +2 -0
- package/dist/__tests__/step-show-json.test.d.ts.map +1 -0
- package/dist/__tests__/step-show-json.test.js +311 -0
- package/dist/__tests__/step-show-json.test.js.map +1 -0
- package/dist/__tests__/step-timing.test.d.ts +2 -0
- package/dist/__tests__/step-timing.test.d.ts.map +1 -0
- package/dist/__tests__/step-timing.test.js +345 -0
- package/dist/__tests__/step-timing.test.js.map +1 -0
- package/dist/__tests__/store-global-cas.test.d.ts +2 -0
- package/dist/__tests__/store-global-cas.test.d.ts.map +1 -0
- package/dist/__tests__/store-global-cas.test.js +235 -0
- package/dist/__tests__/store-global-cas.test.js.map +1 -0
- package/dist/__tests__/store-storage-root.test.d.ts +2 -0
- package/dist/__tests__/store-storage-root.test.d.ts.map +1 -0
- package/dist/__tests__/store-storage-root.test.js +43 -0
- package/dist/__tests__/store-storage-root.test.js.map +1 -0
- package/dist/__tests__/store-unified-threads.test.d.ts +2 -0
- package/dist/__tests__/store-unified-threads.test.d.ts.map +1 -0
- package/dist/__tests__/store-unified-threads.test.js +189 -0
- package/dist/__tests__/store-unified-threads.test.js.map +1 -0
- package/dist/__tests__/thread-cancel-status.test.d.ts +2 -0
- package/dist/__tests__/thread-cancel-status.test.d.ts.map +1 -0
- package/dist/__tests__/thread-cancel-status.test.js +111 -0
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -0
- package/dist/__tests__/thread-list-filters.test.d.ts +2 -0
- package/dist/__tests__/thread-list-filters.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-filters.test.js +442 -0
- package/dist/__tests__/thread-list-filters.test.js.map +1 -0
- package/dist/__tests__/thread-location.test.d.ts +2 -0
- package/dist/__tests__/thread-location.test.d.ts.map +1 -0
- package/dist/__tests__/thread-location.test.js +159 -0
- package/dist/__tests__/thread-location.test.js.map +1 -0
- package/dist/__tests__/thread-read-quota.test.d.ts +2 -0
- package/dist/__tests__/thread-read-quota.test.d.ts.map +1 -0
- package/dist/__tests__/thread-read-quota.test.js +546 -0
- package/dist/__tests__/thread-read-quota.test.js.map +1 -0
- package/dist/__tests__/thread-read-xml-tags.test.d.ts +2 -0
- package/dist/__tests__/thread-read-xml-tags.test.d.ts.map +1 -0
- package/dist/__tests__/thread-read-xml-tags.test.js +610 -0
- package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -0
- package/dist/__tests__/thread-resume.test.d.ts +2 -0
- package/dist/__tests__/thread-resume.test.d.ts.map +1 -0
- package/dist/__tests__/thread-resume.test.js +592 -0
- package/dist/__tests__/thread-resume.test.js.map +1 -0
- package/dist/__tests__/thread-show-status.test.d.ts +2 -0
- package/dist/__tests__/thread-show-status.test.d.ts.map +1 -0
- package/dist/__tests__/thread-show-status.test.js +267 -0
- package/dist/__tests__/thread-show-status.test.js.map +1 -0
- package/dist/__tests__/thread-start-cwd-cli.test.d.ts +2 -0
- package/dist/__tests__/thread-start-cwd-cli.test.d.ts.map +1 -0
- package/dist/__tests__/thread-start-cwd-cli.test.js +130 -0
- package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -0
- package/dist/__tests__/thread-step-count.test.d.ts +2 -0
- package/dist/__tests__/thread-step-count.test.d.ts.map +1 -0
- package/dist/__tests__/thread-step-count.test.js +55 -0
- package/dist/__tests__/thread-step-count.test.js.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.d.ts +2 -0
- package/dist/__tests__/thread-suspend-step.test.d.ts.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.js +155 -0
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -0
- package/dist/__tests__/thread-suspended-display.test.d.ts +2 -0
- package/dist/__tests__/thread-suspended-display.test.d.ts.map +1 -0
- package/dist/__tests__/thread-suspended-display.test.js +247 -0
- package/dist/__tests__/thread-suspended-display.test.js.map +1 -0
- package/dist/__tests__/thread-test-helpers.d.ts +4 -0
- package/dist/__tests__/thread-test-helpers.d.ts.map +1 -0
- package/dist/__tests__/thread-test-helpers.js +23 -0
- package/dist/__tests__/thread-test-helpers.js.map +1 -0
- package/dist/__tests__/thread.test.d.ts +2 -0
- package/dist/__tests__/thread.test.d.ts.map +1 -0
- package/dist/__tests__/thread.test.js +883 -0
- package/dist/__tests__/thread.test.js.map +1 -0
- package/dist/__tests__/validate-semantic.test.d.ts +2 -0
- package/dist/__tests__/validate-semantic.test.d.ts.map +1 -0
- package/dist/__tests__/validate-semantic.test.js +408 -0
- package/dist/__tests__/validate-semantic.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.d.ts +2 -0
- package/dist/__tests__/workflow-resolution.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +308 -0
- package/dist/__tests__/workflow-resolution.test.js.map +1 -0
- package/dist/background/background.d.ts +38 -0
- package/dist/background/background.d.ts.map +1 -0
- package/dist/background/background.js +123 -0
- package/dist/background/background.js.map +1 -0
- package/dist/background/index.d.ts +3 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +2 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/types.d.ts +9 -0
- package/dist/background/types.d.ts.map +1 -0
- package/dist/background/types.js +2 -0
- package/dist/background/types.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +535 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +41 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +252 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/log.d.ts +26 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +79 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/prompt.d.ts +6 -0
- package/dist/commands/prompt.d.ts.map +1 -0
- package/dist/commands/prompt.js +67 -0
- package/dist/commands/prompt.js.map +1 -0
- package/dist/commands/setup.d.ts +73 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +522 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/shared.d.ts +31 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +154 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/step.d.ts +18 -0
- package/dist/commands/step.d.ts.map +1 -0
- package/dist/commands/step.js +257 -0
- package/dist/commands/step.js.map +1 -0
- package/dist/commands/thread-time-parser.d.ts +6 -0
- package/dist/commands/thread-time-parser.d.ts.map +1 -0
- package/dist/commands/thread-time-parser.js +22 -0
- package/dist/commands/thread-time-parser.js.map +1 -0
- package/dist/commands/thread.d.ts +38 -0
- package/dist/commands/thread.d.ts.map +1 -0
- package/dist/commands/thread.js +1087 -0
- package/dist/commands/thread.js.map +1 -0
- package/dist/commands/workflow.d.ts +24 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +138 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +10 -0
- package/dist/format.js.map +1 -0
- package/dist/include.d.ts +12 -0
- package/dist/include.d.ts.map +1 -0
- package/dist/include.js +35 -0
- package/dist/include.js.map +1 -0
- package/dist/moderator/__tests__/evaluate.test.d.ts +2 -0
- package/dist/moderator/__tests__/evaluate.test.d.ts.map +1 -0
- package/dist/moderator/__tests__/evaluate.test.js +167 -0
- package/dist/moderator/__tests__/evaluate.test.js.map +1 -0
- package/dist/moderator/evaluate.d.ts +6 -0
- package/dist/moderator/evaluate.d.ts.map +1 -0
- package/dist/moderator/evaluate.js +65 -0
- package/dist/moderator/evaluate.js.map +1 -0
- package/dist/moderator/index.d.ts +4 -0
- package/dist/moderator/index.d.ts.map +1 -0
- package/dist/moderator/index.js +3 -0
- package/dist/moderator/index.js.map +1 -0
- package/dist/moderator/types.d.ts +25 -0
- package/dist/moderator/types.d.ts.map +1 -0
- package/dist/moderator/types.js +4 -0
- package/dist/moderator/types.js.map +1 -0
- package/dist/schemas.d.ts +16 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +17 -0
- package/dist/schemas.js.map +1 -0
- package/dist/store.d.ts +77 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +392 -0
- package/dist/store.js.map +1 -0
- package/dist/validate-semantic.d.ts +7 -0
- package/dist/validate-semantic.d.ts.map +1 -0
- package/dist/validate-semantic.js +263 -0
- package/dist/validate-semantic.js.map +1 -0
- package/dist/validate.d.ts +16 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +115 -0
- package/dist/validate.js.map +1 -0
- package/package.json +44 -0
- package/src/__tests__/adapter-json-roundtrip.test.ts +181 -0
- package/src/__tests__/config.test.ts +740 -0
- package/src/__tests__/current-role.test.ts +438 -0
- package/src/__tests__/e2e-mock-agent.test.ts +498 -0
- package/src/__tests__/fixtures/e2e-completed-resume.mock.yaml +15 -0
- package/src/__tests__/fixtures/e2e-count.mock.yaml +19 -0
- package/src/__tests__/fixtures/e2e-count.workflow.yaml +45 -0
- package/src/__tests__/fixtures/e2e-linear.mock.yaml +13 -0
- package/src/__tests__/fixtures/e2e-linear.workflow.yaml +32 -0
- package/src/__tests__/fixtures/e2e-loop.mock.yaml +25 -0
- package/src/__tests__/fixtures/e2e-loop.workflow.yaml +36 -0
- package/src/__tests__/fixtures/e2e-mismatch.mock.yaml +16 -0
- package/src/__tests__/fixtures/e2e-mustache.mock.yaml +15 -0
- package/src/__tests__/fixtures/e2e-mustache.workflow.yaml +34 -0
- package/src/__tests__/fixtures/e2e-suspend.mock.yaml +14 -0
- package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +24 -0
- package/src/__tests__/include-tag.test.ts +84 -0
- package/src/__tests__/log.test.ts +181 -0
- package/src/__tests__/moderator-evaluate.test.ts +186 -0
- package/src/__tests__/preload.ts +7 -0
- package/src/__tests__/prompt.test.ts +129 -0
- package/src/__tests__/resolve-head-hash.test.ts +86 -0
- package/src/__tests__/setup-agent-discovery.test.ts +167 -0
- package/src/__tests__/setup-complexity.test.ts +381 -0
- package/src/__tests__/setup-validate.test.ts +148 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +144 -0
- package/src/__tests__/spawn-agent-json.test.ts +100 -0
- package/src/__tests__/step-read.test.ts +632 -0
- package/src/__tests__/step-show-json.test.ts +373 -0
- package/src/__tests__/step-timing.test.ts +392 -0
- package/src/__tests__/store-global-cas.test.ts +308 -0
- package/src/__tests__/store-storage-root.test.ts +49 -0
- package/src/__tests__/store-unified-threads.test.ts +235 -0
- package/src/__tests__/thread-cancel-status.test.ts +138 -0
- package/src/__tests__/thread-list-filters.test.ts +572 -0
- package/src/__tests__/thread-location.test.ts +186 -0
- package/src/__tests__/thread-read-quota.test.ts +613 -0
- package/src/__tests__/thread-read-xml-tags.test.ts +717 -0
- package/src/__tests__/thread-resume.test.ts +710 -0
- package/src/__tests__/thread-show-status.test.ts +317 -0
- package/src/__tests__/thread-start-cwd-cli.test.ts +164 -0
- package/src/__tests__/thread-step-count.test.ts +70 -0
- package/src/__tests__/thread-suspend-step.test.ts +181 -0
- package/src/__tests__/thread-suspended-display.test.ts +287 -0
- package/src/__tests__/thread-test-helpers.ts +37 -0
- package/src/__tests__/thread.test.ts +1025 -0
- package/src/__tests__/validate-semantic.test.ts +474 -0
- package/src/__tests__/workflow-resolution.test.ts +421 -0
- package/src/background/background.ts +147 -0
- package/src/background/index.ts +11 -0
- package/src/background/types.ts +9 -0
- package/src/cli.ts +692 -0
- package/src/commands/config.ts +304 -0
- package/src/commands/log.ts +116 -0
- package/src/commands/prompt.ts +81 -0
- package/src/commands/setup.ts +603 -0
- package/src/commands/shared.ts +227 -0
- package/src/commands/step.ts +343 -0
- package/src/commands/thread-time-parser.ts +23 -0
- package/src/commands/thread.ts +1575 -0
- package/src/commands/workflow.ts +213 -0
- package/src/format.ts +12 -0
- package/src/include.ts +37 -0
- package/src/moderator/__tests__/evaluate.test.ts +199 -0
- package/src/moderator/evaluate.ts +80 -0
- package/src/moderator/index.ts +7 -0
- package/src/moderator/types.ts +24 -0
- package/src/schemas.ts +26 -0
- package/src/store.ts +479 -0
- package/src/validate-semantic.ts +304 -0
- 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
|