argsbarg 5.0.2 → 5.0.3
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 +9 -2
- package/index.d.ts +10 -5
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +0 -11
- package/src/cli-tool/cli-smoke.test.ts +0 -3
- package/src/cli-tool/create.test.ts +0 -3
- package/src/cli-tool/full-example-capabilities.test.ts +0 -2
- package/src/config/context.test.ts +0 -1
- package/src/config/file.test.ts +0 -5
- package/src/config/resolve.test.ts +0 -7
- package/src/config/validate.test.ts +0 -6
- package/src/configure/configure.test.ts +0 -11
- package/src/docs/api-guide.test.ts +0 -4
- package/src/docs/docs.test.ts +0 -20
- package/src/docs/mcp-resources.test.ts +0 -6
- package/src/formats.test.ts +0 -5
- package/src/headless.test.ts +0 -4
- package/src/headless.ts +5 -4
- package/src/hidden-mcpb.test.ts +0 -12
- package/src/install/binary-placement.test.ts +0 -5
- package/src/install/gh-release-update.test.ts +0 -5
- package/src/install/install-validate.test.ts +0 -5
- package/src/install/mcp-opencode.test.ts +0 -4
- package/src/install/targets.test.ts +0 -10
- package/src/invoke.test.ts +0 -7
- package/src/mcp/claude.test.ts +0 -4
- package/src/mcp/env.test.ts +0 -5
- package/src/mcp/zip.test.ts +0 -1
- package/src/mcp.integration.test.ts +0 -22
- package/src/parse.test.ts +0 -28
package/src/formats.test.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "./formats.ts";
|
|
13
13
|
import { CliValueFormat } from "./types.ts";
|
|
14
14
|
|
|
15
|
-
/** ParseDurationMs parses minutes and hours. */
|
|
16
15
|
test("parseDurationMs parses minutes and hours", () => {
|
|
17
16
|
expect(parseDurationMs("30s")).toBe(30_000);
|
|
18
17
|
expect(parseDurationMs("5m")).toBe(5 * 60 * 1000);
|
|
@@ -20,25 +19,21 @@ test("parseDurationMs parses minutes and hours", () => {
|
|
|
20
19
|
expect(parseDurationMs("1d")).toBe(24 * 60 * 60 * 1000);
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
/** ParseCommaList splits and trims. */
|
|
24
22
|
test("parseCommaList splits and trims", () => {
|
|
25
23
|
expect(parseCommaList("a,b")).toEqual(["a", "b"]);
|
|
26
24
|
expect(parseCommaList(" a , b , ")).toEqual(["a", "b"]);
|
|
27
25
|
});
|
|
28
26
|
|
|
29
|
-
/** ParseDate validates calendar dates. */
|
|
30
27
|
test("parseDate validates calendar dates", () => {
|
|
31
28
|
expect(parseDate("2026-06-22")).toBe("2026-06-22");
|
|
32
29
|
expect(() => parseDate("2026-02-30")).toThrow();
|
|
33
30
|
});
|
|
34
31
|
|
|
35
|
-
/** ParseDateTime normalizes to UTC ISO. */
|
|
36
32
|
test("parseDateTime normalizes to UTC ISO", () => {
|
|
37
33
|
expect(parseDateTime("2026-06-22T15:00:00Z")).toBe("2026-06-22T15:00:00.000Z");
|
|
38
34
|
expect(() => parseDateTime("2026-06-22")).toThrow();
|
|
39
35
|
});
|
|
40
36
|
|
|
41
|
-
/** ValidateFormatValue rejects invalid duration. */
|
|
42
37
|
test("validateFormatValue rejects invalid duration", () => {
|
|
43
38
|
expect(() => validateFormatValue("nope", CliValueFormat.Duration)).toThrow();
|
|
44
39
|
});
|
package/src/headless.test.ts
CHANGED
|
@@ -12,14 +12,12 @@ import {
|
|
|
12
12
|
wantsExplicitJson,
|
|
13
13
|
} from "./headless.ts";
|
|
14
14
|
|
|
15
|
-
/** WantsExplicitJson includes MCP invocation. */
|
|
16
15
|
test("wantsExplicitJson includes MCP invocation", () => {
|
|
17
16
|
expect(wantsExplicitJson({ invocation: "cli" }, false)).toBe(false);
|
|
18
17
|
expect(wantsExplicitJson({ invocation: "mcp" }, false)).toBe(true);
|
|
19
18
|
expect(wantsExplicitJson({ invocation: "cli" }, true)).toBe(true);
|
|
20
19
|
});
|
|
21
20
|
|
|
22
|
-
/** Tests that shouldRunHeadless is true for MCP and json. */
|
|
23
21
|
test("shouldRunHeadless is true for MCP and json", () => {
|
|
24
22
|
expect(shouldRunHeadless({ invocation: "mcp" }, false)).toBe(true);
|
|
25
23
|
expect(shouldRunHeadless({ invocation: "cli" }, true)).toBe(true);
|
|
@@ -27,7 +25,6 @@ test("shouldRunHeadless is true for MCP and json", () => {
|
|
|
27
25
|
expect(shouldRunHeadless({ invocation: "cli" }, false, false, false)).toBe(true);
|
|
28
26
|
});
|
|
29
27
|
|
|
30
|
-
/** Tests that shouldRunHeadlessWithPositionals requires positionals in non-tty. */
|
|
31
28
|
test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
|
|
32
29
|
expect(shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, [], false, false)).toBe(
|
|
33
30
|
false,
|
|
@@ -54,7 +51,6 @@ test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
|
|
|
54
51
|
).toBe(true);
|
|
55
52
|
});
|
|
56
53
|
|
|
57
|
-
/** Tests that formatDryRunMessage prefixes dry-run output. */
|
|
58
54
|
test("formatDryRunMessage prefixes dry-run output", () => {
|
|
59
55
|
expect(formatDryRunMessage("hello", false)).toBe("hello");
|
|
60
56
|
expect(formatDryRunMessage("hello", true)).toBe("[DRY RUN] hello");
|
package/src/headless.ts
CHANGED
|
@@ -57,14 +57,15 @@ export function shouldRunHeadlessWithYes(
|
|
|
57
57
|
return opts.yes && opts.hasRequiredArgs;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* Exits when non-interactive mode is used without `--yes`.
|
|
62
|
-
* @param hint - Command-specific guidance appended to the error
|
|
63
|
-
*/
|
|
60
|
+
/** Exits when non-interactive mode is used without `--yes`. */
|
|
64
61
|
export function requireYesInNonTty(
|
|
62
|
+
/** True when `--yes` was passed on the command line. */
|
|
65
63
|
yes: boolean,
|
|
64
|
+
/** Command-specific guidance appended to the error message. */
|
|
66
65
|
hint: string,
|
|
66
|
+
/** When true, skip the check (dry-run preview). */
|
|
67
67
|
dryRun = false,
|
|
68
|
+
/** Injectable TTY probe for tests. */
|
|
68
69
|
interactive: boolean = isInteractiveTty,
|
|
69
70
|
): void {
|
|
70
71
|
if (dryRun) return;
|
package/src/hidden-mcpb.test.ts
CHANGED
|
@@ -59,13 +59,11 @@ const hiddenFixture: CliProgram = {
|
|
|
59
59
|
|
|
60
60
|
/** Tests for hidden commands and options. */
|
|
61
61
|
describe("hidden commands and options", () => {
|
|
62
|
-
/** Parse root includes hidden commands. */
|
|
63
62
|
test("parse root includes hidden commands", () => {
|
|
64
63
|
const parse = cliParseRoot(hiddenFixture);
|
|
65
64
|
expect(parse.commands?.map((c) => c.key)).toContain("secret");
|
|
66
65
|
});
|
|
67
66
|
|
|
68
|
-
/** Presentation root omits hidden commands. */
|
|
69
67
|
test("presentation root omits hidden commands", () => {
|
|
70
68
|
const presentation = cliPresentationRoot(hiddenFixture);
|
|
71
69
|
const keys = presentation.commands?.map((c) => c.key) ?? [];
|
|
@@ -73,27 +71,23 @@ describe("hidden commands and options", () => {
|
|
|
73
71
|
expect(keys).not.toContain("secret");
|
|
74
72
|
});
|
|
75
73
|
|
|
76
|
-
/** Root help omits hidden commands. */
|
|
77
74
|
test("root help omits hidden commands", () => {
|
|
78
75
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), [], false);
|
|
79
76
|
expect(help).toContain("public");
|
|
80
77
|
expect(help).not.toContain("secret");
|
|
81
78
|
});
|
|
82
79
|
|
|
83
|
-
/** Hidden command -h still works. */
|
|
84
80
|
test("hidden command -h still works", () => {
|
|
85
81
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["secret"], false);
|
|
86
82
|
expect(help).toContain("Hidden command.");
|
|
87
83
|
});
|
|
88
84
|
|
|
89
|
-
/** Tests that help omits hidden options. */
|
|
90
85
|
test("help omits hidden options", () => {
|
|
91
86
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["flags"], false);
|
|
92
87
|
expect(help).toContain("--visible");
|
|
93
88
|
expect(help).not.toContain("secret-flag");
|
|
94
89
|
});
|
|
95
90
|
|
|
96
|
-
/** Tests that schema export omits hidden nodes and options. */
|
|
97
91
|
test("schema export omits hidden nodes and options", () => {
|
|
98
92
|
const schema = cliSchemaExport(hiddenFixture);
|
|
99
93
|
const keys = schema.commands?.map((c) => c.key) ?? [];
|
|
@@ -103,7 +97,6 @@ describe("hidden commands and options", () => {
|
|
|
103
97
|
expect(flags?.options?.map((o) => o.name)).toEqual(["visible"]);
|
|
104
98
|
});
|
|
105
99
|
|
|
106
|
-
/** MCP tools omit hidden commands. */
|
|
107
100
|
test("MCP tools omit hidden commands", () => {
|
|
108
101
|
const tools = collectMcpTools(hiddenFixture);
|
|
109
102
|
expect(tools.map((t) => t.name)).toEqual(["public", "flags"]);
|
|
@@ -112,7 +105,6 @@ describe("hidden commands and options", () => {
|
|
|
112
105
|
|
|
113
106
|
/** Tests for mcp router. */
|
|
114
107
|
describe("mcp router", () => {
|
|
115
|
-
/** Presentation exposes mcp bundle but not hidden serve. */
|
|
116
108
|
test("presentation exposes mcp bundle but not hidden serve", () => {
|
|
117
109
|
const builtins = exportPresentationBuiltins(hiddenFixture);
|
|
118
110
|
const mcp = builtins.find((b) => b.key === "mcp");
|
|
@@ -121,7 +113,6 @@ describe("mcp router", () => {
|
|
|
121
113
|
expect(mcp?.fallbackCommand).toBe("serve");
|
|
122
114
|
});
|
|
123
115
|
|
|
124
|
-
/** Mcp help lists bundle. */
|
|
125
116
|
test("mcp help lists bundle", () => {
|
|
126
117
|
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["mcp"], false);
|
|
127
118
|
expect(help).toContain("bundle");
|
|
@@ -131,7 +122,6 @@ describe("mcp router", () => {
|
|
|
131
122
|
|
|
132
123
|
/** Tests for mcp bundle. */
|
|
133
124
|
describe("mcp bundle", () => {
|
|
134
|
-
/** Tests that generateMcpManifest uses mcpServerId and binary entry. */
|
|
135
125
|
test("generateMcpManifest uses mcpServerId and binary entry", () => {
|
|
136
126
|
const manifest = generateMcpManifest(hiddenFixture, "myapp");
|
|
137
127
|
expect(manifest.name).toBe("myapp");
|
|
@@ -145,7 +135,6 @@ describe("mcp bundle", () => {
|
|
|
145
135
|
expect((manifest.compatibility as { platforms: string[] }).platforms).toEqual(["darwin"]);
|
|
146
136
|
});
|
|
147
137
|
|
|
148
|
-
/** DefaultMcpBundlePaths. */
|
|
149
138
|
test("defaultMcpBundlePaths", () => {
|
|
150
139
|
const cwd = "/tmp/work";
|
|
151
140
|
const paths = defaultMcpBundlePaths(hiddenFixture, cwd);
|
|
@@ -241,7 +230,6 @@ describe("mcp bundle", () => {
|
|
|
241
230
|
}
|
|
242
231
|
});
|
|
243
232
|
|
|
244
|
-
/** RunMcpBundle errors when no bundle flags enabled. */
|
|
245
233
|
test("runMcpBundle errors when no bundle flags enabled", () => {
|
|
246
234
|
const fixture: CliProgram = {
|
|
247
235
|
...hiddenFixture,
|
|
@@ -36,13 +36,11 @@ afterEach(() => {
|
|
|
36
36
|
|
|
37
37
|
/** Tests for isExternallyManagedBinary. */
|
|
38
38
|
describe("isExternallyManagedBinary", () => {
|
|
39
|
-
/** Tests that false when command is not on PATH. */
|
|
40
39
|
test("false when command is not on PATH", () => {
|
|
41
40
|
process.env.PATH = tmp;
|
|
42
41
|
expect(isExternallyManagedBinary("placementapp-not-on-path")).toBe(false);
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
/** Tests that true when PATH resolves to execPath. */
|
|
46
44
|
test("true when PATH resolves to execPath", () => {
|
|
47
45
|
const bin = join(tmp, "placementapp");
|
|
48
46
|
writeFileSync(bin, "#!/bin/sh\n", "utf8");
|
|
@@ -51,7 +49,6 @@ describe("isExternallyManagedBinary", () => {
|
|
|
51
49
|
expect(isExternallyManagedBinary("placementapp", bin)).toBe(true);
|
|
52
50
|
});
|
|
53
51
|
|
|
54
|
-
/** Tests that true when PATH entry is a symlink to execPath. */
|
|
55
52
|
test("true when PATH entry is a symlink to execPath", () => {
|
|
56
53
|
const bin = join(tmp, "placementapp");
|
|
57
54
|
const target = join(tmp, "real-binary");
|
|
@@ -61,7 +58,6 @@ describe("isExternallyManagedBinary", () => {
|
|
|
61
58
|
expect(isExternallyManagedBinary("placementapp", target)).toBe(true);
|
|
62
59
|
});
|
|
63
60
|
|
|
64
|
-
/** Tests that false when PATH points at a different binary. */
|
|
65
61
|
test("false when PATH points at a different binary", () => {
|
|
66
62
|
const bin = join(tmp, "placementapp");
|
|
67
63
|
const other = join(tmp, "other");
|
|
@@ -107,7 +103,6 @@ describe("isAppInstalled", () => {
|
|
|
107
103
|
|
|
108
104
|
/** Tests for resolvePathCommand. */
|
|
109
105
|
describe("resolvePathCommand", () => {
|
|
110
|
-
/** Tests that returns undefined when missing. */
|
|
111
106
|
test("returns undefined when missing", () => {
|
|
112
107
|
process.env.PATH = tmp;
|
|
113
108
|
expect(resolvePathCommand("missing-cmd")).toBeUndefined();
|
|
@@ -5,27 +5,22 @@ Tests for install/gh-release-update module behavior.
|
|
|
5
5
|
import { expect, test } from "bun:test";
|
|
6
6
|
import { isAlreadyCurrent, parseReleaseTag } from "./gh-release-update.ts";
|
|
7
7
|
|
|
8
|
-
/** ParseReleaseTag strips leading v. */
|
|
9
8
|
test("parseReleaseTag strips leading v", () => {
|
|
10
9
|
expect(parseReleaseTag("v1.4.3")).toBe("1.4.3");
|
|
11
10
|
});
|
|
12
11
|
|
|
13
|
-
/** ParseReleaseTag leaves bare semver unchanged. */
|
|
14
12
|
test("parseReleaseTag leaves bare semver unchanged", () => {
|
|
15
13
|
expect(parseReleaseTag("1.4.3")).toBe("1.4.3");
|
|
16
14
|
});
|
|
17
15
|
|
|
18
|
-
/** ParseReleaseTag throws on empty tag. */
|
|
19
16
|
test("parseReleaseTag throws on empty tag", () => {
|
|
20
17
|
expect(() => parseReleaseTag("")).toThrow("Release tag is empty");
|
|
21
18
|
});
|
|
22
19
|
|
|
23
|
-
/** Tests that isAlreadyCurrent returns true when versions match. */
|
|
24
20
|
test("isAlreadyCurrent returns true when versions match", () => {
|
|
25
21
|
expect(isAlreadyCurrent("1.4.3", "1.4.3")).toBe(true);
|
|
26
22
|
});
|
|
27
23
|
|
|
28
|
-
/** Tests that isAlreadyCurrent returns false when versions differ. */
|
|
29
24
|
test("isAlreadyCurrent returns false when versions differ", () => {
|
|
30
25
|
expect(isAlreadyCurrent("1.4.2", "1.4.3")).toBe(false);
|
|
31
26
|
});
|
|
@@ -16,12 +16,10 @@ const base: CliProgram = {
|
|
|
16
16
|
|
|
17
17
|
/** Tests for validateConfigureConfig. */
|
|
18
18
|
describe("validateConfigureConfig", () => {
|
|
19
|
-
/** Tests that accepts empty install config. */
|
|
20
19
|
test("accepts empty install config", () => {
|
|
21
20
|
expect(() => cliValidateProgram(base)).not.toThrow();
|
|
22
21
|
});
|
|
23
22
|
|
|
24
|
-
/** Rejects removed allSkills shorthand. */
|
|
25
23
|
test("rejects removed allSkills shorthand", () => {
|
|
26
24
|
const program = {
|
|
27
25
|
...base,
|
|
@@ -31,7 +29,6 @@ describe("validateConfigureConfig", () => {
|
|
|
31
29
|
expect(() => cliValidateProgram(program)).toThrow(/allSkills/);
|
|
32
30
|
});
|
|
33
31
|
|
|
34
|
-
/** Rejects both sides of pair without both integration. */
|
|
35
32
|
test("rejects both sides of pair without both integration", () => {
|
|
36
33
|
const program: CliProgram = {
|
|
37
34
|
...base,
|
|
@@ -44,7 +41,6 @@ describe("validateConfigureConfig", () => {
|
|
|
44
41
|
expect(() => cliValidateProgram(program)).toThrow(/both/);
|
|
45
42
|
});
|
|
46
43
|
|
|
47
|
-
/** Rejects explicit MCP target in skill integration mode. */
|
|
48
44
|
test("rejects explicit MCP target in skill integration mode", () => {
|
|
49
45
|
const program: CliProgram = {
|
|
50
46
|
...base,
|
|
@@ -56,7 +52,6 @@ describe("validateConfigureConfig", () => {
|
|
|
56
52
|
expect(() => cliValidateProgram(program)).toThrow(/cursorMcp/);
|
|
57
53
|
});
|
|
58
54
|
|
|
59
|
-
/** Allows explicit pair targets with both integration. */
|
|
60
55
|
test("allows explicit pair targets with both integration", () => {
|
|
61
56
|
const program: CliProgram = {
|
|
62
57
|
...base,
|
|
@@ -47,7 +47,6 @@ afterEach(() => {
|
|
|
47
47
|
|
|
48
48
|
/** Tests for opencode mcp config. */
|
|
49
49
|
describe("opencode mcp config", () => {
|
|
50
|
-
/** ResolveOpenCodeConfigPathForInstall prefers existing file. */
|
|
51
50
|
test("resolveOpenCodeConfigPathForInstall prefers existing file", () => {
|
|
52
51
|
const dir = opencodeConfigDir(home);
|
|
53
52
|
mkdirSync(dir, { recursive: true });
|
|
@@ -56,7 +55,6 @@ describe("opencode mcp config", () => {
|
|
|
56
55
|
expect(resolveOpenCodeConfigPathForInstall(home)).toBe(existing);
|
|
57
56
|
});
|
|
58
57
|
|
|
59
|
-
/** ResolveOpenCodeConfigPathForInstall defaults to config.json. */
|
|
60
58
|
test("resolveOpenCodeConfigPathForInstall defaults to config.json", () => {
|
|
61
59
|
mkdirSync(opencodeConfigDir(home), { recursive: true });
|
|
62
60
|
expect(resolveOpenCodeConfigPathForInstall(home)).toBe(
|
|
@@ -64,7 +62,6 @@ describe("opencode mcp config", () => {
|
|
|
64
62
|
);
|
|
65
63
|
});
|
|
66
64
|
|
|
67
|
-
/** MergeOpenCodeMcpConfig writes mcp block. */
|
|
68
65
|
test("mergeOpenCodeMcpConfig writes mcp block", () => {
|
|
69
66
|
const path = resolveOpenCodeConfigPathForInstall(home);
|
|
70
67
|
mkdirSync(opencodeConfigDir(home), { recursive: true });
|
|
@@ -95,7 +92,6 @@ describe("opencode mcp config", () => {
|
|
|
95
92
|
expect(detectOpenCodeMcpConfigPath(home, "testapp")).toBe(path);
|
|
96
93
|
});
|
|
97
94
|
|
|
98
|
-
/** RemoveOpenCodeMcpConfig deletes server entry. */
|
|
99
95
|
test("removeOpenCodeMcpConfig deletes server entry", () => {
|
|
100
96
|
const path = resolveOpenCodeConfigPathForInstall(home);
|
|
101
97
|
mkdirSync(opencodeConfigDir(home), { recursive: true });
|
|
@@ -11,12 +11,10 @@ import { isArtifactInScope } from "./target-scope.ts";
|
|
|
11
11
|
|
|
12
12
|
/** Tests for normalizeInstallRawOpts. */
|
|
13
13
|
describe("normalizeInstallRawOpts", () => {
|
|
14
|
-
/** Bare install sets all. */
|
|
15
14
|
test("bare install sets all", () => {
|
|
16
15
|
expect(normalizeInstallRawOpts({})).toEqual({ all: "1" });
|
|
17
16
|
});
|
|
18
17
|
|
|
19
|
-
/** Bare uninstall sets all. */
|
|
20
18
|
test("bare uninstall sets all", () => {
|
|
21
19
|
expect(normalizeUninstallRawOpts({})).toEqual({
|
|
22
20
|
uninstall: "1",
|
|
@@ -24,7 +22,6 @@ describe("normalizeInstallRawOpts", () => {
|
|
|
24
22
|
});
|
|
25
23
|
});
|
|
26
24
|
|
|
27
|
-
/** Configure-only install unchanged. */
|
|
28
25
|
test("configure-only install unchanged", () => {
|
|
29
26
|
expect(normalizeInstallRawOpts({ configure: "1" })).toEqual({ configure: "1" });
|
|
30
27
|
});
|
|
@@ -32,12 +29,10 @@ describe("normalizeInstallRawOpts", () => {
|
|
|
32
29
|
|
|
33
30
|
/** Tests for resolveAgentIntegration. */
|
|
34
31
|
describe("resolveAgentIntegration", () => {
|
|
35
|
-
/** Defaults to skill without MCP. */
|
|
36
32
|
test("defaults to skill without MCP", () => {
|
|
37
33
|
expect(resolveAgentIntegration(undefined, false)).toBe("skill");
|
|
38
34
|
});
|
|
39
35
|
|
|
40
|
-
/** Defaults to mcp when MCP enabled. */
|
|
41
36
|
test("defaults to mcp when MCP enabled", () => {
|
|
42
37
|
expect(resolveAgentIntegration(undefined, true)).toBe("mcp");
|
|
43
38
|
});
|
|
@@ -45,14 +40,12 @@ describe("resolveAgentIntegration", () => {
|
|
|
45
40
|
|
|
46
41
|
/** Tests for resolveEffectiveInstallTargets. */
|
|
47
42
|
describe("resolveEffectiveInstallTargets", () => {
|
|
48
|
-
/** Defaults app and configure not in --all. */
|
|
49
43
|
test("defaults app and configure not in --all", () => {
|
|
50
44
|
const t = resolveEffectiveInstallTargets(undefined);
|
|
51
45
|
expect(t.app.includedInAll).toBe(false);
|
|
52
46
|
expect(t.configure.includedInAll).toBe(false);
|
|
53
47
|
});
|
|
54
48
|
|
|
55
|
-
/** Skill mode includes skills in --all not MCP pairs. */
|
|
56
49
|
test("skill mode includes skills in --all not MCP pairs", () => {
|
|
57
50
|
const program: Pick<CliProgram, "mcpServer"> = {};
|
|
58
51
|
const t = resolveEffectiveInstallTargets(undefined, program);
|
|
@@ -60,7 +53,6 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
60
53
|
expect(t.cursorMcp.includedInAll).toBe(false);
|
|
61
54
|
});
|
|
62
55
|
|
|
63
|
-
/** Mcp mode includes MCP in --all not paired skills. */
|
|
64
56
|
test("mcp mode includes MCP in --all not paired skills", () => {
|
|
65
57
|
const program: Pick<CliProgram, "mcpServer"> = { mcpServer: { enabled: true } };
|
|
66
58
|
const t = resolveEffectiveInstallTargets(undefined, program);
|
|
@@ -69,7 +61,6 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
69
61
|
expect(t.claudeDesktopMcp.includedInAll).toBe(true);
|
|
70
62
|
});
|
|
71
63
|
|
|
72
|
-
/** Tests that both mode includes MCP and skills. */
|
|
73
64
|
test("both mode includes MCP and skills", () => {
|
|
74
65
|
const program: Pick<CliProgram, "mcpServer"> = { mcpServer: { enabled: true } };
|
|
75
66
|
const t = resolveEffectiveInstallTargets({ agentIntegration: "both" }, program);
|
|
@@ -77,7 +68,6 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
77
68
|
expect(t.cursorSkill.includedInAll).toBe(true);
|
|
78
69
|
});
|
|
79
70
|
|
|
80
|
-
/** Tests that per-key override disables one skill. */
|
|
81
71
|
test("per-key override disables one skill", () => {
|
|
82
72
|
const t = resolveEffectiveInstallTargets({
|
|
83
73
|
targets: { cursorSkill: false },
|
package/src/invoke.test.ts
CHANGED
|
@@ -91,7 +91,6 @@ test("Cli.invoke accepts valid Enum value", async () => {
|
|
|
91
91
|
expect(result.stdout.trim()).toBe("dev");
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
/** Varargs trailing option after positionals via Cli.invoke. */
|
|
95
94
|
test("varargs trailing option after positionals via Cli.invoke", async () => {
|
|
96
95
|
const root = varargsReadFixture();
|
|
97
96
|
cliValidateProgram(root);
|
|
@@ -101,7 +100,6 @@ test("varargs trailing option after positionals via Cli.invoke", async () => {
|
|
|
101
100
|
expect(pr.opts.json).toBe("1");
|
|
102
101
|
});
|
|
103
102
|
|
|
104
|
-
/** Varargs option before positionals. */
|
|
105
103
|
test("varargs option before positionals", () => {
|
|
106
104
|
const root = varargsReadFixture();
|
|
107
105
|
cliValidateProgram(root);
|
|
@@ -111,7 +109,6 @@ test("varargs option before positionals", () => {
|
|
|
111
109
|
expect(pr.opts.json).toBe("1");
|
|
112
110
|
});
|
|
113
111
|
|
|
114
|
-
/** Varargs multiple files then trailing option. */
|
|
115
112
|
test("varargs multiple files then trailing option", () => {
|
|
116
113
|
const root = varargsReadFixture();
|
|
117
114
|
cliValidateProgram(root);
|
|
@@ -121,7 +118,6 @@ test("varargs multiple files then trailing option", () => {
|
|
|
121
118
|
expect(pr.opts.json).toBe("1");
|
|
122
119
|
});
|
|
123
120
|
|
|
124
|
-
/** Varargs double dash forces positional. */
|
|
125
121
|
test("varargs double dash forces positional", () => {
|
|
126
122
|
const root = varargsReadFixture();
|
|
127
123
|
cliValidateProgram(root);
|
|
@@ -131,7 +127,6 @@ test("varargs double dash forces positional", () => {
|
|
|
131
127
|
expect(pr.opts.json).toBeUndefined();
|
|
132
128
|
});
|
|
133
129
|
|
|
134
|
-
/** Varargs unknown flag errors. */
|
|
135
130
|
test("varargs unknown flag errors", async () => {
|
|
136
131
|
const root = varargsReadFixture();
|
|
137
132
|
cliValidateProgram(root);
|
|
@@ -140,7 +135,6 @@ test("varargs unknown flag errors", async () => {
|
|
|
140
135
|
expect(result.stderr).toContain("--unknown");
|
|
141
136
|
});
|
|
142
137
|
|
|
143
|
-
/** Varargs scoped help in tail. */
|
|
144
138
|
test("varargs scoped help in tail", () => {
|
|
145
139
|
const root = varargsReadFixture();
|
|
146
140
|
cliValidateProgram(root);
|
|
@@ -172,7 +166,6 @@ test("ctx.positional returns single slot value", async () => {
|
|
|
172
166
|
expect(captured).toBe("./file");
|
|
173
167
|
});
|
|
174
168
|
|
|
175
|
-
/** Tests that ctx.positional returns varargs array. */
|
|
176
169
|
test("ctx.positional returns varargs array", async () => {
|
|
177
170
|
const root = varargsReadFixture();
|
|
178
171
|
let captured: string | string[] | undefined;
|
package/src/mcp/claude.test.ts
CHANGED
|
@@ -35,12 +35,10 @@ const configFixture: CliProgram = {
|
|
|
35
35
|
|
|
36
36
|
/** Tests for claude plugin. */
|
|
37
37
|
describe("claude plugin", () => {
|
|
38
|
-
/** PluginName is kebab-case. */
|
|
39
38
|
test("pluginName is kebab-case", () => {
|
|
40
39
|
expect(pluginName({ ...configFixture, key: "MyApp" })).toBe("my-app");
|
|
41
40
|
});
|
|
42
41
|
|
|
43
|
-
/** Tests that generatePluginManifest includes userConfig from program.appConfig. */
|
|
44
42
|
test("generatePluginManifest includes userConfig from program.appConfig", () => {
|
|
45
43
|
const manifest = generatePluginManifest(configFixture, "myapp");
|
|
46
44
|
expect(manifest.name).toBe("myapp");
|
|
@@ -50,7 +48,6 @@ describe("claude plugin", () => {
|
|
|
50
48
|
expect(userConfig.api_token?.sensitive).toBe(true);
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
/** Tests that generatePluginMcpJson uses CLAUDE_PLUGIN_ROOT and env mapping. */
|
|
54
51
|
test("generatePluginMcpJson uses CLAUDE_PLUGIN_ROOT and env mapping", () => {
|
|
55
52
|
const json = generatePluginMcpJson(configFixture, "myapp");
|
|
56
53
|
const entry = json.myapp as { command: string; args: string[]; env: Record<string, string> };
|
|
@@ -59,7 +56,6 @@ describe("claude plugin", () => {
|
|
|
59
56
|
expect(entry.env.API_TOKEN).toBe("${user_config.api_token}");
|
|
60
57
|
});
|
|
61
58
|
|
|
62
|
-
/** DefaultClaudePluginPaths. */
|
|
63
59
|
test("defaultClaudePluginPaths", () => {
|
|
64
60
|
const cwd = "/tmp/work";
|
|
65
61
|
const paths = defaultClaudePluginPaths(configFixture, cwd);
|
package/src/mcp/env.test.ts
CHANGED
|
@@ -33,7 +33,6 @@ describe("mcp/env", () => {
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
/** ApplyShellEnv does not overwrite host vars except PATH merge. */
|
|
37
36
|
test("applyShellEnv does not overwrite host vars except PATH merge", () => {
|
|
38
37
|
const prev = process.env.HOME;
|
|
39
38
|
process.env.HOME = "/host/home";
|
|
@@ -78,25 +77,21 @@ fi
|
|
|
78
77
|
else process.env[TEST_VAR] = prevTest;
|
|
79
78
|
});
|
|
80
79
|
|
|
81
|
-
/** Defaults on when shellEnv is undefined. */
|
|
82
80
|
test("defaults on when shellEnv is undefined", () => {
|
|
83
81
|
bootstrapMcpEnv({});
|
|
84
82
|
expect(process.env[TEST_VAR]).toBe("from_fake_shell");
|
|
85
83
|
});
|
|
86
84
|
|
|
87
|
-
/** Runs when shellEnv is true. */
|
|
88
85
|
test("runs when shellEnv is true", () => {
|
|
89
86
|
bootstrapMcpEnv({ shellEnv: true });
|
|
90
87
|
expect(process.env[TEST_VAR]).toBe("from_fake_shell");
|
|
91
88
|
});
|
|
92
89
|
|
|
93
|
-
/** Tests that uses explicit shell path when shellEnv is a string. */
|
|
94
90
|
test("uses explicit shell path when shellEnv is a string", () => {
|
|
95
91
|
bootstrapMcpEnv({ shellEnv: fakeShell });
|
|
96
92
|
expect(process.env[TEST_VAR]).toBe("from_fake_shell");
|
|
97
93
|
});
|
|
98
94
|
|
|
99
|
-
/** Tests that skips capture when shellEnv is false. */
|
|
100
95
|
test("skips capture when shellEnv is false", () => {
|
|
101
96
|
bootstrapMcpEnv({ shellEnv: false });
|
|
102
97
|
expect(process.env[TEST_VAR]).toBeUndefined();
|
package/src/mcp/zip.test.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { zipStore } from "./zip.ts";
|
|
11
11
|
|
|
12
|
-
/** ZipStore preserves unix executable mode on extract. */
|
|
13
12
|
test("zipStore preserves unix executable mode on extract", () => {
|
|
14
13
|
const work = mkdtempSync(join(tmpdir(), "zip-exec-"));
|
|
15
14
|
const data = Buffer.from("#!/bin/sh\necho hi\n");
|
|
@@ -17,12 +17,10 @@ import { cliSchemaExport } from "./schema.ts";
|
|
|
17
17
|
import { mcpRequest, nestedMcpFixture, testProgram } from "./test-fixtures.ts";
|
|
18
18
|
import { cliValidateProgram } from "./validate.ts";
|
|
19
19
|
|
|
20
|
-
/** Tests that sanitizeToolSegment normalizes dotted app keys. */
|
|
21
20
|
test("sanitizeToolSegment normalizes dotted app keys", () => {
|
|
22
21
|
expect(sanitizeToolSegment("minimal.ts")).toBe("minimal_ts");
|
|
23
22
|
});
|
|
24
23
|
|
|
25
|
-
/** McpToolDescription formats CLI path and root-leaf prefix. */
|
|
26
24
|
test("mcpToolDescription formats CLI path and root-leaf prefix", () => {
|
|
27
25
|
expect(mcpToolDescription(["stat", "owner", "lookup"], "nested.ts", "Resolve owner info.")).toBe(
|
|
28
26
|
"stat owner lookup — Resolve owner info.",
|
|
@@ -31,7 +29,6 @@ test("mcpToolDescription formats CLI path and root-leaf prefix", () => {
|
|
|
31
29
|
expect(mcpToolDescription([], "helloapp", "Tiny demo.")).toBe("helloapp — Tiny demo.");
|
|
32
30
|
});
|
|
33
31
|
|
|
34
|
-
/** Tests that collectMcpTools lists user leaf commands only. */
|
|
35
32
|
test("collectMcpTools lists user leaf commands only", () => {
|
|
36
33
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
37
34
|
const names = tools.map((t) => t.name);
|
|
@@ -192,7 +189,6 @@ test("outputSchema cannot be set on both leaf and mcpTool", () => {
|
|
|
192
189
|
expect(() => cliValidateProgram(root)).toThrow(/Set outputSchema on the leaf only/);
|
|
193
190
|
});
|
|
194
191
|
|
|
195
|
-
/** Tests that collectMcpTools merges parent options into inputSchema. */
|
|
196
192
|
test("collectMcpTools merges parent options into inputSchema", () => {
|
|
197
193
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
198
194
|
const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
|
|
@@ -230,14 +226,12 @@ test("collectMcpTools includes outputSchema when set on leaf", () => {
|
|
|
230
226
|
});
|
|
231
227
|
});
|
|
232
228
|
|
|
233
|
-
/** Tests that collectMcpTools omits outputSchema when leaf has none. */
|
|
234
229
|
test("collectMcpTools omits outputSchema when leaf has none", () => {
|
|
235
230
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
236
231
|
const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
|
|
237
232
|
expect(lookup.outputSchema).toBeUndefined();
|
|
238
233
|
});
|
|
239
234
|
|
|
240
|
-
/** McpToolCallToArgv builds nested lookup argv. */
|
|
241
235
|
test("mcpToolCallToArgv builds nested lookup argv", () => {
|
|
242
236
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
243
237
|
const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
|
|
@@ -249,7 +243,6 @@ test("mcpToolCallToArgv builds nested lookup argv", () => {
|
|
|
249
243
|
expect(argv).toEqual(["stat", "owner", "lookup", "--json", "--user-name", "alice", "./x"]);
|
|
250
244
|
});
|
|
251
245
|
|
|
252
|
-
/** McpToolCallToArgv expands varargs positionals. */
|
|
253
246
|
test("mcpToolCallToArgv expands varargs positionals", () => {
|
|
254
247
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
255
248
|
const read = tools.find((t) => t.name === "read")!;
|
|
@@ -324,7 +317,6 @@ test("mcpServer on non-root node is rejected", () => {
|
|
|
324
317
|
expect(() => cliValidateProgram(root)).toThrow(/mcpServer is only supported on the program root/);
|
|
325
318
|
});
|
|
326
319
|
|
|
327
|
-
/** McpTool on root is rejected. */
|
|
328
320
|
test("mcpTool on root is rejected", () => {
|
|
329
321
|
const root = testProgram({
|
|
330
322
|
key: "app",
|
|
@@ -358,7 +350,6 @@ test("mcpTool on routing node is rejected", () => {
|
|
|
358
350
|
expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
|
|
359
351
|
});
|
|
360
352
|
|
|
361
|
-
/** BuildToolCallSuccess returns stdout only. */
|
|
362
353
|
test("buildToolCallSuccess returns stdout only", () => {
|
|
363
354
|
const result = buildToolCallSuccess("hello\n", "");
|
|
364
355
|
expect(result.isError).toBe(false);
|
|
@@ -366,7 +357,6 @@ test("buildToolCallSuccess returns stdout only", () => {
|
|
|
366
357
|
expect(result.structuredContent).toBeUndefined();
|
|
367
358
|
});
|
|
368
359
|
|
|
369
|
-
/** BuildToolCallSuccess adds stderr as second content block. */
|
|
370
360
|
test("buildToolCallSuccess adds stderr as second content block", () => {
|
|
371
361
|
const result = buildToolCallSuccess("out\n", "warn\n");
|
|
372
362
|
expect(result.content).toEqual([
|
|
@@ -376,7 +366,6 @@ test("buildToolCallSuccess adds stderr as second content block", () => {
|
|
|
376
366
|
expect(result.structuredContent).toBeUndefined();
|
|
377
367
|
});
|
|
378
368
|
|
|
379
|
-
/** BuildToolCallSuccess stderr-only still includes stdout slot. */
|
|
380
369
|
test("buildToolCallSuccess stderr-only still includes stdout slot", () => {
|
|
381
370
|
const result = buildToolCallSuccess("", "warn\n");
|
|
382
371
|
expect(result.content).toEqual([
|
|
@@ -385,26 +374,22 @@ test("buildToolCallSuccess stderr-only still includes stdout slot", () => {
|
|
|
385
374
|
]);
|
|
386
375
|
});
|
|
387
376
|
|
|
388
|
-
/** BuildToolCallSuccess parses JSON structuredContent. */
|
|
389
377
|
test("buildToolCallSuccess parses JSON structuredContent", () => {
|
|
390
378
|
const result = buildToolCallSuccess('{"a":1}\n', "");
|
|
391
379
|
expect(result.structuredContent).toEqual({ a: 1 });
|
|
392
380
|
expect(result.content[0]?.text).toBe('{"a":1}\n');
|
|
393
381
|
});
|
|
394
382
|
|
|
395
|
-
/** BuildToolCallSuccess skips structuredContent for plain text. */
|
|
396
383
|
test("buildToolCallSuccess skips structuredContent for plain text", () => {
|
|
397
384
|
const result = buildToolCallSuccess("lookup user=x\n", "");
|
|
398
385
|
expect(result.structuredContent).toBeUndefined();
|
|
399
386
|
});
|
|
400
387
|
|
|
401
|
-
/** BuildToolCallSuccess parses JSON primitives. */
|
|
402
388
|
test("buildToolCallSuccess parses JSON primitives", () => {
|
|
403
389
|
const result = buildToolCallSuccess("true\n", "");
|
|
404
390
|
expect(result.structuredContent).toBe(true);
|
|
405
391
|
});
|
|
406
392
|
|
|
407
|
-
/** MCP initialize returns tools and resources capabilities. */
|
|
408
393
|
test("MCP initialize returns tools and resources capabilities", async () => {
|
|
409
394
|
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }]);
|
|
410
395
|
const res = responses.get(1) as { result: { capabilities: Record<string, unknown> } };
|
|
@@ -412,7 +397,6 @@ test("MCP initialize returns tools and resources capabilities", async () => {
|
|
|
412
397
|
expect(res.result.capabilities.resources).toBeDefined();
|
|
413
398
|
});
|
|
414
399
|
|
|
415
|
-
/** MCP tools/list includes stat_owner_lookup. */
|
|
416
400
|
test("MCP tools/list includes stat_owner_lookup", async () => {
|
|
417
401
|
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }]);
|
|
418
402
|
const res = responses.get(2) as {
|
|
@@ -423,7 +407,6 @@ test("MCP tools/list includes stat_owner_lookup", async () => {
|
|
|
423
407
|
expect(lookup?.inputSchema.required).toContain("path");
|
|
424
408
|
});
|
|
425
409
|
|
|
426
|
-
/** MCP resources/read returns schema JSON. */
|
|
427
410
|
test("MCP resources/read returns schema JSON", async () => {
|
|
428
411
|
const responses = await mcpRequest([
|
|
429
412
|
{ jsonrpc: "2.0", id: 3, method: "resources/read", params: { uri: "nested_ts://schema" } },
|
|
@@ -493,21 +476,18 @@ test("MCP tools/call errors on missing required positional", async () => {
|
|
|
493
476
|
expect(res.result.content[0]?.text).toContain("Missing argument: path");
|
|
494
477
|
});
|
|
495
478
|
|
|
496
|
-
/** MCP ping returns empty result. */
|
|
497
479
|
test("MCP ping returns empty result", async () => {
|
|
498
480
|
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 99, method: "ping", params: {} }]);
|
|
499
481
|
const res = responses.get(99) as { result: Record<string, never> };
|
|
500
482
|
expect(res.result).toEqual({});
|
|
501
483
|
});
|
|
502
484
|
|
|
503
|
-
/** Tests that minimal.ts mcp without opt-in fails. */
|
|
504
485
|
test("minimal.ts mcp without opt-in fails", async () => {
|
|
505
486
|
const { stderr, exitCode } = await $`bun run examples/minimal.ts mcp`.nothrow().quiet();
|
|
506
487
|
expect(exitCode).toBe(1);
|
|
507
488
|
expect(stderr.toString()).toContain("MCP is not available");
|
|
508
489
|
});
|
|
509
490
|
|
|
510
|
-
/** MCP resources/list includes custom resource. */
|
|
511
491
|
test("MCP resources/list includes custom resource", async () => {
|
|
512
492
|
const responses = await mcpRequest(
|
|
513
493
|
[{ jsonrpc: "2.0", id: 10, method: "resources/list", params: {} }],
|
|
@@ -537,7 +517,6 @@ test("MCP resources/read returns docs topic resource body", async () => {
|
|
|
537
517
|
expect(res.result.contents[0]?.text).toBe("# MCP test readme\n");
|
|
538
518
|
});
|
|
539
519
|
|
|
540
|
-
/** MCP resources/read returns custom resource body. */
|
|
541
520
|
test("MCP resources/read returns custom resource body", async () => {
|
|
542
521
|
const responses = await mcpRequest(
|
|
543
522
|
[{ jsonrpc: "2.0", id: 11, method: "resources/read", params: { uri: "test://hello" } }],
|
|
@@ -547,7 +526,6 @@ test("MCP resources/read returns custom resource body", async () => {
|
|
|
547
526
|
expect(res.result.contents[0]?.text).toBe("hello resource");
|
|
548
527
|
});
|
|
549
528
|
|
|
550
|
-
/** MCP resources/read unknown URI returns error. */
|
|
551
529
|
test("MCP resources/read unknown URI returns error", async () => {
|
|
552
530
|
const responses = await mcpRequest(
|
|
553
531
|
[{ jsonrpc: "2.0", id: 12, method: "resources/read", params: { uri: "missing://nope" } }],
|