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/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,30 @@ 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
+ const { argMin = 1, argMax = 1 } = p;
181
+ let r: string;
182
+ if (argMax === 1) {
183
+ r = argMin === 0 ? "[" + p.name + "]" : "<" + p.name + ">";
184
+ } else {
185
+ r = argMin === 0 ? "[" + p.name + "...]" : "<" + p.name + "...>";
186
+ }
187
+ if (!color) return r;
188
+ return style.aquaBold(r);
189
+ }
190
+
166
191
  // ── Box Rendering ─────────────────────────────────────────────────────────────
167
192
 
193
+ /** A single help table row: left column text and right-column description. */
168
194
  interface HelpRow {
195
+ /** Option flag or subcommand / positional label. */
169
196
  label: string;
197
+ /** Explanatory text (may be wrapped to multiple display lines). */
170
198
  description: string;
171
199
  }
172
200
 
201
+ /** Renders a free-text or notes box with a Unicode border and `title` header. */
173
202
  function renderTextBox(title: string, lines: string[], hw: number, color: boolean): string[] {
174
203
  if (lines.length === 0) return [];
175
204
 
@@ -208,6 +237,7 @@ function renderTextBox(title: string, lines: string[], hw: number, color: boolea
208
237
  return out;
209
238
  }
210
239
 
240
+ /** Renders a two-column label/description table in a box (options, subcommands, positionals). */
211
241
  function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boolean): string[] {
212
242
  if (rows.length === 0) return [];
213
243
 
@@ -273,6 +303,7 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
273
303
 
274
304
  // ── Usage & Rows ──────────────────────────────────────────────────────────────
275
305
 
306
+ /** Builds one or two usage line strings (OPTIONS / COMMAND / ARGS) for the help header. */
276
307
  function usageLines(
277
308
  appName: string,
278
309
  helpPath: string[],
@@ -304,25 +335,25 @@ function usageLines(
304
335
  return out;
305
336
  }
306
337
 
307
- function rowsForOptions(defs: CliOptionDef[], color: boolean): HelpRow[] {
338
+ /** Table rows for named options, including a synthetic `--help, -h` row. */
339
+ function rowsForOptions(defs: CliOption[], color: boolean): HelpRow[] {
308
340
  const rows: HelpRow[] = [];
309
341
  const helpLabel = color
310
342
  ? style.aquaBold("--help, ") + style.greenBright("-h")
311
343
  : "--help, -h";
312
344
  rows.push({ label: helpLabel, description: "Show help for this command." });
313
345
  for (const o of defs) {
314
- if (o.positional) continue;
315
346
  rows.push({ label: cliOptionLabel(o, color), description: o.description });
316
347
  }
317
348
  return rows;
318
349
  }
319
350
 
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 }));
351
+ /** Table rows for positional `CliPositional` definitions. */
352
+ function rowsForPositionals(defs: CliPositional[], color: boolean): HelpRow[] {
353
+ return defs.map((p) => ({ label: cliPositionalLabel(p, color), description: p.description }));
324
354
  }
325
355
 
356
+ /** Table rows for subcommands, sorted by key. */
326
357
  function rowsForSubcommands(cmds: CliCommand[]): HelpRow[] {
327
358
  return cmds
328
359
  .sort((a, b) => a.key.localeCompare(b.key))
@@ -331,9 +362,13 @@ function rowsForSubcommands(cmds: CliCommand[]): HelpRow[] {
331
362
 
332
363
  // ── Main Help Render ──────────────────────────────────────────────────────────
333
364
 
365
+ /**
366
+ * Renders full help for the app root or a nested command, following `helpPath` from the root key.
367
+ * `useStderr` is reserved for call-site consistency; width and color use stdout TTY.
368
+ */
334
369
  export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr: boolean): string {
335
370
  const hw = getHelpWidth();
336
- const color = isTTY(1);
371
+ const color = isTTY();
337
372
 
338
373
  if (helpPath.length === 0) {
339
374
  const lines: string[] = [];
@@ -345,7 +380,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
345
380
  lines.push(
346
381
  renderTextBox(
347
382
  "Usage",
348
- usageLines(schema.key, helpPath, (schema.children ?? []).length > 0, false, color),
383
+ usageLines(schema.key, helpPath, (schema.commands ?? []).length > 0, false, color),
349
384
  hw,
350
385
  color,
351
386
  ).join("\n"),
@@ -356,16 +391,16 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
356
391
  lines.push("");
357
392
  lines.push(optBox.join("\n"));
358
393
  }
359
- if ((schema.children ?? []).length > 0) {
394
+ if ((schema.commands ?? []).length > 0) {
360
395
  lines.push("");
361
396
  lines.push(
362
- renderTableBox("Commands", rowsForSubcommands(schema.children ?? []), hw, color).join("\n"),
397
+ renderTableBox("Commands", rowsForSubcommands(schema.commands ?? []), hw, color).join("\n"),
363
398
  );
364
399
  }
365
400
  return lines.join("\n") + "\n\n";
366
401
  }
367
402
 
368
- let layer = schema.children ?? [];
403
+ let layer = schema.commands ?? [];
369
404
  let node: CliCommand | undefined;
370
405
  for (const seg of helpPath) {
371
406
  const ch = layer.find((c) => c.key === seg);
@@ -373,7 +408,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
373
408
  return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
374
409
  }
375
410
  node = ch;
376
- layer = ch.children ?? [];
411
+ layer = ch.commands ?? [];
377
412
  }
378
413
  if (!node) {
379
414
  return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
@@ -388,7 +423,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
388
423
  lines.push(
389
424
  renderTextBox(
390
425
  "Usage",
391
- usageLines(schema.key, helpPath, (node.children ?? []).length > 0, (node.positionals ?? []).length > 0, color),
426
+ usageLines(schema.key, helpPath, (node.commands ?? []).length > 0, (node.positionals ?? []).length > 0, color),
392
427
  hw,
393
428
  color,
394
429
  ).join("\n"),
@@ -406,7 +441,7 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
406
441
  lines.push(posBox.join("\n"));
407
442
  }
408
443
 
409
- const subBox = renderTableBox("Subcommands", rowsForSubcommands(node.children ?? []), hw, color);
444
+ const subBox = renderTableBox("Subcommands", rowsForSubcommands(node.commands ?? []), hw, color);
410
445
  if (subBox.length > 0) {
411
446
  lines.push("");
412
447
  lines.push(subBox.join("\n"));