argsbarg 0.1.0 → 1.0.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/src/index.test.ts CHANGED
@@ -7,31 +7,33 @@ It keeps the CLI contract stable by catching routing, option handling, and gener
7
7
  shell output regressions.
8
8
  */
9
9
 
10
- import {
11
- CliCommand,
12
- createOption,
13
- CliOptionKind,
14
- CliFallbackMode,
15
- cliValidateRoot,
16
- parse,
17
- postParseValidate,
18
- completionBashScript,
19
- completionZshScript,
20
- ParseKind,
21
- } from "./index.ts";
10
+ import { completionBashScript, completionZshScript } from "./completion.ts";
11
+ import { CliCommand, CliFallbackMode, CliOptionKind } from "./index.ts";
12
+ import { ParseKind, parse, postParseValidate } from "./parse.ts";
13
+ import { cliValidateRoot } from "./validate.ts";
22
14
  import { expect, test } from "bun:test";
23
15
 
24
16
  test("bundled short presence flags", () => {
25
17
  const root: CliCommand = {
26
18
  key: "app",
27
19
  description: "",
28
- children: [
20
+ commands: [
29
21
  {
30
22
  key: "x",
31
23
  description: "cmd",
32
24
  options: [
33
- createOption("a", "", { kind: CliOptionKind.Presence, shortName: "a" }),
34
- createOption("b", "", { kind: CliOptionKind.Presence, shortName: "b" }),
25
+ {
26
+ name: "a",
27
+ description: "",
28
+ kind: CliOptionKind.Presence,
29
+ shortName: "a",
30
+ },
31
+ {
32
+ name: "b",
33
+ description: "",
34
+ kind: CliOptionKind.Presence,
35
+ shortName: "b",
36
+ },
35
37
  ],
36
38
  handler: () => {},
37
39
  },
@@ -48,11 +50,17 @@ test("long option equals", () => {
48
50
  const root: CliCommand = {
49
51
  key: "app",
50
52
  description: "",
51
- children: [
53
+ commands: [
52
54
  {
53
55
  key: "x",
54
56
  description: "cmd",
55
- options: [createOption("name", "", { kind: CliOptionKind.String })],
57
+ options: [
58
+ {
59
+ name: "name",
60
+ description: "",
61
+ kind: CliOptionKind.String,
62
+ },
63
+ ],
56
64
  handler: () => {},
57
65
  },
58
66
  ],
@@ -67,11 +75,17 @@ test("fallback missing or unknown root flags", () => {
67
75
  const root: CliCommand = {
68
76
  key: "app",
69
77
  description: "",
70
- children: [
78
+ commands: [
71
79
  {
72
80
  key: "hello",
73
81
  description: "Say hi.",
74
- options: [createOption("name", "", { kind: CliOptionKind.String })],
82
+ options: [
83
+ {
84
+ name: "name",
85
+ description: "",
86
+ kind: CliOptionKind.String,
87
+ },
88
+ ],
75
89
  handler: () => {},
76
90
  },
77
91
  ],
@@ -89,7 +103,7 @@ test("unknown command", () => {
89
103
  const root: CliCommand = {
90
104
  key: "app",
91
105
  description: "",
92
- children: [{ key: "hello", description: "", handler: () => {} }],
106
+ commands: [{ key: "hello", description: "", handler: () => {} }],
93
107
  };
94
108
  cliValidateRoot(root);
95
109
  const pr = parse(root, ["nope"]);
@@ -101,7 +115,7 @@ test("implicit help empty", () => {
101
115
  const root: CliCommand = {
102
116
  key: "app",
103
117
  description: "",
104
- children: [{ key: "x", description: "", handler: () => {} }],
118
+ commands: [{ key: "x", description: "", handler: () => {} }],
105
119
  };
106
120
  cliValidateRoot(root);
107
121
  const pr = parse(root, []);
@@ -113,11 +127,17 @@ test("invalid number post validate", () => {
113
127
  const root: CliCommand = {
114
128
  key: "app",
115
129
  description: "",
116
- children: [
130
+ commands: [
117
131
  {
118
132
  key: "x",
119
133
  description: "",
120
- options: [createOption("n", "", { kind: CliOptionKind.Number })],
134
+ options: [
135
+ {
136
+ name: "n",
137
+ description: "",
138
+ kind: CliOptionKind.Number,
139
+ },
140
+ ],
121
141
  handler: () => {},
122
142
  },
123
143
  ],
@@ -133,11 +153,17 @@ test("supports scientific notation in numbers", () => {
133
153
  const root: CliCommand = {
134
154
  key: "app",
135
155
  description: "",
136
- children: [
156
+ commands: [
137
157
  {
138
158
  key: "x",
139
159
  description: "",
140
- options: [createOption("n", "", { kind: CliOptionKind.Number })],
160
+ options: [
161
+ {
162
+ name: "n",
163
+ description: "",
164
+ kind: CliOptionKind.Number,
165
+ },
166
+ ],
141
167
  handler: () => {},
142
168
  },
143
169
  ],
@@ -153,7 +179,7 @@ test("root must not have handler", () => {
153
179
  const root: CliCommand = {
154
180
  key: "app",
155
181
  description: "",
156
- children: [{ key: "x", description: "", handler: () => {} }],
182
+ commands: [{ key: "x", description: "", handler: () => {} }],
157
183
  handler: () => {},
158
184
  };
159
185
  expect(() => cliValidateRoot(root)).toThrow(/Program root must not set handler/);
@@ -164,9 +190,15 @@ test("root must not have positionals", () => {
164
190
  key: "app",
165
191
  description: "",
166
192
  positionals: [
167
- createOption("p", "", { kind: CliOptionKind.String, positional: true }),
193
+ {
194
+ name: "p",
195
+ description: "",
196
+ kind: CliOptionKind.String,
197
+ argMin: 1,
198
+ argMax: 1,
199
+ },
168
200
  ],
169
- children: [{ key: "x", description: "", handler: () => {} }],
201
+ commands: [{ key: "x", description: "", handler: () => {} }],
170
202
  };
171
203
  expect(() => cliValidateRoot(root)).toThrow(/Program root must not declare positionals/);
172
204
  });
@@ -175,7 +207,7 @@ test("completion scripts contain app name", () => {
175
207
  const root: CliCommand = {
176
208
  key: "myapp",
177
209
  description: "Test",
178
- children: [{ key: "hello", description: "Say hello.", handler: () => {} }],
210
+ commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
179
211
  };
180
212
  cliValidateRoot(root);
181
213
  const bash = completionBashScript(root);
@@ -192,7 +224,7 @@ test("completion scripts do not emit invalid bash substitutions", () => {
192
224
  const root: CliCommand = {
193
225
  key: "app",
194
226
  description: "Test",
195
- children: [{ key: "hello", description: "Say hello.", handler: () => {} }],
227
+ commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
196
228
  };
197
229
  cliValidateRoot(root);
198
230
  const bash = completionBashScript(root);
@@ -203,7 +235,7 @@ test("completion scripts escape shell-sensitive command text in zsh", () => {
203
235
  const root: CliCommand = {
204
236
  key: "app",
205
237
  description: "Test",
206
- children: [
238
+ commands: [
207
239
  {
208
240
  key: "quote'cmd",
209
241
  description: "Say 'hello' and keep going.",
@@ -220,7 +252,7 @@ test("completion scripts keep dotted app names in registration names", () => {
220
252
  const root: CliCommand = {
221
253
  key: "minimal.ts",
222
254
  description: "Test",
223
- children: [{ key: "hello", description: "Say hello.", handler: () => {} }],
255
+ commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
224
256
  };
225
257
  cliValidateRoot(root);
226
258
 
@@ -235,13 +267,25 @@ test("stops parsing options at --", () => {
235
267
  const root: CliCommand = {
236
268
  key: "app",
237
269
  description: "",
238
- children: [
270
+ commands: [
239
271
  {
240
272
  key: "x",
241
273
  description: "cmd",
242
- options: [createOption("name", "", { kind: CliOptionKind.String })],
274
+ options: [
275
+ {
276
+ name: "name",
277
+ description: "",
278
+ kind: CliOptionKind.String,
279
+ },
280
+ ],
243
281
  positionals: [
244
- createOption("files", "", { kind: CliOptionKind.String, positional: true, argMax: 0, argMin: 0 })
282
+ {
283
+ name: "files",
284
+ description: "",
285
+ kind: CliOptionKind.String,
286
+ argMin: 0,
287
+ argMax: 0,
288
+ },
245
289
  ],
246
290
  handler: () => {},
247
291
  },
package/src/index.ts CHANGED
@@ -7,18 +7,7 @@ It gives consumers one stable import path without forcing them to know the inter
7
7
  module layout.
8
8
  */
9
9
 
10
- export { cliBuiltinCompletionGroup, completionBashScript, completionZshScript } from "./completion.ts";
11
- export { cliRun, cliErrWithHelp } from "./runtime";
12
10
  export { CliContext } from "./context.ts";
13
- export { cliOptionLabel, cliHelpRender } from "./help.ts";
14
- export { ParseKind, parse, postParseValidate } from "./parse.ts";
15
- export type { ParseResult } from "./parse.ts";
16
- export {
17
- CliOptionKind,
18
- CliFallbackMode,
19
- CliSchemaValidationError,
20
- createOption,
21
- } from "./types.ts";
22
- export type { CliOptionDef, CliCommand, CliHandler } from "./types.ts";
23
- export { fullStringIsDouble, strictParseDouble } from "./utils.ts";
24
- export { cliValidateRoot } from "./validate.ts";
11
+ export { cliErrWithHelp, cliRun } from "./runtime";
12
+ export { CliFallbackMode, CliOptionKind, CliSchemaValidationError } from "./types.ts";
13
+ export type { CliCommand, CliHandler, CliOption, CliPositional } from "./types.ts";
package/src/parse.ts CHANGED
@@ -10,7 +10,7 @@ across every entry path.
10
10
  import { CliContext } from "./context.ts";
11
11
  import {
12
12
  CliCommand,
13
- CliOptionDef,
13
+ CliOption,
14
14
  CliOptionKind,
15
15
  CliFallbackMode,
16
16
  } from "./types.ts";
@@ -18,22 +18,35 @@ import { fullStringIsDouble } from "./utils.ts";
18
18
 
19
19
  // ── Parse Result ──────────────────────────────────────────────────────────────
20
20
 
21
- /** Outcome of a parse: success, help request, or fatal user error. */
21
+ /**
22
+ * Outcome of a parse: success, help request, or fatal user error.
23
+ */
22
24
  export enum ParseKind {
25
+ /** Parsed successfully; options and positionals are valid. */
23
26
  Ok = "ok",
27
+ /** User requested help (explicit or implicit). */
24
28
  Help = "help",
29
+ /** User error (unknown command, bad option, etc.). */
25
30
  Error = "error",
26
31
  }
27
32
 
28
33
  /** Structured parse output: routed path, merged options, positional args, and help/error metadata. */
29
34
  export interface ParseResult {
35
+ /** Parse outcome (ok, help, or error). */
30
36
  kind: ParseKind;
37
+ /** Routed subcommand keys from the program root (e.g. `["hello"]`). */
31
38
  path: string[];
39
+ /** Merged long/short option values as string values (presence → `"1"`). */
32
40
  opts: Record<string, string>;
41
+ /** Positional arguments for the leaf command, in order. */
33
42
  args: string[];
43
+ /** True when the user passed `-h` / `--help` explicitly. */
34
44
  helpExplicit: boolean;
45
+ /** Path segments for scoped help (empty for root help). */
35
46
  helpPath: string[];
47
+ /** User-facing error message when `kind === Error`. */
36
48
  errorMsg: string;
49
+ /** Help path to render next to an error (for contextual help). */
37
50
  errorHelpPath: string[];
38
51
  }
39
52
 
@@ -42,32 +55,41 @@ export interface ParseResult {
42
55
  const helpShort = "-h";
43
56
  const helpLong = "--help";
44
57
 
58
+ /** Returns true if the argv token is `-h` or `--help`. */
45
59
  function isHelpTok(tok: string): boolean {
46
60
  return tok === helpShort || tok === helpLong;
47
61
  }
48
62
 
63
+ /** Looks up a subcommand or routing node by `key`. */
49
64
  function findChild(cmds: CliCommand[], name: string): CliCommand | undefined {
50
65
  return cmds.find((c) => c.key === name);
51
66
  }
52
67
 
53
- function findOptionByName(defs: CliOptionDef[], name: string): CliOptionDef | undefined {
68
+ /** Resolves a long-option definition by name (without leading `--`). */
69
+ function findOptionByName(defs: CliOption[], name: string): CliOption | undefined {
54
70
  return defs.find((o) => o.name === name);
55
71
  }
56
72
 
57
- function findOptionDefByShort(defs: CliOptionDef[], short: string): CliOptionDef | undefined {
58
- return defs.find((o) => !o.positional && o.shortName === short);
73
+ /** Resolves a short-option definition by its single character. */
74
+ function findOptionDefByShort(defs: CliOption[], short: string): CliOption | undefined {
75
+ return defs.find((o) => o.shortName === short);
59
76
  }
60
77
 
61
78
  // ── Option Consumption ────────────────────────────────────────────────────────
62
79
 
80
+ /** State from scanning argv for flags: error text, lenient early exit, or `--` seen. */
63
81
  interface ConsumeReport {
82
+ /** User-facing error when option parsing failed; null on success. */
64
83
  err: string | null;
84
+ /** True when lenient mode stopped on an unknown option token. */
65
85
  stoppedOnUnknown: boolean;
86
+ /** True when `--` was read (remaining argv is positional-only). */
66
87
  sawDoubleDash: boolean;
67
88
  }
68
89
 
90
+ /** Consumes argv from index `i` for long/short options, updating `opts` until a non-option or `--`. */
69
91
  function consumeOptions(
70
- defs: CliOptionDef[],
92
+ defs: CliOption[],
71
93
  lenientUnknown: boolean,
72
94
  argv: string[],
73
95
  i: number,
@@ -75,6 +97,7 @@ function consumeOptions(
75
97
  ): { report: ConsumeReport; nextIndex: number } {
76
98
  let idx = i;
77
99
 
100
+ /** Parses a single `--name` or `--name=value` token. Returns an error string, `""` if unknown and lenient, or `null` on success. */
78
101
  function consumeLong(tok: string): string | null {
79
102
  const body = tok.slice(2);
80
103
  let optName: string;
@@ -118,6 +141,7 @@ function consumeOptions(
118
141
  return null;
119
142
  }
120
143
 
144
+ /** Parses a bundled or single `-x` / `-nval` short token. */
121
145
  function consumeShort(tok: string): string | null {
122
146
  if (tok.length < 2) return `Unexpected option token: ${tok}`;
123
147
  const shorts = tok.slice(1);
@@ -183,6 +207,7 @@ function consumeOptions(
183
207
 
184
208
  // ── Positional Collection ─────────────────────────────────────────────────────
185
209
 
210
+ /** Fills `args` for a leaf from `startIdx` according to `node.positionals`. */
186
211
  function finishLeaf(
187
212
  node: CliCommand,
188
213
  startIdx: number,
@@ -190,6 +215,7 @@ function finishLeaf(
190
215
  path: string[],
191
216
  opts: Record<string, string>,
192
217
  ): ParseResult {
218
+ /** Builds a parse error for positional consumption failures. */
193
219
  function errorResult(msg: string): ParseResult {
194
220
  const pr: ParseResult = {
195
221
  kind: ParseKind.Error,
@@ -208,8 +234,6 @@ function finishLeaf(
208
234
  const args: string[] = [];
209
235
 
210
236
  for (const p of node.positionals ?? []) {
211
- if (!p.positional) continue;
212
-
213
237
  if (p.argMax === 1) {
214
238
  if (p.argMin >= 1) {
215
239
  if (idx >= argv.length) {
@@ -252,6 +276,7 @@ function finishLeaf(
252
276
 
253
277
  // ── Main Parser ───────────────────────────────────────────────────────────────
254
278
 
279
+ /** Builds a help-request result for the current routing path. */
255
280
  function helpResult(p: string[], explicit: boolean): ParseResult {
256
281
  return {
257
282
  kind: ParseKind.Help,
@@ -265,6 +290,9 @@ function helpResult(p: string[], explicit: boolean): ParseResult {
265
290
  };
266
291
  }
267
292
 
293
+ /**
294
+ * Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
295
+ */
268
296
  export function parse(root: CliCommand, argv: string[]): ParseResult {
269
297
  let i = 0;
270
298
  const path: string[] = [];
@@ -302,7 +330,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
302
330
  if (i >= argv.length) {
303
331
  if (root.fallbackCommand !== undefined && ((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOnly || (root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown)) {
304
332
  cmdName = root.fallbackCommand;
305
- node = findChild(root.children ?? [], cmdName);
333
+ node = findChild(root.commands ?? [], cmdName);
306
334
  if (!node) {
307
335
  return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
308
336
  }
@@ -311,7 +339,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
311
339
  }
312
340
  } else {
313
341
  const peek = argv[i];
314
- const childPick = !forcePositionals ? findChild(root.children ?? [], peek) : undefined;
342
+ const childPick = !forcePositionals ? findChild(root.commands ?? [], peek) : undefined;
315
343
 
316
344
  if (childPick !== undefined) {
317
345
  cmdName = peek;
@@ -325,14 +353,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
325
353
 
326
354
  if (canRouteUnknown) {
327
355
  cmdName = root.fallbackCommand!;
328
- node = findChild(root.children ?? [], cmdName);
356
+ node = findChild(root.commands ?? [], cmdName);
329
357
  if (!node) {
330
358
  return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
331
359
  }
332
360
  } else {
333
361
  cmdName = peek;
334
362
  if (!forcePositionals) i += 1;
335
- node = findChild(root.children ?? [], cmdName);
363
+ node = findChild(root.commands ?? [], cmdName);
336
364
  if (!node) {
337
365
  return {
338
366
  kind: ParseKind.Error,
@@ -379,7 +407,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
379
407
  }
380
408
 
381
409
  if (i >= argv.length) {
382
- if ((current.children ?? []).length > 0) {
410
+ if ((current.commands ?? []).length > 0) {
383
411
  return helpResult(path, false);
384
412
  }
385
413
  return finishLeaf(current, i, argv, path, opts);
@@ -400,7 +428,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
400
428
  }
401
429
 
402
430
  if (!forcePositionals) {
403
- const childOpt = findChild(current.children ?? [], tok);
431
+ const childOpt = findChild(current.commands ?? [], tok);
404
432
  if (childOpt !== undefined) {
405
433
  i += 1;
406
434
  path.push(tok);
@@ -409,7 +437,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
409
437
  }
410
438
  }
411
439
 
412
- if ((current.children ?? []).length > 0) {
440
+ if ((current.commands ?? []).length > 0) {
413
441
  return {
414
442
  kind: ParseKind.Error,
415
443
  path,
@@ -428,11 +456,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
428
456
 
429
457
  // ── Post-Parse Validation ─────────────────────────────────────────────────────
430
458
 
459
+ /**
460
+ * Validates option keys and numeric values for an Ok parse, merging in-scope options along `pr.path`.
461
+ */
431
462
  export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResult {
432
463
  if (pr.kind !== ParseKind.Ok) return pr;
433
464
 
434
465
  let defs = [...(root.options ?? [])];
435
- let cmds = root.children ?? [];
466
+ let cmds = root.commands ?? [];
436
467
 
437
468
  for (const seg of pr.path) {
438
469
  const ch = findChild(cmds, seg);
@@ -449,8 +480,7 @@ export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResul
449
480
  };
450
481
  }
451
482
  defs.push(...(ch.options ?? []));
452
- defs.push(...(ch.positionals ?? []));
453
- cmds = ch.children ?? [];
483
+ cmds = ch.commands ?? [];
454
484
  }
455
485
 
456
486
  for (const [k, v] of Object.entries(pr.opts)) {
package/src/runtime.ts CHANGED
@@ -19,7 +19,7 @@ import { cliValidateRoot } from "./validate.ts";
19
19
  */
20
20
  function cliRootMergedWithBuiltins(root: CliCommand): CliCommand {
21
21
  const merged = { ...root };
22
- merged.children = [...(root.children ?? []), cliBuiltinCompletionGroup(root.key)];
22
+ merged.commands = [...(root.commands ?? []), cliBuiltinCompletionGroup(root.key)];
23
23
  return merged;
24
24
  }
25
25
 
@@ -76,7 +76,7 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
76
76
 
77
77
  let current = merged;
78
78
  for (const seg of pr.path) {
79
- const ch = (current.children ?? []).find((candidate: CliCommand) => candidate.key === seg);
79
+ const ch = (current.commands ?? []).find((candidate: CliCommand) => candidate.key === seg);
80
80
  if (!ch) {
81
81
  process.stderr.write("Internal error: missing handler for path.\n");
82
82
  process.exit(1);
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- This module defines the CLI schema, option kinds, fallback modes, and helpers.
2
+ This module defines the CLI schema, option kinds, and fallback modes.
3
3
  It is the shared declarative model that parsing, validation, help, and completion all
4
4
  read from, so the package has one source of truth.
5
5
 
@@ -12,8 +12,11 @@ import type { CliContext } from "./context.ts";
12
12
  * Option kinds: presence (boolean flag), string (free-form text), or number (strict double).
13
13
  */
14
14
  export enum CliOptionKind {
15
+ /** Boolean flag: no value token (may be implicit `"1"` when set). */
15
16
  Presence = "presence",
17
+ /** Free-form string value. */
16
18
  String = "string",
19
+ /** Strict floating-point value (parsed at validation time). */
17
20
  Number = "number",
18
21
  }
19
22
 
@@ -22,18 +25,25 @@ export enum CliOptionKind {
22
25
  * Only the program root may set a non-default mode or a non-nil fallbackCommand.
23
26
  */
24
27
  export enum CliFallbackMode {
25
- /** Use the fallback only when argv has no first subcommand token. */
28
+ /**
29
+ * If argv has no first subcommand, route to `fallbackCommand`; if the first token is unknown, error.
30
+ */
26
31
  MissingOnly = "missingOnly",
27
- /** Use the fallback when the first token is missing or not a known child name. */
32
+ /**
33
+ * If argv has no first subcommand or the first token is not a known child, route to `fallbackCommand`.
34
+ */
28
35
  MissingOrUnknown = "missingOrUnknown",
29
- /** Use the fallback only when the first token is not a known child name. */
36
+ /**
37
+ * If the first token is present but not a known child, route to `fallbackCommand`.
38
+ * When the first subcommand token is missing (empty argv), do not use fallback (implicit root help).
39
+ */
30
40
  UnknownOnly = "unknownOnly",
31
41
  }
32
42
 
33
43
  /**
34
- * One CLI flag, option, or positional definition.
44
+ * A named flag or value option (`--long`, `-short`), listed on `CliCommand.options`.
35
45
  */
36
- export interface CliOptionDef {
46
+ export interface CliOption {
37
47
  /** Option name (e.g., "name", "verbose"). */
38
48
  name: string;
39
49
  /** Description shown in help. */
@@ -42,19 +52,29 @@ export interface CliOptionDef {
42
52
  kind: CliOptionKind;
43
53
  /** Short option character (e.g., 'n' for -n). */
44
54
  shortName?: string;
45
- /** Whether this is a positional argument (true) or a flag/option (false). */
46
- positional: boolean;
47
- /** Minimum number of values required (for positionals). */
55
+ }
56
+
57
+ /**
58
+ * An ordered positional argument slot, listed on `CliCommand.positionals`.
59
+ */
60
+ export interface CliPositional {
61
+ /** Positional name (used in help and error messages). */
62
+ name: string;
63
+ /** Description shown in help. */
64
+ description: string;
65
+ /** Value kind for each consumed token. */
66
+ kind: CliOptionKind;
67
+ /** Minimum number of values required. */
48
68
  argMin: number;
49
- /** Maximum number of values allowed (0 = unlimited, for positionals). */
69
+ /** Maximum number of values (0 = unlimited, for a varargs tail). */
50
70
  argMax: number;
51
71
  }
52
72
 
53
73
  /**
54
- * A command node: routing group (has children) or leaf (has handler).
74
+ * A command node: routing group (has commands) or leaf (has handler).
55
75
  *
56
76
  * The value passed to cliRun is the program root: name is the app/binary name,
57
- * children are top-level subcommands, options are global flags.
77
+ * commands are top-level subcommands, options are global flags.
58
78
  * The root must not set handler or declare positionals (validated at startup).
59
79
  */
60
80
  export interface CliCommand {
@@ -65,11 +85,11 @@ export interface CliCommand {
65
85
  /** Additional notes shown in help (supports {app} placeholder). */
66
86
  notes?: string;
67
87
  /** Global or command-level flags/options. */
68
- options?: CliOptionDef[];
88
+ options?: CliOption[];
69
89
  /** Positional argument definitions. */
70
- positionals?: CliOptionDef[];
71
- /** Child subcommands (empty for leaf commands). */
72
- children?: CliCommand[];
90
+ positionals?: CliPositional[];
91
+ /** Nested subcommands (empty for leaf commands). */
92
+ commands?: CliCommand[];
73
93
  /** Handler function for leaf commands. */
74
94
  handler?: CliHandler;
75
95
  /** Default top-level subcommand when argv omits a command or uses an unknown first token (root only). */
@@ -88,27 +108,9 @@ export type CliHandler = (ctx: CliContext) => void | Promise<void>;
88
108
  * Error thrown when the static CliCommand tree violates ArgsBarg rules.
89
109
  */
90
110
  export class CliSchemaValidationError extends Error {
111
+ /** Creates a schema validation error with a human-readable rule violation. */
91
112
  constructor(message: string) {
92
113
  super(message);
93
114
  this.name = "CliSchemaValidationError";
94
115
  }
95
116
  }
96
-
97
- /**
98
- * Creates a new CliOptionDef with sensible defaults.
99
- */
100
- export function createOption(
101
- name: string,
102
- description: string,
103
- options?: Partial<CliOptionDef>,
104
- ): CliOptionDef {
105
- return {
106
- name,
107
- description,
108
- kind: options?.kind ?? CliOptionKind.Presence,
109
- shortName: options?.shortName,
110
- positional: options?.positional ?? false,
111
- argMin: options?.argMin ?? 1,
112
- argMax: options?.argMax ?? 1,
113
- };
114
- }