argsbarg 2.0.0 → 2.1.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.0] - 2026-06-20
11
+
12
+
13
+ ## [2.0.1] - 2026-06-20
14
+
15
+ ### Removed
16
+
17
+ - **`CliContext.schema`** — use `ctx.program` (removed alias; `program` is the only field).
18
+
10
19
  ## [2.0.0] - 2026-06-20
11
20
 
12
21
  ### Changed (breaking)
@@ -171,7 +180,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
171
180
  - Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
172
181
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
173
182
 
174
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v2.0.0...HEAD
183
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v2.1.0...HEAD
184
+ [2.1.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.1.0
185
+ [2.0.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.0.1
175
186
  [2.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.0.0
176
187
  [1.5.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.5.0
177
188
  [1.4.3]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.4.3
package/README.md CHANGED
@@ -181,7 +181,7 @@ Add `CliPositional` entries to the command’s `positionals` list (separate from
181
181
  - `ctx.typedOpt<T>("custom", parseFn)` — pass a custom parsing function for type-safe option resolution.
182
182
  - `ctx.args` — positional words in order as `string[]`.
183
183
  - `ctx.positional("name")` — named positional lookup; varargs slots return `string[]`, single slots return `string | undefined`.
184
- - `ctx.schema` / `ctx.program` — program root (`CliProgram`) for contextual help.
184
+ - `ctx.program` — program root (`CliProgram`) for contextual help.
185
185
 
186
186
  ### Capabilities (built-ins)
187
187
 
package/index.d.ts CHANGED
@@ -7,13 +7,11 @@ export declare class CliContext {
7
7
  readonly appName: string;
8
8
  readonly commandPath: string[];
9
9
  readonly args: string[];
10
- readonly schema: CliProgram;
10
+ readonly program: CliProgram;
11
11
  readonly opts: Record<string, string>;
12
12
  readonly invocation: CliInvocation;
13
- /** Program root schema (same as {@link schema}). */
14
- get program(): CliProgram;
15
13
  /** Captures the program root, routed path, positional words, and option map for a leaf handler. */
16
- constructor(appName: string, commandPath: string[], args: string[], opts: Record<string, string>, schema: CliProgram, invocation?: CliInvocation);
14
+ constructor(appName: string, commandPath: string[], args: string[], opts: Record<string, string>, program: CliProgram, invocation?: CliInvocation);
17
15
  /** Returns whether a presence flag was set (including implicit "1" for boolean options). */
18
16
  hasFlag(name: string): boolean;
19
17
  /** Returns the string value for a string-valued option, if present. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "//just": "echo this app uses justfile for development tasks"
package/src/context.ts CHANGED
@@ -18,29 +18,24 @@ export class CliContext {
18
18
  readonly appName: string;
19
19
  readonly commandPath: string[];
20
20
  readonly args: string[];
21
- readonly schema: CliProgram;
21
+ readonly program: CliProgram;
22
22
  readonly opts: Record<string, string>;
23
23
  readonly invocation: CliInvocation;
24
24
 
25
- /** Program root schema (same as {@link schema}). */
26
- get program(): CliProgram {
27
- return this.schema;
28
- }
29
-
30
25
  /** Captures the program root, routed path, positional words, and option map for a leaf handler. */
31
26
  constructor(
32
27
  appName: string,
33
28
  commandPath: string[],
34
29
  args: string[],
35
30
  opts: Record<string, string>,
36
- schema: CliProgram,
31
+ program: CliProgram,
37
32
  invocation: CliInvocation = "cli",
38
33
  ) {
39
34
  this.appName = appName;
40
35
  this.commandPath = commandPath;
41
36
  this.args = args;
42
37
  this.opts = opts;
43
- this.schema = schema;
38
+ this.program = program;
44
39
  this.invocation = invocation;
45
40
  }
46
41
 
@@ -85,7 +80,7 @@ export class CliContext {
85
80
  private _positionalMap(): Record<string, string | string[]> {
86
81
  if (this._posMap) return this._posMap;
87
82
 
88
- let node: CliNode = this.schema;
83
+ let node: CliNode = this.program;
89
84
  for (const seg of this.commandPath) {
90
85
  if (!isCliRouter(node)) {
91
86
  this._posMap = {};
package/src/index.test.ts CHANGED
@@ -26,7 +26,7 @@ import { generateSkillBundle } from "./skill/generate.ts";
26
26
  import { cliSkillInstall } from "./skill/install.ts";
27
27
  import { ParseKind, parse, postParseValidate } from "./parse.ts";
28
28
  import { cliSchemaJson } from "./schema.ts";
29
- import { cliValidateRoot } from "./validate.ts";
29
+ import { cliValidateProgram } from "./validate.ts";
30
30
  import { expect, test } from "bun:test";
31
31
  import { $ } from "bun";
32
32
  import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -59,7 +59,7 @@ test("bundled short presence flags", () => {
59
59
  },
60
60
  ],
61
61
  };
62
- cliValidateRoot(root);
62
+ cliValidateProgram(root);
63
63
  const pr = postParseValidate(root, parse(root, ["x", "-ab"]));
64
64
  expect(pr.kind).toBe(ParseKind.Ok);
65
65
  expect(pr.opts["a"]).toBe("1");
@@ -85,7 +85,7 @@ test("long option equals", () => {
85
85
  },
86
86
  ],
87
87
  };
88
- cliValidateRoot(root);
88
+ cliValidateProgram(root);
89
89
  const pr = postParseValidate(root, parse(root, ["x", "--name=pat"]));
90
90
  expect(pr.kind).toBe(ParseKind.Ok);
91
91
  expect(pr.opts["name"]).toBe("pat");
@@ -112,7 +112,7 @@ test("fallback missing or unknown root flags", () => {
112
112
  fallbackCommand: "hello",
113
113
  fallbackMode: CliFallbackMode.MissingOrUnknown,
114
114
  };
115
- cliValidateRoot(root);
115
+ cliValidateProgram(root);
116
116
  const pr = postParseValidate(root, parse(root, ["--name", "bob"]));
117
117
  expect(pr.kind).toBe(ParseKind.Ok);
118
118
  expect(pr.path).toEqual(["hello"]);
@@ -125,7 +125,7 @@ test("unknown command", () => {
125
125
  description: "",
126
126
  commands: [{ key: "hello", description: "", handler: () => {} }],
127
127
  };
128
- cliValidateRoot(root);
128
+ cliValidateProgram(root);
129
129
  const pr = parse(root, ["nope"]);
130
130
  expect(pr.kind).toBe(ParseKind.Error);
131
131
  expect(pr.errorMsg).toContain("Unknown command");
@@ -137,7 +137,7 @@ test("implicit help empty", () => {
137
137
  description: "",
138
138
  commands: [{ key: "x", description: "", handler: () => {} }],
139
139
  };
140
- cliValidateRoot(root);
140
+ cliValidateProgram(root);
141
141
  const pr = parse(root, []);
142
142
  expect(pr.kind).toBe(ParseKind.Help);
143
143
  expect(pr.helpExplicit).toBe(false);
@@ -162,7 +162,7 @@ test("invalid number post validate", () => {
162
162
  },
163
163
  ],
164
164
  };
165
- cliValidateRoot(root);
165
+ cliValidateProgram(root);
166
166
  let pr = parse(root, ["x", "--n", "notnum"]);
167
167
  pr = postParseValidate(root, pr);
168
168
  expect(pr.kind).toBe(ParseKind.Error);
@@ -188,7 +188,7 @@ test("supports scientific notation in numbers", () => {
188
188
  },
189
189
  ],
190
190
  };
191
- cliValidateRoot(root);
191
+ cliValidateProgram(root);
192
192
  let pr = parse(root, ["x", "--n", "1.23e4"]);
193
193
  pr = postParseValidate(root, pr);
194
194
  expect(pr.kind).toBe(ParseKind.Ok);
@@ -203,7 +203,7 @@ test("completion scripts contain app name", () => {
203
203
  description: "Test",
204
204
  commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
205
205
  };
206
- cliValidateRoot(root);
206
+ cliValidateProgram(root);
207
207
  const bash = completionBashScript(root);
208
208
  expect(bash).toContain("bash completion for myapp");
209
209
  expect(bash).toContain("complete -F _myapp myapp");
@@ -220,7 +220,7 @@ test("completion scripts do not emit invalid bash substitutions", () => {
220
220
  description: "Test",
221
221
  commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
222
222
  };
223
- cliValidateRoot(root);
223
+ cliValidateProgram(root);
224
224
  const bash = completionBashScript(root);
225
225
  expect(bash).not.toContain("${${");
226
226
  });
@@ -237,7 +237,7 @@ test("completion scripts escape shell-sensitive command text in zsh", () => {
237
237
  },
238
238
  ],
239
239
  };
240
- cliValidateRoot(root);
240
+ cliValidateProgram(root);
241
241
  const zsh = completionZshScript(root);
242
242
  expect(zsh).toContain("quote'\\''cmd:Say '\\''hello'\\'' and keep going.");
243
243
  });
@@ -248,7 +248,7 @@ test("completion scripts keep dotted app names in registration names", () => {
248
248
  description: "Test",
249
249
  commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
250
250
  };
251
- cliValidateRoot(root);
251
+ cliValidateProgram(root);
252
252
 
253
253
  const bash = completionBashScript(root);
254
254
  expect(bash).toContain("complete -F _minimal_ts minimal.ts");
@@ -283,7 +283,7 @@ test("trailing options after bounded positionals", () => {
283
283
  },
284
284
  ],
285
285
  };
286
- cliValidateRoot(root);
286
+ cliValidateProgram(root);
287
287
  const pr = postParseValidate(root, parse(root, ["x", "./file", "--verbose"]));
288
288
  expect(pr.kind).toBe(ParseKind.Ok);
289
289
  expect(pr.args).toEqual(["./file"]);
@@ -330,7 +330,7 @@ test("trailing options include parent-scoped flags", () => {
330
330
  },
331
331
  ],
332
332
  };
333
- cliValidateRoot(root);
333
+ cliValidateProgram(root);
334
334
  const pr = postParseValidate(root, parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]));
335
335
  expect(pr.kind).toBe(ParseKind.Ok);
336
336
  expect(pr.path).toEqual(["group", "leaf"]);
@@ -367,7 +367,7 @@ test("varargs tail parses trailing options", () => {
367
367
  },
368
368
  ],
369
369
  };
370
- cliValidateRoot(root);
370
+ cliValidateProgram(root);
371
371
  const pr = postParseValidate(root, parse(root, ["x", "./file", "--json"]));
372
372
  expect(pr.kind).toBe(ParseKind.Ok);
373
373
  expect(pr.args).toEqual(["./file"]);
@@ -402,7 +402,7 @@ test("stops parsing options at --", () => {
402
402
  },
403
403
  ],
404
404
  };
405
- cliValidateRoot(root);
405
+ cliValidateProgram(root);
406
406
  const pr = postParseValidate(root, parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"]));
407
407
  expect(pr.kind).toBe(ParseKind.Ok);
408
408
  expect(pr.opts["name"]).toBe("pat");
@@ -429,7 +429,7 @@ test("missing required option returns error", () => {
429
429
  },
430
430
  ],
431
431
  };
432
- cliValidateRoot(root);
432
+ cliValidateProgram(root);
433
433
  const pr = postParseValidate(root, parse(root, ["x"]));
434
434
  expect(pr.kind).toBe(ParseKind.Error);
435
435
  expect(pr.errorMsg).toContain("Missing required option: --req");
@@ -455,7 +455,7 @@ test("provided required option parses ok", () => {
455
455
  },
456
456
  ],
457
457
  };
458
- cliValidateRoot(root);
458
+ cliValidateProgram(root);
459
459
  const pr = postParseValidate(root, parse(root, ["x", "--req", "val"]));
460
460
  expect(pr.kind).toBe(ParseKind.Ok);
461
461
  expect(pr.opts["req"]).toBe("val");
@@ -481,7 +481,7 @@ test("presence option cannot be required", () => {
481
481
  },
482
482
  ],
483
483
  };
484
- expect(() => cliValidateRoot(root)).toThrow(/Presence option cannot be required/);
484
+ expect(() => cliValidateProgram(root)).toThrow(/Presence option cannot be required/);
485
485
  });
486
486
 
487
487
  test("leaf completion help prints correctly", async () => {
@@ -541,7 +541,7 @@ test("parse recognizes --schema at the program root", () => {
541
541
  },
542
542
  ],
543
543
  };
544
- cliValidateRoot(root);
544
+ cliValidateProgram(root);
545
545
  const pr = parse(root, ["--schema"]);
546
546
  expect(pr.kind).toBe(ParseKind.Schema);
547
547
  });
@@ -595,7 +595,7 @@ test("reserved option name schema is rejected", () => {
595
595
  },
596
596
  ],
597
597
  };
598
- expect(() => cliValidateRoot(root)).toThrow(/reserved for --schema/);
598
+ expect(() => cliValidateProgram(root)).toThrow(/reserved for --schema/);
599
599
  });
600
600
 
601
601
  test("root help lists --schema built-in", () => {
@@ -825,7 +825,7 @@ test("reserved command name install is rejected", () => {
825
825
  },
826
826
  ],
827
827
  };
828
- expect(() => cliValidateRoot(root)).toThrow(/Reserved command name: install/);
828
+ expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: install/);
829
829
  });
830
830
 
831
831
  test("top-level command name mcp is allowed without mcpServer", () => {
@@ -840,7 +840,7 @@ test("top-level command name mcp is allowed without mcpServer", () => {
840
840
  },
841
841
  ],
842
842
  };
843
- expect(() => cliValidateRoot(root)).not.toThrow();
843
+ expect(() => cliValidateProgram(root)).not.toThrow();
844
844
  });
845
845
 
846
846
  test("top-level command name mcp is rejected when mcpServer is set", () => {
@@ -856,7 +856,7 @@ test("top-level command name mcp is rejected when mcpServer is set", () => {
856
856
  },
857
857
  ],
858
858
  };
859
- expect(() => cliValidateRoot(root)).toThrow(/Reserved command name: mcp/);
859
+ expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: mcp/);
860
860
  });
861
861
 
862
862
  test("mcpServer on non-root node is rejected", () => {
@@ -872,7 +872,7 @@ test("mcpServer on non-root node is rejected", () => {
872
872
  },
873
873
  ],
874
874
  } as unknown as CliProgram;
875
- expect(() => cliValidateRoot(root)).toThrow(/mcpServer is only supported on the program root/);
875
+ expect(() => cliValidateProgram(root)).toThrow(/mcpServer is only supported on the program root/);
876
876
  });
877
877
 
878
878
  test("mcpTool on root is rejected", () => {
@@ -882,7 +882,7 @@ test("mcpTool on root is rejected", () => {
882
882
  mcpTool: { enabled: false },
883
883
  handler: () => {},
884
884
  };
885
- expect(() => cliValidateRoot(root)).toThrow(/mcpTool is only supported on leaf commands/);
885
+ expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
886
886
  });
887
887
 
888
888
  test("mcpTool on routing node is rejected", () => {
@@ -904,7 +904,7 @@ test("mcpTool on routing node is rejected", () => {
904
904
  },
905
905
  ],
906
906
  };
907
- expect(() => cliValidateRoot(root)).toThrow(/mcpTool is only supported on leaf commands/);
907
+ expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
908
908
  });
909
909
 
910
910
  test("buildToolCallSuccess returns stdout only", () => {
@@ -1059,7 +1059,7 @@ test("ctx.invocation is mcp via cliInvoke", async () => {
1059
1059
  seen = ctx.invocation;
1060
1060
  },
1061
1061
  };
1062
- cliValidateRoot(root);
1062
+ cliValidateProgram(root);
1063
1063
  const result = await cliInvoke(root, []);
1064
1064
  expect(result.kind).toBe("ok");
1065
1065
  expect(seen).toBe("mcp");
@@ -1109,7 +1109,7 @@ test("cliInvoke rejects invalid Enum value", async () => {
1109
1109
  },
1110
1110
  ],
1111
1111
  };
1112
- cliValidateRoot(root);
1112
+ cliValidateProgram(root);
1113
1113
  const result = await cliInvoke(root, ["--mode", "staging"]);
1114
1114
  expect(result.kind).toBe("error");
1115
1115
  expect(result.errorMsg).toContain("not one of");
@@ -1132,30 +1132,30 @@ test("cliInvoke accepts valid Enum value", async () => {
1132
1132
  },
1133
1133
  ],
1134
1134
  };
1135
- cliValidateRoot(root);
1135
+ cliValidateProgram(root);
1136
1136
  const result = await cliInvoke(root, ["--mode", "dev"]);
1137
1137
  expect(result.kind).toBe("ok");
1138
1138
  expect(result.stdout.trim()).toBe("dev");
1139
1139
  });
1140
1140
 
1141
- test("cliValidateRoot rejects Enum with no choices", () => {
1141
+ test("cliValidateProgram rejects Enum with no choices", () => {
1142
1142
  const root: CliProgram = {
1143
1143
  key: "app",
1144
1144
  description: "",
1145
1145
  handler: () => {},
1146
1146
  options: [{ name: "mode", description: "", kind: CliOptionKind.Enum, choices: [] }],
1147
1147
  };
1148
- expect(() => cliValidateRoot(root)).toThrow(/requires non-empty choices/);
1148
+ expect(() => cliValidateProgram(root)).toThrow(/requires non-empty choices/);
1149
1149
  });
1150
1150
 
1151
- test("cliValidateRoot rejects Enum with duplicate choices", () => {
1151
+ test("cliValidateProgram rejects Enum with duplicate choices", () => {
1152
1152
  const root: CliProgram = {
1153
1153
  key: "app",
1154
1154
  description: "",
1155
1155
  handler: () => {},
1156
1156
  options: [{ name: "mode", description: "", kind: CliOptionKind.Enum, choices: ["a", "a"] }],
1157
1157
  };
1158
- expect(() => cliValidateRoot(root)).toThrow(/choices must be distinct/);
1158
+ expect(() => cliValidateProgram(root)).toThrow(/choices must be distinct/);
1159
1159
  });
1160
1160
 
1161
1161
  test("mcpTool.description override wins without requiresEnv suffix", () => {
@@ -1194,7 +1194,7 @@ test("mcpTool.requiresEnv appended to auto description", () => {
1194
1194
  expect(tools[0]!.description).toContain("[requires env: TOKEN]");
1195
1195
  });
1196
1196
 
1197
- test("cliValidateRoot rejects duplicate mcpResources URIs", () => {
1197
+ test("cliValidateProgram rejects duplicate mcpResources URIs", () => {
1198
1198
  const root: CliProgram = {
1199
1199
  key: "app",
1200
1200
  description: "",
@@ -1206,10 +1206,10 @@ test("cliValidateRoot rejects duplicate mcpResources URIs", () => {
1206
1206
  },
1207
1207
  commands: [{ key: "x", description: "", handler: () => {} }],
1208
1208
  };
1209
- expect(() => cliValidateRoot(root)).toThrow(/URIs must be unique/);
1209
+ expect(() => cliValidateProgram(root)).toThrow(/URIs must be unique/);
1210
1210
  });
1211
1211
 
1212
- test("cliValidateRoot rejects resource URI matching schemaResourceUri", () => {
1212
+ test("cliValidateProgram rejects resource URI matching schemaResourceUri", () => {
1213
1213
  const root: CliProgram = {
1214
1214
  key: "app",
1215
1215
  description: "",
@@ -1219,7 +1219,7 @@ test("cliValidateRoot rejects resource URI matching schemaResourceUri", () => {
1219
1219
  },
1220
1220
  commands: [{ key: "x", description: "", handler: () => {} }],
1221
1221
  };
1222
- expect(() => cliValidateRoot(root)).toThrow(/conflicts with the built-in schema resource/);
1222
+ expect(() => cliValidateProgram(root)).toThrow(/conflicts with the built-in schema resource/);
1223
1223
  });
1224
1224
 
1225
1225
  test("allMcpResources includes custom resources", () => {
@@ -1430,7 +1430,7 @@ function nestedDocsFallbackFixture(): CliProgram {
1430
1430
 
1431
1431
  test("nested fallback routes to default when argv exhausted at router", () => {
1432
1432
  const root = nestedDocsFallbackFixture();
1433
- cliValidateRoot(root);
1433
+ cliValidateProgram(root);
1434
1434
  const pr = postParseValidate(root, parse(root, ["docs"]));
1435
1435
  expect(pr.kind).toBe(ParseKind.Ok);
1436
1436
  expect(pr.path).toEqual(["docs", "guide"]);
@@ -1470,7 +1470,7 @@ test("nested fallback MissingOrUnknown routes unknown token to default", () => {
1470
1470
  },
1471
1471
  ],
1472
1472
  };
1473
- cliValidateRoot(root);
1473
+ cliValidateProgram(root);
1474
1474
  const pr = postParseValidate(root, parse(root, ["docs", "extra-topic"]));
1475
1475
  expect(pr.kind).toBe(ParseKind.Ok);
1476
1476
  expect(pr.path).toEqual(["docs", "guide"]);
@@ -1479,13 +1479,13 @@ test("nested fallback MissingOrUnknown routes unknown token to default", () => {
1479
1479
 
1480
1480
  test("nested fallback MissingOnly errors on unknown subcommand", () => {
1481
1481
  const root = nestedDocsFallbackFixture();
1482
- cliValidateRoot(root);
1482
+ cliValidateProgram(root);
1483
1483
  const pr = parse(root, ["docs", "nope"]);
1484
1484
  expect(pr.kind).toBe(ParseKind.Error);
1485
1485
  expect(pr.errorMsg).toContain("Unknown subcommand");
1486
1486
  });
1487
1487
 
1488
- test("cliValidateRoot rejects invalid nested fallbackCommand", () => {
1488
+ test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
1489
1489
  const root: CliProgram = {
1490
1490
  key: "app",
1491
1491
  description: "",
@@ -1504,17 +1504,17 @@ test("cliValidateRoot rejects invalid nested fallbackCommand", () => {
1504
1504
  },
1505
1505
  ],
1506
1506
  };
1507
- expect(() => cliValidateRoot(root)).toThrow(/fallbackCommand 'missing' is not a child of 'docs'/);
1507
+ expect(() => cliValidateProgram(root)).toThrow(/fallbackCommand 'missing' is not a child of 'docs'/);
1508
1508
  });
1509
1509
 
1510
- test("cliValidateRoot accepts nested fallbackCommand when child exists", () => {
1510
+ test("cliValidateProgram accepts nested fallbackCommand when child exists", () => {
1511
1511
  const root = nestedDocsFallbackFixture();
1512
- expect(() => cliValidateRoot(root)).not.toThrow();
1512
+ expect(() => cliValidateProgram(root)).not.toThrow();
1513
1513
  });
1514
1514
 
1515
1515
  test("nested router scoped help does not route to fallback", () => {
1516
1516
  const root = nestedDocsFallbackFixture();
1517
- cliValidateRoot(root);
1517
+ cliValidateProgram(root);
1518
1518
  const pr = parse(root, ["docs", "--help"]);
1519
1519
  expect(pr.kind).toBe(ParseKind.Help);
1520
1520
  expect(pr.helpPath).toEqual(["docs"]);
@@ -1526,7 +1526,7 @@ test("nested router scoped help does not route to fallback", () => {
1526
1526
 
1527
1527
  test("varargs trailing option after positionals via cliInvoke", async () => {
1528
1528
  const root = varargsReadFixture();
1529
- cliValidateRoot(root);
1529
+ cliValidateProgram(root);
1530
1530
  const pr = postParseValidate(root, parse(root, ["read", "file.txt", "--json"]));
1531
1531
  expect(pr.kind).toBe(ParseKind.Ok);
1532
1532
  expect(pr.args).toEqual(["file.txt"]);
@@ -1535,7 +1535,7 @@ test("varargs trailing option after positionals via cliInvoke", async () => {
1535
1535
 
1536
1536
  test("varargs option before positionals", () => {
1537
1537
  const root = varargsReadFixture();
1538
- cliValidateRoot(root);
1538
+ cliValidateProgram(root);
1539
1539
  const pr = postParseValidate(root, parse(root, ["read", "--json", "file.txt"]));
1540
1540
  expect(pr.kind).toBe(ParseKind.Ok);
1541
1541
  expect(pr.args).toEqual(["file.txt"]);
@@ -1544,7 +1544,7 @@ test("varargs option before positionals", () => {
1544
1544
 
1545
1545
  test("varargs multiple files then trailing option", () => {
1546
1546
  const root = varargsReadFixture();
1547
- cliValidateRoot(root);
1547
+ cliValidateProgram(root);
1548
1548
  const pr = postParseValidate(root, parse(root, ["read", "a.txt", "b.txt", "--json"]));
1549
1549
  expect(pr.kind).toBe(ParseKind.Ok);
1550
1550
  expect(pr.args).toEqual(["a.txt", "b.txt"]);
@@ -1553,7 +1553,7 @@ test("varargs multiple files then trailing option", () => {
1553
1553
 
1554
1554
  test("varargs double dash forces positional", () => {
1555
1555
  const root = varargsReadFixture();
1556
- cliValidateRoot(root);
1556
+ cliValidateProgram(root);
1557
1557
  const pr = postParseValidate(root, parse(root, ["read", "file.txt", "--", "--json"]));
1558
1558
  expect(pr.kind).toBe(ParseKind.Ok);
1559
1559
  expect(pr.args).toEqual(["file.txt", "--json"]);
@@ -1562,7 +1562,7 @@ test("varargs double dash forces positional", () => {
1562
1562
 
1563
1563
  test("varargs unknown flag errors", async () => {
1564
1564
  const root = varargsReadFixture();
1565
- cliValidateRoot(root);
1565
+ cliValidateProgram(root);
1566
1566
  const result = await cliInvoke(root, ["read", "--unknown"]);
1567
1567
  expect(result.kind).toBe("error");
1568
1568
  expect(result.stderr).toContain("--unknown");
@@ -1570,7 +1570,7 @@ test("varargs unknown flag errors", async () => {
1570
1570
 
1571
1571
  test("varargs scoped help in tail", () => {
1572
1572
  const root = varargsReadFixture();
1573
- cliValidateRoot(root);
1573
+ cliValidateProgram(root);
1574
1574
  const pr = parse(root, ["read", "file.txt", "--help"]);
1575
1575
  expect(pr.kind).toBe(ParseKind.Help);
1576
1576
  expect(pr.helpPath).toContain("read");
@@ -1593,7 +1593,7 @@ test("ctx.positional returns single slot value", async () => {
1593
1593
  ],
1594
1594
  };
1595
1595
  let captured: string | string[] | undefined;
1596
- cliValidateRoot(root);
1596
+ cliValidateProgram(root);
1597
1597
  await cliInvoke(root, ["x", "./file"]);
1598
1598
  expect(captured).toBe("./file");
1599
1599
  });
@@ -1606,7 +1606,7 @@ test("ctx.positional returns varargs array", async () => {
1606
1606
  captured = ctx.positional("files");
1607
1607
  };
1608
1608
  }
1609
- cliValidateRoot(root);
1609
+ cliValidateProgram(root);
1610
1610
  await cliInvoke(root, ["read", "a.txt", "b.txt"]);
1611
1611
  expect(captured).toEqual(["a.txt", "b.txt"]);
1612
1612
  });
@@ -1629,7 +1629,7 @@ test("ctx.positional returns undefined for absent optional slot", async () => {
1629
1629
  ],
1630
1630
  };
1631
1631
  let captured: string | string[] | undefined;
1632
- cliValidateRoot(root);
1632
+ cliValidateProgram(root);
1633
1633
  await cliInvoke(root, ["x"]);
1634
1634
  expect(captured).toBeUndefined();
1635
1635
  });
@@ -1644,7 +1644,7 @@ test("ctx.positional varargs matches ctx.args", async () => {
1644
1644
  args = ctx.args;
1645
1645
  };
1646
1646
  }
1647
- cliValidateRoot(root);
1647
+ cliValidateProgram(root);
1648
1648
  await cliInvoke(root, ["read", "a.txt", "b.txt"]);
1649
1649
  expect(positional).toEqual(args);
1650
1650
  });
@@ -1692,7 +1692,7 @@ test("install config on non-root node is rejected", () => {
1692
1692
  },
1693
1693
  ],
1694
1694
  } as unknown as CliProgram;
1695
- expect(() => cliValidateRoot(root)).toThrow(/install is only supported on the program root/);
1695
+ expect(() => cliValidateProgram(root)).toThrow(/install is only supported on the program root/);
1696
1696
  });
1697
1697
 
1698
1698
  test("generateSkillBundle includes frontmatter and command catalog", () => {
package/src/runtime.ts CHANGED
@@ -121,6 +121,6 @@ export function cliErrWithHelp(ctx: CliContext, msg: string): never {
121
121
  const color = process.stderr.isTTY;
122
122
  const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
123
123
  process.stderr.write(line + "\n");
124
- process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.schema), ctx.commandPath, true));
124
+ process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.program), ctx.commandPath, true));
125
125
  process.exit(1);
126
126
  }
package/src/validate.ts CHANGED
@@ -30,9 +30,6 @@ export function cliValidateProgram(program: CliProgram): void {
30
30
  walkNode(program, program, true);
31
31
  }
32
32
 
33
- /** @deprecated Internal alias — use cliValidateProgram */
34
- export const cliValidateRoot = cliValidateProgram;
35
-
36
33
  function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
37
34
  if (!isRoot) {
38
35
  const rogue = node as CliProgram;