argsbarg 5.1.8 → 5.1.9

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 (91) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/docs/cli-program.md +3 -1
  3. package/docs/config-schema.md +5 -3
  4. package/docs/configure.md +13 -7
  5. package/docs/distribution-homebrew.md +3 -2
  6. package/examples/full-example/justfile +2 -5
  7. package/index.d.ts +10 -1
  8. package/package.json +1 -1
  9. package/src/builtins/builtins.test.ts +1 -3
  10. package/src/builtins/completion-bash.ts +6 -27
  11. package/src/builtins/completion-group.ts +1 -2
  12. package/src/builtins/completion-simulate-shared.ts +2 -10
  13. package/src/builtins/completion-zsh.ts +7 -33
  14. package/src/builtins/config.test.ts +28 -0
  15. package/src/builtins/config.ts +40 -10
  16. package/src/builtins/configure-copy.ts +3 -6
  17. package/src/builtins/dispatch.ts +2 -10
  18. package/src/builtins/export.ts +1 -3
  19. package/src/builtins/presentation.ts +2 -7
  20. package/src/builtins/registry.ts +1 -5
  21. package/src/cli-tool/create.test.ts +3 -6
  22. package/src/cli-tool/create.ts +5 -22
  23. package/src/cli-tool/post-create.ts +1 -3
  24. package/src/cli-tool/program.ts +1 -2
  25. package/src/cli-tool/run-create.ts +2 -8
  26. package/src/cli.ts +6 -27
  27. package/src/config/bindings.test.ts +72 -0
  28. package/src/config/bindings.ts +115 -0
  29. package/src/config/bootstrap.ts +80 -65
  30. package/src/config/context.test.ts +45 -0
  31. package/src/config/context.ts +74 -14
  32. package/src/config/entry.ts +1 -3
  33. package/src/config/file.test.ts +46 -3
  34. package/src/config/file.ts +59 -19
  35. package/src/config/resolve.ts +1 -4
  36. package/src/config/schema.ts +1 -3
  37. package/src/config/validate.test.ts +3 -11
  38. package/src/config/validate.ts +30 -30
  39. package/src/config.integration.test.ts +1 -5
  40. package/src/configure/configure.test.ts +19 -5
  41. package/src/configure/index.ts +17 -15
  42. package/src/docs/api-guide.ts +1 -6
  43. package/src/docs/builtin.ts +1 -5
  44. package/src/docs/docs.test.ts +2 -6
  45. package/src/docs/mcp-guide.ts +3 -13
  46. package/src/docs/mcp-resources.test.ts +1 -3
  47. package/src/docs/mcp-resources.ts +1 -6
  48. package/src/docs/save.ts +2 -10
  49. package/src/formats.test.ts +1 -7
  50. package/src/formats.ts +1 -5
  51. package/src/headless.test.ts +5 -17
  52. package/src/help.ts +9 -51
  53. package/src/hidden-mcpb.test.ts +3 -12
  54. package/src/hidden.ts +1 -3
  55. package/src/install/binary-placement.test.ts +2 -6
  56. package/src/install/binary-placement.ts +1 -4
  57. package/src/install/gh-release-update.ts +5 -22
  58. package/src/install/mcp-codex.ts +1 -2
  59. package/src/install/mcp-config.ts +2 -12
  60. package/src/install/mcp-opencode.test.ts +1 -3
  61. package/src/install/mcp-opencode.ts +3 -15
  62. package/src/install/plan.ts +2 -10
  63. package/src/install/status.ts +3 -5
  64. package/src/install/target-base.ts +2 -11
  65. package/src/install/target-detect.ts +1 -5
  66. package/src/install/target-effective.ts +3 -13
  67. package/src/install/target-mcp-cli.ts +4 -26
  68. package/src/install/target-mcp-json.ts +3 -14
  69. package/src/install/target-plan-build.ts +1 -5
  70. package/src/install/target-registry.ts +13 -21
  71. package/src/install/target-scope.ts +8 -25
  72. package/src/install/target-types.ts +1 -6
  73. package/src/install/targets/app.ts +2 -2
  74. package/src/install/targets/opencode-mcp.ts +1 -6
  75. package/src/install/targets.test.ts +3 -10
  76. package/src/install/uninstall.ts +2 -9
  77. package/src/invoke.test.ts +1 -3
  78. package/src/mcp/bundle.ts +4 -19
  79. package/src/mcp/claude.test.ts +3 -3
  80. package/src/mcp/claude.ts +4 -17
  81. package/src/mcp/env.ts +1 -3
  82. package/src/mcp/server.ts +3 -12
  83. package/src/mcp/tools.ts +2 -11
  84. package/src/mcp.integration.test.ts +4 -10
  85. package/src/parse.test.ts +9 -34
  86. package/src/parse.ts +10 -44
  87. package/src/schema.ts +2 -10
  88. package/src/skill/hint.ts +1 -5
  89. package/src/skill/install.ts +1 -5
  90. package/src/test-fixtures.ts +1 -3
  91. package/src/validate.ts +29 -90
package/src/parse.test.ts CHANGED
@@ -12,12 +12,7 @@ import { cliPresentationRoot } from "./builtins/presentation.ts";
12
12
  import { cliHelpRender } from "./help.ts";
13
13
  import { Cli, CliFallbackMode, CliOptionKind, type CliProgram } from "./index.ts";
14
14
  import { applyShellEnv } from "./mcp/env.ts";
15
- import {
16
- allMcpResources,
17
- collectMcpTools,
18
- mcpToolCallToArgv,
19
- resolveMcpSchemaUri,
20
- } from "./mcp/tools.ts";
15
+ import { allMcpResources, collectMcpTools, mcpToolCallToArgv, resolveMcpSchemaUri } from "./mcp/tools.ts";
21
16
  import { ParseKind, parse, postParseValidate } from "./parse.ts";
22
17
  import { cliSchemaJson } from "./schema.ts";
23
18
  import { generatePluginSkillBundle, generateSkillBundle } from "./skill/generate.ts";
@@ -338,10 +333,7 @@ test("trailing options include parent-scoped flags", () => {
338
333
  ],
339
334
  });
340
335
  cliValidateProgram(root);
341
- const pr = postParseValidate(
342
- root,
343
- parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]),
344
- );
336
+ const pr = postParseValidate(root, parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]));
345
337
  expect(pr.kind).toBe(ParseKind.Ok);
346
338
  expect(pr.path).toEqual(["group", "leaf"]);
347
339
  expect(pr.args).toEqual(["./file"]);
@@ -415,10 +407,7 @@ test("stops parsing options at --", () => {
415
407
  ],
416
408
  });
417
409
  cliValidateProgram(root);
418
- const pr = postParseValidate(
419
- root,
420
- parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"]),
421
- );
410
+ const pr = postParseValidate(root, parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"]));
422
411
  expect(pr.kind).toBe(ParseKind.Ok);
423
412
  expect(pr.opts.name).toBe("pat");
424
413
  expect(pr.args).toEqual(["--name", "bob", "-x"]);
@@ -505,9 +494,7 @@ test("presence option cannot be required", () => {
505
494
  test("leaf completion help prints correctly", async () => {
506
495
  // Test the fix where `completion zsh -h` on a leaf root was incorrectly ignored.
507
496
  // We run this as a subprocess so we don't accidentally exit the test runner.
508
- const { stdout, stderr, exitCode } = await $`bun run examples/minimal.ts completion zsh -h`
509
- .nothrow()
510
- .quiet();
497
+ const { stdout, stderr, exitCode } = await $`bun run examples/minimal.ts completion zsh -h`.nothrow().quiet();
511
498
  const out = stdout.toString();
512
499
  expect(exitCode).toBe(0);
513
500
  expect(out).toContain("Show help for this command.");
@@ -517,9 +504,7 @@ test("leaf completion help prints correctly", async () => {
517
504
 
518
505
  /** Docs schema exports JSON for nested CLIs. */
519
506
  test("docs schema exports JSON for nested CLIs", async () => {
520
- const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts docs schema`
521
- .nothrow()
522
- .quiet();
507
+ const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts docs schema`.nothrow().quiet();
523
508
  expect(exitCode).toBe(0);
524
509
  expect(stderr.toString()).toBe("");
525
510
 
@@ -543,11 +528,7 @@ test("docs schema exports JSON for leaf roots", async () => {
543
528
  expect(schema.key).toBe("minimal.ts");
544
529
  expect(schema.positionals[0].name).toBe("name");
545
530
  expect(schema.options[0].name).toBe("verbose");
546
- expect(schema.commands.map((c: { key: string }) => c.key)).toEqual([
547
- "version",
548
- "configure",
549
- "docs",
550
- ]);
531
+ expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["version", "configure", "docs"]);
551
532
  });
552
533
 
553
534
  test("version builtin prints program version", async () => {
@@ -956,9 +937,7 @@ test("Enum completions list choices in bash script", () => {
956
937
  {
957
938
  key: "run",
958
939
  description: "",
959
- options: [
960
- { name: "mode", description: "m", kind: CliOptionKind.Enum, choices: ["dev", "prod"] },
961
- ],
940
+ options: [{ name: "mode", description: "m", kind: CliOptionKind.Enum, choices: ["dev", "prod"] }],
962
941
  handler: () => {},
963
942
  },
964
943
  ],
@@ -1047,9 +1026,7 @@ test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
1047
1026
  },
1048
1027
  ],
1049
1028
  });
1050
- expect(() => cliValidateProgram(root)).toThrow(
1051
- /fallbackCommand 'missing' is not a child of 'docs'/,
1052
- );
1029
+ expect(() => cliValidateProgram(root)).toThrow(/fallbackCommand 'missing' is not a child of 'docs'/);
1053
1030
  });
1054
1031
 
1055
1032
  test("cliValidateProgram accepts nested fallbackCommand when child exists", () => {
@@ -1279,9 +1256,7 @@ test("cliSkillInstall claude target uses .claude/skills", () => {
1279
1256
  try {
1280
1257
  const files = cliSkillInstall(nestedMcpFixture, "claude", { rimraf: true });
1281
1258
  expect(files.some((f) => f.includes(".claude/skills/nested_ts/"))).toBe(true);
1282
- expect(readFileSync(join(cwd, ".claude", "skills", "nested_ts", "SKILL.md"), "utf8")).toContain(
1283
- "Claude Code",
1284
- );
1259
+ expect(readFileSync(join(cwd, ".claude", "skills", "nested_ts", "SKILL.md"), "utf8")).toContain("Claude Code");
1285
1260
  } finally {
1286
1261
  process.chdir(prev);
1287
1262
  rmSync(cwd, { recursive: true, force: true });
package/src/parse.ts CHANGED
@@ -204,8 +204,7 @@ function consumeOptions(
204
204
  report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
205
205
  nextIndex: idx,
206
206
  };
207
- if (err)
208
- return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
207
+ if (err) return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
209
208
  } else {
210
209
  const err = consumeShort(tok);
211
210
  if (err === "")
@@ -213,8 +212,7 @@ function consumeOptions(
213
212
  report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
214
213
  nextIndex: idx,
215
214
  };
216
- if (err)
217
- return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
215
+ if (err) return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
218
216
  }
219
217
  }
220
218
 
@@ -317,11 +315,7 @@ function finishLeaf(
317
315
  }
318
316
  }
319
317
  if (count < argMin) {
320
- return errorResult(
321
- `Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`,
322
- path,
323
- [],
324
- );
318
+ return errorResult(`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`, path, []);
325
319
  }
326
320
  }
327
321
 
@@ -360,11 +354,7 @@ function finishLeaf(
360
354
  // ── Main Parser ───────────────────────────────────────────────────────────────
361
355
 
362
356
  /** Builds a user-error parse result; `path` defaults to `errorHelpPath`. */
363
- function errorResult(
364
- errorMsg: string,
365
- errorHelpPath: string[] = [],
366
- path: string[] = errorHelpPath,
367
- ): ParseResult {
357
+ function errorResult(errorMsg: string, errorHelpPath: string[] = [], path: string[] = errorHelpPath): ParseResult {
368
358
  return {
369
359
  kind: ParseKind.Error,
370
360
  path,
@@ -466,9 +456,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
466
456
  node = findChild(root.commands, cmdName);
467
457
  if (!node) {
468
458
  return errorResult(
469
- forcePositionals
470
- ? `Expected subcommand but got positional: ${cmdName}`
471
- : `Unknown command: ${cmdName}`,
459
+ forcePositionals ? `Expected subcommand but got positional: ${cmdName}` : `Unknown command: ${cmdName}`,
472
460
  path,
473
461
  [],
474
462
  );
@@ -504,10 +492,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
504
492
  if (isCliRouter(current) && current.commands.length > 0) {
505
493
  const fb = current.fallbackCommand;
506
494
  const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
507
- if (
508
- fb !== undefined &&
509
- (fm === CliFallbackMode.MissingOnly || fm === CliFallbackMode.MissingOrUnknown)
510
- ) {
495
+ if (fb !== undefined && (fm === CliFallbackMode.MissingOnly || fm === CliFallbackMode.MissingOrUnknown)) {
511
496
  const fbNode = findChild(current.commands, fb);
512
497
  if (fbNode) {
513
498
  path.push(fb);
@@ -520,15 +505,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
520
505
  if (!isCliLeaf(current)) {
521
506
  return helpResult(path, false);
522
507
  }
523
- return finishLeaf(
524
- current,
525
- i,
526
- argv,
527
- path,
528
- opts,
529
- collectOptionDefs(root, path),
530
- forcePositionals,
531
- );
508
+ return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
532
509
  }
533
510
 
534
511
  const tok = argv[i];
@@ -550,8 +527,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
550
527
  const fb = current.fallbackCommand;
551
528
  const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
552
529
  const canRouteUnknown =
553
- fb !== undefined &&
554
- (fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
530
+ fb !== undefined && (fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
555
531
 
556
532
  if (canRouteUnknown && fb !== undefined) {
557
533
  const fbNode = findChild(current.commands, fb);
@@ -563,9 +539,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
563
539
  }
564
540
 
565
541
  return errorResult(
566
- forcePositionals
567
- ? `Expected subcommand but got positional: ${tok}`
568
- : `Unknown subcommand: ${tok}`,
542
+ forcePositionals ? `Expected subcommand but got positional: ${tok}` : `Unknown subcommand: ${tok}`,
569
543
  path,
570
544
  );
571
545
  }
@@ -573,15 +547,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
573
547
  if (!isCliLeaf(current)) {
574
548
  return helpResult(path, false);
575
549
  }
576
- return finishLeaf(
577
- current,
578
- i,
579
- argv,
580
- path,
581
- opts,
582
- collectOptionDefs(root, path),
583
- forcePositionals,
584
- );
550
+ return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
585
551
  }
586
552
  }
587
553
 
package/src/schema.ts CHANGED
@@ -5,13 +5,7 @@ This module serializes the CLI schema tree to JSON for machine-readable introspe
5
5
  import { type CliSchemaExport, exportPresentationBuiltins } from "./builtins/export.ts";
6
6
  import { cliResolveNotes } from "./help.ts";
7
7
  import { visibleOptions } from "./hidden.ts";
8
- import {
9
- type CliNode,
10
- type CliProgram,
11
- isCliLeaf,
12
- isCliRouter,
13
- leafOutputSchema,
14
- } from "./types.ts";
8
+ import { type CliNode, type CliProgram, isCliLeaf, isCliRouter, leafOutputSchema } from "./types.ts";
15
9
 
16
10
  const RESERVED = new Set(["completion", "configure", "docs", "mcp", "version"]);
17
11
 
@@ -55,9 +49,7 @@ function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
55
49
 
56
50
  const children = isCliRouter(cmd) ? cmd.commands.filter((ch) => !RESERVED.has(ch.key)) : [];
57
51
  if (children.length > 0) {
58
- out.commands = children
59
- .map((ch) => exportCommand(ch, root))
60
- .filter((ch): ch is CliSchemaExport => ch !== null);
52
+ out.commands = children.map((ch) => exportCommand(ch, root)).filter((ch): ch is CliSchemaExport => ch !== null);
61
53
  }
62
54
 
63
55
  return out;
package/src/skill/hint.ts CHANGED
@@ -9,11 +9,7 @@ export function generatedFileHtmlComment(source: string): string {
9
9
  }
10
10
 
11
11
  /** Prepends a hint, or inserts after frontmatter when requested. */
12
- export function insertGeneratedHint(
13
- content: string,
14
- hint: string,
15
- options?: { afterFrontmatter?: boolean },
16
- ): string {
12
+ export function insertGeneratedHint(content: string, hint: string, options?: { afterFrontmatter?: boolean }): string {
17
13
  if (options?.afterFrontmatter) {
18
14
  const match = content.match(MARKDOWN_FRONTMATTER_RE);
19
15
  if (match) {
@@ -30,11 +30,7 @@ function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean):
30
30
  }
31
31
 
32
32
  /** Writes SKILL.md and reference.md; returns changed file paths. */
33
- export function cliSkillInstall(
34
- root: CliProgram,
35
- target: SkillTarget,
36
- opts: SkillInstallOpts,
37
- ): string[] {
33
+ export function cliSkillInstall(root: CliProgram, target: SkillTarget, opts: SkillInstallOpts): string[] {
38
34
  const bundle = generateSkillBundle(root, target);
39
35
  const { skillMd, referenceMd } = applySkillInstallHints(root, bundle.skillMd, bundle.referenceMd);
40
36
  const dir = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
@@ -1,9 +1,7 @@
1
1
  import type { CliProgram } from "./types.ts";
2
2
  import { CliFallbackMode, CliOptionKind } from "./types.ts";
3
3
 
4
- export function testProgram(
5
- prog: Record<string, unknown> & { key: string; description: string },
6
- ): CliProgram {
4
+ export function testProgram(prog: Record<string, unknown> & { key: string; description: string }): CliProgram {
7
5
  return { version: "0.0.0", ...prog } as CliProgram;
8
6
  }
9
7
 
package/src/validate.ts CHANGED
@@ -29,15 +29,11 @@ function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
29
29
  }
30
30
  for (const reserved of DOCS_BUILTIN_TOPIC_KEYS) {
31
31
  if (reserved in docs.topics) {
32
- throw new CliSchemaValidationError(
33
- `docs.topics key '${reserved}' is reserved for the docs built-in`,
34
- );
32
+ throw new CliSchemaValidationError(`docs.topics key '${reserved}' is reserved for the docs built-in`);
35
33
  }
36
34
  }
37
35
  if (docs.defaultTopic !== undefined && !(docs.defaultTopic in docs.topics)) {
38
- throw new CliSchemaValidationError(
39
- `docs.defaultTopic '${docs.defaultTopic}' is not a key in docs.topics`,
40
- );
36
+ throw new CliSchemaValidationError(`docs.defaultTopic '${docs.defaultTopic}' is not a key in docs.topics`);
41
37
  }
42
38
  for (const key of keys) {
43
39
  const text = docs.topics[key]?.text;
@@ -57,18 +53,14 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
57
53
  const envNames = new Set<string>();
58
54
  for (const [key, entry] of Object.entries(entries)) {
59
55
  if (key.length === 0) {
60
- throw new CliSchemaValidationError(
61
- "program.appConfig.entries keys must be non-empty strings",
62
- );
56
+ throw new CliSchemaValidationError("program.appConfig.entries keys must be non-empty strings");
63
57
  }
64
58
  if (entry === undefined || typeof entry !== "object") {
65
59
  throw new CliSchemaValidationError(`program.appConfig.entries['${key}'] must be an object`);
66
60
  }
67
61
  const description = entry.description;
68
62
  if (typeof description !== "string" || description.trim().length === 0) {
69
- throw new CliSchemaValidationError(
70
- `program.appConfig.entries['${key}'].description must be a non-empty string`,
71
- );
63
+ throw new CliSchemaValidationError(`program.appConfig.entries['${key}'].description must be a non-empty string`);
72
64
  }
73
65
  if (entry.env !== undefined) {
74
66
  if (typeof entry.env !== "string" || entry.env.length === 0) {
@@ -82,9 +74,7 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
82
74
  envNames.add(entry.env);
83
75
  }
84
76
  if (entry.resolve !== undefined && typeof entry.resolve !== "function") {
85
- throw new CliSchemaValidationError(
86
- `program.appConfig.entries['${key}'].resolve must be a function when set`,
87
- );
77
+ throw new CliSchemaValidationError(`program.appConfig.entries['${key}'].resolve must be a function when set`);
88
78
  }
89
79
  }
90
80
 
@@ -98,9 +88,7 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
98
88
  const properties = jsonSchema.properties;
99
89
  if (properties !== undefined) {
100
90
  if (typeof properties !== "object" || properties === null || Array.isArray(properties)) {
101
- throw new CliSchemaValidationError(
102
- "program.appConfig.jsonSchema.properties must be an object when set",
103
- );
91
+ throw new CliSchemaValidationError("program.appConfig.jsonSchema.properties must be an object when set");
104
92
  }
105
93
  for (const key of Object.keys(entries)) {
106
94
  if (!(key in properties)) {
@@ -133,9 +121,7 @@ function validateConfigureConfig(program: CliProgram): void {
133
121
  if (!configure) return;
134
122
 
135
123
  if ("prefix" in configure) {
136
- throw new CliSchemaValidationError(
137
- "configure.prefix removed; app binary installs via Homebrew",
138
- );
124
+ throw new CliSchemaValidationError("configure.prefix removed; app binary installs via Homebrew");
139
125
  }
140
126
 
141
127
  if (!configure.targets) return;
@@ -178,10 +164,7 @@ function validateConfigureConfig(program: CliProgram): void {
178
164
  }
179
165
 
180
166
  for (const mcpKey of MCP_KEYS) {
181
- if (
182
- !mcpServerRequiredForArtifact(mcpKey, mcpEnabled) &&
183
- installTargetExplicitTruthy(targets[mcpKey])
184
- ) {
167
+ if (!mcpServerRequiredForArtifact(mcpKey, mcpEnabled) && installTargetExplicitTruthy(targets[mcpKey])) {
185
168
  throw new CliSchemaValidationError(`configure.targets.${mcpKey} requires mcpServer.enabled`);
186
169
  }
187
170
  }
@@ -194,15 +177,11 @@ export function cliValidateProgram(program: CliProgram): void {
194
177
  }
195
178
 
196
179
  if (program.mcpServer !== undefined && program.mcpServer.enabled !== true) {
197
- throw new CliSchemaValidationError(
198
- "mcpServer requires enabled: true; omit mcpServer to disable MCP",
199
- );
180
+ throw new CliSchemaValidationError("mcpServer requires enabled: true; omit mcpServer to disable MCP");
200
181
  }
201
182
 
202
183
  if (program.docs !== undefined && program.docs.enabled !== true) {
203
- throw new CliSchemaValidationError(
204
- "docs requires enabled: true; omit docs to disable bundled documentation",
205
- );
184
+ throw new CliSchemaValidationError("docs requires enabled: true; omit docs to disable bundled documentation");
206
185
  }
207
186
 
208
187
  if (program.docs?.enabled === true) {
@@ -235,24 +214,16 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
235
214
  if (!isRoot) {
236
215
  const rogue = node as CliProgram;
237
216
  if (rogue.mcpServer !== undefined) {
238
- throw new CliSchemaValidationError(
239
- `mcpServer is only supported on the program root (not on ${node.key})`,
240
- );
217
+ throw new CliSchemaValidationError(`mcpServer is only supported on the program root (not on ${node.key})`);
241
218
  }
242
219
  if (rogue.configure !== undefined) {
243
- throw new CliSchemaValidationError(
244
- `configure is only supported on the program root (not on ${node.key})`,
245
- );
220
+ throw new CliSchemaValidationError(`configure is only supported on the program root (not on ${node.key})`);
246
221
  }
247
222
  if (rogue.docs !== undefined) {
248
- throw new CliSchemaValidationError(
249
- `docs is only supported on the program root (not on ${node.key})`,
250
- );
223
+ throw new CliSchemaValidationError(`docs is only supported on the program root (not on ${node.key})`);
251
224
  }
252
225
  if (rogue.appConfig !== undefined) {
253
- throw new CliSchemaValidationError(
254
- `appConfig is only supported on the program root (not on ${node.key})`,
255
- );
226
+ throw new CliSchemaValidationError(`appConfig is only supported on the program root (not on ${node.key})`);
256
227
  }
257
228
  }
258
229
 
@@ -266,20 +237,13 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
266
237
  throw new CliSchemaValidationError("Set outputSchema on the leaf only, not under mcpTool");
267
238
  }
268
239
  const resolved = outputSchema ?? legacyOutputSchema;
269
- if (
270
- resolved !== undefined &&
271
- (typeof resolved !== "object" || resolved === null || Array.isArray(resolved))
272
- ) {
273
- throw new CliSchemaValidationError(
274
- "outputSchema must be a JSON Schema object (not null or an array)",
275
- );
240
+ if (resolved !== undefined && (typeof resolved !== "object" || resolved === null || Array.isArray(resolved))) {
241
+ throw new CliSchemaValidationError("outputSchema must be a JSON Schema object (not null or an array)");
276
242
  }
277
243
  } else {
278
244
  const rogue = node as unknown as CliLeaf;
279
245
  if (rogue.mcpTool !== undefined) {
280
- throw new CliSchemaValidationError(
281
- `mcpTool is only supported on leaf commands (not on ${node.key})`,
282
- );
246
+ throw new CliSchemaValidationError(`mcpTool is only supported on leaf commands (not on ${node.key})`);
283
247
  }
284
248
  }
285
249
 
@@ -290,9 +254,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
290
254
  for (const uri of uris) {
291
255
  if (reserved.has(uri)) {
292
256
  const kind = uri === schemaUri ? "built-in schema resource" : "auto docs topic resource";
293
- throw new CliSchemaValidationError(
294
- `mcpServer.resources URI '${uri}' conflicts with ${kind}`,
295
- );
257
+ throw new CliSchemaValidationError(`mcpServer.resources URI '${uri}' conflicts with ${kind}`);
296
258
  }
297
259
  }
298
260
  if (new Set(uris).size !== uris.length) {
@@ -316,9 +278,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
316
278
  if (node.fallbackCommand !== undefined) {
317
279
  const valid = node.commands.find((c) => c.key === node.fallbackCommand);
318
280
  if (!valid) {
319
- throw new CliSchemaValidationError(
320
- `fallbackCommand '${node.fallbackCommand}' is not a child of '${node.key}'`,
321
- );
281
+ throw new CliSchemaValidationError(`fallbackCommand '${node.fallbackCommand}' is not a child of '${node.key}'`);
322
282
  }
323
283
  }
324
284
 
@@ -336,21 +296,15 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
336
296
  const seenShorts = new Set<string>();
337
297
  for (const opt of options) {
338
298
  if (opt.required && opt.kind === CliOptionKind.Presence) {
339
- throw new CliSchemaValidationError(
340
- `Presence option cannot be required: ${scopeKey}/${opt.name}`,
341
- );
299
+ throw new CliSchemaValidationError(`Presence option cannot be required: ${scopeKey}/${opt.name}`);
342
300
  }
343
301
 
344
302
  if (opt.shortName !== undefined) {
345
303
  if (opt.shortName === "h") {
346
- throw new CliSchemaValidationError(
347
- `Short alias -h is reserved for help: ${scopeKey}/${opt.name}`,
348
- );
304
+ throw new CliSchemaValidationError(`Short alias -h is reserved for help: ${scopeKey}/${opt.name}`);
349
305
  }
350
306
  if (seenShorts.has(opt.shortName)) {
351
- throw new CliSchemaValidationError(
352
- `Duplicate short alias -${opt.shortName} in scope ${scopeKey}`,
353
- );
307
+ throw new CliSchemaValidationError(`Duplicate short alias -${opt.shortName} in scope ${scopeKey}`);
354
308
  }
355
309
  seenShorts.add(opt.shortName);
356
310
  }
@@ -362,9 +316,7 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
362
316
  );
363
317
  }
364
318
  if (new Set(opt.choices).size !== opt.choices.length) {
365
- throw new CliSchemaValidationError(
366
- `Option '${opt.name}' on '${scopeKey}': Enum choices must be distinct`,
367
- );
319
+ throw new CliSchemaValidationError(`Option '${opt.name}' on '${scopeKey}': Enum choices must be distinct`);
368
320
  }
369
321
  for (const choice of opt.choices) {
370
322
  if (choice.length === 0) {
@@ -374,9 +326,7 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
374
326
  }
375
327
  }
376
328
  } else if (opt.choices !== undefined) {
377
- throw new CliSchemaValidationError(
378
- `Option '${opt.name}' on '${scopeKey}': choices is only valid for Enum kind`,
379
- );
329
+ throw new CliSchemaValidationError(`Option '${opt.name}' on '${scopeKey}': choices is only valid for Enum kind`);
380
330
  }
381
331
 
382
332
  if (opt.format !== undefined || opt.pattern !== undefined || opt.default !== undefined) {
@@ -398,9 +348,7 @@ function validateOptionValueMetadata(scopeKey: string, opt: import("./types.ts")
398
348
  }
399
349
 
400
350
  if (opt.format !== undefined && opt.pattern !== undefined) {
401
- throw new CliSchemaValidationError(
402
- `Option ${label}: format and pattern are mutually exclusive`,
403
- );
351
+ throw new CliSchemaValidationError(`Option ${label}: format and pattern are mutually exclusive`);
404
352
  }
405
353
 
406
354
  if (opt.format !== undefined) {
@@ -433,15 +381,10 @@ function validateOptionValueMetadata(scopeKey: string, opt: import("./types.ts")
433
381
  }
434
382
  }
435
383
 
436
- function validatePositionals(
437
- scopeKey: string,
438
- positionals: import("./types.ts").CliPositional[],
439
- ): void {
384
+ function validatePositionals(scopeKey: string, positionals: import("./types.ts").CliPositional[]): void {
440
385
  for (const p of positionals) {
441
386
  if (p.argMin !== undefined && p.argMin < 0) {
442
- throw new CliSchemaValidationError(
443
- `argMin must be >= 0 for positional ${scopeKey}/${p.name}`,
444
- );
387
+ throw new CliSchemaValidationError(`argMin must be >= 0 for positional ${scopeKey}/${p.name}`);
445
388
  }
446
389
  if (p.argMax !== undefined && p.argMax < 0) {
447
390
  throw new CliSchemaValidationError(
@@ -450,9 +393,7 @@ function validatePositionals(
450
393
  }
451
394
  const { argMin = 1, argMax = 1 } = p;
452
395
  if (argMax > 0 && argMin > argMax) {
453
- throw new CliSchemaValidationError(
454
- `argMin must not exceed argMax for positional ${scopeKey}/${p.name}`,
455
- );
396
+ throw new CliSchemaValidationError(`argMin must not exceed argMax for positional ${scopeKey}/${p.name}`);
456
397
  }
457
398
  }
458
399
 
@@ -473,9 +414,7 @@ function validatePositionals(
473
414
  }
474
415
  const { argMax = 1 } = positional;
475
416
  if (argMax === 0 && idx + 1 < positionals.length) {
476
- throw new CliSchemaValidationError(
477
- `Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`,
478
- );
417
+ throw new CliSchemaValidationError(`Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`);
479
418
  }
480
419
  }
481
420
  }