argsbarg 4.1.1 → 5.0.2
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/CHANGELOG.md +34 -1
- package/README.md +5 -5
- package/docs/README.md +3 -3
- package/docs/ai-skills.md +9 -9
- package/docs/bundled-docs.md +4 -4
- package/docs/cli-program.md +8 -8
- package/docs/config-schema.md +2 -2
- package/docs/configure.md +177 -0
- package/docs/developing.md +1 -1
- package/docs/distribution-homebrew.md +12 -9
- package/docs/mcp.md +9 -9
- package/examples/full-example/README.md +3 -3
- package/examples/full-example/justfile +6 -6
- package/examples/full-example/scripts/formula-shared.ts +3 -2
- package/examples/full-example/src/program.ts +3 -3
- package/examples/nested.ts +1 -1
- package/index.d.ts +23 -16
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +105 -61
- package/src/builtins/completion-group.ts +4 -6
- package/src/builtins/config.test.ts +8 -0
- package/src/builtins/configure-copy.ts +86 -0
- package/src/builtins/configure.ts +70 -0
- package/src/builtins/dispatch.ts +13 -33
- package/src/builtins/index.ts +1 -1
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/registry.ts +6 -6
- package/src/capabilities.ts +22 -13
- package/src/cli-tool/cli-smoke.test.ts +21 -3
- package/src/cli-tool/create.test.ts +36 -1
- package/src/cli-tool/create.ts +26 -4
- package/src/cli-tool/full-example-capabilities.test.ts +10 -2
- package/src/cli-tool/main.ts +0 -0
- package/src/cli-tool/program.ts +20 -5
- package/src/cli-tool/run-create.ts +8 -19
- package/src/config/bootstrap.ts +11 -9
- package/src/config/context.test.ts +8 -0
- package/src/config/file.test.ts +13 -1
- package/src/config/resolve.test.ts +18 -0
- package/src/config/resolve.ts +2 -2
- package/src/config/validate.test.ts +11 -0
- package/src/config.integration.test.ts +5 -0
- package/src/configure/configure.test.ts +170 -0
- package/src/configure/index.ts +298 -0
- package/src/configure/prompt.ts +47 -0
- package/src/docs/api-guide.test.ts +10 -0
- package/src/docs/builtin.ts +3 -5
- package/src/docs/docs.test.ts +32 -5
- package/src/docs/mcp-guide.ts +4 -4
- package/src/docs/mcp-resources.test.ts +10 -0
- package/src/formats.test.ts +9 -0
- package/src/headless.test.ts +10 -0
- package/src/hidden-mcpb.test.ts +22 -0
- package/src/index.ts +2 -2
- package/src/install/binary-placement.test.ts +14 -0
- package/src/install/gh-release-update.test.ts +9 -0
- package/src/install/install-validate.test.ts +15 -5
- package/src/install/mcp-codex.test.ts +7 -0
- package/src/install/mcp-openclaw.test.ts +6 -0
- package/src/install/mcp-opencode.test.ts +10 -0
- package/src/install/opts.ts +17 -0
- package/src/install/status.test.ts +9 -0
- package/src/install/target-effective.ts +8 -8
- package/src/install/target-scope.ts +11 -8
- package/src/install/targets/configure.ts +1 -1
- package/src/install/targets.test.ts +23 -4
- package/src/invoke.test.ts +15 -1
- package/src/mcp/claude.test.ts +10 -0
- package/src/mcp/env.test.ts +12 -0
- package/src/mcp/tools.ts +1 -1
- package/src/mcp/zip.test.ts +5 -0
- package/src/mcp.integration.test.ts +43 -4
- package/src/parse.test.ts +82 -13
- package/src/schema.ts +1 -9
- package/src/skill/hint.ts +2 -2
- package/src/types.ts +22 -14
- package/src/validate.ts +17 -17
- package/docs/install.md +0 -206
- package/src/builtins/install.ts +0 -106
- package/src/builtins/uninstall.ts +0 -80
- package/src/install/index.ts +0 -409
- package/src/install/install.test.ts +0 -317
package/src/config/file.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/file module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -40,7 +44,9 @@ function withHome<T>(fn: (home: string) => T): T {
|
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
/** Tests for config/file. */
|
|
43
48
|
describe("config/file", () => {
|
|
49
|
+
/** BuildProgramUserConfig from program.appConfig env entries. */
|
|
44
50
|
test("buildProgramUserConfig from program.appConfig env entries", () => {
|
|
45
51
|
const cfg = buildProgramUserConfig(program);
|
|
46
52
|
expect(cfg?.api_token).toMatchObject({
|
|
@@ -51,6 +57,7 @@ describe("config/file", () => {
|
|
|
51
57
|
expect(cfg?.port).toBeUndefined();
|
|
52
58
|
});
|
|
53
59
|
|
|
60
|
+
/** ResolveAppConfigPath uses config.json. */
|
|
54
61
|
test("resolveAppConfigPath uses config.json", () => {
|
|
55
62
|
withHome((home) => {
|
|
56
63
|
expect(resolveAppConfigPath(program)).toBe(
|
|
@@ -59,6 +66,7 @@ describe("config/file", () => {
|
|
|
59
66
|
});
|
|
60
67
|
});
|
|
61
68
|
|
|
69
|
+
/** ResolveAppConfig prefers host env over file. */
|
|
62
70
|
test("resolveAppConfig prefers host env over file", () => {
|
|
63
71
|
withHome((_home) => {
|
|
64
72
|
const prevToken = process.env.API_TOKEN;
|
|
@@ -76,6 +84,7 @@ describe("config/file", () => {
|
|
|
76
84
|
});
|
|
77
85
|
});
|
|
78
86
|
|
|
87
|
+
/** MissingRequiredConfig and formatMissingConfigMessage. */
|
|
79
88
|
test("missingRequiredConfig and formatMissingConfigMessage", () => {
|
|
80
89
|
const prev = process.env.API_TOKEN;
|
|
81
90
|
delete process.env.API_TOKEN;
|
|
@@ -85,12 +94,13 @@ describe("config/file", () => {
|
|
|
85
94
|
expect(missing).toContain("apiToken");
|
|
86
95
|
expect(missing).not.toContain("port");
|
|
87
96
|
const msg = formatMissingConfigMessage(program, missing);
|
|
88
|
-
expect(msg).toContain("
|
|
97
|
+
expect(msg).toContain("configure");
|
|
89
98
|
} finally {
|
|
90
99
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
91
100
|
}
|
|
92
101
|
});
|
|
93
102
|
|
|
103
|
+
/** Rejects unknown keys on read. */
|
|
94
104
|
test("rejects unknown keys on read", () => {
|
|
95
105
|
withHome(() => {
|
|
96
106
|
const configPath = resolveAppConfigPath(program);
|
|
@@ -100,6 +110,7 @@ describe("config/file", () => {
|
|
|
100
110
|
});
|
|
101
111
|
});
|
|
102
112
|
|
|
113
|
+
/** WriteAppConfigFile round-trip. */
|
|
103
114
|
test("writeAppConfigFile round-trip", () => {
|
|
104
115
|
withHome(() => {
|
|
105
116
|
writeAppConfigFile(program, { apiToken: "saved" });
|
|
@@ -108,6 +119,7 @@ describe("config/file", () => {
|
|
|
108
119
|
});
|
|
109
120
|
});
|
|
110
121
|
|
|
122
|
+
/** Tests that uninstallAppConfig removes config directory recursively. */
|
|
111
123
|
test("uninstallAppConfig removes config directory recursively", () => {
|
|
112
124
|
withHome(() => {
|
|
113
125
|
writeAppConfigFile(program, { apiToken: "saved" });
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/resolve module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import type { CliProgram } from "../types.ts";
|
|
3
7
|
import { captureMappedHostEnv, exportConfigToEnv, resolveAppConfig } from "./resolve.ts";
|
|
@@ -25,7 +29,9 @@ const program: CliProgram = {
|
|
|
25
29
|
handler: () => {},
|
|
26
30
|
};
|
|
27
31
|
|
|
32
|
+
/** Tests for config/resolve. */
|
|
28
33
|
describe("config/resolve", () => {
|
|
34
|
+
/** Tests that prefers env over file for mapped keys. */
|
|
29
35
|
test("prefers env over file for mapped keys", () => {
|
|
30
36
|
const prev = process.env.API_TOKEN;
|
|
31
37
|
process.env.API_TOKEN = "from-env";
|
|
@@ -38,6 +44,7 @@ describe("config/resolve", () => {
|
|
|
38
44
|
}
|
|
39
45
|
});
|
|
40
46
|
|
|
47
|
+
/** Tests that uses file when env empty. */
|
|
41
48
|
test("uses file when env empty", () => {
|
|
42
49
|
const prev = process.env.API_TOKEN;
|
|
43
50
|
delete process.env.API_TOKEN;
|
|
@@ -49,6 +56,7 @@ describe("config/resolve", () => {
|
|
|
49
56
|
}
|
|
50
57
|
});
|
|
51
58
|
|
|
59
|
+
/** Tests that empty string in env or file counts as missing. */
|
|
52
60
|
test("empty string in env or file counts as missing", () => {
|
|
53
61
|
const prev = process.env.API_TOKEN;
|
|
54
62
|
process.env.API_TOKEN = "";
|
|
@@ -61,6 +69,7 @@ describe("config/resolve", () => {
|
|
|
61
69
|
}
|
|
62
70
|
});
|
|
63
71
|
|
|
72
|
+
/** Applies jsonSchema default when file and env absent. */
|
|
64
73
|
test("applies jsonSchema default when file and env absent", () => {
|
|
65
74
|
const prev = process.env.API_TOKEN;
|
|
66
75
|
delete process.env.API_TOKEN;
|
|
@@ -73,6 +82,7 @@ describe("config/resolve", () => {
|
|
|
73
82
|
}
|
|
74
83
|
});
|
|
75
84
|
|
|
85
|
+
/** All-string mode uses entry.default. */
|
|
76
86
|
test("all-string mode uses entry.default", () => {
|
|
77
87
|
const stringProgram: CliProgram = {
|
|
78
88
|
...program,
|
|
@@ -86,6 +96,7 @@ describe("config/resolve", () => {
|
|
|
86
96
|
expect(resolved.greeting).toBe("world");
|
|
87
97
|
});
|
|
88
98
|
|
|
99
|
+
/** Tests that prefers captured host env over file when process.env was exported from file. */
|
|
89
100
|
test("prefers captured host env over file when process.env was exported from file", () => {
|
|
90
101
|
const hostEnv = { API_TOKEN: "from-host" };
|
|
91
102
|
process.env.API_TOKEN = "from-file-export";
|
|
@@ -97,6 +108,7 @@ describe("config/resolve", () => {
|
|
|
97
108
|
}
|
|
98
109
|
});
|
|
99
110
|
|
|
111
|
+
/** ExportConfigToEnv does not overwrite host env. */
|
|
100
112
|
test("exportConfigToEnv does not overwrite host env", () => {
|
|
101
113
|
const prev = process.env.API_TOKEN;
|
|
102
114
|
process.env.API_TOKEN = "from-host";
|
|
@@ -110,6 +122,7 @@ describe("config/resolve", () => {
|
|
|
110
122
|
}
|
|
111
123
|
});
|
|
112
124
|
|
|
125
|
+
/** Resolve callback supplies value when env and file are absent. */
|
|
113
126
|
test("resolve callback supplies value when env and file are absent", () => {
|
|
114
127
|
const resolveProgram: CliProgram = {
|
|
115
128
|
...program,
|
|
@@ -135,6 +148,7 @@ describe("config/resolve", () => {
|
|
|
135
148
|
}
|
|
136
149
|
});
|
|
137
150
|
|
|
151
|
+
/** Env overrides resolve callback. */
|
|
138
152
|
test("env overrides resolve callback", () => {
|
|
139
153
|
const resolveProgram: CliProgram = {
|
|
140
154
|
...program,
|
|
@@ -161,6 +175,7 @@ describe("config/resolve", () => {
|
|
|
161
175
|
}
|
|
162
176
|
});
|
|
163
177
|
|
|
178
|
+
/** File overrides resolve callback. */
|
|
164
179
|
test("file overrides resolve callback", () => {
|
|
165
180
|
const resolveProgram: CliProgram = {
|
|
166
181
|
...program,
|
|
@@ -186,6 +201,7 @@ describe("config/resolve", () => {
|
|
|
186
201
|
}
|
|
187
202
|
});
|
|
188
203
|
|
|
204
|
+
/** Tests that falls back to env when resolve returns undefined. */
|
|
189
205
|
test("falls back to env when resolve returns undefined", () => {
|
|
190
206
|
const resolveProgram: CliProgram = {
|
|
191
207
|
...program,
|
|
@@ -212,6 +228,7 @@ describe("config/resolve", () => {
|
|
|
212
228
|
}
|
|
213
229
|
});
|
|
214
230
|
|
|
231
|
+
/** Resolve callback is skipped when env is set. */
|
|
215
232
|
test("resolve callback is skipped when env is set", () => {
|
|
216
233
|
let resolveCalled = false;
|
|
217
234
|
const resolveProgram: CliProgram = {
|
|
@@ -243,6 +260,7 @@ describe("config/resolve", () => {
|
|
|
243
260
|
}
|
|
244
261
|
});
|
|
245
262
|
|
|
263
|
+
/** Tests that async resolve is ignored with stderr warning. */
|
|
246
264
|
test("async resolve is ignored with stderr warning", () => {
|
|
247
265
|
const resolveProgram: CliProgram = {
|
|
248
266
|
...program,
|
package/src/config/resolve.ts
CHANGED
|
@@ -226,7 +226,7 @@ export function formatMissingConfigMessage(program: CliProgram, keys: string[]):
|
|
|
226
226
|
const path = displayAppConfigPath(program);
|
|
227
227
|
return [
|
|
228
228
|
`Missing required configuration: ${list}`,
|
|
229
|
-
`Configure interactively: ${program.key}
|
|
229
|
+
`Configure interactively: ${program.key} configure`,
|
|
230
230
|
`Or set via: ${program.key} config set <key> <value>`,
|
|
231
231
|
`Config file: ${path}`,
|
|
232
232
|
`See: ${program.key} docs mcp`,
|
|
@@ -239,7 +239,7 @@ export function formatMcpMissingConfigMessage(program: CliProgram, keys: string[
|
|
|
239
239
|
const path = displayAppConfigPath(program);
|
|
240
240
|
return [
|
|
241
241
|
`Missing required configuration: ${list}`,
|
|
242
|
-
`Configure: ${program.key}
|
|
242
|
+
`Configure: ${program.key} configure`,
|
|
243
243
|
`Or set via: ${program.key} config set`,
|
|
244
244
|
`Config file: ${path}`,
|
|
245
245
|
].join("\n");
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/validate module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { parseConfigSetValue, validateConfigDocument } from "./validate.ts";
|
|
3
7
|
|
|
@@ -17,7 +21,9 @@ const rootSchema = {
|
|
|
17
21
|
},
|
|
18
22
|
};
|
|
19
23
|
|
|
24
|
+
/** Tests for config/validate. */
|
|
20
25
|
describe("config/validate", () => {
|
|
26
|
+
/** Tests that accepts valid document. */
|
|
21
27
|
test("accepts valid document", () => {
|
|
22
28
|
const result = validateConfigDocument(
|
|
23
29
|
{ apiToken: "x", maxRetries: 3, prefs: { ttl: 3600 } },
|
|
@@ -27,12 +33,14 @@ describe("config/validate", () => {
|
|
|
27
33
|
expect(result.errors).toEqual([]);
|
|
28
34
|
});
|
|
29
35
|
|
|
36
|
+
/** Rejects missing required property. */
|
|
30
37
|
test("rejects missing required property", () => {
|
|
31
38
|
const result = validateConfigDocument({ apiToken: "x" }, rootSchema);
|
|
32
39
|
expect(result.valid).toBe(false);
|
|
33
40
|
expect(result.errors.some((e) => e.includes("maxRetries"))).toBe(true);
|
|
34
41
|
});
|
|
35
42
|
|
|
43
|
+
/** Rejects unknown property when additionalProperties is false. */
|
|
36
44
|
test("rejects unknown property when additionalProperties is false", () => {
|
|
37
45
|
const result = validateConfigDocument(
|
|
38
46
|
{ apiToken: "x", maxRetries: 1, extra: true },
|
|
@@ -42,16 +50,19 @@ describe("config/validate", () => {
|
|
|
42
50
|
expect(result.errors.some((e) => e.includes("extra"))).toBe(true);
|
|
43
51
|
});
|
|
44
52
|
|
|
53
|
+
/** Rejects type mismatch. */
|
|
45
54
|
test("rejects type mismatch", () => {
|
|
46
55
|
const result = validateConfigDocument({ apiToken: "x", maxRetries: "nope" }, rootSchema);
|
|
47
56
|
expect(result.valid).toBe(false);
|
|
48
57
|
});
|
|
49
58
|
|
|
59
|
+
/** ParseConfigSetValue coerces number and boolean. */
|
|
50
60
|
test("parseConfigSetValue coerces number and boolean", () => {
|
|
51
61
|
expect(parseConfigSetValue("5", { type: "integer" }, rootSchema, false)).toBe(5);
|
|
52
62
|
expect(parseConfigSetValue("true", { type: "boolean" }, rootSchema, false)).toBe(true);
|
|
53
63
|
});
|
|
54
64
|
|
|
65
|
+
/** ParseConfigSetValue requires --json for objects. */
|
|
55
66
|
test("parseConfigSetValue requires --json for objects", () => {
|
|
56
67
|
expect(() => parseConfigSetValue('{"ttl":1}', { type: "object" }, rootSchema, false)).toThrow(
|
|
57
68
|
/--json/,
|
|
@@ -10,6 +10,7 @@ import { bootstrapAppConfig } from "./config/bootstrap.ts";
|
|
|
10
10
|
import { resolveAppConfigPath } from "./config/file.ts";
|
|
11
11
|
import { mcpRequest, testProgram } from "./test-fixtures.ts";
|
|
12
12
|
|
|
13
|
+
/** Tests that bootstrapAppConfig prefers host env over config file. */
|
|
13
14
|
test("bootstrapAppConfig prefers host env over config file", () => {
|
|
14
15
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-env-"));
|
|
15
16
|
const prevHome = process.env.HOME;
|
|
@@ -43,6 +44,7 @@ test("bootstrapAppConfig prefers host env over config file", () => {
|
|
|
43
44
|
}
|
|
44
45
|
});
|
|
45
46
|
|
|
47
|
+
/** MCP program.appConfig fails when required config missing. */
|
|
46
48
|
test("MCP program.appConfig fails when required config missing", async () => {
|
|
47
49
|
const responses = await mcpRequest(
|
|
48
50
|
[
|
|
@@ -60,6 +62,7 @@ test("MCP program.appConfig fails when required config missing", async () => {
|
|
|
60
62
|
expect(res.result.content[0]?.text).toContain("argsTestSecret");
|
|
61
63
|
});
|
|
62
64
|
|
|
65
|
+
/** MCP program.appConfig succeeds when env present. */
|
|
63
66
|
test("MCP program.appConfig succeeds when env present", async () => {
|
|
64
67
|
const responses = await mcpRequest(
|
|
65
68
|
[
|
|
@@ -77,6 +80,7 @@ test("MCP program.appConfig succeeds when env present", async () => {
|
|
|
77
80
|
expect(res.result.content[0]?.text.trim()).toBe("sekrit");
|
|
78
81
|
});
|
|
79
82
|
|
|
83
|
+
/** MCP config file loads and exports vars for tool handlers. */
|
|
80
84
|
test("MCP config file loads and exports vars for tool handlers", async () => {
|
|
81
85
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-mcp-"));
|
|
82
86
|
const configFile = join(dir, ".local", "lib", "mcp_test", "config");
|
|
@@ -106,6 +110,7 @@ test("MCP config file loads and exports vars for tool handlers", async () => {
|
|
|
106
110
|
rmSync(dir, { recursive: true, force: true });
|
|
107
111
|
});
|
|
108
112
|
|
|
113
|
+
/** Cli.run docs api skips required appConfig exit. */
|
|
109
114
|
test("Cli.run docs api skips required appConfig exit", async () => {
|
|
110
115
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-docs-skip-"));
|
|
111
116
|
const configFile = join(dir, ".local", "lib", "docs_skip_test", "config");
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for configure/configure module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
appConfigHasEntries,
|
|
11
|
+
parseConfigureOpts,
|
|
12
|
+
validateConfigureOpts,
|
|
13
|
+
} from "../configure/index.ts";
|
|
14
|
+
import { detectInstalledArtifacts } from "../install/detect-installed.ts";
|
|
15
|
+
import { parseInstallOpts } from "../install/opts.ts";
|
|
16
|
+
import { resolveClaudeDesktopMcpPath, resolveInstallPaths } from "../install/paths.ts";
|
|
17
|
+
import { buildInstallPlan, buildUpdatePlan } from "../install/plan.ts";
|
|
18
|
+
import { buildUninstallPlan } from "../install/uninstall.ts";
|
|
19
|
+
import type { CliProgram } from "../types.ts";
|
|
20
|
+
|
|
21
|
+
const fixture: CliProgram = {
|
|
22
|
+
key: "testapp",
|
|
23
|
+
version: "0.0.0",
|
|
24
|
+
description: "Test",
|
|
25
|
+
mcpServer: { enabled: true },
|
|
26
|
+
handler: () => {},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let home: string;
|
|
30
|
+
let prevHome: string | undefined;
|
|
31
|
+
let prevXdg: string | undefined;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
home = mkdtempSync(join(tmpdir(), "argsbarg-configure-"));
|
|
35
|
+
prevHome = process.env.HOME;
|
|
36
|
+
prevXdg = process.env.XDG_CONFIG_HOME;
|
|
37
|
+
process.env.HOME = home;
|
|
38
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
43
|
+
else process.env.HOME = prevHome;
|
|
44
|
+
if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
45
|
+
else process.env.XDG_CONFIG_HOME = prevXdg;
|
|
46
|
+
rmSync(home, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/** Tests for configure opts. */
|
|
50
|
+
describe("configure opts", () => {
|
|
51
|
+
/** Validate rejects multiple modes. */
|
|
52
|
+
test("validate rejects multiple modes", () => {
|
|
53
|
+
const opts = parseConfigureOpts({ sync: "1", status: "1" });
|
|
54
|
+
expect(validateConfigureOpts(opts)).toContain("only one");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Sync requires --yes. */
|
|
58
|
+
test("sync requires --yes", () => {
|
|
59
|
+
const opts = parseConfigureOpts({ sync: "1" });
|
|
60
|
+
expect(validateConfigureOpts(opts)).toContain("--yes");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/** Remove-all requires --yes. */
|
|
64
|
+
test("remove-all requires --yes", () => {
|
|
65
|
+
const opts = parseConfigureOpts({ "remove-all": "1" });
|
|
66
|
+
expect(validateConfigureOpts(opts)).toContain("--yes");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** Tests for install paths. */
|
|
71
|
+
describe("install paths", () => {
|
|
72
|
+
/** ResolveInstallPaths includes skill and mcp paths. */
|
|
73
|
+
test("resolveInstallPaths includes skill and mcp paths", () => {
|
|
74
|
+
const paths = resolveInstallPaths(fixture);
|
|
75
|
+
expect(paths.cursorSkillDir).toContain(".cursor/skills");
|
|
76
|
+
expect(paths.cursorMcpPath).toContain("mcp.json");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/** Claude desktop mcp path on darwin. */
|
|
80
|
+
test("claude desktop mcp path on darwin", () => {
|
|
81
|
+
const prev = process.platform;
|
|
82
|
+
Object.defineProperty(process, "platform", { value: "darwin" });
|
|
83
|
+
try {
|
|
84
|
+
expect(resolveClaudeDesktopMcpPath(home)).toBe(
|
|
85
|
+
join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
86
|
+
);
|
|
87
|
+
} finally {
|
|
88
|
+
Object.defineProperty(process, "platform", { value: prev });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/** Tests for detect installed. */
|
|
94
|
+
describe("detect installed", () => {
|
|
95
|
+
/** Detects cursor mcp when configured. */
|
|
96
|
+
test("detects cursor mcp when configured", () => {
|
|
97
|
+
const paths = resolveInstallPaths(fixture);
|
|
98
|
+
mkdirSync(join(home, ".cursor"), { recursive: true });
|
|
99
|
+
writeFileSync(
|
|
100
|
+
paths.cursorMcpPath,
|
|
101
|
+
JSON.stringify({ mcpServers: { testapp: { command: "testapp", args: ["mcp"] } } }),
|
|
102
|
+
"utf8",
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const detected = detectInstalledArtifacts(paths, fixture);
|
|
106
|
+
expect(detected.cursorMcp).toBe(true);
|
|
107
|
+
expect(detected.app).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/** Tests for sync plan. */
|
|
112
|
+
describe("sync plan", () => {
|
|
113
|
+
/** BuildUpdatePlan greenfield includes agent targets. */
|
|
114
|
+
test("buildUpdatePlan greenfield includes agent targets", () => {
|
|
115
|
+
const paths = resolveInstallPaths(fixture);
|
|
116
|
+
const plan = buildUpdatePlan(fixture, paths, parseInstallOpts({ reinstall: "1", yes: "1" }));
|
|
117
|
+
expect(plan.length).toBeGreaterThan(0);
|
|
118
|
+
expect(plan.some((a) => a.kind === "app")).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/** BuildInstallPlan --all omits app self-install. */
|
|
122
|
+
test("buildInstallPlan --all omits app self-install", () => {
|
|
123
|
+
const paths = resolveInstallPaths(fixture);
|
|
124
|
+
const plan = buildInstallPlan(fixture, paths, parseInstallOpts({ all: "1" }));
|
|
125
|
+
expect(plan.some((a) => a.kind === "app")).toBe(false);
|
|
126
|
+
expect(plan.some((a) => a.kind.endsWith("-mcp"))).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/** BuildInstallPlan respects configure.agentIntegration skill. */
|
|
130
|
+
test("buildInstallPlan respects configure.agentIntegration skill", () => {
|
|
131
|
+
const skillApp: CliProgram = {
|
|
132
|
+
...fixture,
|
|
133
|
+
configure: { agentIntegration: "skill" },
|
|
134
|
+
};
|
|
135
|
+
const paths = resolveInstallPaths(skillApp);
|
|
136
|
+
mkdirSync(join(home, ".claude"), { recursive: true });
|
|
137
|
+
mkdirSync(join(home, ".cursor"), { recursive: true });
|
|
138
|
+
const plan = buildInstallPlan(skillApp, paths, parseInstallOpts({ mcp: "1" }));
|
|
139
|
+
expect(plan.some((a) => a.kind.endsWith("-mcp"))).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/** Tests for remove plan. */
|
|
144
|
+
describe("remove plan", () => {
|
|
145
|
+
/** BuildUninstallPlan --all with nothing installed is empty. */
|
|
146
|
+
test("buildUninstallPlan --all with nothing installed is empty", () => {
|
|
147
|
+
const paths = resolveInstallPaths(fixture);
|
|
148
|
+
const plan = buildUninstallPlan(fixture, paths, parseInstallOpts({ uninstall: "1", all: "1" }));
|
|
149
|
+
expect(plan.length).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/** Tests for app config wizard. */
|
|
154
|
+
describe("app config wizard", () => {
|
|
155
|
+
/** Tests that appConfigHasEntries when entries exist. */
|
|
156
|
+
test("appConfigHasEntries when entries exist", () => {
|
|
157
|
+
expect(
|
|
158
|
+
appConfigHasEntries({
|
|
159
|
+
...fixture,
|
|
160
|
+
appConfig: { entries: { token: { description: "API token." } } },
|
|
161
|
+
}),
|
|
162
|
+
).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/** Tests that appConfigHasEntries false for empty entries or missing appConfig. */
|
|
166
|
+
test("appConfigHasEntries false for empty entries or missing appConfig", () => {
|
|
167
|
+
expect(appConfigHasEntries({ ...fixture, appConfig: { entries: {} } })).toBe(false);
|
|
168
|
+
expect(appConfigHasEntries(fixture)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|