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/completion.ts CHANGED
@@ -8,43 +8,51 @@ It keeps completion aligned with the runtime schema so the generated commands,
8
8
  options, and descriptions stay in sync with the CLI definition.
9
9
  */
10
10
 
11
- import { CliCommand, CliOptionDef } from "./types.ts";
11
+ import { CliCommand, CliOption } from "./types.ts";
12
12
 
13
13
  // ── Shared Types ───────────────────────────────────────────────────────────────
14
14
 
15
+ /** One tab-completion scope: child commands, options, and path key for the schema walk. */
15
16
  interface ScopeRec {
17
+ /** Child `CliCommand` keys at this scope. */
16
18
  kids: CliCommand[];
17
- opts: CliOptionDef[];
19
+ /** Options in scope (for option token completion). */
20
+ opts: CliOption[];
21
+ /** `/`-separated path of command keys from the root, or `""` for the root. */
18
22
  path: string;
23
+ /** True when a positional tail exists (bash/zsh may offer filenames). */
19
24
  wantsFiles: boolean;
20
25
  }
21
26
 
27
+ /** True if the command declares at least one positional (used to enable file completion). */
22
28
  function hasPositionalArguments(cmd: CliCommand): boolean {
23
- return (cmd.positionals ?? []).some((p) => p.positional);
29
+ return (cmd.positionals ?? []).length > 0;
24
30
  }
25
31
 
32
+ /** Recursively appends a scope record for `cmd` and its subtree into `acc`. */
26
33
  function walkScopes(cmdPath: string, cmd: CliCommand, acc: ScopeRec[]): void {
27
34
  acc.push({
28
- kids: cmd.children ?? [],
35
+ kids: cmd.commands ?? [],
29
36
  opts: cmd.options ?? [],
30
37
  path: cmdPath,
31
38
  wantsFiles: hasPositionalArguments(cmd),
32
39
  });
33
- for (const ch of cmd.children ?? []) {
40
+ for (const ch of cmd.commands ?? []) {
34
41
  const nextPath = cmdPath === "" ? ch.key : cmdPath + "/" + ch.key;
35
42
  walkScopes(nextPath, ch, acc);
36
43
  }
37
44
  }
38
45
 
46
+ /** Flattens the schema into a list of completion scopes (root + every command path). */
39
47
  function collectScopes(schema: CliCommand): ScopeRec[] {
40
48
  const acc: ScopeRec[] = [];
41
49
  acc.push({
42
- kids: schema.children ?? [],
50
+ kids: schema.commands ?? [],
43
51
  opts: schema.options ?? [],
44
52
  path: "",
45
53
  wantsFiles: false,
46
54
  });
47
- for (const c of schema.children ?? []) {
55
+ for (const c of schema.commands ?? []) {
48
56
  walkScopes(c.key, c, acc);
49
57
  }
50
58
  return acc;
@@ -52,14 +60,17 @@ function collectScopes(schema: CliCommand): ScopeRec[] {
52
60
 
53
61
  // ── Helpers ────────────────────────────────────────────────────────────────────
54
62
 
63
+ /** Produces a shell-safe identifier from the app or command name (alnum → `_`). */
55
64
  function identToken(s: string): string {
56
65
  return s.replace(/[^a-zA-Z0-9]/g, "_");
57
66
  }
58
67
 
68
+ /** Escapes a string for use inside single quotes in generated shell scripts. */
59
69
  function escShellSingleQuoted(s: string): string {
60
70
  return s.replace(/'/g, "'\\''");
61
71
  }
62
72
 
73
+ /** Sanitizes the app key for generated shell function names (non-alnum removed). */
63
74
  function mainName(schemaName: string): string {
64
75
  return schemaName.replace(/[^a-zA-Z0-9]/g, "_");
65
76
  }
@@ -69,6 +80,7 @@ const kHelpShort = "-h";
69
80
 
70
81
  // ── Bash Completion ────────────────────────────────────────────────────────────
71
82
 
83
+ /** Emits the bash helper that classifies a `--long` or `--long=val` token for each scope. */
72
84
  function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
73
85
  let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
74
86
  o += " local sid=\"$1\" w=\"$2\" nw=\"$3\"\n";
@@ -78,7 +90,6 @@ function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
78
90
  o += " case $w in\n";
79
91
  o += " " + kHelpLong + "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n".replace(/\$\{kHelpLong\}/g, kHelpLong).replace(/\$\{kHelpShort\}/g, kHelpShort);
80
92
  for (const op of sc.opts) {
81
- if (op.positional) continue;
82
93
  const base = "--" + op.name;
83
94
  if (op.kind === "presence") {
84
95
  o += " " + base + "|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base);
@@ -97,6 +108,7 @@ function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
97
108
  return o;
98
109
  }
99
110
 
111
+ /** Emits the bash helper that classifies a bundled/short `-x` / `-abc` token for each scope. */
100
112
  function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
101
113
  let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
102
114
  o += " local sid=\"$1\" w=\"$2\"\n";
@@ -112,7 +124,6 @@ function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
112
124
  o += " case $ch in\n";
113
125
  let boolChars = "";
114
126
  for (const op of sc.opts) {
115
- if (op.positional) continue;
116
127
  if (!op.shortName) continue;
117
128
  if (op.kind === "presence") {
118
129
  boolChars += op.shortName + "|";
@@ -139,6 +150,7 @@ function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
139
150
  return o;
140
151
  }
141
152
 
153
+ /** Emits the bash helper that maps a non-option token to the child scope id. */
142
154
  function emitMatchChild(ident: string, scopes: ScopeRec[], pathIndex: Record<string, number>): string {
143
155
  let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
144
156
  o += " local sid=\"$1\" w=\"$2\"\n";
@@ -161,6 +173,7 @@ function emitMatchChild(ident: string, scopes: ScopeRec[], pathIndex: Record<str
161
173
  return o;
162
174
  }
163
175
 
176
+ /** Emits bash that replays argv up to the current word to find the active completion scope. */
164
177
  function emitSimulate(ident: string): string {
165
178
  let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
166
179
  o += " local i=1 sid=0 w steps next\n";
@@ -198,6 +211,7 @@ function emitSimulate(ident: string): string {
198
211
  return o;
199
212
  }
200
213
 
214
+ /** Emits the main `COMPREPLY` driver and `complete -F` registration for bash. */
201
215
  function emitMainBodyBash(schema: CliCommand, ident: string): string {
202
216
  const main = mainName(schema.key);
203
217
  let o = "_${main}() {\n".replace("${main}", main);
@@ -231,6 +245,7 @@ function emitMainBodyBash(schema: CliCommand, ident: string): string {
231
245
  return o;
232
246
  }
233
247
 
248
+ /** Returns a self-contained bash `complete` script for the given program schema. */
234
249
  export function completionBashScript(schema: CliCommand): string {
235
250
  const ident = identToken(schema.key);
236
251
  const scopes = collectScopes(schema);
@@ -246,7 +261,6 @@ export function completionBashScript(schema: CliCommand): string {
246
261
  out += "A_" + ident + "_" + i + "_opts=()\n";
247
262
  out += "A_" + ident + "_" + i + "_opts+=('" + kHelpLong + "' '" + kHelpShort + "')\n";
248
263
  for (const o of sc.opts) {
249
- if (o.positional) continue;
250
264
  out += "A_" + ident + "_" + i + "_opts+=('--" + o.name + "')\n";
251
265
  if (o.shortName) {
252
266
  out += "A_" + ident + "_" + i + "_opts+=('-" + o.shortName + "')\n";
@@ -274,6 +288,7 @@ export function completionBashScript(schema: CliCommand): string {
274
288
 
275
289
  // ── Zsh Completion ─────────────────────────────────────────────────────────────
276
290
 
291
+ /** Emits zsh `typeset` arrays of options and subcommands for each scope. */
277
292
  function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
278
293
  let out = "";
279
294
  for (const [i, sc] of scopes.entries()) {
@@ -305,6 +320,7 @@ function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
305
320
  return out;
306
321
  }
307
322
 
323
+ /** Zsh: long-option classifier function source (mirrors bash consume_long). */
308
324
  function emitConsumeLongZsh(ident: string, scopes: ScopeRec[]): string {
309
325
  let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
310
326
  o += " local sid=\"$1\" w=\"$2\" nw=\"$3\"\n";
@@ -314,7 +330,6 @@ function emitConsumeLongZsh(ident: string, scopes: ScopeRec[]): string {
314
330
  o += " case $w in\n";
315
331
  o += " " + kHelpLong + "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n".replace(/\$\{kHelpLong\}/g, kHelpLong).replace(/\$\{kHelpShort\}/g, kHelpShort);
316
332
  for (const op of sc.opts) {
317
- if (op.positional) continue;
318
333
  const base = "--" + op.name;
319
334
  if (op.kind === "presence") {
320
335
  o += " " + base + "|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base);
@@ -333,6 +348,7 @@ function emitConsumeLongZsh(ident: string, scopes: ScopeRec[]): string {
333
348
  return o;
334
349
  }
335
350
 
351
+ /** Zsh: short-option classifier function source. */
336
352
  function emitConsumeShortZsh(ident: string, scopes: ScopeRec[]): string {
337
353
  let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
338
354
  o += " local sid=\"$1\" w=\"$2\"\n";
@@ -348,7 +364,6 @@ function emitConsumeShortZsh(ident: string, scopes: ScopeRec[]): string {
348
364
  o += " case $ch in\n";
349
365
  let boolChars = "";
350
366
  for (const op of sc.opts) {
351
- if (op.positional) continue;
352
367
  if (!op.shortName) continue;
353
368
  if (op.kind === "presence") {
354
369
  boolChars += op.shortName + "|";
@@ -375,6 +390,7 @@ function emitConsumeShortZsh(ident: string, scopes: ScopeRec[]): string {
375
390
  return o;
376
391
  }
377
392
 
393
+ /** Zsh: subcommand name → scope id matching helper. */
378
394
  function emitMatchChildZsh(ident: string, scopes: ScopeRec[], pathIndex: Record<string, number>): string {
379
395
  let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
380
396
  o += " local sid=\"$1\" w=\"$2\"\n";
@@ -397,6 +413,7 @@ function emitMatchChildZsh(ident: string, scopes: ScopeRec[], pathIndex: Record<
397
413
  return o;
398
414
  }
399
415
 
416
+ /** Zsh: simulates word traversal to the current completion context (sets `REPLY_SID`). */
400
417
  function emitSimulateZsh(ident: string): string {
401
418
  let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
402
419
  o += " local i=2 sid=0 w steps next\n";
@@ -434,6 +451,7 @@ function emitSimulateZsh(ident: string): string {
434
451
  return o;
435
452
  }
436
453
 
454
+ /** Zsh: `_main` completer and `compdef` registration. */
437
455
  function emitMainBodyZsh(schema: CliCommand, ident: string): string {
438
456
  const main = mainName(schema.key);
439
457
  let o = "_${main}() {\n".replace("${main}", main);
@@ -465,6 +483,7 @@ function emitMainBodyZsh(schema: CliCommand, ident: string): string {
465
483
  return o;
466
484
  }
467
485
 
486
+ /** Returns a self-contained zsh completion script for the given program schema. */
468
487
  export function completionZshScript(schema: CliCommand): string {
469
488
  const ident = identToken(schema.key);
470
489
  const scopes = collectScopes(schema);
@@ -484,13 +503,13 @@ export function completionZshScript(schema: CliCommand): string {
484
503
  }
485
504
 
486
505
  /**
487
- * Builds the static `completion` / `bash` / `zsh` subtree used for shell integration.
506
+ * Builds the static `completion` / `bash` / `zsh` command subtree (merged into the program root at runtime).
488
507
  */
489
508
  export function cliBuiltinCompletionGroup(appName: string): CliCommand {
490
509
  return {
491
510
  key: "completion",
492
511
  description: "Generate the autocompletion script for shells.",
493
- children: [
512
+ commands: [
494
513
  {
495
514
  key: "bash",
496
515
  description: "Print a bash tab-completion script.",
package/src/context.ts CHANGED
@@ -20,6 +20,7 @@ export class CliContext {
20
20
  readonly schema: CliCommand;
21
21
  readonly opts: Record<string, string>;
22
22
 
23
+ /** Captures the merged program root, routed path, positional words, and option map for a leaf handler. */
23
24
  constructor(
24
25
  appName: string,
25
26
  commandPath: string[],
@@ -35,7 +36,7 @@ export class CliContext {
35
36
  }
36
37
 
37
38
  /** Returns whether a presence flag was set (including implicit "1" for boolean options). */
38
- flag(name: string): boolean {
39
+ hasFlag(name: string): boolean {
39
40
  return this.opts[name] !== undefined;
40
41
  }
41
42
 
package/src/help.ts CHANGED
@@ -7,32 +7,41 @@ It keeps help formatting shared across help and error paths so users see one con
7
7
  style no matter how help is reached.
8
8
  */
9
9
 
10
- import { CliCommand, CliOptionDef, CliOptionKind } from "./types.ts";
10
+ import { CliCommand, CliOption, CliOptionKind, CliPositional } from "./types.ts";
11
11
 
12
12
  // ── ANSI Style Helpers ────────────────────────────────────────────────────────
13
13
 
14
+ /** SGR wrappers for TTY help output. */
14
15
  const style = {
16
+ /** Joins a message with a prefix and a reset (or suffix) for ANSI SGR. */
15
17
  wrap(prefix: string, body: string, suffix: string): string {
16
18
  return prefix + body + suffix;
17
19
  },
20
+ /** Renders the message in red. */
18
21
  red(msg: string): string {
19
22
  return this.wrap("\u001B[31m", msg, "\u001B[0m");
20
23
  },
24
+ /** Renders the message in gray. */
21
25
  gray(msg: string): string {
22
26
  return this.wrap("\u001B[90m", msg, "\u001B[0m");
23
27
  },
28
+ /** Renders the message in bold. */
24
29
  bold(msg: string): string {
25
30
  return this.wrap("\u001B[1m", msg, "\u001B[0m");
26
31
  },
32
+ /** Renders the message in white. */
27
33
  white(msg: string): string {
28
34
  return this.wrap("\u001B[37m", msg, "\u001B[0m");
29
35
  },
36
+ /** Renders the message in bright aqua + bold. */
30
37
  aquaBold(msg: string): string {
31
38
  return this.wrap("\u001B[96m\u001B[1m", msg, "\u001B[0m");
32
39
  },
40
+ /** Renders the message in bright green. */
33
41
  greenBright(msg: string): string {
34
42
  return this.wrap("\u001B[92m", msg, "\u001B[0m");
35
43
  },
44
+ /** Renders a section title: gray and bold. */
36
45
  grayBoldTitle(title: string): string {
37
46
  return this.gray(this.bold(title));
38
47
  },
@@ -49,11 +58,13 @@ const kBoxH = "\u2500"; // ─
49
58
 
50
59
  // ── Terminal Detection ────────────────────────────────────────────────────────
51
60
 
61
+ /** Returns a minimum column width for help, clamped to stdout width when known. */
52
62
  function getHelpWidth(): number {
53
63
  return Math.max(40, process.stdout.columns || 80);
54
64
  }
55
65
 
56
- function isTTY(fd: number): boolean {
66
+ /** True when stdout is a TTY (used to decide on color). */
67
+ function isTTY(): boolean {
57
68
  return process.stdout.isTTY !== undefined;
58
69
  }
59
70
 
@@ -78,20 +89,24 @@ function visibleWidth(s: string): number {
78
89
  return w;
79
90
  }
80
91
 
92
+ /** Repeats the horizontal box-drawing character `n` times. */
81
93
  function repeatBoxH(n: number): string {
82
94
  return kBoxH.repeat(Math.max(0, n));
83
95
  }
84
96
 
97
+ /** Returns a string of `n` spaces. */
85
98
  function spaces(n: number): string {
86
99
  return " ".repeat(Math.max(0, n));
87
100
  }
88
101
 
102
+ /** Pads `s` to visible width (ANSI-aware) to `width` columns. */
89
103
  function padVisible(s: string, width: number): string {
90
104
  return s + spaces(Math.max(0, width - visibleWidth(s)));
91
105
  }
92
106
 
93
107
  // ── Text Wrapping ─────────────────────────────────────────────────────────────
94
108
 
109
+ /** Word-wraps a single line of text to a maximum `width` in columns. */
95
110
  function wrapParagraph(text: string, width: number): string[] {
96
111
  const available = Math.max(1, width);
97
112
  const out: string[] = [];
@@ -113,6 +128,7 @@ function wrapParagraph(text: string, width: number): string[] {
113
128
  return out;
114
129
  }
115
130
 
131
+ /** Splits on newlines and wraps each logical line, preserving intentional leading-indent lines. */
116
132
  function wrapText(text: string, width: number): string[] {
117
133
  const out: string[] = [];
118
134
  const lines = text.split("\n");
@@ -134,6 +150,7 @@ function wrapText(text: string, width: number): string[] {
134
150
 
135
151
  // ── Option Label Formatting ───────────────────────────────────────────────────
136
152
 
153
+ /** Suffix for `--name` in usage (e.g. ` <string>`) based on value kind. */
137
154
  function optKindLabel(k: CliOptionKind): string {
138
155
  switch (k) {
139
156
  case CliOptionKind.Presence:
@@ -145,13 +162,8 @@ function optKindLabel(k: CliOptionKind): string {
145
162
  }
146
163
  }
147
164
 
148
- export function cliOptionLabel(o: CliOptionDef, color: boolean): string {
149
- if (o.positional) {
150
- if (o.argMax === 1) {
151
- return o.argMin === 0 ? "[" + o.name + "]" : "<" + o.name + ">";
152
- }
153
- return o.argMin === 0 ? "[" + o.name + "...]" : "<" + o.name + "...>";
154
- }
165
+ /** Formats a flag/value option for help tables: `--name`, optional short, optional kind hint. */
166
+ export function cliOptionLabel(o: CliOption, color: boolean): string {
155
167
  let r = "--" + o.name + optKindLabel(o.kind);
156
168
  if (o.shortName) r += ", -" + o.shortName;
157
169
  if (!color) return r;
@@ -163,13 +175,29 @@ export function cliOptionLabel(o: CliOptionDef, color: boolean): string {
163
175
  return style.aquaBold(left) + " " + style.greenBright(right);
164
176
  }
165
177
 
178
+ /** Formats a positional slot label (`<n>`, `[n]`, or varargs) for help. */
179
+ export function cliPositionalLabel(p: CliPositional, color: boolean): string {
180
+ let r: string;
181
+ if (p.argMax === 1) {
182
+ r = p.argMin === 0 ? "[" + p.name + "]" : "<" + p.name + ">";
183
+ } else {
184
+ r = p.argMin === 0 ? "[" + p.name + "...]" : "<" + p.name + "...>";
185
+ }
186
+ if (!color) return r;
187
+ return style.aquaBold(r);
188
+ }
189
+
166
190
  // ── Box Rendering ─────────────────────────────────────────────────────────────
167
191
 
192
+ /** A single help table row: left column text and right-column description. */
168
193
  interface HelpRow {
194
+ /** Option flag or subcommand / positional label. */
169
195
  label: string;
196
+ /** Explanatory text (may be wrapped to multiple display lines). */
170
197
  description: string;
171
198
  }
172
199
 
200
+ /** Renders a free-text or notes box with a Unicode border and `title` header. */
173
201
  function renderTextBox(title: string, lines: string[], hw: number, color: boolean): string[] {
174
202
  if (lines.length === 0) return [];
175
203
 
@@ -208,6 +236,7 @@ function renderTextBox(title: string, lines: string[], hw: number, color: boolea
208
236
  return out;
209
237
  }
210
238
 
239
+ /** Renders a two-column label/description table in a box (options, subcommands, positionals). */
211
240
  function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boolean): string[] {
212
241
  if (rows.length === 0) return [];
213
242
 
@@ -273,6 +302,7 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
273
302
 
274
303
  // ── Usage & Rows ──────────────────────────────────────────────────────────────
275
304
 
305
+ /** Builds one or two usage line strings (OPTIONS / COMMAND / ARGS) for the help header. */
276
306
  function usageLines(
277
307
  appName: string,
278
308
  helpPath: string[],
@@ -304,25 +334,25 @@ function usageLines(
304
334
  return out;
305
335
  }
306
336
 
307
- function rowsForOptions(defs: CliOptionDef[], color: boolean): HelpRow[] {
337
+ /** Table rows for named options, including a synthetic `--help, -h` row. */
338
+ function rowsForOptions(defs: CliOption[], color: boolean): HelpRow[] {
308
339
  const rows: HelpRow[] = [];
309
340
  const helpLabel = color
310
341
  ? style.aquaBold("--help, ") + style.greenBright("-h")
311
342
  : "--help, -h";
312
343
  rows.push({ label: helpLabel, description: "Show help for this command." });
313
344
  for (const o of defs) {
314
- if (o.positional) continue;
315
345
  rows.push({ label: cliOptionLabel(o, color), description: o.description });
316
346
  }
317
347
  return rows;
318
348
  }
319
349
 
320
- function rowsForPositionals(defs: CliOptionDef[], color: boolean): HelpRow[] {
321
- return defs
322
- .filter((o) => o.positional)
323
- .map((o) => ({ label: cliOptionLabel(o, color), description: o.description }));
350
+ /** Table rows for positional `CliPositional` definitions. */
351
+ function rowsForPositionals(defs: CliPositional[], color: boolean): HelpRow[] {
352
+ return defs.map((p) => ({ label: cliPositionalLabel(p, color), description: p.description }));
324
353
  }
325
354
 
355
+ /** Table rows for subcommands, sorted by key. */
326
356
  function rowsForSubcommands(cmds: CliCommand[]): HelpRow[] {
327
357
  return cmds
328
358
  .sort((a, b) => a.key.localeCompare(b.key))
@@ -331,9 +361,13 @@ function rowsForSubcommands(cmds: CliCommand[]): HelpRow[] {
331
361
 
332
362
  // ── Main Help Render ──────────────────────────────────────────────────────────
333
363
 
364
+ /**
365
+ * Renders full help for the app root or a nested command, following `helpPath` from the root key.
366
+ * `useStderr` is reserved for call-site consistency; width and color use stdout TTY.
367
+ */
334
368
  export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr: boolean): string {
335
369
  const hw = getHelpWidth();
336
- const color = isTTY(1);
370
+ const color = isTTY();
337
371
 
338
372
  if (helpPath.length === 0) {
339
373
  const lines: string[] = [];
@@ -345,7 +379,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
345
379
  lines.push(
346
380
  renderTextBox(
347
381
  "Usage",
348
- usageLines(schema.key, helpPath, (schema.children ?? []).length > 0, false, color),
382
+ usageLines(schema.key, helpPath, (schema.commands ?? []).length > 0, false, color),
349
383
  hw,
350
384
  color,
351
385
  ).join("\n"),
@@ -356,16 +390,16 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
356
390
  lines.push("");
357
391
  lines.push(optBox.join("\n"));
358
392
  }
359
- if ((schema.children ?? []).length > 0) {
393
+ if ((schema.commands ?? []).length > 0) {
360
394
  lines.push("");
361
395
  lines.push(
362
- renderTableBox("Commands", rowsForSubcommands(schema.children ?? []), hw, color).join("\n"),
396
+ renderTableBox("Commands", rowsForSubcommands(schema.commands ?? []), hw, color).join("\n"),
363
397
  );
364
398
  }
365
399
  return lines.join("\n") + "\n\n";
366
400
  }
367
401
 
368
- let layer = schema.children ?? [];
402
+ let layer = schema.commands ?? [];
369
403
  let node: CliCommand | undefined;
370
404
  for (const seg of helpPath) {
371
405
  const ch = layer.find((c) => c.key === seg);
@@ -373,7 +407,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
373
407
  return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
374
408
  }
375
409
  node = ch;
376
- layer = ch.children ?? [];
410
+ layer = ch.commands ?? [];
377
411
  }
378
412
  if (!node) {
379
413
  return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
@@ -388,7 +422,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
388
422
  lines.push(
389
423
  renderTextBox(
390
424
  "Usage",
391
- usageLines(schema.key, helpPath, (node.children ?? []).length > 0, (node.positionals ?? []).length > 0, color),
425
+ usageLines(schema.key, helpPath, (node.commands ?? []).length > 0, (node.positionals ?? []).length > 0, color),
392
426
  hw,
393
427
  color,
394
428
  ).join("\n"),
@@ -406,7 +440,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
406
440
  lines.push(posBox.join("\n"));
407
441
  }
408
442
 
409
- const subBox = renderTableBox("Subcommands", rowsForSubcommands(node.children ?? []), hw, color);
443
+ const subBox = renderTableBox("Subcommands", rowsForSubcommands(node.commands ?? []), hw, color);
410
444
  if (subBox.length > 0) {
411
445
  lines.push("");
412
446
  lines.push(subBox.join("\n"));