argsbarg 5.0.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 +8 -1
- package/docs/distribution-homebrew.md +2 -0
- package/examples/full-example/scripts/formula-shared.ts +1 -0
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +23 -0
- package/src/builtins/config.test.ts +8 -0
- package/src/cli-tool/cli-smoke.test.ts +8 -0
- package/src/cli-tool/create.test.ts +13 -0
- package/src/cli-tool/full-example-capabilities.test.ts +8 -0
- package/src/cli-tool/main.ts +0 -0
- package/src/config/context.test.ts +8 -0
- package/src/config/file.test.ts +12 -0
- package/src/config/resolve.test.ts +18 -0
- package/src/config/validate.test.ts +11 -0
- package/src/config.integration.test.ts +5 -0
- package/src/configure/configure.test.ts +22 -0
- package/src/configure/index.ts +14 -0
- package/src/configure/prompt.ts +7 -0
- package/src/docs/api-guide.test.ts +10 -0
- package/src/docs/docs.test.ts +27 -0
- 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/install/binary-placement.test.ts +14 -0
- package/src/install/gh-release-update.test.ts +9 -0
- package/src/install/install-validate.test.ts +10 -0
- 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/status.test.ts +9 -0
- package/src/install/targets.test.ts +19 -0
- package/src/invoke.test.ts +14 -0
- package/src/mcp/claude.test.ts +10 -0
- package/src/mcp/env.test.ts +12 -0
- package/src/mcp/zip.test.ts +5 -0
- package/src/mcp.integration.test.ts +39 -0
- package/src/parse.test.ts +71 -0
package/src/hidden-mcpb.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for hidden-mcpb module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -53,12 +57,15 @@ const hiddenFixture: CliProgram = {
|
|
|
53
57
|
],
|
|
54
58
|
};
|
|
55
59
|
|
|
60
|
+
/** Tests for hidden commands and options. */
|
|
56
61
|
describe("hidden commands and options", () => {
|
|
62
|
+
/** Parse root includes hidden commands. */
|
|
57
63
|
test("parse root includes hidden commands", () => {
|
|
58
64
|
const parse = cliParseRoot(hiddenFixture);
|
|
59
65
|
expect(parse.commands?.map((c) => c.key)).toContain("secret");
|
|
60
66
|
});
|
|
61
67
|
|
|
68
|
+
/** Presentation root omits hidden commands. */
|
|
62
69
|
test("presentation root omits hidden commands", () => {
|
|
63
70
|
const presentation = cliPresentationRoot(hiddenFixture);
|
|
64
71
|
const keys = presentation.commands?.map((c) => c.key) ?? [];
|
|
@@ -66,23 +73,27 @@ describe("hidden commands and options", () => {
|
|
|
66
73
|
expect(keys).not.toContain("secret");
|
|
67
74
|
});
|
|
68
75
|
|
|
76
|
+
/** Root help omits hidden commands. */
|
|
69
77
|
test("root help omits hidden commands", () => {
|
|
70
78
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), [], false);
|
|
71
79
|
expect(help).toContain("public");
|
|
72
80
|
expect(help).not.toContain("secret");
|
|
73
81
|
});
|
|
74
82
|
|
|
83
|
+
/** Hidden command -h still works. */
|
|
75
84
|
test("hidden command -h still works", () => {
|
|
76
85
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["secret"], false);
|
|
77
86
|
expect(help).toContain("Hidden command.");
|
|
78
87
|
});
|
|
79
88
|
|
|
89
|
+
/** Tests that help omits hidden options. */
|
|
80
90
|
test("help omits hidden options", () => {
|
|
81
91
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["flags"], false);
|
|
82
92
|
expect(help).toContain("--visible");
|
|
83
93
|
expect(help).not.toContain("secret-flag");
|
|
84
94
|
});
|
|
85
95
|
|
|
96
|
+
/** Tests that schema export omits hidden nodes and options. */
|
|
86
97
|
test("schema export omits hidden nodes and options", () => {
|
|
87
98
|
const schema = cliSchemaExport(hiddenFixture);
|
|
88
99
|
const keys = schema.commands?.map((c) => c.key) ?? [];
|
|
@@ -92,13 +103,16 @@ describe("hidden commands and options", () => {
|
|
|
92
103
|
expect(flags?.options?.map((o) => o.name)).toEqual(["visible"]);
|
|
93
104
|
});
|
|
94
105
|
|
|
106
|
+
/** MCP tools omit hidden commands. */
|
|
95
107
|
test("MCP tools omit hidden commands", () => {
|
|
96
108
|
const tools = collectMcpTools(hiddenFixture);
|
|
97
109
|
expect(tools.map((t) => t.name)).toEqual(["public", "flags"]);
|
|
98
110
|
});
|
|
99
111
|
});
|
|
100
112
|
|
|
113
|
+
/** Tests for mcp router. */
|
|
101
114
|
describe("mcp router", () => {
|
|
115
|
+
/** Presentation exposes mcp bundle but not hidden serve. */
|
|
102
116
|
test("presentation exposes mcp bundle but not hidden serve", () => {
|
|
103
117
|
const builtins = exportPresentationBuiltins(hiddenFixture);
|
|
104
118
|
const mcp = builtins.find((b) => b.key === "mcp");
|
|
@@ -107,6 +121,7 @@ describe("mcp router", () => {
|
|
|
107
121
|
expect(mcp?.fallbackCommand).toBe("serve");
|
|
108
122
|
});
|
|
109
123
|
|
|
124
|
+
/** Mcp help lists bundle. */
|
|
110
125
|
test("mcp help lists bundle", () => {
|
|
111
126
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["mcp"], false);
|
|
112
127
|
expect(help).toContain("bundle");
|
|
@@ -114,7 +129,9 @@ describe("mcp router", () => {
|
|
|
114
129
|
});
|
|
115
130
|
});
|
|
116
131
|
|
|
132
|
+
/** Tests for mcp bundle. */
|
|
117
133
|
describe("mcp bundle", () => {
|
|
134
|
+
/** Tests that generateMcpManifest uses mcpServerId and binary entry. */
|
|
118
135
|
test("generateMcpManifest uses mcpServerId and binary entry", () => {
|
|
119
136
|
const manifest = generateMcpManifest(hiddenFixture, "myapp");
|
|
120
137
|
expect(manifest.name).toBe("myapp");
|
|
@@ -128,6 +145,7 @@ describe("mcp bundle", () => {
|
|
|
128
145
|
expect((manifest.compatibility as { platforms: string[] }).platforms).toEqual(["darwin"]);
|
|
129
146
|
});
|
|
130
147
|
|
|
148
|
+
/** DefaultMcpBundlePaths. */
|
|
131
149
|
test("defaultMcpBundlePaths", () => {
|
|
132
150
|
const cwd = "/tmp/work";
|
|
133
151
|
const paths = defaultMcpBundlePaths(hiddenFixture, cwd);
|
|
@@ -135,6 +153,7 @@ describe("mcp bundle", () => {
|
|
|
135
153
|
expect(paths.outPath).toBe(join(cwd, "dist", "myapp.mcpb"));
|
|
136
154
|
});
|
|
137
155
|
|
|
156
|
+
/** Tests that packMcpBundle writes zip with manifest and binary. */
|
|
138
157
|
test("packMcpBundle writes zip with manifest and binary", () => {
|
|
139
158
|
const work = mkdtempSync(join(tmpdir(), "mcpb-test-"));
|
|
140
159
|
try {
|
|
@@ -155,6 +174,7 @@ describe("mcp bundle", () => {
|
|
|
155
174
|
}
|
|
156
175
|
});
|
|
157
176
|
|
|
177
|
+
/** RunMcpBundle prints mcpb and plugin paths. */
|
|
158
178
|
test("runMcpBundle prints mcpb and plugin paths", () => {
|
|
159
179
|
const work = mkdtempSync(join(tmpdir(), "mcpb-run-"));
|
|
160
180
|
const stdout: string[] = [];
|
|
@@ -188,6 +208,7 @@ describe("mcp bundle", () => {
|
|
|
188
208
|
}
|
|
189
209
|
});
|
|
190
210
|
|
|
211
|
+
/** RunMcpBundle with claudePlugin only prints plugin path. */
|
|
191
212
|
test("runMcpBundle with claudePlugin only prints plugin path", () => {
|
|
192
213
|
const work = mkdtempSync(join(tmpdir(), "mcpb-run-"));
|
|
193
214
|
const stdout: string[] = [];
|
|
@@ -220,6 +241,7 @@ describe("mcp bundle", () => {
|
|
|
220
241
|
}
|
|
221
242
|
});
|
|
222
243
|
|
|
244
|
+
/** RunMcpBundle errors when no bundle flags enabled. */
|
|
223
245
|
test("runMcpBundle errors when no bundle flags enabled", () => {
|
|
224
246
|
const fixture: CliProgram = {
|
|
225
247
|
...hiddenFixture,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/binary-placement module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
6
|
import { chmodSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -30,12 +34,15 @@ afterEach(() => {
|
|
|
30
34
|
rmSync(tmp, { recursive: true, force: true });
|
|
31
35
|
});
|
|
32
36
|
|
|
37
|
+
/** Tests for isExternallyManagedBinary. */
|
|
33
38
|
describe("isExternallyManagedBinary", () => {
|
|
39
|
+
/** Tests that false when command is not on PATH. */
|
|
34
40
|
test("false when command is not on PATH", () => {
|
|
35
41
|
process.env.PATH = tmp;
|
|
36
42
|
expect(isExternallyManagedBinary("placementapp-not-on-path")).toBe(false);
|
|
37
43
|
});
|
|
38
44
|
|
|
45
|
+
/** Tests that true when PATH resolves to execPath. */
|
|
39
46
|
test("true when PATH resolves to execPath", () => {
|
|
40
47
|
const bin = join(tmp, "placementapp");
|
|
41
48
|
writeFileSync(bin, "#!/bin/sh\n", "utf8");
|
|
@@ -44,6 +51,7 @@ describe("isExternallyManagedBinary", () => {
|
|
|
44
51
|
expect(isExternallyManagedBinary("placementapp", bin)).toBe(true);
|
|
45
52
|
});
|
|
46
53
|
|
|
54
|
+
/** Tests that true when PATH entry is a symlink to execPath. */
|
|
47
55
|
test("true when PATH entry is a symlink to execPath", () => {
|
|
48
56
|
const bin = join(tmp, "placementapp");
|
|
49
57
|
const target = join(tmp, "real-binary");
|
|
@@ -53,6 +61,7 @@ describe("isExternallyManagedBinary", () => {
|
|
|
53
61
|
expect(isExternallyManagedBinary("placementapp", target)).toBe(true);
|
|
54
62
|
});
|
|
55
63
|
|
|
64
|
+
/** Tests that false when PATH points at a different binary. */
|
|
56
65
|
test("false when PATH points at a different binary", () => {
|
|
57
66
|
const bin = join(tmp, "placementapp");
|
|
58
67
|
const other = join(tmp, "other");
|
|
@@ -63,7 +72,9 @@ describe("isExternallyManagedBinary", () => {
|
|
|
63
72
|
});
|
|
64
73
|
});
|
|
65
74
|
|
|
75
|
+
/** Tests for isAppInstalled. */
|
|
66
76
|
describe("isAppInstalled", () => {
|
|
77
|
+
/** Tests that true when externally managed. */
|
|
67
78
|
test("true when externally managed", () => {
|
|
68
79
|
const bin = join(tmp, program.key);
|
|
69
80
|
const prevExec = process.execPath;
|
|
@@ -78,6 +89,7 @@ describe("isAppInstalled", () => {
|
|
|
78
89
|
}
|
|
79
90
|
});
|
|
80
91
|
|
|
92
|
+
/** Tests that false when not on PATH and no local copy. */
|
|
81
93
|
test("false when not on PATH and no local copy", () => {
|
|
82
94
|
const home = mkdtempSync(join(tmpdir(), "argsbarg-placement-home-"));
|
|
83
95
|
const prevHome = process.env.HOME;
|
|
@@ -93,7 +105,9 @@ describe("isAppInstalled", () => {
|
|
|
93
105
|
});
|
|
94
106
|
});
|
|
95
107
|
|
|
108
|
+
/** Tests for resolvePathCommand. */
|
|
96
109
|
describe("resolvePathCommand", () => {
|
|
110
|
+
/** Tests that returns undefined when missing. */
|
|
97
111
|
test("returns undefined when missing", () => {
|
|
98
112
|
process.env.PATH = tmp;
|
|
99
113
|
expect(resolvePathCommand("missing-cmd")).toBeUndefined();
|
|
@@ -1,22 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/gh-release-update module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { expect, test } from "bun:test";
|
|
2
6
|
import { isAlreadyCurrent, parseReleaseTag } from "./gh-release-update.ts";
|
|
3
7
|
|
|
8
|
+
/** ParseReleaseTag strips leading v. */
|
|
4
9
|
test("parseReleaseTag strips leading v", () => {
|
|
5
10
|
expect(parseReleaseTag("v1.4.3")).toBe("1.4.3");
|
|
6
11
|
});
|
|
7
12
|
|
|
13
|
+
/** ParseReleaseTag leaves bare semver unchanged. */
|
|
8
14
|
test("parseReleaseTag leaves bare semver unchanged", () => {
|
|
9
15
|
expect(parseReleaseTag("1.4.3")).toBe("1.4.3");
|
|
10
16
|
});
|
|
11
17
|
|
|
18
|
+
/** ParseReleaseTag throws on empty tag. */
|
|
12
19
|
test("parseReleaseTag throws on empty tag", () => {
|
|
13
20
|
expect(() => parseReleaseTag("")).toThrow("Release tag is empty");
|
|
14
21
|
});
|
|
15
22
|
|
|
23
|
+
/** Tests that isAlreadyCurrent returns true when versions match. */
|
|
16
24
|
test("isAlreadyCurrent returns true when versions match", () => {
|
|
17
25
|
expect(isAlreadyCurrent("1.4.3", "1.4.3")).toBe(true);
|
|
18
26
|
});
|
|
19
27
|
|
|
28
|
+
/** Tests that isAlreadyCurrent returns false when versions differ. */
|
|
20
29
|
test("isAlreadyCurrent returns false when versions differ", () => {
|
|
21
30
|
expect(isAlreadyCurrent("1.4.2", "1.4.3")).toBe(false);
|
|
22
31
|
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/install-validate module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import type { CliProgram } from "../types.ts";
|
|
3
7
|
import { CliSchemaValidationError } from "../types.ts";
|
|
@@ -10,11 +14,14 @@ const base: CliProgram = {
|
|
|
10
14
|
handler: () => {},
|
|
11
15
|
};
|
|
12
16
|
|
|
17
|
+
/** Tests for validateConfigureConfig. */
|
|
13
18
|
describe("validateConfigureConfig", () => {
|
|
19
|
+
/** Tests that accepts empty install config. */
|
|
14
20
|
test("accepts empty install config", () => {
|
|
15
21
|
expect(() => cliValidateProgram(base)).not.toThrow();
|
|
16
22
|
});
|
|
17
23
|
|
|
24
|
+
/** Rejects removed allSkills shorthand. */
|
|
18
25
|
test("rejects removed allSkills shorthand", () => {
|
|
19
26
|
const program = {
|
|
20
27
|
...base,
|
|
@@ -24,6 +31,7 @@ describe("validateConfigureConfig", () => {
|
|
|
24
31
|
expect(() => cliValidateProgram(program)).toThrow(/allSkills/);
|
|
25
32
|
});
|
|
26
33
|
|
|
34
|
+
/** Rejects both sides of pair without both integration. */
|
|
27
35
|
test("rejects both sides of pair without both integration", () => {
|
|
28
36
|
const program: CliProgram = {
|
|
29
37
|
...base,
|
|
@@ -36,6 +44,7 @@ describe("validateConfigureConfig", () => {
|
|
|
36
44
|
expect(() => cliValidateProgram(program)).toThrow(/both/);
|
|
37
45
|
});
|
|
38
46
|
|
|
47
|
+
/** Rejects explicit MCP target in skill integration mode. */
|
|
39
48
|
test("rejects explicit MCP target in skill integration mode", () => {
|
|
40
49
|
const program: CliProgram = {
|
|
41
50
|
...base,
|
|
@@ -47,6 +56,7 @@ describe("validateConfigureConfig", () => {
|
|
|
47
56
|
expect(() => cliValidateProgram(program)).toThrow(/cursorMcp/);
|
|
48
57
|
});
|
|
49
58
|
|
|
59
|
+
/** Allows explicit pair targets with both integration. */
|
|
50
60
|
test("allows explicit pair targets with both integration", () => {
|
|
51
61
|
const program: CliProgram = {
|
|
52
62
|
...base,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/mcp-codex module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -19,7 +23,9 @@ afterEach(() => {
|
|
|
19
23
|
rmSync(home, { recursive: true, force: true });
|
|
20
24
|
});
|
|
21
25
|
|
|
26
|
+
/** Tests for codex mcp config. */
|
|
22
27
|
describe("codex mcp config", () => {
|
|
28
|
+
/** ReadCodexMcpEntry parses simple command/args. */
|
|
23
29
|
test("readCodexMcpEntry parses simple command/args", () => {
|
|
24
30
|
const path = resolveCodexConfigPath(home);
|
|
25
31
|
mkdirSync(join(home, ".codex"), { recursive: true });
|
|
@@ -38,6 +44,7 @@ args = ["mcp"]
|
|
|
38
44
|
expect(codexMcpHasServer(home, "testapp")).toBe(true);
|
|
39
45
|
});
|
|
40
46
|
|
|
47
|
+
/** ReadCodexMcpEntry parses transport stdio block. */
|
|
41
48
|
test("readCodexMcpEntry parses transport stdio block", () => {
|
|
42
49
|
const path = resolveCodexConfigPath(home);
|
|
43
50
|
mkdirSync(join(home, ".codex"), { recursive: true });
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/mcp-openclaw module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -19,7 +23,9 @@ afterEach(() => {
|
|
|
19
23
|
rmSync(home, { recursive: true, force: true });
|
|
20
24
|
});
|
|
21
25
|
|
|
26
|
+
/** Tests for openclaw mcp config. */
|
|
22
27
|
describe("openclaw mcp config", () => {
|
|
28
|
+
/** OpenclawMcpHasServer detects configured server. */
|
|
23
29
|
test("openclawMcpHasServer detects configured server", () => {
|
|
24
30
|
const path = resolveOpenclawConfigPath(home);
|
|
25
31
|
mkdirSync(join(home, ".openclaw"), { recursive: true });
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/mcp-opencode module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
6
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -41,7 +45,9 @@ afterEach(() => {
|
|
|
41
45
|
rmSync(home, { recursive: true, force: true });
|
|
42
46
|
});
|
|
43
47
|
|
|
48
|
+
/** Tests for opencode mcp config. */
|
|
44
49
|
describe("opencode mcp config", () => {
|
|
50
|
+
/** ResolveOpenCodeConfigPathForInstall prefers existing file. */
|
|
45
51
|
test("resolveOpenCodeConfigPathForInstall prefers existing file", () => {
|
|
46
52
|
const dir = opencodeConfigDir(home);
|
|
47
53
|
mkdirSync(dir, { recursive: true });
|
|
@@ -50,6 +56,7 @@ describe("opencode mcp config", () => {
|
|
|
50
56
|
expect(resolveOpenCodeConfigPathForInstall(home)).toBe(existing);
|
|
51
57
|
});
|
|
52
58
|
|
|
59
|
+
/** ResolveOpenCodeConfigPathForInstall defaults to config.json. */
|
|
53
60
|
test("resolveOpenCodeConfigPathForInstall defaults to config.json", () => {
|
|
54
61
|
mkdirSync(opencodeConfigDir(home), { recursive: true });
|
|
55
62
|
expect(resolveOpenCodeConfigPathForInstall(home)).toBe(
|
|
@@ -57,6 +64,7 @@ describe("opencode mcp config", () => {
|
|
|
57
64
|
);
|
|
58
65
|
});
|
|
59
66
|
|
|
67
|
+
/** MergeOpenCodeMcpConfig writes mcp block. */
|
|
60
68
|
test("mergeOpenCodeMcpConfig writes mcp block", () => {
|
|
61
69
|
const path = resolveOpenCodeConfigPathForInstall(home);
|
|
62
70
|
mkdirSync(opencodeConfigDir(home), { recursive: true });
|
|
@@ -70,6 +78,7 @@ describe("opencode mcp config", () => {
|
|
|
70
78
|
expect(data.mcp.testapp).toEqual(entry);
|
|
71
79
|
});
|
|
72
80
|
|
|
81
|
+
/** DetectOpenCodeMcpConfigPath finds entry across filenames. */
|
|
73
82
|
test("detectOpenCodeMcpConfigPath finds entry across filenames", () => {
|
|
74
83
|
const dir = opencodeConfigDir(home);
|
|
75
84
|
mkdirSync(dir, { recursive: true });
|
|
@@ -86,6 +95,7 @@ describe("opencode mcp config", () => {
|
|
|
86
95
|
expect(detectOpenCodeMcpConfigPath(home, "testapp")).toBe(path);
|
|
87
96
|
});
|
|
88
97
|
|
|
98
|
+
/** RemoveOpenCodeMcpConfig deletes server entry. */
|
|
89
99
|
test("removeOpenCodeMcpConfig deletes server entry", () => {
|
|
90
100
|
const path = resolveOpenCodeConfigPathForInstall(home);
|
|
91
101
|
mkdirSync(opencodeConfigDir(home), { recursive: true });
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/status module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -22,7 +26,9 @@ afterEach(() => {
|
|
|
22
26
|
rmSync(home, { recursive: true, force: true });
|
|
23
27
|
});
|
|
24
28
|
|
|
29
|
+
/** Tests for resolveInstallTargetPreview. */
|
|
25
30
|
describe("resolveInstallTargetPreview", () => {
|
|
31
|
+
/** Mcp app previews MCP keys for all and mcp scopes. */
|
|
26
32
|
test("mcp app previews MCP keys for all and mcp scopes", () => {
|
|
27
33
|
const program: CliProgram = {
|
|
28
34
|
key: "mcpapp",
|
|
@@ -39,6 +45,7 @@ describe("resolveInstallTargetPreview", () => {
|
|
|
39
45
|
expect(preview.all.every((k) => k.endsWith("Mcp") || k === "configure")).toBe(true);
|
|
40
46
|
});
|
|
41
47
|
|
|
48
|
+
/** Tests that shell app previews skill keys. */
|
|
42
49
|
test("shell app previews skill keys", () => {
|
|
43
50
|
const program: CliProgram = {
|
|
44
51
|
key: "cliapp",
|
|
@@ -55,7 +62,9 @@ describe("resolveInstallTargetPreview", () => {
|
|
|
55
62
|
});
|
|
56
63
|
});
|
|
57
64
|
|
|
65
|
+
/** Tests for printInstallStatus json. */
|
|
58
66
|
describe("printInstallStatus json", () => {
|
|
67
|
+
/** Includes agentIntegration and effective scopes. */
|
|
59
68
|
test("includes agentIntegration and effective scopes", () => {
|
|
60
69
|
const program: CliProgram = {
|
|
61
70
|
key: "app",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for install/targets module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import type { CliProgram } from "../types.ts";
|
|
3
7
|
import { normalizeInstallRawOpts } from "./normalize.ts";
|
|
@@ -5,11 +9,14 @@ import { normalizeUninstallRawOpts } from "./normalize-uninstall.ts";
|
|
|
5
9
|
import { resolveAgentIntegration, resolveEffectiveInstallTargets } from "./target-effective.ts";
|
|
6
10
|
import { isArtifactInScope } from "./target-scope.ts";
|
|
7
11
|
|
|
12
|
+
/** Tests for normalizeInstallRawOpts. */
|
|
8
13
|
describe("normalizeInstallRawOpts", () => {
|
|
14
|
+
/** Bare install sets all. */
|
|
9
15
|
test("bare install sets all", () => {
|
|
10
16
|
expect(normalizeInstallRawOpts({})).toEqual({ all: "1" });
|
|
11
17
|
});
|
|
12
18
|
|
|
19
|
+
/** Bare uninstall sets all. */
|
|
13
20
|
test("bare uninstall sets all", () => {
|
|
14
21
|
expect(normalizeUninstallRawOpts({})).toEqual({
|
|
15
22
|
uninstall: "1",
|
|
@@ -17,28 +24,35 @@ describe("normalizeInstallRawOpts", () => {
|
|
|
17
24
|
});
|
|
18
25
|
});
|
|
19
26
|
|
|
27
|
+
/** Configure-only install unchanged. */
|
|
20
28
|
test("configure-only install unchanged", () => {
|
|
21
29
|
expect(normalizeInstallRawOpts({ configure: "1" })).toEqual({ configure: "1" });
|
|
22
30
|
});
|
|
23
31
|
});
|
|
24
32
|
|
|
33
|
+
/** Tests for resolveAgentIntegration. */
|
|
25
34
|
describe("resolveAgentIntegration", () => {
|
|
35
|
+
/** Defaults to skill without MCP. */
|
|
26
36
|
test("defaults to skill without MCP", () => {
|
|
27
37
|
expect(resolveAgentIntegration(undefined, false)).toBe("skill");
|
|
28
38
|
});
|
|
29
39
|
|
|
40
|
+
/** Defaults to mcp when MCP enabled. */
|
|
30
41
|
test("defaults to mcp when MCP enabled", () => {
|
|
31
42
|
expect(resolveAgentIntegration(undefined, true)).toBe("mcp");
|
|
32
43
|
});
|
|
33
44
|
});
|
|
34
45
|
|
|
46
|
+
/** Tests for resolveEffectiveInstallTargets. */
|
|
35
47
|
describe("resolveEffectiveInstallTargets", () => {
|
|
48
|
+
/** Defaults app and configure not in --all. */
|
|
36
49
|
test("defaults app and configure not in --all", () => {
|
|
37
50
|
const t = resolveEffectiveInstallTargets(undefined);
|
|
38
51
|
expect(t.app.includedInAll).toBe(false);
|
|
39
52
|
expect(t.configure.includedInAll).toBe(false);
|
|
40
53
|
});
|
|
41
54
|
|
|
55
|
+
/** Skill mode includes skills in --all not MCP pairs. */
|
|
42
56
|
test("skill mode includes skills in --all not MCP pairs", () => {
|
|
43
57
|
const program: Pick<CliProgram, "mcpServer"> = {};
|
|
44
58
|
const t = resolveEffectiveInstallTargets(undefined, program);
|
|
@@ -46,6 +60,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
46
60
|
expect(t.cursorMcp.includedInAll).toBe(false);
|
|
47
61
|
});
|
|
48
62
|
|
|
63
|
+
/** Mcp mode includes MCP in --all not paired skills. */
|
|
49
64
|
test("mcp mode includes MCP in --all not paired skills", () => {
|
|
50
65
|
const program: Pick<CliProgram, "mcpServer"> = { mcpServer: { enabled: true } };
|
|
51
66
|
const t = resolveEffectiveInstallTargets(undefined, program);
|
|
@@ -54,6 +69,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
54
69
|
expect(t.claudeDesktopMcp.includedInAll).toBe(true);
|
|
55
70
|
});
|
|
56
71
|
|
|
72
|
+
/** Tests that both mode includes MCP and skills. */
|
|
57
73
|
test("both mode includes MCP and skills", () => {
|
|
58
74
|
const program: Pick<CliProgram, "mcpServer"> = { mcpServer: { enabled: true } };
|
|
59
75
|
const t = resolveEffectiveInstallTargets({ agentIntegration: "both" }, program);
|
|
@@ -61,6 +77,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
61
77
|
expect(t.cursorSkill.includedInAll).toBe(true);
|
|
62
78
|
});
|
|
63
79
|
|
|
80
|
+
/** Tests that per-key override disables one skill. */
|
|
64
81
|
test("per-key override disables one skill", () => {
|
|
65
82
|
const t = resolveEffectiveInstallTargets({
|
|
66
83
|
targets: { cursorSkill: false },
|
|
@@ -69,6 +86,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
69
86
|
expect(t.cursorSkill.enabled).toBe(false);
|
|
70
87
|
});
|
|
71
88
|
|
|
89
|
+
/** Scoped --mcp uses effective targets not all MCP hosts. */
|
|
72
90
|
test("scoped --mcp uses effective targets not all MCP hosts", () => {
|
|
73
91
|
const program: CliProgram = {
|
|
74
92
|
key: "app",
|
|
@@ -95,6 +113,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
95
113
|
).toBe(true);
|
|
96
114
|
});
|
|
97
115
|
|
|
116
|
+
/** Scoped --mcp --skill --configure includes skill and mcp not only configure. */
|
|
98
117
|
test("scoped --mcp --skill --configure includes skill and mcp not only configure", () => {
|
|
99
118
|
const program: CliProgram = {
|
|
100
119
|
key: "app",
|
package/src/invoke.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { CliLeaf } from "./types.ts";
|
|
|
12
12
|
import { isCliRouter } from "./types.ts";
|
|
13
13
|
import { cliValidateProgram } from "./validate.ts";
|
|
14
14
|
|
|
15
|
+
/** Tests that ctx.invocation is cli via Cli.run. */
|
|
15
16
|
test("ctx.invocation is cli via Cli.run", async () => {
|
|
16
17
|
const indexPath = join(import.meta.dir, "index.ts");
|
|
17
18
|
const { stdout } = await $`bun -e ${`
|
|
@@ -28,6 +29,7 @@ await new Cli(program).run([]);
|
|
|
28
29
|
expect(stdout.toString().trim()).toBe("cli");
|
|
29
30
|
});
|
|
30
31
|
|
|
32
|
+
/** Tests that ctx.invocation is mcp via Cli.invoke. */
|
|
31
33
|
test("ctx.invocation is mcp via Cli.invoke", async () => {
|
|
32
34
|
let seen = "";
|
|
33
35
|
const root = testProgram({
|
|
@@ -43,6 +45,7 @@ test("ctx.invocation is mcp via Cli.invoke", async () => {
|
|
|
43
45
|
expect(seen).toBe("mcp");
|
|
44
46
|
});
|
|
45
47
|
|
|
48
|
+
/** Cli.invoke rejects invalid Enum value. */
|
|
46
49
|
test("Cli.invoke rejects invalid Enum value", async () => {
|
|
47
50
|
const root = testProgram({
|
|
48
51
|
key: "app",
|
|
@@ -64,6 +67,7 @@ test("Cli.invoke rejects invalid Enum value", async () => {
|
|
|
64
67
|
expect(result.errorMsg).toContain("not one of");
|
|
65
68
|
});
|
|
66
69
|
|
|
70
|
+
/** Cli.invoke accepts valid Enum value. */
|
|
67
71
|
test("Cli.invoke accepts valid Enum value", async () => {
|
|
68
72
|
const root = testProgram({
|
|
69
73
|
key: "app",
|
|
@@ -87,6 +91,7 @@ test("Cli.invoke accepts valid Enum value", async () => {
|
|
|
87
91
|
expect(result.stdout.trim()).toBe("dev");
|
|
88
92
|
});
|
|
89
93
|
|
|
94
|
+
/** Varargs trailing option after positionals via Cli.invoke. */
|
|
90
95
|
test("varargs trailing option after positionals via Cli.invoke", async () => {
|
|
91
96
|
const root = varargsReadFixture();
|
|
92
97
|
cliValidateProgram(root);
|
|
@@ -96,6 +101,7 @@ test("varargs trailing option after positionals via Cli.invoke", async () => {
|
|
|
96
101
|
expect(pr.opts.json).toBe("1");
|
|
97
102
|
});
|
|
98
103
|
|
|
104
|
+
/** Varargs option before positionals. */
|
|
99
105
|
test("varargs option before positionals", () => {
|
|
100
106
|
const root = varargsReadFixture();
|
|
101
107
|
cliValidateProgram(root);
|
|
@@ -105,6 +111,7 @@ test("varargs option before positionals", () => {
|
|
|
105
111
|
expect(pr.opts.json).toBe("1");
|
|
106
112
|
});
|
|
107
113
|
|
|
114
|
+
/** Varargs multiple files then trailing option. */
|
|
108
115
|
test("varargs multiple files then trailing option", () => {
|
|
109
116
|
const root = varargsReadFixture();
|
|
110
117
|
cliValidateProgram(root);
|
|
@@ -114,6 +121,7 @@ test("varargs multiple files then trailing option", () => {
|
|
|
114
121
|
expect(pr.opts.json).toBe("1");
|
|
115
122
|
});
|
|
116
123
|
|
|
124
|
+
/** Varargs double dash forces positional. */
|
|
117
125
|
test("varargs double dash forces positional", () => {
|
|
118
126
|
const root = varargsReadFixture();
|
|
119
127
|
cliValidateProgram(root);
|
|
@@ -123,6 +131,7 @@ test("varargs double dash forces positional", () => {
|
|
|
123
131
|
expect(pr.opts.json).toBeUndefined();
|
|
124
132
|
});
|
|
125
133
|
|
|
134
|
+
/** Varargs unknown flag errors. */
|
|
126
135
|
test("varargs unknown flag errors", async () => {
|
|
127
136
|
const root = varargsReadFixture();
|
|
128
137
|
cliValidateProgram(root);
|
|
@@ -131,6 +140,7 @@ test("varargs unknown flag errors", async () => {
|
|
|
131
140
|
expect(result.stderr).toContain("--unknown");
|
|
132
141
|
});
|
|
133
142
|
|
|
143
|
+
/** Varargs scoped help in tail. */
|
|
134
144
|
test("varargs scoped help in tail", () => {
|
|
135
145
|
const root = varargsReadFixture();
|
|
136
146
|
cliValidateProgram(root);
|
|
@@ -140,6 +150,7 @@ test("varargs scoped help in tail", () => {
|
|
|
140
150
|
expect(pr.helpExplicit).toBe(true);
|
|
141
151
|
});
|
|
142
152
|
|
|
153
|
+
/** Tests that ctx.positional returns single slot value. */
|
|
143
154
|
test("ctx.positional returns single slot value", async () => {
|
|
144
155
|
const root = testProgram({
|
|
145
156
|
key: "app",
|
|
@@ -161,6 +172,7 @@ test("ctx.positional returns single slot value", async () => {
|
|
|
161
172
|
expect(captured).toBe("./file");
|
|
162
173
|
});
|
|
163
174
|
|
|
175
|
+
/** Tests that ctx.positional returns varargs array. */
|
|
164
176
|
test("ctx.positional returns varargs array", async () => {
|
|
165
177
|
const root = varargsReadFixture();
|
|
166
178
|
let captured: string | string[] | undefined;
|
|
@@ -174,6 +186,7 @@ test("ctx.positional returns varargs array", async () => {
|
|
|
174
186
|
expect(captured).toEqual(["a.txt", "b.txt"]);
|
|
175
187
|
});
|
|
176
188
|
|
|
189
|
+
/** Tests that ctx.positional returns undefined for absent optional slot. */
|
|
177
190
|
test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
178
191
|
const root = testProgram({
|
|
179
192
|
key: "app",
|
|
@@ -197,6 +210,7 @@ test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
|
197
210
|
expect(captured).toBeUndefined();
|
|
198
211
|
});
|
|
199
212
|
|
|
213
|
+
/** Tests that ctx.positional varargs matches ctx.args. */
|
|
200
214
|
test("ctx.positional varargs matches ctx.args", async () => {
|
|
201
215
|
const root = varargsReadFixture();
|
|
202
216
|
let positional: string | string[] | undefined;
|
package/src/mcp/claude.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for mcp/claude module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { execSync } from "node:child_process";
|
|
3
7
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -29,11 +33,14 @@ const configFixture: CliProgram = {
|
|
|
29
33
|
commands: [{ key: "run", description: "Run.", handler: () => {} }],
|
|
30
34
|
};
|
|
31
35
|
|
|
36
|
+
/** Tests for claude plugin. */
|
|
32
37
|
describe("claude plugin", () => {
|
|
38
|
+
/** PluginName is kebab-case. */
|
|
33
39
|
test("pluginName is kebab-case", () => {
|
|
34
40
|
expect(pluginName({ ...configFixture, key: "MyApp" })).toBe("my-app");
|
|
35
41
|
});
|
|
36
42
|
|
|
43
|
+
/** Tests that generatePluginManifest includes userConfig from program.appConfig. */
|
|
37
44
|
test("generatePluginManifest includes userConfig from program.appConfig", () => {
|
|
38
45
|
const manifest = generatePluginManifest(configFixture, "myapp");
|
|
39
46
|
expect(manifest.name).toBe("myapp");
|
|
@@ -43,6 +50,7 @@ describe("claude plugin", () => {
|
|
|
43
50
|
expect(userConfig.api_token?.sensitive).toBe(true);
|
|
44
51
|
});
|
|
45
52
|
|
|
53
|
+
/** Tests that generatePluginMcpJson uses CLAUDE_PLUGIN_ROOT and env mapping. */
|
|
46
54
|
test("generatePluginMcpJson uses CLAUDE_PLUGIN_ROOT and env mapping", () => {
|
|
47
55
|
const json = generatePluginMcpJson(configFixture, "myapp");
|
|
48
56
|
const entry = json.myapp as { command: string; args: string[]; env: Record<string, string> };
|
|
@@ -51,12 +59,14 @@ describe("claude plugin", () => {
|
|
|
51
59
|
expect(entry.env.API_TOKEN).toBe("${user_config.api_token}");
|
|
52
60
|
});
|
|
53
61
|
|
|
62
|
+
/** DefaultClaudePluginPaths. */
|
|
54
63
|
test("defaultClaudePluginPaths", () => {
|
|
55
64
|
const cwd = "/tmp/work";
|
|
56
65
|
const paths = defaultClaudePluginPaths(configFixture, cwd);
|
|
57
66
|
expect(paths.pluginZipPath).toBe(join(cwd, "dist", "claude-plugin", "myapp.zip"));
|
|
58
67
|
});
|
|
59
68
|
|
|
69
|
+
/** Tests that packClaudePlugin writes zip with MCP pointer skill only. */
|
|
60
70
|
test("packClaudePlugin writes zip with MCP pointer skill only", () => {
|
|
61
71
|
const work = mkdtempSync(join(tmpdir(), "claude-plugin-test-"));
|
|
62
72
|
try {
|