argsbarg 5.0.1 → 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 +15 -1
- package/docs/distribution-homebrew.md +2 -0
- package/examples/full-example/scripts/formula-shared.ts +1 -0
- package/index.d.ts +10 -5
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +12 -0
- package/src/builtins/config.test.ts +8 -0
- package/src/cli-tool/cli-smoke.test.ts +5 -0
- package/src/cli-tool/create.test.ts +10 -0
- package/src/cli-tool/full-example-capabilities.test.ts +6 -0
- package/src/cli-tool/main.ts +0 -0
- package/src/config/context.test.ts +7 -0
- package/src/config/file.test.ts +7 -0
- package/src/config/resolve.test.ts +11 -0
- package/src/config/validate.test.ts +5 -0
- package/src/config.integration.test.ts +5 -0
- package/src/configure/configure.test.ts +11 -0
- package/src/configure/index.ts +14 -0
- package/src/configure/prompt.ts +7 -0
- package/src/docs/api-guide.test.ts +6 -0
- package/src/docs/docs.test.ts +7 -0
- package/src/docs/mcp-resources.test.ts +4 -0
- package/src/formats.test.ts +4 -0
- package/src/headless.test.ts +6 -0
- package/src/headless.ts +5 -4
- package/src/hidden-mcpb.test.ts +10 -0
- package/src/install/binary-placement.test.ts +9 -0
- package/src/install/gh-release-update.test.ts +4 -0
- package/src/install/install-validate.test.ts +5 -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 +6 -0
- package/src/install/status.test.ts +9 -0
- package/src/install/targets.test.ts +9 -0
- package/src/invoke.test.ts +7 -0
- package/src/mcp/claude.test.ts +6 -0
- package/src/mcp/env.test.ts +7 -0
- package/src/mcp/zip.test.ts +4 -0
- package/src/mcp.integration.test.ts +17 -0
- package/src/parse.test.ts +43 -0
package/src/formats.test.ts
CHANGED
package/src/headless.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for headless module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { expect, test } from "bun:test";
|
|
2
6
|
import {
|
|
3
7
|
formatDryRunMessage,
|
|
@@ -30,6 +34,7 @@ test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
|
|
|
30
34
|
);
|
|
31
35
|
});
|
|
32
36
|
|
|
37
|
+
/** Tests that shouldRunHeadlessWithYes requires yes in non-tty. */
|
|
33
38
|
test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
|
|
34
39
|
expect(
|
|
35
40
|
shouldRunHeadlessWithYes({ invocation: "cli" }, { yes: true, hasRequiredArgs: true }, false),
|
|
@@ -51,6 +56,7 @@ test("formatDryRunMessage prefixes dry-run output", () => {
|
|
|
51
56
|
expect(formatDryRunMessage("hello", true)).toBe("[DRY RUN] hello");
|
|
52
57
|
});
|
|
53
58
|
|
|
59
|
+
/** Tests that requireYesInNonTty exits without yes in non-tty. */
|
|
54
60
|
test("requireYesInNonTty exits without yes in non-tty", () => {
|
|
55
61
|
const originalExit = process.exit;
|
|
56
62
|
let code: number | undefined;
|
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
|
@@ -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,6 +57,7 @@ const hiddenFixture: CliProgram = {
|
|
|
53
57
|
],
|
|
54
58
|
};
|
|
55
59
|
|
|
60
|
+
/** Tests for hidden commands and options. */
|
|
56
61
|
describe("hidden commands and options", () => {
|
|
57
62
|
test("parse root includes hidden commands", () => {
|
|
58
63
|
const parse = cliParseRoot(hiddenFixture);
|
|
@@ -98,6 +103,7 @@ describe("hidden commands and options", () => {
|
|
|
98
103
|
});
|
|
99
104
|
});
|
|
100
105
|
|
|
106
|
+
/** Tests for mcp router. */
|
|
101
107
|
describe("mcp router", () => {
|
|
102
108
|
test("presentation exposes mcp bundle but not hidden serve", () => {
|
|
103
109
|
const builtins = exportPresentationBuiltins(hiddenFixture);
|
|
@@ -114,6 +120,7 @@ describe("mcp router", () => {
|
|
|
114
120
|
});
|
|
115
121
|
});
|
|
116
122
|
|
|
123
|
+
/** Tests for mcp bundle. */
|
|
117
124
|
describe("mcp bundle", () => {
|
|
118
125
|
test("generateMcpManifest uses mcpServerId and binary entry", () => {
|
|
119
126
|
const manifest = generateMcpManifest(hiddenFixture, "myapp");
|
|
@@ -135,6 +142,7 @@ describe("mcp bundle", () => {
|
|
|
135
142
|
expect(paths.outPath).toBe(join(cwd, "dist", "myapp.mcpb"));
|
|
136
143
|
});
|
|
137
144
|
|
|
145
|
+
/** Tests that packMcpBundle writes zip with manifest and binary. */
|
|
138
146
|
test("packMcpBundle writes zip with manifest and binary", () => {
|
|
139
147
|
const work = mkdtempSync(join(tmpdir(), "mcpb-test-"));
|
|
140
148
|
try {
|
|
@@ -155,6 +163,7 @@ describe("mcp bundle", () => {
|
|
|
155
163
|
}
|
|
156
164
|
});
|
|
157
165
|
|
|
166
|
+
/** RunMcpBundle prints mcpb and plugin paths. */
|
|
158
167
|
test("runMcpBundle prints mcpb and plugin paths", () => {
|
|
159
168
|
const work = mkdtempSync(join(tmpdir(), "mcpb-run-"));
|
|
160
169
|
const stdout: string[] = [];
|
|
@@ -188,6 +197,7 @@ describe("mcp bundle", () => {
|
|
|
188
197
|
}
|
|
189
198
|
});
|
|
190
199
|
|
|
200
|
+
/** RunMcpBundle with claudePlugin only prints plugin path. */
|
|
191
201
|
test("runMcpBundle with claudePlugin only prints plugin path", () => {
|
|
192
202
|
const work = mkdtempSync(join(tmpdir(), "mcpb-run-"));
|
|
193
203
|
const stdout: string[] = [];
|
|
@@ -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,6 +34,7 @@ afterEach(() => {
|
|
|
30
34
|
rmSync(tmp, { recursive: true, force: true });
|
|
31
35
|
});
|
|
32
36
|
|
|
37
|
+
/** Tests for isExternallyManagedBinary. */
|
|
33
38
|
describe("isExternallyManagedBinary", () => {
|
|
34
39
|
test("false when command is not on PATH", () => {
|
|
35
40
|
process.env.PATH = tmp;
|
|
@@ -63,7 +68,9 @@ describe("isExternallyManagedBinary", () => {
|
|
|
63
68
|
});
|
|
64
69
|
});
|
|
65
70
|
|
|
71
|
+
/** Tests for isAppInstalled. */
|
|
66
72
|
describe("isAppInstalled", () => {
|
|
73
|
+
/** Tests that true when externally managed. */
|
|
67
74
|
test("true when externally managed", () => {
|
|
68
75
|
const bin = join(tmp, program.key);
|
|
69
76
|
const prevExec = process.execPath;
|
|
@@ -78,6 +85,7 @@ describe("isAppInstalled", () => {
|
|
|
78
85
|
}
|
|
79
86
|
});
|
|
80
87
|
|
|
88
|
+
/** Tests that false when not on PATH and no local copy. */
|
|
81
89
|
test("false when not on PATH and no local copy", () => {
|
|
82
90
|
const home = mkdtempSync(join(tmpdir(), "argsbarg-placement-home-"));
|
|
83
91
|
const prevHome = process.env.HOME;
|
|
@@ -93,6 +101,7 @@ describe("isAppInstalled", () => {
|
|
|
93
101
|
});
|
|
94
102
|
});
|
|
95
103
|
|
|
104
|
+
/** Tests for resolvePathCommand. */
|
|
96
105
|
describe("resolvePathCommand", () => {
|
|
97
106
|
test("returns undefined when missing", () => {
|
|
98
107
|
process.env.PATH = tmp;
|
|
@@ -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,6 +14,7 @@ const base: CliProgram = {
|
|
|
10
14
|
handler: () => {},
|
|
11
15
|
};
|
|
12
16
|
|
|
17
|
+
/** Tests for validateConfigureConfig. */
|
|
13
18
|
describe("validateConfigureConfig", () => {
|
|
14
19
|
test("accepts empty install config", () => {
|
|
15
20
|
expect(() => cliValidateProgram(base)).not.toThrow();
|
|
@@ -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,6 +45,7 @@ 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", () => {
|
|
45
50
|
test("resolveOpenCodeConfigPathForInstall prefers existing file", () => {
|
|
46
51
|
const dir = opencodeConfigDir(home);
|
|
@@ -70,6 +75,7 @@ describe("opencode mcp config", () => {
|
|
|
70
75
|
expect(data.mcp.testapp).toEqual(entry);
|
|
71
76
|
});
|
|
72
77
|
|
|
78
|
+
/** DetectOpenCodeMcpConfigPath finds entry across filenames. */
|
|
73
79
|
test("detectOpenCodeMcpConfigPath finds entry across filenames", () => {
|
|
74
80
|
const dir = opencodeConfigDir(home);
|
|
75
81
|
mkdirSync(dir, { 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,6 +9,7 @@ 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", () => {
|
|
9
14
|
test("bare install sets all", () => {
|
|
10
15
|
expect(normalizeInstallRawOpts({})).toEqual({ all: "1" });
|
|
@@ -22,6 +27,7 @@ describe("normalizeInstallRawOpts", () => {
|
|
|
22
27
|
});
|
|
23
28
|
});
|
|
24
29
|
|
|
30
|
+
/** Tests for resolveAgentIntegration. */
|
|
25
31
|
describe("resolveAgentIntegration", () => {
|
|
26
32
|
test("defaults to skill without MCP", () => {
|
|
27
33
|
expect(resolveAgentIntegration(undefined, false)).toBe("skill");
|
|
@@ -32,6 +38,7 @@ describe("resolveAgentIntegration", () => {
|
|
|
32
38
|
});
|
|
33
39
|
});
|
|
34
40
|
|
|
41
|
+
/** Tests for resolveEffectiveInstallTargets. */
|
|
35
42
|
describe("resolveEffectiveInstallTargets", () => {
|
|
36
43
|
test("defaults app and configure not in --all", () => {
|
|
37
44
|
const t = resolveEffectiveInstallTargets(undefined);
|
|
@@ -69,6 +76,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
69
76
|
expect(t.cursorSkill.enabled).toBe(false);
|
|
70
77
|
});
|
|
71
78
|
|
|
79
|
+
/** Scoped --mcp uses effective targets not all MCP hosts. */
|
|
72
80
|
test("scoped --mcp uses effective targets not all MCP hosts", () => {
|
|
73
81
|
const program: CliProgram = {
|
|
74
82
|
key: "app",
|
|
@@ -95,6 +103,7 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
95
103
|
).toBe(true);
|
|
96
104
|
});
|
|
97
105
|
|
|
106
|
+
/** Scoped --mcp --skill --configure includes skill and mcp not only configure. */
|
|
98
107
|
test("scoped --mcp --skill --configure includes skill and mcp not only configure", () => {
|
|
99
108
|
const program: CliProgram = {
|
|
100
109
|
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",
|
|
@@ -140,6 +144,7 @@ test("varargs scoped help in tail", () => {
|
|
|
140
144
|
expect(pr.helpExplicit).toBe(true);
|
|
141
145
|
});
|
|
142
146
|
|
|
147
|
+
/** Tests that ctx.positional returns single slot value. */
|
|
143
148
|
test("ctx.positional returns single slot value", async () => {
|
|
144
149
|
const root = testProgram({
|
|
145
150
|
key: "app",
|
|
@@ -174,6 +179,7 @@ test("ctx.positional returns varargs array", async () => {
|
|
|
174
179
|
expect(captured).toEqual(["a.txt", "b.txt"]);
|
|
175
180
|
});
|
|
176
181
|
|
|
182
|
+
/** Tests that ctx.positional returns undefined for absent optional slot. */
|
|
177
183
|
test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
178
184
|
const root = testProgram({
|
|
179
185
|
key: "app",
|
|
@@ -197,6 +203,7 @@ test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
|
197
203
|
expect(captured).toBeUndefined();
|
|
198
204
|
});
|
|
199
205
|
|
|
206
|
+
/** Tests that ctx.positional varargs matches ctx.args. */
|
|
200
207
|
test("ctx.positional varargs matches ctx.args", async () => {
|
|
201
208
|
const root = varargsReadFixture();
|
|
202
209
|
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,6 +33,7 @@ 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", () => {
|
|
33
38
|
test("pluginName is kebab-case", () => {
|
|
34
39
|
expect(pluginName({ ...configFixture, key: "MyApp" })).toBe("my-app");
|
|
@@ -57,6 +62,7 @@ describe("claude plugin", () => {
|
|
|
57
62
|
expect(paths.pluginZipPath).toBe(join(cwd, "dist", "claude-plugin", "myapp.zip"));
|
|
58
63
|
});
|
|
59
64
|
|
|
65
|
+
/** Tests that packClaudePlugin writes zip with MCP pointer skill only. */
|
|
60
66
|
test("packClaudePlugin writes zip with MCP pointer skill only", () => {
|
|
61
67
|
const work = mkdtempSync(join(tmpdir(), "claude-plugin-test-"));
|
|
62
68
|
try {
|
package/src/mcp/env.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for mcp/env module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
6
|
import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -6,7 +10,9 @@ import { applyShellEnv, bootstrapMcpEnv } from "./env.ts";
|
|
|
6
10
|
|
|
7
11
|
const TEST_VAR = "ARGS_BARG_SHELL_ENV_TEST";
|
|
8
12
|
|
|
13
|
+
/** Tests for mcp/env. */
|
|
9
14
|
describe("mcp/env", () => {
|
|
15
|
+
/** ApplyShellEnv merges PATH and fills missing vars. */
|
|
10
16
|
test("applyShellEnv merges PATH and fills missing vars", () => {
|
|
11
17
|
const prevPath = process.env.PATH;
|
|
12
18
|
const prevTest = process.env[TEST_VAR];
|
|
@@ -40,6 +46,7 @@ describe("mcp/env", () => {
|
|
|
40
46
|
});
|
|
41
47
|
});
|
|
42
48
|
|
|
49
|
+
/** Tests for bootstrapMcpEnv. */
|
|
43
50
|
describe("bootstrapMcpEnv", () => {
|
|
44
51
|
let fakeShell: string;
|
|
45
52
|
let prevShell: string | undefined;
|
package/src/mcp/zip.test.ts
CHANGED
|
@@ -42,6 +42,7 @@ test("collectMcpTools lists user leaf commands only", () => {
|
|
|
42
42
|
expect(lookup.description).toBe("stat owner lookup — Resolve owner info.");
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
/** Tests that collectMcpTools appends leaf notes to MCP tool description. */
|
|
45
46
|
test("collectMcpTools appends leaf notes to MCP tool description", () => {
|
|
46
47
|
const root = testProgram({
|
|
47
48
|
key: "app",
|
|
@@ -61,6 +62,7 @@ test("collectMcpTools appends leaf notes to MCP tool description", () => {
|
|
|
61
62
|
expect(tools[0]?.description).toBe("run — Run.\n\nUse `--json` for structured output.");
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
/** Tests that collectMcpTools appends notes after mcpTool.description override. */
|
|
64
66
|
test("collectMcpTools appends notes after mcpTool.description override", () => {
|
|
65
67
|
const root = testProgram({
|
|
66
68
|
key: "app",
|
|
@@ -81,6 +83,7 @@ test("collectMcpTools appends notes after mcpTool.description override", () => {
|
|
|
81
83
|
expect(tools[0]?.description).toBe("Custom MCP text.\n\nOperational hint.");
|
|
82
84
|
});
|
|
83
85
|
|
|
86
|
+
/** Tests that collectMcpTools resolves {argsbarg:program} in appended notes. */
|
|
84
87
|
test("collectMcpTools resolves {argsbarg:program} in appended notes", () => {
|
|
85
88
|
const root = testProgram({
|
|
86
89
|
key: "myapp",
|
|
@@ -100,6 +103,7 @@ test("collectMcpTools resolves {argsbarg:program} in appended notes", () => {
|
|
|
100
103
|
expect(tools[0]?.description).toContain("See `myapp docs api`.");
|
|
101
104
|
});
|
|
102
105
|
|
|
106
|
+
/** CliSchemaExport includes leaf outputSchema. */
|
|
103
107
|
test("cliSchemaExport includes leaf outputSchema", () => {
|
|
104
108
|
const root = testProgram({
|
|
105
109
|
key: "app",
|
|
@@ -125,6 +129,7 @@ test("cliSchemaExport includes leaf outputSchema", () => {
|
|
|
125
129
|
});
|
|
126
130
|
});
|
|
127
131
|
|
|
132
|
+
/** CliSchemaExport accepts legacy mcpTool.outputSchema. */
|
|
128
133
|
test("cliSchemaExport accepts legacy mcpTool.outputSchema", () => {
|
|
129
134
|
const root = testProgram({
|
|
130
135
|
key: "app",
|
|
@@ -147,6 +152,7 @@ test("cliSchemaExport accepts legacy mcpTool.outputSchema", () => {
|
|
|
147
152
|
});
|
|
148
153
|
});
|
|
149
154
|
|
|
155
|
+
/** Tests that outputSchema must be a JSON Schema object. */
|
|
150
156
|
test("outputSchema must be a JSON Schema object", () => {
|
|
151
157
|
const root = testProgram({
|
|
152
158
|
key: "app",
|
|
@@ -164,6 +170,7 @@ test("outputSchema must be a JSON Schema object", () => {
|
|
|
164
170
|
expect(() => cliValidateProgram(root)).toThrow(/outputSchema must be a JSON Schema object/);
|
|
165
171
|
});
|
|
166
172
|
|
|
173
|
+
/** Tests that outputSchema cannot be set on both leaf and mcpTool. */
|
|
167
174
|
test("outputSchema cannot be set on both leaf and mcpTool", () => {
|
|
168
175
|
const root = testProgram({
|
|
169
176
|
key: "app",
|
|
@@ -190,6 +197,7 @@ test("collectMcpTools merges parent options into inputSchema", () => {
|
|
|
190
197
|
expect(schema.required).toContain("path");
|
|
191
198
|
});
|
|
192
199
|
|
|
200
|
+
/** Tests that collectMcpTools includes outputSchema when set on leaf. */
|
|
193
201
|
test("collectMcpTools includes outputSchema when set on leaf", () => {
|
|
194
202
|
const root = testProgram({
|
|
195
203
|
key: "app",
|
|
@@ -242,6 +250,7 @@ test("mcpToolCallToArgv expands varargs positionals", () => {
|
|
|
242
250
|
expect(argv).toEqual(["read", "a", "b"]);
|
|
243
251
|
});
|
|
244
252
|
|
|
253
|
+
/** Tests that reserved command name configure is rejected. */
|
|
245
254
|
test("reserved command name configure is rejected", () => {
|
|
246
255
|
const root = testProgram({
|
|
247
256
|
key: "app",
|
|
@@ -257,6 +266,7 @@ test("reserved command name configure is rejected", () => {
|
|
|
257
266
|
expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: configure/);
|
|
258
267
|
});
|
|
259
268
|
|
|
269
|
+
/** Tests that top-level command name mcp is allowed without mcpServer. */
|
|
260
270
|
test("top-level command name mcp is allowed without mcpServer", () => {
|
|
261
271
|
const root = testProgram({
|
|
262
272
|
key: "app",
|
|
@@ -272,6 +282,7 @@ test("top-level command name mcp is allowed without mcpServer", () => {
|
|
|
272
282
|
expect(() => cliValidateProgram(root)).not.toThrow();
|
|
273
283
|
});
|
|
274
284
|
|
|
285
|
+
/** Tests that top-level command name mcp is rejected when mcpServer is enabled. */
|
|
275
286
|
test("top-level command name mcp is rejected when mcpServer is enabled", () => {
|
|
276
287
|
const root = testProgram({
|
|
277
288
|
key: "app",
|
|
@@ -288,6 +299,7 @@ test("top-level command name mcp is rejected when mcpServer is enabled", () => {
|
|
|
288
299
|
expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: mcp/);
|
|
289
300
|
});
|
|
290
301
|
|
|
302
|
+
/** McpServer on non-root node is rejected. */
|
|
291
303
|
test("mcpServer on non-root node is rejected", () => {
|
|
292
304
|
const root = {
|
|
293
305
|
key: "app",
|
|
@@ -315,6 +327,7 @@ test("mcpTool on root is rejected", () => {
|
|
|
315
327
|
expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
|
|
316
328
|
});
|
|
317
329
|
|
|
330
|
+
/** McpTool on routing node is rejected. */
|
|
318
331
|
test("mcpTool on routing node is rejected", () => {
|
|
319
332
|
const root = testProgram({
|
|
320
333
|
key: "app",
|
|
@@ -403,6 +416,7 @@ test("MCP resources/read returns schema JSON", async () => {
|
|
|
403
416
|
expect(schema.key).toBe("nested.ts");
|
|
404
417
|
});
|
|
405
418
|
|
|
419
|
+
/** MCP tools/call runs stat_owner_lookup. */
|
|
406
420
|
test("MCP tools/call runs stat_owner_lookup", async () => {
|
|
407
421
|
const readme = join(import.meta.dir, "..", "README.md");
|
|
408
422
|
const responses = await mcpRequest([
|
|
@@ -421,6 +435,7 @@ test("MCP tools/call runs stat_owner_lookup", async () => {
|
|
|
421
435
|
expect(res.result.content[0]?.text).toContain("lookup user=test");
|
|
422
436
|
});
|
|
423
437
|
|
|
438
|
+
/** MCP tools/call returns structuredContent for JSON stdout. */
|
|
424
439
|
test("MCP tools/call returns structuredContent for JSON stdout", async () => {
|
|
425
440
|
const readme = join(import.meta.dir, "..", "README.md");
|
|
426
441
|
const responses = await mcpRequest([
|
|
@@ -446,6 +461,7 @@ test("MCP tools/call returns structuredContent for JSON stdout", async () => {
|
|
|
446
461
|
expect(JSON.parse(res.result.content[0]?.text.trim())).toEqual({ user: "test", path: readme });
|
|
447
462
|
});
|
|
448
463
|
|
|
464
|
+
/** MCP tools/call errors on missing required positional. */
|
|
449
465
|
test("MCP tools/call errors on missing required positional", async () => {
|
|
450
466
|
const responses = await mcpRequest([
|
|
451
467
|
{
|
|
@@ -484,6 +500,7 @@ test("MCP resources/list includes custom resource", async () => {
|
|
|
484
500
|
expect(uris).toContain("test://hello");
|
|
485
501
|
});
|
|
486
502
|
|
|
503
|
+
/** MCP resources/read returns docs topic resource body. */
|
|
487
504
|
test("MCP resources/read returns docs topic resource body", async () => {
|
|
488
505
|
const responses = await mcpRequest(
|
|
489
506
|
[
|