argsbarg 0.1.0 → 1.0.1

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,30 +10,43 @@ across every entry path.
10
10
  import { CliContext } from "./context.ts";
11
11
  import {
12
12
  CliCommand,
13
- CliOptionDef,
14
- CliOptionKind,
15
13
  CliFallbackMode,
14
+ CliOption,
15
+ CliOptionKind,
16
16
  } from "./types.ts";
17
17
  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,10 +234,9 @@ function finishLeaf(
208
234
  const args: string[] = [];
209
235
 
210
236
  for (const p of node.positionals ?? []) {
211
- if (!p.positional) continue;
212
-
213
- if (p.argMax === 1) {
214
- if (p.argMin >= 1) {
237
+ const { argMin = 1, argMax = 1 } = p;
238
+ if (argMax === 1) {
239
+ if (argMin >= 1) {
215
240
  if (idx >= argv.length) {
216
241
  return errorResult(`Missing positional argument: ${p.name}`);
217
242
  }
@@ -225,21 +250,21 @@ function finishLeaf(
225
250
  }
226
251
 
227
252
  let count = 0;
228
- if (p.argMax === 0) {
253
+ if (argMax === 0) {
229
254
  while (idx < argv.length) {
230
255
  args.push(argv[idx]);
231
256
  idx += 1;
232
257
  count += 1;
233
258
  }
234
259
  } else {
235
- while (count < p.argMax && idx < argv.length) {
260
+ while (count < argMax && idx < argv.length) {
236
261
  args.push(argv[idx]);
237
262
  idx += 1;
238
263
  count += 1;
239
264
  }
240
265
  }
241
- if (count < p.argMin) {
242
- return errorResult(`Expected at least ${p.argMin} argument(s) for ${p.name}, got ${count}`);
266
+ if (count < argMin) {
267
+ return errorResult(`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`);
243
268
  }
244
269
  }
245
270
 
@@ -252,6 +277,7 @@ function finishLeaf(
252
277
 
253
278
  // ── Main Parser ───────────────────────────────────────────────────────────────
254
279
 
280
+ /** Builds a help-request result for the current routing path. */
255
281
  function helpResult(p: string[], explicit: boolean): ParseResult {
256
282
  return {
257
283
  kind: ParseKind.Help,
@@ -265,6 +291,9 @@ function helpResult(p: string[], explicit: boolean): ParseResult {
265
291
  };
266
292
  }
267
293
 
294
+ /**
295
+ * Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
296
+ */
268
297
  export function parse(root: CliCommand, argv: string[]): ParseResult {
269
298
  let i = 0;
270
299
  const path: string[] = [];
@@ -302,7 +331,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
302
331
  if (i >= argv.length) {
303
332
  if (root.fallbackCommand !== undefined && ((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOnly || (root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown)) {
304
333
  cmdName = root.fallbackCommand;
305
- node = findChild(root.children ?? [], cmdName);
334
+ node = findChild(root.commands ?? [], cmdName);
306
335
  if (!node) {
307
336
  return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
308
337
  }
@@ -311,7 +340,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
311
340
  }
312
341
  } else {
313
342
  const peek = argv[i];
314
- const childPick = !forcePositionals ? findChild(root.children ?? [], peek) : undefined;
343
+ const childPick = !forcePositionals ? findChild(root.commands ?? [], peek) : undefined;
315
344
 
316
345
  if (childPick !== undefined) {
317
346
  cmdName = peek;
@@ -325,14 +354,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
325
354
 
326
355
  if (canRouteUnknown) {
327
356
  cmdName = root.fallbackCommand!;
328
- node = findChild(root.children ?? [], cmdName);
357
+ node = findChild(root.commands ?? [], cmdName);
329
358
  if (!node) {
330
359
  return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
331
360
  }
332
361
  } else {
333
362
  cmdName = peek;
334
363
  if (!forcePositionals) i += 1;
335
- node = findChild(root.children ?? [], cmdName);
364
+ node = findChild(root.commands ?? [], cmdName);
336
365
  if (!node) {
337
366
  return {
338
367
  kind: ParseKind.Error,
@@ -379,7 +408,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
379
408
  }
380
409
 
381
410
  if (i >= argv.length) {
382
- if ((current.children ?? []).length > 0) {
411
+ if ((current.commands ?? []).length > 0) {
383
412
  return helpResult(path, false);
384
413
  }
385
414
  return finishLeaf(current, i, argv, path, opts);
@@ -400,7 +429,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
400
429
  }
401
430
 
402
431
  if (!forcePositionals) {
403
- const childOpt = findChild(current.children ?? [], tok);
432
+ const childOpt = findChild(current.commands ?? [], tok);
404
433
  if (childOpt !== undefined) {
405
434
  i += 1;
406
435
  path.push(tok);
@@ -409,7 +438,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
409
438
  }
410
439
  }
411
440
 
412
- if ((current.children ?? []).length > 0) {
441
+ if ((current.commands ?? []).length > 0) {
413
442
  return {
414
443
  kind: ParseKind.Error,
415
444
  path,
@@ -428,11 +457,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
428
457
 
429
458
  // ── Post-Parse Validation ─────────────────────────────────────────────────────
430
459
 
460
+ /**
461
+ * Validates option keys and numeric values for an Ok parse, merging in-scope options along `pr.path`.
462
+ */
431
463
  export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResult {
432
464
  if (pr.kind !== ParseKind.Ok) return pr;
433
465
 
434
466
  let defs = [...(root.options ?? [])];
435
- let cmds = root.children ?? [];
467
+ let cmds = root.commands ?? [];
436
468
 
437
469
  for (const seg of pr.path) {
438
470
  const ch = findChild(cmds, seg);
@@ -449,8 +481,7 @@ export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResul
449
481
  };
450
482
  }
451
483
  defs.push(...(ch.options ?? []));
452
- defs.push(...(ch.positionals ?? []));
453
- cmds = ch.children ?? [];
484
+ cmds = ch.commands ?? [];
454
485
  }
455
486
 
456
487
  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,35 @@ 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). */
48
- argMin: number;
49
- /** Maximum number of values allowed (0 = unlimited, for positionals). */
50
- argMax: number;
51
55
  }
52
56
 
53
57
  /**
54
- * A command node: routing group (has children) or leaf (has handler).
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
+ /**
68
+ * Minimum number of values required (default 1).
69
+ * Use `0` for an optional slot when paired with `argMax: 1`, or a varargs tail with `argMax: 0`.
70
+ */
71
+ argMin?: number;
72
+ /**
73
+ * Maximum number of values (`1` = a single required or optional word; default 1). Use `0` for an
74
+ * unbounded varargs tail (must be the last slot in the command’s `positionals` list).
75
+ */
76
+ argMax?: number;
77
+ }
78
+
79
+ /**
80
+ * A command node: routing group (has commands) or leaf (has handler).
55
81
  *
56
82
  * 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.
83
+ * commands are top-level subcommands, options are global flags.
58
84
  * The root must not set handler or declare positionals (validated at startup).
59
85
  */
60
86
  export interface CliCommand {
@@ -65,11 +91,11 @@ export interface CliCommand {
65
91
  /** Additional notes shown in help (supports {app} placeholder). */
66
92
  notes?: string;
67
93
  /** Global or command-level flags/options. */
68
- options?: CliOptionDef[];
94
+ options?: CliOption[];
69
95
  /** Positional argument definitions. */
70
- positionals?: CliOptionDef[];
71
- /** Child subcommands (empty for leaf commands). */
72
- children?: CliCommand[];
96
+ positionals?: CliPositional[];
97
+ /** Nested subcommands (empty for leaf commands). */
98
+ commands?: CliCommand[];
73
99
  /** Handler function for leaf commands. */
74
100
  handler?: CliHandler;
75
101
  /** Default top-level subcommand when argv omits a command or uses an unknown first token (root only). */
@@ -88,27 +114,9 @@ export type CliHandler = (ctx: CliContext) => void | Promise<void>;
88
114
  * Error thrown when the static CliCommand tree violates ArgsBarg rules.
89
115
  */
90
116
  export class CliSchemaValidationError extends Error {
117
+ /** Creates a schema validation error with a human-readable rule violation. */
91
118
  constructor(message: string) {
92
119
  super(message);
93
120
  this.name = "CliSchemaValidationError";
94
121
  }
95
122
  }
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
- }