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.
Files changed (38) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/docs/distribution-homebrew.md +2 -0
  3. package/examples/full-example/scripts/formula-shared.ts +1 -0
  4. package/package.json +1 -1
  5. package/src/builtins/builtins.test.ts +23 -0
  6. package/src/builtins/config.test.ts +8 -0
  7. package/src/cli-tool/cli-smoke.test.ts +8 -0
  8. package/src/cli-tool/create.test.ts +13 -0
  9. package/src/cli-tool/full-example-capabilities.test.ts +8 -0
  10. package/src/cli-tool/main.ts +0 -0
  11. package/src/config/context.test.ts +8 -0
  12. package/src/config/file.test.ts +12 -0
  13. package/src/config/resolve.test.ts +18 -0
  14. package/src/config/validate.test.ts +11 -0
  15. package/src/config.integration.test.ts +5 -0
  16. package/src/configure/configure.test.ts +22 -0
  17. package/src/configure/index.ts +14 -0
  18. package/src/configure/prompt.ts +7 -0
  19. package/src/docs/api-guide.test.ts +10 -0
  20. package/src/docs/docs.test.ts +27 -0
  21. package/src/docs/mcp-resources.test.ts +10 -0
  22. package/src/formats.test.ts +9 -0
  23. package/src/headless.test.ts +10 -0
  24. package/src/hidden-mcpb.test.ts +22 -0
  25. package/src/install/binary-placement.test.ts +14 -0
  26. package/src/install/gh-release-update.test.ts +9 -0
  27. package/src/install/install-validate.test.ts +10 -0
  28. package/src/install/mcp-codex.test.ts +7 -0
  29. package/src/install/mcp-openclaw.test.ts +6 -0
  30. package/src/install/mcp-opencode.test.ts +10 -0
  31. package/src/install/status.test.ts +9 -0
  32. package/src/install/targets.test.ts +19 -0
  33. package/src/invoke.test.ts +14 -0
  34. package/src/mcp/claude.test.ts +10 -0
  35. package/src/mcp/env.test.ts +12 -0
  36. package/src/mcp/zip.test.ts +5 -0
  37. package/src/mcp.integration.test.ts +39 -0
  38. package/src/parse.test.ts +71 -0
@@ -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",
@@ -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;
@@ -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 {