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 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];
@@ -27,6 +33,7 @@ describe("mcp/env", () => {
27
33
  }
28
34
  });
29
35
 
36
+ /** ApplyShellEnv does not overwrite host vars except PATH merge. */
30
37
  test("applyShellEnv does not overwrite host vars except PATH merge", () => {
31
38
  const prev = process.env.HOME;
32
39
  process.env.HOME = "/host/home";
@@ -40,6 +47,7 @@ describe("mcp/env", () => {
40
47
  });
41
48
  });
42
49
 
50
+ /** Tests for bootstrapMcpEnv. */
43
51
  describe("bootstrapMcpEnv", () => {
44
52
  let fakeShell: string;
45
53
  let prevShell: string | undefined;
@@ -70,21 +78,25 @@ fi
70
78
  else process.env[TEST_VAR] = prevTest;
71
79
  });
72
80
 
81
+ /** Defaults on when shellEnv is undefined. */
73
82
  test("defaults on when shellEnv is undefined", () => {
74
83
  bootstrapMcpEnv({});
75
84
  expect(process.env[TEST_VAR]).toBe("from_fake_shell");
76
85
  });
77
86
 
87
+ /** Runs when shellEnv is true. */
78
88
  test("runs when shellEnv is true", () => {
79
89
  bootstrapMcpEnv({ shellEnv: true });
80
90
  expect(process.env[TEST_VAR]).toBe("from_fake_shell");
81
91
  });
82
92
 
93
+ /** Tests that uses explicit shell path when shellEnv is a string. */
83
94
  test("uses explicit shell path when shellEnv is a string", () => {
84
95
  bootstrapMcpEnv({ shellEnv: fakeShell });
85
96
  expect(process.env[TEST_VAR]).toBe("from_fake_shell");
86
97
  });
87
98
 
99
+ /** Tests that skips capture when shellEnv is false. */
88
100
  test("skips capture when shellEnv is false", () => {
89
101
  bootstrapMcpEnv({ shellEnv: false });
90
102
  expect(process.env[TEST_VAR]).toBeUndefined();
@@ -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";
@@ -5,6 +9,7 @@ import { tmpdir } from "node:os";
5
9
  import { join } from "node:path";
6
10
  import { zipStore } from "./zip.ts";
7
11
 
12
+ /** ZipStore preserves unix executable mode on extract. */
8
13
  test("zipStore preserves unix executable mode on extract", () => {
9
14
  const work = mkdtempSync(join(tmpdir(), "zip-exec-"));
10
15
  const data = Buffer.from("#!/bin/sh\necho hi\n");
@@ -17,10 +17,12 @@ 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. */
20
21
  test("sanitizeToolSegment normalizes dotted app keys", () => {
21
22
  expect(sanitizeToolSegment("minimal.ts")).toBe("minimal_ts");
22
23
  });
23
24
 
25
+ /** McpToolDescription formats CLI path and root-leaf prefix. */
24
26
  test("mcpToolDescription formats CLI path and root-leaf prefix", () => {
25
27
  expect(mcpToolDescription(["stat", "owner", "lookup"], "nested.ts", "Resolve owner info.")).toBe(
26
28
  "stat owner lookup — Resolve owner info.",
@@ -29,6 +31,7 @@ test("mcpToolDescription formats CLI path and root-leaf prefix", () => {
29
31
  expect(mcpToolDescription([], "helloapp", "Tiny demo.")).toBe("helloapp — Tiny demo.");
30
32
  });
31
33
 
34
+ /** Tests that collectMcpTools lists user leaf commands only. */
32
35
  test("collectMcpTools lists user leaf commands only", () => {
33
36
  const tools = collectMcpTools(nestedMcpFixture);
34
37
  const names = tools.map((t) => t.name);
@@ -42,6 +45,7 @@ test("collectMcpTools lists user leaf commands only", () => {
42
45
  expect(lookup.description).toBe("stat owner lookup — Resolve owner info.");
43
46
  });
44
47
 
48
+ /** Tests that collectMcpTools appends leaf notes to MCP tool description. */
45
49
  test("collectMcpTools appends leaf notes to MCP tool description", () => {
46
50
  const root = testProgram({
47
51
  key: "app",
@@ -61,6 +65,7 @@ test("collectMcpTools appends leaf notes to MCP tool description", () => {
61
65
  expect(tools[0]?.description).toBe("run — Run.\n\nUse `--json` for structured output.");
62
66
  });
63
67
 
68
+ /** Tests that collectMcpTools appends notes after mcpTool.description override. */
64
69
  test("collectMcpTools appends notes after mcpTool.description override", () => {
65
70
  const root = testProgram({
66
71
  key: "app",
@@ -81,6 +86,7 @@ test("collectMcpTools appends notes after mcpTool.description override", () => {
81
86
  expect(tools[0]?.description).toBe("Custom MCP text.\n\nOperational hint.");
82
87
  });
83
88
 
89
+ /** Tests that collectMcpTools resolves {argsbarg:program} in appended notes. */
84
90
  test("collectMcpTools resolves {argsbarg:program} in appended notes", () => {
85
91
  const root = testProgram({
86
92
  key: "myapp",
@@ -100,6 +106,7 @@ test("collectMcpTools resolves {argsbarg:program} in appended notes", () => {
100
106
  expect(tools[0]?.description).toContain("See `myapp docs api`.");
101
107
  });
102
108
 
109
+ /** CliSchemaExport includes leaf outputSchema. */
103
110
  test("cliSchemaExport includes leaf outputSchema", () => {
104
111
  const root = testProgram({
105
112
  key: "app",
@@ -125,6 +132,7 @@ test("cliSchemaExport includes leaf outputSchema", () => {
125
132
  });
126
133
  });
127
134
 
135
+ /** CliSchemaExport accepts legacy mcpTool.outputSchema. */
128
136
  test("cliSchemaExport accepts legacy mcpTool.outputSchema", () => {
129
137
  const root = testProgram({
130
138
  key: "app",
@@ -147,6 +155,7 @@ test("cliSchemaExport accepts legacy mcpTool.outputSchema", () => {
147
155
  });
148
156
  });
149
157
 
158
+ /** Tests that outputSchema must be a JSON Schema object. */
150
159
  test("outputSchema must be a JSON Schema object", () => {
151
160
  const root = testProgram({
152
161
  key: "app",
@@ -164,6 +173,7 @@ test("outputSchema must be a JSON Schema object", () => {
164
173
  expect(() => cliValidateProgram(root)).toThrow(/outputSchema must be a JSON Schema object/);
165
174
  });
166
175
 
176
+ /** Tests that outputSchema cannot be set on both leaf and mcpTool. */
167
177
  test("outputSchema cannot be set on both leaf and mcpTool", () => {
168
178
  const root = testProgram({
169
179
  key: "app",
@@ -182,6 +192,7 @@ test("outputSchema cannot be set on both leaf and mcpTool", () => {
182
192
  expect(() => cliValidateProgram(root)).toThrow(/Set outputSchema on the leaf only/);
183
193
  });
184
194
 
195
+ /** Tests that collectMcpTools merges parent options into inputSchema. */
185
196
  test("collectMcpTools merges parent options into inputSchema", () => {
186
197
  const tools = collectMcpTools(nestedMcpFixture);
187
198
  const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
@@ -190,6 +201,7 @@ test("collectMcpTools merges parent options into inputSchema", () => {
190
201
  expect(schema.required).toContain("path");
191
202
  });
192
203
 
204
+ /** Tests that collectMcpTools includes outputSchema when set on leaf. */
193
205
  test("collectMcpTools includes outputSchema when set on leaf", () => {
194
206
  const root = testProgram({
195
207
  key: "app",
@@ -218,12 +230,14 @@ test("collectMcpTools includes outputSchema when set on leaf", () => {
218
230
  });
219
231
  });
220
232
 
233
+ /** Tests that collectMcpTools omits outputSchema when leaf has none. */
221
234
  test("collectMcpTools omits outputSchema when leaf has none", () => {
222
235
  const tools = collectMcpTools(nestedMcpFixture);
223
236
  const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
224
237
  expect(lookup.outputSchema).toBeUndefined();
225
238
  });
226
239
 
240
+ /** McpToolCallToArgv builds nested lookup argv. */
227
241
  test("mcpToolCallToArgv builds nested lookup argv", () => {
228
242
  const tools = collectMcpTools(nestedMcpFixture);
229
243
  const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
@@ -235,6 +249,7 @@ test("mcpToolCallToArgv builds nested lookup argv", () => {
235
249
  expect(argv).toEqual(["stat", "owner", "lookup", "--json", "--user-name", "alice", "./x"]);
236
250
  });
237
251
 
252
+ /** McpToolCallToArgv expands varargs positionals. */
238
253
  test("mcpToolCallToArgv expands varargs positionals", () => {
239
254
  const tools = collectMcpTools(nestedMcpFixture);
240
255
  const read = tools.find((t) => t.name === "read")!;
@@ -242,6 +257,7 @@ test("mcpToolCallToArgv expands varargs positionals", () => {
242
257
  expect(argv).toEqual(["read", "a", "b"]);
243
258
  });
244
259
 
260
+ /** Tests that reserved command name configure is rejected. */
245
261
  test("reserved command name configure is rejected", () => {
246
262
  const root = testProgram({
247
263
  key: "app",
@@ -257,6 +273,7 @@ test("reserved command name configure is rejected", () => {
257
273
  expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: configure/);
258
274
  });
259
275
 
276
+ /** Tests that top-level command name mcp is allowed without mcpServer. */
260
277
  test("top-level command name mcp is allowed without mcpServer", () => {
261
278
  const root = testProgram({
262
279
  key: "app",
@@ -272,6 +289,7 @@ test("top-level command name mcp is allowed without mcpServer", () => {
272
289
  expect(() => cliValidateProgram(root)).not.toThrow();
273
290
  });
274
291
 
292
+ /** Tests that top-level command name mcp is rejected when mcpServer is enabled. */
275
293
  test("top-level command name mcp is rejected when mcpServer is enabled", () => {
276
294
  const root = testProgram({
277
295
  key: "app",
@@ -288,6 +306,7 @@ test("top-level command name mcp is rejected when mcpServer is enabled", () => {
288
306
  expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: mcp/);
289
307
  });
290
308
 
309
+ /** McpServer on non-root node is rejected. */
291
310
  test("mcpServer on non-root node is rejected", () => {
292
311
  const root = {
293
312
  key: "app",
@@ -305,6 +324,7 @@ test("mcpServer on non-root node is rejected", () => {
305
324
  expect(() => cliValidateProgram(root)).toThrow(/mcpServer is only supported on the program root/);
306
325
  });
307
326
 
327
+ /** McpTool on root is rejected. */
308
328
  test("mcpTool on root is rejected", () => {
309
329
  const root = testProgram({
310
330
  key: "app",
@@ -315,6 +335,7 @@ test("mcpTool on root is rejected", () => {
315
335
  expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
316
336
  });
317
337
 
338
+ /** McpTool on routing node is rejected. */
318
339
  test("mcpTool on routing node is rejected", () => {
319
340
  const root = testProgram({
320
341
  key: "app",
@@ -337,6 +358,7 @@ test("mcpTool on routing node is rejected", () => {
337
358
  expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
338
359
  });
339
360
 
361
+ /** BuildToolCallSuccess returns stdout only. */
340
362
  test("buildToolCallSuccess returns stdout only", () => {
341
363
  const result = buildToolCallSuccess("hello\n", "");
342
364
  expect(result.isError).toBe(false);
@@ -344,6 +366,7 @@ test("buildToolCallSuccess returns stdout only", () => {
344
366
  expect(result.structuredContent).toBeUndefined();
345
367
  });
346
368
 
369
+ /** BuildToolCallSuccess adds stderr as second content block. */
347
370
  test("buildToolCallSuccess adds stderr as second content block", () => {
348
371
  const result = buildToolCallSuccess("out\n", "warn\n");
349
372
  expect(result.content).toEqual([
@@ -353,6 +376,7 @@ test("buildToolCallSuccess adds stderr as second content block", () => {
353
376
  expect(result.structuredContent).toBeUndefined();
354
377
  });
355
378
 
379
+ /** BuildToolCallSuccess stderr-only still includes stdout slot. */
356
380
  test("buildToolCallSuccess stderr-only still includes stdout slot", () => {
357
381
  const result = buildToolCallSuccess("", "warn\n");
358
382
  expect(result.content).toEqual([
@@ -361,22 +385,26 @@ test("buildToolCallSuccess stderr-only still includes stdout slot", () => {
361
385
  ]);
362
386
  });
363
387
 
388
+ /** BuildToolCallSuccess parses JSON structuredContent. */
364
389
  test("buildToolCallSuccess parses JSON structuredContent", () => {
365
390
  const result = buildToolCallSuccess('{"a":1}\n', "");
366
391
  expect(result.structuredContent).toEqual({ a: 1 });
367
392
  expect(result.content[0]?.text).toBe('{"a":1}\n');
368
393
  });
369
394
 
395
+ /** BuildToolCallSuccess skips structuredContent for plain text. */
370
396
  test("buildToolCallSuccess skips structuredContent for plain text", () => {
371
397
  const result = buildToolCallSuccess("lookup user=x\n", "");
372
398
  expect(result.structuredContent).toBeUndefined();
373
399
  });
374
400
 
401
+ /** BuildToolCallSuccess parses JSON primitives. */
375
402
  test("buildToolCallSuccess parses JSON primitives", () => {
376
403
  const result = buildToolCallSuccess("true\n", "");
377
404
  expect(result.structuredContent).toBe(true);
378
405
  });
379
406
 
407
+ /** MCP initialize returns tools and resources capabilities. */
380
408
  test("MCP initialize returns tools and resources capabilities", async () => {
381
409
  const responses = await mcpRequest([{ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }]);
382
410
  const res = responses.get(1) as { result: { capabilities: Record<string, unknown> } };
@@ -384,6 +412,7 @@ test("MCP initialize returns tools and resources capabilities", async () => {
384
412
  expect(res.result.capabilities.resources).toBeDefined();
385
413
  });
386
414
 
415
+ /** MCP tools/list includes stat_owner_lookup. */
387
416
  test("MCP tools/list includes stat_owner_lookup", async () => {
388
417
  const responses = await mcpRequest([{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }]);
389
418
  const res = responses.get(2) as {
@@ -394,6 +423,7 @@ test("MCP tools/list includes stat_owner_lookup", async () => {
394
423
  expect(lookup?.inputSchema.required).toContain("path");
395
424
  });
396
425
 
426
+ /** MCP resources/read returns schema JSON. */
397
427
  test("MCP resources/read returns schema JSON", async () => {
398
428
  const responses = await mcpRequest([
399
429
  { jsonrpc: "2.0", id: 3, method: "resources/read", params: { uri: "nested_ts://schema" } },
@@ -403,6 +433,7 @@ test("MCP resources/read returns schema JSON", async () => {
403
433
  expect(schema.key).toBe("nested.ts");
404
434
  });
405
435
 
436
+ /** MCP tools/call runs stat_owner_lookup. */
406
437
  test("MCP tools/call runs stat_owner_lookup", async () => {
407
438
  const readme = join(import.meta.dir, "..", "README.md");
408
439
  const responses = await mcpRequest([
@@ -421,6 +452,7 @@ test("MCP tools/call runs stat_owner_lookup", async () => {
421
452
  expect(res.result.content[0]?.text).toContain("lookup user=test");
422
453
  });
423
454
 
455
+ /** MCP tools/call returns structuredContent for JSON stdout. */
424
456
  test("MCP tools/call returns structuredContent for JSON stdout", async () => {
425
457
  const readme = join(import.meta.dir, "..", "README.md");
426
458
  const responses = await mcpRequest([
@@ -446,6 +478,7 @@ test("MCP tools/call returns structuredContent for JSON stdout", async () => {
446
478
  expect(JSON.parse(res.result.content[0]?.text.trim())).toEqual({ user: "test", path: readme });
447
479
  });
448
480
 
481
+ /** MCP tools/call errors on missing required positional. */
449
482
  test("MCP tools/call errors on missing required positional", async () => {
450
483
  const responses = await mcpRequest([
451
484
  {
@@ -460,18 +493,21 @@ test("MCP tools/call errors on missing required positional", async () => {
460
493
  expect(res.result.content[0]?.text).toContain("Missing argument: path");
461
494
  });
462
495
 
496
+ /** MCP ping returns empty result. */
463
497
  test("MCP ping returns empty result", async () => {
464
498
  const responses = await mcpRequest([{ jsonrpc: "2.0", id: 99, method: "ping", params: {} }]);
465
499
  const res = responses.get(99) as { result: Record<string, never> };
466
500
  expect(res.result).toEqual({});
467
501
  });
468
502
 
503
+ /** Tests that minimal.ts mcp without opt-in fails. */
469
504
  test("minimal.ts mcp without opt-in fails", async () => {
470
505
  const { stderr, exitCode } = await $`bun run examples/minimal.ts mcp`.nothrow().quiet();
471
506
  expect(exitCode).toBe(1);
472
507
  expect(stderr.toString()).toContain("MCP is not available");
473
508
  });
474
509
 
510
+ /** MCP resources/list includes custom resource. */
475
511
  test("MCP resources/list includes custom resource", async () => {
476
512
  const responses = await mcpRequest(
477
513
  [{ jsonrpc: "2.0", id: 10, method: "resources/list", params: {} }],
@@ -484,6 +520,7 @@ test("MCP resources/list includes custom resource", async () => {
484
520
  expect(uris).toContain("test://hello");
485
521
  });
486
522
 
523
+ /** MCP resources/read returns docs topic resource body. */
487
524
  test("MCP resources/read returns docs topic resource body", async () => {
488
525
  const responses = await mcpRequest(
489
526
  [
@@ -500,6 +537,7 @@ test("MCP resources/read returns docs topic resource body", async () => {
500
537
  expect(res.result.contents[0]?.text).toBe("# MCP test readme\n");
501
538
  });
502
539
 
540
+ /** MCP resources/read returns custom resource body. */
503
541
  test("MCP resources/read returns custom resource body", async () => {
504
542
  const responses = await mcpRequest(
505
543
  [{ jsonrpc: "2.0", id: 11, method: "resources/read", params: { uri: "test://hello" } }],
@@ -509,6 +547,7 @@ test("MCP resources/read returns custom resource body", async () => {
509
547
  expect(res.result.contents[0]?.text).toBe("hello resource");
510
548
  });
511
549
 
550
+ /** MCP resources/read unknown URI returns error. */
512
551
  test("MCP resources/read unknown URI returns error", async () => {
513
552
  const responses = await mcpRequest(
514
553
  [{ jsonrpc: "2.0", id: 12, method: "resources/read", params: { uri: "missing://nope" } }],