argsbarg 1.2.0 → 1.3.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.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Project context for AI agents
3
+ ---
4
+
5
+ Always include in context before answering or making changes in this repository:
6
+
7
+ - `README.md`
8
+ - `.cursor/rules/*`
@@ -0,0 +1,13 @@
1
+ ## Summary
2
+
3
+ <!-- What does this PR change and why? -->
4
+
5
+ ## Changelog
6
+
7
+ **Every PR must update `CHANGELOG.md` under `## [Unreleased]`**: use `### Added`, `### Changed`, `### Fixed`, or `### Removed` as appropriate; one idea per bullet.
8
+
9
+ <!-- If you claimed no-op above, briefly say why no changelog entry is warranted. -->
10
+
11
+ ## Testing
12
+
13
+ <!-- How did you verify this (local build, tests, manual run, etc.)? -->
@@ -0,0 +1 @@
1
+ - [ ] --schema feature for ai agents
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.0] - 2026-06-18
11
+
12
+ ### Added
13
+
14
+ - **`--schema`** — prints the full CLI tree as JSON to stdout (exit 0). Handlers are omitted; the injected `completion` subtree is excluded. Option name `schema` is reserved.
15
+
16
+ ## [1.2.1] - 2026-06-18
17
+
18
+ ### Changed
19
+
20
+ - **Trailing options** — when a leaf command has only bounded positionals (`argMax !== 0`), options may appear after positional arguments (e.g. `cmd ./file --verbose`). Commands with a varargs tail (`argMax: 0`) keep the previous behavior.
21
+ - **`examples/nested.ts`** — `stat` accepts `--json`; `stat owner lookup` prints JSON when the flag is set.
22
+
10
23
  ## [1.2.0] - 2026-04-24
11
24
 
12
25
  ### Added
@@ -64,7 +77,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
64
77
  - 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`).
65
78
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
66
79
 
67
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v1.2.0...HEAD
80
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v1.3.0...HEAD
81
+ [1.3.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.3.0
82
+ [1.2.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.2.1
68
83
  [1.2.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.2.0
69
84
  [1.1.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.1.1
70
85
  [1.1.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.1.0
package/CLAUDE.md ADDED
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Project context for AI agents
3
+ ---
4
+
5
+ Always include in context before answering or making changes in this repository:
6
+
7
+ - `./README.md`
8
+ - .cursor/rules/*
package/README.md CHANGED
@@ -88,9 +88,11 @@ Everything you need for a first-class CLI:
88
88
  Every app gets:
89
89
 
90
90
  - `-h` / `--help` at any routing depth (scoped help).
91
+ - **`--schema`** at the program root — print the full command tree as JSON (for tooling and agents).
91
92
  - **`completion bash` / `completion zsh`** — print shell completion scripts to stdout (injected by `cliRun`).
92
93
 
93
94
  Do not declare a top-level command named **`completion`** — it is reserved for this built-in.
95
+ Do not declare an option named **`schema`** — it is reserved for `--schema`.
94
96
 
95
97
 
96
98
  ### Shell completions
@@ -16,6 +16,13 @@ const cli: CliCommand = {
16
16
  {
17
17
  key: "stat",
18
18
  description: "File metadata.",
19
+ options: [
20
+ {
21
+ name: "json",
22
+ description: "Emit handler output as JSON.",
23
+ kind: CliOptionKind.Presence,
24
+ },
25
+ ],
19
26
  commands: [
20
27
  {
21
28
  key: "owner",
@@ -46,7 +53,11 @@ const cli: CliCommand = {
46
53
  console.error("Missing path.");
47
54
  process.exit(1);
48
55
  }
49
- console.log(`lookup user=${user} path=${path}`);
56
+ if (ctx.hasFlag("json")) {
57
+ console.log(JSON.stringify({ user, path }));
58
+ } else {
59
+ console.log(`lookup user=${user} path=${path}`);
60
+ }
50
61
  },
51
62
  },
52
63
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "//just": "echo this app uses justfile for development tasks"
package/src/index.test.ts CHANGED
@@ -10,6 +10,7 @@ shell output regressions.
10
10
  import { completionBashScript, completionZshScript } from "./completion.ts";
11
11
  import { CliCommand, CliFallbackMode, CliOptionKind } from "./index.ts";
12
12
  import { ParseKind, parse, postParseValidate } from "./parse.ts";
13
+ import { cliSchemaJson } from "./schema.ts";
13
14
  import { cliValidateRoot } from "./validate.ts";
14
15
  import { expect, test } from "bun:test";
15
16
  import { $ } from "bun";
@@ -238,6 +239,123 @@ test("completion scripts keep dotted app names in registration names", () => {
238
239
  expect(zsh).toContain("compdef _minimal_ts minimal.ts");
239
240
  });
240
241
 
242
+ test("trailing options after bounded positionals", () => {
243
+ const root: CliCommand = {
244
+ key: "app",
245
+ description: "",
246
+ commands: [
247
+ {
248
+ key: "x",
249
+ description: "cmd",
250
+ options: [
251
+ {
252
+ name: "verbose",
253
+ description: "",
254
+ kind: CliOptionKind.Presence,
255
+ },
256
+ ],
257
+ positionals: [
258
+ {
259
+ name: "path",
260
+ description: "",
261
+ kind: CliOptionKind.String,
262
+ },
263
+ ],
264
+ handler: () => {},
265
+ },
266
+ ],
267
+ };
268
+ cliValidateRoot(root);
269
+ const pr = postParseValidate(root, parse(root, ["x", "./file", "--verbose"]));
270
+ expect(pr.kind).toBe(ParseKind.Ok);
271
+ expect(pr.args).toEqual(["./file"]);
272
+ expect(pr.opts["verbose"]).toBe("1");
273
+ });
274
+
275
+ test("trailing options include parent-scoped flags", () => {
276
+ const root: CliCommand = {
277
+ key: "app",
278
+ description: "",
279
+ commands: [
280
+ {
281
+ key: "group",
282
+ description: "group",
283
+ options: [
284
+ {
285
+ name: "json",
286
+ description: "",
287
+ kind: CliOptionKind.Presence,
288
+ },
289
+ ],
290
+ commands: [
291
+ {
292
+ key: "leaf",
293
+ description: "leaf",
294
+ options: [
295
+ {
296
+ name: "user",
297
+ description: "",
298
+ kind: CliOptionKind.String,
299
+ shortName: "u",
300
+ },
301
+ ],
302
+ positionals: [
303
+ {
304
+ name: "path",
305
+ description: "",
306
+ kind: CliOptionKind.String,
307
+ },
308
+ ],
309
+ handler: () => {},
310
+ },
311
+ ],
312
+ },
313
+ ],
314
+ };
315
+ cliValidateRoot(root);
316
+ const pr = postParseValidate(root, parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]));
317
+ expect(pr.kind).toBe(ParseKind.Ok);
318
+ expect(pr.path).toEqual(["group", "leaf"]);
319
+ expect(pr.args).toEqual(["./file"]);
320
+ expect(pr.opts["user"]).toBe("alice");
321
+ expect(pr.opts["json"]).toBe("1");
322
+ });
323
+
324
+ test("varargs tail does not parse trailing options", () => {
325
+ const root: CliCommand = {
326
+ key: "app",
327
+ description: "",
328
+ commands: [
329
+ {
330
+ key: "x",
331
+ description: "cmd",
332
+ options: [
333
+ {
334
+ name: "json",
335
+ description: "",
336
+ kind: CliOptionKind.Presence,
337
+ },
338
+ ],
339
+ positionals: [
340
+ {
341
+ name: "files",
342
+ description: "",
343
+ kind: CliOptionKind.String,
344
+ argMin: 0,
345
+ argMax: 0,
346
+ },
347
+ ],
348
+ handler: () => {},
349
+ },
350
+ ],
351
+ };
352
+ cliValidateRoot(root);
353
+ const pr = postParseValidate(root, parse(root, ["x", "./file", "--json"]));
354
+ expect(pr.kind).toBe(ParseKind.Ok);
355
+ expect(pr.args).toEqual(["./file", "--json"]);
356
+ expect(pr.opts["json"]).toBeUndefined();
357
+ });
358
+
241
359
  test("stops parsing options at --", () => {
242
360
  const root: CliCommand = {
243
361
  key: "app",
@@ -357,4 +475,99 @@ test("leaf completion help prints correctly", async () => {
357
475
  expect(out).toContain("Show help for this command.");
358
476
  expect(out).toContain("Output is the whole script.");
359
477
  expect(stderr.toString()).toBe("");
478
+ });
479
+
480
+ test("--schema exports JSON for nested CLIs", async () => {
481
+ const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts --schema`.nothrow().quiet();
482
+ expect(exitCode).toBe(0);
483
+ expect(stderr.toString()).toBe("");
484
+
485
+ const schema = JSON.parse(stdout.toString());
486
+ expect(schema.key).toBe("nested.ts");
487
+ expect(schema.fallbackCommand).toBe("read");
488
+ expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["stat", "read"]);
489
+ expect(schema.commands).not.toContainEqual(expect.objectContaining({ key: "completion" }));
490
+
491
+ const lookup = schema.commands[0].commands[0].commands[0];
492
+ expect(lookup.key).toBe("lookup");
493
+ expect(lookup.positionals[0].name).toBe("path");
494
+ });
495
+
496
+ test("--schema exports JSON for leaf roots", async () => {
497
+ const { stdout, exitCode } = await $`bun run examples/minimal.ts --schema`.nothrow().quiet();
498
+ expect(exitCode).toBe(0);
499
+
500
+ const schema = JSON.parse(stdout.toString());
501
+ expect(schema.key).toBe("minimal.ts");
502
+ expect(schema.positionals[0].name).toBe("name");
503
+ expect(schema.options[0].name).toBe("verbose");
504
+ });
505
+
506
+ test("parse recognizes --schema at the program root", () => {
507
+ const root: CliCommand = {
508
+ key: "app",
509
+ description: "demo",
510
+ commands: [
511
+ {
512
+ key: "x",
513
+ description: "cmd",
514
+ handler: () => {},
515
+ },
516
+ ],
517
+ };
518
+ cliValidateRoot(root);
519
+ const pr = parse(root, ["--schema"]);
520
+ expect(pr.kind).toBe(ParseKind.Schema);
521
+ });
522
+
523
+ test("cliSchemaJson omits handlers and completion built-ins", () => {
524
+ const root: CliCommand = {
525
+ key: "app",
526
+ description: "demo",
527
+ commands: [
528
+ {
529
+ key: "x",
530
+ description: "cmd",
531
+ handler: () => {},
532
+ },
533
+ {
534
+ key: "completion",
535
+ description: "should not appear",
536
+ commands: [
537
+ {
538
+ key: "bash",
539
+ description: "",
540
+ handler: () => {},
541
+ },
542
+ ],
543
+ },
544
+ ],
545
+ };
546
+
547
+ const schema = JSON.parse(cliSchemaJson(root));
548
+ expect(schema.commands).toHaveLength(1);
549
+ expect(schema.commands[0].key).toBe("x");
550
+ expect(schema).not.toHaveProperty("handler");
551
+ });
552
+
553
+ test("reserved option name schema is rejected", () => {
554
+ const root: CliCommand = {
555
+ key: "app",
556
+ description: "",
557
+ commands: [
558
+ {
559
+ key: "x",
560
+ description: "cmd",
561
+ options: [
562
+ {
563
+ name: "schema",
564
+ description: "",
565
+ kind: CliOptionKind.String,
566
+ },
567
+ ],
568
+ handler: () => {},
569
+ },
570
+ ],
571
+ };
572
+ expect(() => cliValidateRoot(root)).toThrow(/reserved for --schema/);
360
573
  });
package/src/parse.ts CHANGED
@@ -26,6 +26,8 @@ export enum ParseKind {
26
26
  Ok = "ok",
27
27
  /** User requested help (explicit or implicit). */
28
28
  Help = "help",
29
+ /** User requested machine-readable schema export (`--schema`). */
30
+ Schema = "schema",
29
31
  /** User error (unknown command, bad option, etc.). */
30
32
  Error = "error",
31
33
  }
@@ -54,12 +56,18 @@ export interface ParseResult {
54
56
 
55
57
  const helpShort = "-h";
56
58
  const helpLong = "--help";
59
+ const schemaLong = "--schema";
57
60
 
58
61
  /** Returns true if the argv token is `-h` or `--help`. */
59
62
  function isHelpTok(tok: string): boolean {
60
63
  return tok === helpShort || tok === helpLong;
61
64
  }
62
65
 
66
+ /** Returns true if the argv token is `--schema`. */
67
+ function isSchemaTok(tok: string): boolean {
68
+ return tok === schemaLong;
69
+ }
70
+
63
71
  /** Looks up a subcommand or routing node by `key`. */
64
72
  function findChild(cmds: CliCommand[], name: string): CliCommand | undefined {
65
73
  return cmds.find((c) => c.key === name);
@@ -184,6 +192,7 @@ function consumeOptions(
184
192
  const tok = argv[idx];
185
193
 
186
194
  if (isHelpTok(tok)) break;
195
+ if (isSchemaTok(tok)) break;
187
196
  if (!tok.startsWith("-")) break;
188
197
 
189
198
  if (tok === "--") {
@@ -207,6 +216,26 @@ function consumeOptions(
207
216
 
208
217
  // ── Positional Collection ─────────────────────────────────────────────────────
209
218
 
219
+ /** Merges option defs from the program root along the routed command path. */
220
+ function collectOptionDefs(root: CliCommand, path: string[]): CliOption[] {
221
+ let defs = [...(root.options ?? [])];
222
+ let cmds = root.commands ?? [];
223
+
224
+ for (const seg of path) {
225
+ const ch = findChild(cmds, seg);
226
+ if (!ch) break;
227
+ defs.push(...(ch.options ?? []));
228
+ cmds = ch.commands ?? [];
229
+ }
230
+
231
+ return defs;
232
+ }
233
+
234
+ /** True when every positional slot has bounded arity (no `argMax: 0` varargs tail). */
235
+ function allowsTrailingOptions(positionals: CliCommand["positionals"]): boolean {
236
+ return (positionals ?? []).every((p) => (p.argMax ?? 1) !== 0);
237
+ }
238
+
210
239
  /** Fills `args` for a leaf from `startIdx` according to `node.positionals`. */
211
240
  function finishLeaf(
212
241
  node: CliCommand,
@@ -214,6 +243,8 @@ function finishLeaf(
214
243
  argv: string[],
215
244
  path: string[],
216
245
  opts: Record<string, string>,
246
+ optionDefs: CliOption[],
247
+ forcePositionals: boolean,
217
248
  ): ParseResult {
218
249
  /** Builds a parse error for positional consumption failures. */
219
250
  function errorResult(msg: string): ParseResult {
@@ -243,8 +274,13 @@ function finishLeaf(
243
274
  args.push(argv[idx]);
244
275
  idx += 1;
245
276
  } else if (idx < argv.length) {
246
- args.push(argv[idx]);
247
- idx += 1;
277
+ const tok = argv[idx];
278
+ if (argMin < 1 && tok.startsWith("-")) {
279
+ // Optional slot: leave `-` tokens for trailing option parsing.
280
+ } else {
281
+ args.push(tok);
282
+ idx += 1;
283
+ }
248
284
  }
249
285
  continue;
250
286
  }
@@ -269,7 +305,23 @@ function finishLeaf(
269
305
  }
270
306
 
271
307
  if (idx < argv.length) {
272
- return errorResult("Unexpected extra arguments");
308
+ if (forcePositionals || !allowsTrailingOptions(node.positionals)) {
309
+ return errorResult("Unexpected extra arguments");
310
+ }
311
+
312
+ if (isHelpTok(argv[idx])) {
313
+ return helpResult(path, true);
314
+ }
315
+
316
+ const tailRep = consumeOptions(optionDefs, false, argv, idx, opts);
317
+ if (tailRep.report.err) {
318
+ return errorResult(tailRep.report.err);
319
+ }
320
+ idx = tailRep.nextIndex;
321
+
322
+ if (idx < argv.length) {
323
+ return errorResult("Unexpected extra arguments");
324
+ }
273
325
  }
274
326
 
275
327
  return { kind: ParseKind.Ok, path, opts, args, helpExplicit: false, helpPath: [], errorMsg: "", errorHelpPath: [] };
@@ -291,6 +343,20 @@ function helpResult(p: string[], explicit: boolean): ParseResult {
291
343
  };
292
344
  }
293
345
 
346
+ /** Builds a schema-export result for the program root. */
347
+ function schemaResult(): ParseResult {
348
+ return {
349
+ kind: ParseKind.Schema,
350
+ path: [],
351
+ opts: {},
352
+ args: [],
353
+ helpExplicit: false,
354
+ helpPath: [],
355
+ errorMsg: "",
356
+ errorHelpPath: [],
357
+ };
358
+ }
359
+
294
360
  /**
295
361
  * Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
296
362
  */
@@ -324,12 +390,16 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
324
390
  return helpResult([], true);
325
391
  }
326
392
 
393
+ if (i < argv.length && !forcePositionals && isSchemaTok(argv[i])) {
394
+ return schemaResult();
395
+ }
396
+
327
397
  // Determine which subcommand to route to
328
398
  let cmdName: string;
329
399
  let node: CliCommand | undefined;
330
400
 
331
401
  if (root.handler) {
332
- return finishLeaf(root as any, i, argv, path, opts);
402
+ return finishLeaf(root as CliCommand, i, argv, path, opts, root.options ?? [], forcePositionals);
333
403
  }
334
404
 
335
405
  if (i >= argv.length) {
@@ -415,7 +485,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
415
485
  if ((current.commands ?? []).length > 0) {
416
486
  return helpResult(path, false);
417
487
  }
418
- return finishLeaf(current, i, argv, path, opts);
488
+ return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
419
489
  }
420
490
 
421
491
  const tok = argv[i];
@@ -455,7 +525,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
455
525
  };
456
526
  }
457
527
 
458
- return finishLeaf(current, i, argv, path, opts);
528
+ return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
459
529
  }
460
530
  }
461
531
 
package/src/runtime.ts CHANGED
@@ -10,7 +10,8 @@ the runtime responsibilities remain easy to reason about.
10
10
  import { cliBuiltinCompletionGroup, completionBashScript, completionZshScript } from "./completion.ts";
11
11
  import { CliContext } from "./context.ts";
12
12
  import { cliHelpRender } from "./help.ts";
13
- import { parse, postParseValidate } from "./parse.ts";
13
+ import { parse, postParseValidate, ParseKind } from "./parse.ts";
14
+ import { cliSchemaJson } from "./schema.ts";
14
15
  import { CliCommand } from "./types.ts";
15
16
  import { cliValidateRoot } from "./validate.ts";
16
17
 
@@ -63,11 +64,16 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
63
64
  let pr = parse(parseRoot, argv);
64
65
  pr = postParseValidate(parseRoot, pr);
65
66
 
66
- if (pr.kind === "help") {
67
+ if (pr.kind === ParseKind.Help) {
67
68
  process.stdout.write(cliHelpRender(parseRoot, pr.helpPath, false));
68
69
  process.exit(pr.helpExplicit ? 0 : 1);
69
70
  }
70
71
 
72
+ if (pr.kind === ParseKind.Schema) {
73
+ process.stdout.write(cliSchemaJson(root));
74
+ process.exit(0);
75
+ }
76
+
71
77
  if (pr.kind === "error") {
72
78
  const color = process.stderr.isTTY;
73
79
  const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
package/src/schema.ts ADDED
@@ -0,0 +1,77 @@
1
+ /*
2
+ This module serializes the CLI schema tree to JSON for machine-readable introspection.
3
+ It strips handlers and runtime-only nodes so agents can discover commands, options,
4
+ and positionals in one shot.
5
+
6
+ It keeps schema export aligned with the declarative CliCommand model that drives help
7
+ and completion.
8
+ */
9
+
10
+ import {
11
+ CliCommand,
12
+ CliFallbackMode,
13
+ CliOption,
14
+ CliPositional,
15
+ } from "./types.ts";
16
+
17
+ /** JSON-safe command node (no handlers). */
18
+ export interface CliSchemaExport {
19
+ /** Program or command key. */
20
+ key: string;
21
+ /** Short description shown in help. */
22
+ description: string;
23
+ /** Additional notes shown in help (supports {app} placeholder). */
24
+ notes?: string;
25
+ /** Global or command-level flags/options. */
26
+ options?: CliOption[];
27
+ /** Default top-level subcommand (program root only). */
28
+ fallbackCommand?: string;
29
+ /** How fallbackCommand is applied (program root only). */
30
+ fallbackMode?: CliFallbackMode;
31
+ /** Nested subcommands (routing nodes only). */
32
+ commands?: CliSchemaExport[];
33
+ /** Positional argument definitions (leaf nodes only). */
34
+ positionals?: CliPositional[];
35
+ }
36
+
37
+ /** Converts one `CliCommand` node into a JSON-safe export (handlers omitted). */
38
+ function exportCommand(cmd: CliCommand): CliSchemaExport {
39
+ const out: CliSchemaExport = {
40
+ key: cmd.key,
41
+ description: cmd.description,
42
+ };
43
+
44
+ if ((cmd.notes ?? "").length > 0) {
45
+ out.notes = cmd.notes;
46
+ }
47
+
48
+ if ((cmd.options ?? []).length > 0) {
49
+ out.options = cmd.options;
50
+ }
51
+
52
+ if ("handler" in cmd && cmd.handler) {
53
+ if ((cmd.positionals ?? []).length > 0) {
54
+ out.positionals = cmd.positionals;
55
+ }
56
+ return out;
57
+ }
58
+
59
+ if (cmd.fallbackCommand !== undefined) {
60
+ out.fallbackCommand = cmd.fallbackCommand;
61
+ }
62
+ if (cmd.fallbackMode !== undefined) {
63
+ out.fallbackMode = cmd.fallbackMode;
64
+ }
65
+
66
+ const children = (cmd.commands ?? []).filter((ch) => ch.key !== "completion");
67
+ if (children.length > 0) {
68
+ out.commands = children.map(exportCommand);
69
+ }
70
+
71
+ return out;
72
+ }
73
+
74
+ /** Returns pretty-printed JSON for the full program schema (trailing newline). */
75
+ export function cliSchemaJson(root: CliCommand): string {
76
+ return JSON.stringify(exportCommand(root), null, 2) + "\n";
77
+ }
package/src/validate.ts CHANGED
@@ -69,6 +69,12 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
69
69
  );
70
70
  }
71
71
 
72
+ if (opt.name === "schema") {
73
+ throw new CliSchemaValidationError(
74
+ `Option name "schema" is reserved for --schema: ${cmd.key}/${opt.name}`,
75
+ );
76
+ }
77
+
72
78
  if (opt.shortName !== undefined) {
73
79
  if (opt.shortName === "h") {
74
80
  throw new CliSchemaValidationError(