argsbarg 3.4.1 → 3.5.0

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 (66) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/biome.json +29 -6
  3. package/bun.lock +22 -0
  4. package/docs/install.md +1 -1
  5. package/docs/mcp.md +63 -3
  6. package/index.d.ts +51 -51
  7. package/justfile +27 -6
  8. package/package.json +4 -2
  9. package/scripts/release.ts +26 -9
  10. package/src/builtins/builtins.test.ts +9 -4
  11. package/src/builtins/completion-bash.ts +74 -50
  12. package/src/builtins/completion-fish.ts +3 -8
  13. package/src/builtins/completion-group.ts +1 -1
  14. package/src/builtins/completion-zsh.ts +80 -42
  15. package/src/builtins/dispatch.ts +20 -16
  16. package/src/builtins/export.ts +19 -10
  17. package/src/builtins/index.ts +9 -4
  18. package/src/builtins/install.ts +10 -10
  19. package/src/builtins/mcp.ts +3 -3
  20. package/src/builtins/presentation.ts +8 -8
  21. package/src/builtins/scopes.ts +1 -1
  22. package/src/builtins/version.ts +1 -1
  23. package/src/completion.ts +4 -4
  24. package/src/docs/api-guide.test.ts +2 -2
  25. package/src/docs/api-guide.ts +2 -2
  26. package/src/docs/builtin.ts +27 -8
  27. package/src/docs/docs.test.ts +23 -12
  28. package/src/docs/mcp-guide.ts +112 -11
  29. package/src/docs/resolve.ts +10 -3
  30. package/src/docs/save.ts +11 -3
  31. package/src/headless.test.ts +8 -16
  32. package/src/help.ts +73 -43
  33. package/src/hidden-mcpb.test.ts +8 -10
  34. package/src/hidden.ts +2 -2
  35. package/src/index.test.ts +113 -89
  36. package/src/index.ts +24 -24
  37. package/src/install/binary.ts +12 -5
  38. package/src/install/completions.ts +7 -3
  39. package/src/install/detect-installed.ts +35 -4
  40. package/src/install/gh-release-update.ts +31 -23
  41. package/src/install/index.ts +69 -19
  42. package/src/install/install.test.ts +57 -8
  43. package/src/install/mcp-codex.test.ts +57 -0
  44. package/src/install/mcp-codex.ts +125 -0
  45. package/src/install/mcp-config.ts +12 -5
  46. package/src/install/mcp-opencode.test.ts +98 -0
  47. package/src/install/mcp-opencode.ts +149 -0
  48. package/src/install/paths.ts +51 -3
  49. package/src/install/plan.ts +96 -7
  50. package/src/install/shell.ts +1 -4
  51. package/src/install/status.ts +15 -7
  52. package/src/install/uninstall.ts +49 -5
  53. package/src/install/update.test.ts +2 -2
  54. package/src/install/update.ts +3 -1
  55. package/src/invoke.ts +12 -9
  56. package/src/mcp/bundle.ts +38 -14
  57. package/src/mcp/env.ts +7 -13
  58. package/src/mcp/server.ts +12 -6
  59. package/src/mcp/tools.ts +20 -4
  60. package/src/mcp.ts +3 -3
  61. package/src/parse.ts +96 -24
  62. package/src/runtime.ts +22 -12
  63. package/src/schema.ts +11 -5
  64. package/src/skill/generate.ts +4 -4
  65. package/src/skill/install.ts +6 -2
  66. package/src/validate.ts +21 -16
package/src/parse.ts CHANGED
@@ -7,12 +7,11 @@ It keeps handler dispatch and help on one parser so the CLI behavior stays consi
7
7
  across every entry path.
8
8
  */
9
9
 
10
- import { CliContext } from "./context.ts";
11
10
  import {
12
- type CliLeaf,
13
- CliNode,
14
11
  CliFallbackMode,
15
- CliOption,
12
+ type CliLeaf,
13
+ type CliNode,
14
+ type CliOption,
16
15
  CliOptionKind,
17
16
  isCliLeaf,
18
17
  isCliRouter,
@@ -191,17 +190,30 @@ function consumeOptions(
191
190
 
192
191
  if (tok === "--") {
193
192
  idx += 1;
194
- return { report: { err: null, stoppedOnUnknown: false, sawDoubleDash: true }, nextIndex: idx };
193
+ return {
194
+ report: { err: null, stoppedOnUnknown: false, sawDoubleDash: true },
195
+ nextIndex: idx,
196
+ };
195
197
  }
196
198
 
197
199
  if (tok.startsWith("--")) {
198
200
  const err = consumeLong(tok);
199
- if (err === "") return { report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false }, nextIndex: idx };
200
- if (err) return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
201
+ if (err === "")
202
+ return {
203
+ report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
204
+ nextIndex: idx,
205
+ };
206
+ if (err)
207
+ return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
201
208
  } else {
202
209
  const err = consumeShort(tok);
203
- if (err === "") return { report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false }, nextIndex: idx };
204
- if (err) return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
210
+ if (err === "")
211
+ return {
212
+ report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
213
+ nextIndex: idx,
214
+ };
215
+ if (err)
216
+ return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
205
217
  }
206
218
  }
207
219
 
@@ -212,7 +224,7 @@ function consumeOptions(
212
224
 
213
225
  /** Merges option defs from the program root along the routed command path. */
214
226
  export function collectOptionDefs(root: CliNode, path: string[]): CliOption[] {
215
- let defs = [...(root.options ?? [])];
227
+ const defs = [...(root.options ?? [])];
216
228
  let node: CliNode = root;
217
229
 
218
230
  for (const seg of path) {
@@ -343,7 +355,16 @@ function finishLeaf(
343
355
  }
344
356
  }
345
357
 
346
- return { kind: ParseKind.Ok, path, opts, args, helpExplicit: false, helpPath: [], errorMsg: "", errorHelpPath: [] };
358
+ return {
359
+ kind: ParseKind.Ok,
360
+ path,
361
+ opts,
362
+ args,
363
+ helpExplicit: false,
364
+ helpPath: [],
365
+ errorMsg: "",
366
+ errorHelpPath: [],
367
+ };
347
368
  }
348
369
 
349
370
  // ── Main Parser ───────────────────────────────────────────────────────────────
@@ -414,7 +435,16 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
414
435
  cmdName = root.fallbackCommand;
415
436
  node = findChild(root.commands, cmdName);
416
437
  if (!node) {
417
- return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
438
+ return {
439
+ kind: ParseKind.Error,
440
+ path: [],
441
+ opts: {},
442
+ args: [],
443
+ helpExplicit: false,
444
+ helpPath: [],
445
+ errorMsg: `Unknown command: ${cmdName}`,
446
+ errorHelpPath: path,
447
+ };
418
448
  }
419
449
  } else {
420
450
  return helpResult([], false);
@@ -428,16 +458,26 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
428
458
  i += 1;
429
459
  node = childPick;
430
460
  } else {
461
+ const fallbackCommand = root.fallbackCommand;
431
462
  const canRouteUnknown =
432
- root.fallbackCommand !== undefined &&
463
+ fallbackCommand !== undefined &&
433
464
  ((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
434
465
  (root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.UnknownOnly);
435
466
 
436
467
  if (canRouteUnknown) {
437
- cmdName = root.fallbackCommand!;
468
+ cmdName = fallbackCommand;
438
469
  node = findChild(root.commands, cmdName);
439
470
  if (!node) {
440
- return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
471
+ return {
472
+ kind: ParseKind.Error,
473
+ path: [],
474
+ opts: {},
475
+ args: [],
476
+ helpExplicit: false,
477
+ helpPath: [],
478
+ errorMsg: `Unknown command: ${cmdName}`,
479
+ errorHelpPath: path,
480
+ };
441
481
  }
442
482
  } else {
443
483
  cmdName = peek;
@@ -451,7 +491,9 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
451
491
  args: [],
452
492
  helpExplicit: false,
453
493
  helpPath: [],
454
- errorMsg: forcePositionals ? `Expected subcommand but got positional: ${cmdName}` : `Unknown command: ${cmdName}`,
494
+ errorMsg: forcePositionals
495
+ ? `Expected subcommand but got positional: ${cmdName}`
496
+ : `Unknown command: ${cmdName}`,
455
497
  errorHelpPath: path,
456
498
  };
457
499
  }
@@ -460,7 +502,19 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
460
502
  }
461
503
 
462
504
  path.push(cmdName);
463
- let current = node!;
505
+ if (!node) {
506
+ return {
507
+ kind: ParseKind.Error,
508
+ path,
509
+ opts: {},
510
+ args: [],
511
+ helpExplicit: false,
512
+ helpPath: [],
513
+ errorMsg: `Unknown command: ${cmdName}`,
514
+ errorHelpPath: path,
515
+ };
516
+ }
517
+ let current = node;
464
518
 
465
519
  // Walk the command tree
466
520
  while (true) {
@@ -508,7 +562,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
508
562
  if (!isCliLeaf(current)) {
509
563
  return helpResult(path, false);
510
564
  }
511
- return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
565
+ return finishLeaf(
566
+ current,
567
+ i,
568
+ argv,
569
+ path,
570
+ opts,
571
+ collectOptionDefs(root, path),
572
+ forcePositionals,
573
+ );
512
574
  }
513
575
 
514
576
  const tok = argv[i];
@@ -542,10 +604,10 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
542
604
  fb !== undefined &&
543
605
  (fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
544
606
 
545
- if (canRouteUnknown) {
546
- const fbNode = findChild(current.commands, fb!);
607
+ if (canRouteUnknown && fb !== undefined) {
608
+ const fbNode = findChild(current.commands, fb);
547
609
  if (fbNode) {
548
- path.push(fb!);
610
+ path.push(fb);
549
611
  current = fbNode;
550
612
  continue;
551
613
  }
@@ -558,7 +620,9 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
558
620
  args: [],
559
621
  helpExplicit: false,
560
622
  helpPath: [],
561
- errorMsg: forcePositionals ? `Expected subcommand but got positional: ${tok}` : `Unknown subcommand: ${tok}`,
623
+ errorMsg: forcePositionals
624
+ ? `Expected subcommand but got positional: ${tok}`
625
+ : `Unknown subcommand: ${tok}`,
562
626
  errorHelpPath: path,
563
627
  };
564
628
  }
@@ -566,7 +630,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
566
630
  if (!isCliLeaf(current)) {
567
631
  return helpResult(path, false);
568
632
  }
569
- return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
633
+ return finishLeaf(
634
+ current,
635
+ i,
636
+ argv,
637
+ path,
638
+ opts,
639
+ collectOptionDefs(root, path),
640
+ forcePositionals,
641
+ );
570
642
  }
571
643
  }
572
644
 
@@ -578,7 +650,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
578
650
  export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
579
651
  if (pr.kind !== ParseKind.Ok) return pr;
580
652
 
581
- let defs = [...(root.options ?? [])];
653
+ const defs = [...(root.options ?? [])];
582
654
  let node: CliNode = root;
583
655
 
584
656
  for (const seg of pr.path) {
package/src/runtime.ts CHANGED
@@ -2,26 +2,29 @@
2
2
  This module runs parsed commands, help, errors, completion, and leaf handlers.
3
3
  */
4
4
 
5
- import { resolveCapabilities } from "./capabilities.ts";
6
5
  import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
7
6
  import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
8
- import type { CliRouter } from "./types.ts";
9
- import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
7
+ import { resolveCapabilities } from "./capabilities.ts";
10
8
  import { CliContext } from "./context.ts";
11
9
  import { cliHelpRender } from "./help.ts";
12
- import { parse, postParseValidate, ParseKind } from "./parse.ts";
10
+ import { ParseKind, parse, postParseValidate } from "./parse.ts";
11
+ import type { CliRouter } from "./types.ts";
12
+ import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
13
13
  import { cliValidateProgram } from "./validate.ts";
14
14
 
15
15
  function cliRootMergedWithBuiltins(program: CliProgram): CliRouter {
16
16
  return cliParseRoot(program);
17
17
  }
18
18
 
19
- export async function cliRun(program: CliProgram, argv: string[] = process.argv.slice(2)): Promise<never> {
19
+ export async function cliRun(
20
+ program: CliProgram,
21
+ argv: string[] = process.argv.slice(2),
22
+ ): Promise<never> {
20
23
  try {
21
24
  cliValidateProgram(program);
22
25
  } catch (err) {
23
26
  if (err instanceof Error) {
24
- process.stderr.write(err.message + "\n");
27
+ process.stderr.write(`${err.message}\n`);
25
28
  } else {
26
29
  process.stderr.write("Invalid CLI definition.\n");
27
30
  }
@@ -31,12 +34,16 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
31
34
  const caps = resolveCapabilities(program);
32
35
 
33
36
  if (argv.length >= 1 && argv[0] === "mcp" && !caps.mcp) {
34
- process.stderr.write("MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n");
37
+ process.stderr.write(
38
+ "MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n",
39
+ );
35
40
  process.exit(1);
36
41
  }
37
42
 
38
43
  if (argv.length >= 1 && argv[0] === "install" && !caps.install) {
39
- process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
44
+ process.stderr.write(
45
+ "install is disabled. Remove install.enabled: false from the program root.\n",
46
+ );
40
47
  process.exit(1);
41
48
  }
42
49
 
@@ -75,13 +82,16 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
75
82
  if (pr.kind === "error") {
76
83
  const color = process.stderr.isTTY;
77
84
  const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
78
- process.stderr.write(msg + "\n");
85
+ process.stderr.write(`${msg}\n`);
79
86
  process.stderr.write(cliHelpRender(cliPresentationRoot(program), pr.errorHelpPath, true));
80
87
  process.exit(1);
81
88
  }
82
89
 
83
90
  if (pr.kind === ParseKind.Ok) {
84
- await dispatchBuiltin(program, pr, { isLeafCompletionIntercept, parseRoot: completionParseRoot });
91
+ await dispatchBuiltin(program, pr, {
92
+ isLeafCompletionIntercept,
93
+ parseRoot: completionParseRoot,
94
+ });
85
95
  }
86
96
 
87
97
  let current: CliNode = parseRoot;
@@ -109,7 +119,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
109
119
  process.exit(0);
110
120
  } catch (err) {
111
121
  if (err instanceof Error) {
112
- process.stderr.write(err.message + "\n");
122
+ process.stderr.write(`${err.message}\n`);
113
123
  }
114
124
  process.exit(1);
115
125
  }
@@ -118,7 +128,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
118
128
  export function cliErrWithHelp(ctx: CliContext, msg: string): never {
119
129
  const color = process.stderr.isTTY;
120
130
  const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
121
- process.stderr.write(line + "\n");
131
+ process.stderr.write(`${line}\n`);
122
132
  process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.program), ctx.commandPath, true));
123
133
  process.exit(1);
124
134
  }
package/src/schema.ts CHANGED
@@ -2,10 +2,16 @@
2
2
  This module serializes the CLI schema tree to JSON for machine-readable introspection.
3
3
  */
4
4
 
5
- import { type CliNode, type CliProgram, isCliLeaf, isCliRouter, leafOutputSchema } from "./types.ts";
6
- import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
5
+ import { type CliSchemaExport, exportPresentationBuiltins } from "./builtins/export.ts";
7
6
  import { cliResolveNotes } from "./help.ts";
8
7
  import { visibleOptions } from "./hidden.ts";
8
+ import {
9
+ type CliNode,
10
+ type CliProgram,
11
+ isCliLeaf,
12
+ isCliRouter,
13
+ leafOutputSchema,
14
+ } from "./types.ts";
9
15
 
10
16
  const RESERVED = new Set(["completion", "install", "docs", "mcp", "version"]);
11
17
 
@@ -60,8 +66,8 @@ function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
60
66
  /** Resolves `{argsbarg:program}` in exported notes using the root program key. */
61
67
  function resolveSchemaNotes(node: CliSchemaExport, appKey: string): CliSchemaExport {
62
68
  const out: CliSchemaExport = { ...node };
63
- if ((out.notes ?? "").length > 0) {
64
- out.notes = cliResolveNotes(out.notes!, appKey);
69
+ if ((out.notes ?? "").length > 0 && out.notes !== undefined) {
70
+ out.notes = cliResolveNotes(out.notes, appKey);
65
71
  }
66
72
  if (out.commands) {
67
73
  out.commands = out.commands.map((ch) => resolveSchemaNotes(ch, appKey));
@@ -83,7 +89,7 @@ export function cliSchemaExport(root: CliProgram): CliSchemaExport {
83
89
  }
84
90
 
85
91
  export function cliSchemaJson(root: CliProgram): string {
86
- return JSON.stringify(cliSchemaExport(root), null, 2) + "\n";
92
+ return `${JSON.stringify(cliSchemaExport(root), null, 2)}\n`;
87
93
  }
88
94
 
89
95
  export type { CliSchemaExport };
@@ -3,9 +3,9 @@ This module generates Agent Skills content (SKILL.md + reference.md) from a CLI
3
3
  */
4
4
 
5
5
  import { generateApiGuide } from "../docs/api-guide.ts";
6
+ import { collectMcpTools, type McpToolDef, sanitizeToolSegment } from "../mcp/tools.ts";
6
7
  import { collectOptionDefs } from "../parse.ts";
7
- import { collectMcpTools, sanitizeToolSegment, type McpToolDef } from "../mcp/tools.ts";
8
- import { CliProgram, CliOptionKind } from "../types.ts";
8
+ import { CliOptionKind, type CliProgram } from "../types.ts";
9
9
 
10
10
  export type SkillTarget = "cursor" | "claude";
11
11
 
@@ -18,7 +18,7 @@ export interface SkillBundle {
18
18
  /** Truncates text to maxLen with ellipsis. */
19
19
  function truncate(text: string, maxLen: number): string {
20
20
  if (text.length <= maxLen) return text;
21
- return text.slice(0, maxLen - 1) + "…";
21
+ return `${text.slice(0, maxLen - 1)}…`;
22
22
  }
23
23
 
24
24
  /** Builds third-person skill description for YAML frontmatter. */
@@ -54,7 +54,7 @@ function formatCommandEntry(root: CliProgram, tool: McpToolDef): string {
54
54
  }
55
55
  const enums = opts.filter((o) => o.kind === CliOptionKind.Enum && o.choices?.length);
56
56
  for (const e of enums) {
57
- line += ` (\`--${e.name}\`: ${e.choices!.join(" | ")})`;
57
+ line += ` (\`--${e.name}\`: ${e.choices?.join(" | ")})`;
58
58
  }
59
59
  const varargs = (tool.leaf.positionals ?? []).filter((p) => (p.argMax ?? 1) === 0);
60
60
  if (varargs.length > 0) {
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { CliProgram } from "../types.ts";
4
+ import type { CliProgram } from "../types.ts";
5
5
  import { generateSkillBundle, type SkillTarget } from "./generate.ts";
6
6
  import { applySkillInstallHints } from "./hint.ts";
7
7
 
@@ -25,7 +25,11 @@ function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean):
25
25
  }
26
26
 
27
27
  /** Writes SKILL.md and reference.md; returns changed file paths. */
28
- export function cliSkillInstall(root: CliProgram, target: SkillTarget, opts: SkillInstallOpts): string[] {
28
+ export function cliSkillInstall(
29
+ root: CliProgram,
30
+ target: SkillTarget,
31
+ opts: SkillInstallOpts,
32
+ ): string[] {
29
33
  const bundle = generateSkillBundle(root, target);
30
34
  const { skillMd, referenceMd } = applySkillInstallHints(root, bundle.skillMd, bundle.referenceMd);
31
35
  const dir = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
package/src/validate.ts CHANGED
@@ -3,17 +3,17 @@ This module validates CLI schemas before execution.
3
3
  */
4
4
 
5
5
  import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
6
+ import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
7
+ import { resolveMcpSchemaUri } from "./mcp/tools.ts";
6
8
  import {
7
9
  type CliLeaf,
8
10
  type CliNode,
9
- type CliProgram,
10
11
  CliOptionKind,
12
+ type CliProgram,
11
13
  CliSchemaValidationError,
12
14
  isCliLeaf,
13
15
  isCliRouter,
14
16
  } from "./types.ts";
15
- import { resolveMcpSchemaUri } from "./mcp/tools.ts";
16
- import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
17
17
 
18
18
  /** Validates `docs` configuration on the program root. */
19
19
  function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
@@ -93,17 +93,17 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
93
93
  const rogue = node as CliProgram;
94
94
  if (rogue.mcpServer !== undefined) {
95
95
  throw new CliSchemaValidationError(
96
- "mcpServer is only supported on the program root (not on " + node.key + ")",
96
+ `mcpServer is only supported on the program root (not on ${node.key})`,
97
97
  );
98
98
  }
99
99
  if (rogue.install !== undefined) {
100
100
  throw new CliSchemaValidationError(
101
- "install is only supported on the program root (not on " + node.key + ")",
101
+ `install is only supported on the program root (not on ${node.key})`,
102
102
  );
103
103
  }
104
104
  if (rogue.docs !== undefined) {
105
105
  throw new CliSchemaValidationError(
106
- "docs is only supported on the program root (not on " + node.key + ")",
106
+ `docs is only supported on the program root (not on ${node.key})`,
107
107
  );
108
108
  }
109
109
  }
@@ -115,9 +115,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
115
115
  const outputSchema = node.outputSchema;
116
116
  const legacyOutputSchema = node.mcpTool?.outputSchema;
117
117
  if (outputSchema !== undefined && legacyOutputSchema !== undefined) {
118
- throw new CliSchemaValidationError(
119
- "Set outputSchema on the leaf only, not under mcpTool",
120
- );
118
+ throw new CliSchemaValidationError("Set outputSchema on the leaf only, not under mcpTool");
121
119
  }
122
120
  const resolved = outputSchema ?? legacyOutputSchema;
123
121
  if (
@@ -132,7 +130,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
132
130
  const rogue = node as unknown as CliLeaf;
133
131
  if (rogue.mcpTool !== undefined) {
134
132
  throw new CliSchemaValidationError(
135
- "mcpTool is only supported on leaf commands (not on " + node.key + ")",
133
+ `mcpTool is only supported on leaf commands (not on ${node.key})`,
136
134
  );
137
135
  }
138
136
  }
@@ -160,9 +158,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
160
158
  }
161
159
 
162
160
  if (node.fallbackMode !== undefined && node.fallbackCommand === undefined) {
163
- throw new CliSchemaValidationError(
164
- `fallbackMode requires fallbackCommand on '${node.key}'`,
165
- );
161
+ throw new CliSchemaValidationError(`fallbackMode requires fallbackCommand on '${node.key}'`);
166
162
  }
167
163
 
168
164
  if (node.fallbackCommand !== undefined) {
@@ -233,10 +229,15 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
233
229
  }
234
230
  }
235
231
 
236
- function validatePositionals(scopeKey: string, positionals: import("./types.ts").CliPositional[]): void {
232
+ function validatePositionals(
233
+ scopeKey: string,
234
+ positionals: import("./types.ts").CliPositional[],
235
+ ): void {
237
236
  for (const p of positionals) {
238
237
  if (p.argMin !== undefined && p.argMin < 0) {
239
- throw new CliSchemaValidationError(`argMin must be >= 0 for positional ${scopeKey}/${p.name}`);
238
+ throw new CliSchemaValidationError(
239
+ `argMin must be >= 0 for positional ${scopeKey}/${p.name}`,
240
+ );
240
241
  }
241
242
  if (p.argMax !== undefined && p.argMax < 0) {
242
243
  throw new CliSchemaValidationError(
@@ -262,7 +263,11 @@ function validatePositionals(scopeKey: string, positionals: import("./types.ts")
262
263
  }
263
264
 
264
265
  for (let idx = 0; idx < positionals.length; idx++) {
265
- const { argMax = 1 } = positionals[idx]!;
266
+ const positional = positionals[idx];
267
+ if (!positional) {
268
+ continue;
269
+ }
270
+ const { argMax = 1 } = positional;
266
271
  if (argMax === 0 && idx + 1 < positionals.length) {
267
272
  throw new CliSchemaValidationError(
268
273
  `Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`,