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