padrone 1.3.0 → 1.5.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +105 -284
  3. package/dist/{args-DFEI7_G_.mjs → args-D5PNDyNu.mjs} +46 -21
  4. package/dist/args-D5PNDyNu.mjs.map +1 -0
  5. package/dist/chunk-CjcI7cDX.mjs +15 -0
  6. package/dist/codegen/index.d.mts +28 -3
  7. package/dist/codegen/index.d.mts.map +1 -1
  8. package/dist/codegen/index.mjs +169 -19
  9. package/dist/codegen/index.mjs.map +1 -1
  10. package/dist/command-utils-B1D-HqCd.mjs +1117 -0
  11. package/dist/command-utils-B1D-HqCd.mjs.map +1 -0
  12. package/dist/completion.d.mts +1 -1
  13. package/dist/completion.d.mts.map +1 -1
  14. package/dist/completion.mjs +77 -29
  15. package/dist/completion.mjs.map +1 -1
  16. package/dist/docs/index.d.mts +22 -2
  17. package/dist/docs/index.d.mts.map +1 -1
  18. package/dist/docs/index.mjs +94 -7
  19. package/dist/docs/index.mjs.map +1 -1
  20. package/dist/errors-BiVrBgi6.mjs +114 -0
  21. package/dist/errors-BiVrBgi6.mjs.map +1 -0
  22. package/dist/{formatter-XroimS3Q.d.mts → formatter-DtHzbP22.d.mts} +35 -5
  23. package/dist/formatter-DtHzbP22.d.mts.map +1 -0
  24. package/dist/help-bbmu9-qd.mjs +735 -0
  25. package/dist/help-bbmu9-qd.mjs.map +1 -0
  26. package/dist/index.d.mts +32 -3
  27. package/dist/index.d.mts.map +1 -1
  28. package/dist/index.mjs +495 -267
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/mcp-mLWIdUIu.mjs +379 -0
  31. package/dist/mcp-mLWIdUIu.mjs.map +1 -0
  32. package/dist/serve-B0u43DK7.mjs +404 -0
  33. package/dist/serve-B0u43DK7.mjs.map +1 -0
  34. package/dist/stream-BcC146Ud.mjs +56 -0
  35. package/dist/stream-BcC146Ud.mjs.map +1 -0
  36. package/dist/test.d.mts +1 -1
  37. package/dist/test.mjs +4 -15
  38. package/dist/test.mjs.map +1 -1
  39. package/dist/{types-BS7RP5Ls.d.mts → types-Ch8Mk6Qb.d.mts} +311 -63
  40. package/dist/types-Ch8Mk6Qb.d.mts.map +1 -0
  41. package/dist/{update-check-EbNDkzyV.mjs → update-check-CFX1FV3v.mjs} +2 -2
  42. package/dist/{update-check-EbNDkzyV.mjs.map → update-check-CFX1FV3v.mjs.map} +1 -1
  43. package/dist/zod.d.mts +32 -0
  44. package/dist/zod.d.mts.map +1 -0
  45. package/dist/zod.mjs +50 -0
  46. package/dist/zod.mjs.map +1 -0
  47. package/package.json +10 -2
  48. package/src/args.ts +76 -44
  49. package/src/cli/docs.ts +1 -7
  50. package/src/cli/doctor.ts +195 -10
  51. package/src/cli/index.ts +1 -1
  52. package/src/cli/init.ts +2 -3
  53. package/src/cli/link.ts +2 -2
  54. package/src/codegen/discovery.ts +80 -28
  55. package/src/codegen/index.ts +2 -1
  56. package/src/codegen/parsers/bash.ts +179 -0
  57. package/src/codegen/schema-to-code.ts +2 -1
  58. package/src/colorizer.ts +126 -13
  59. package/src/command-utils.ts +401 -23
  60. package/src/completion.ts +120 -47
  61. package/src/create.ts +483 -130
  62. package/src/docs/index.ts +122 -8
  63. package/src/formatter.ts +173 -125
  64. package/src/help.ts +46 -12
  65. package/src/index.ts +29 -1
  66. package/src/interactive.ts +45 -4
  67. package/src/mcp.ts +390 -0
  68. package/src/repl-loop.ts +16 -3
  69. package/src/runtime.ts +195 -2
  70. package/src/serve.ts +442 -0
  71. package/src/stream.ts +75 -0
  72. package/src/test.ts +7 -16
  73. package/src/type-utils.ts +28 -4
  74. package/src/types.ts +212 -30
  75. package/src/wrap.ts +23 -25
  76. package/src/zod.ts +50 -0
  77. package/dist/args-DFEI7_G_.mjs.map +0 -1
  78. package/dist/chunk-y_GBKt04.mjs +0 -5
  79. package/dist/formatter-XroimS3Q.d.mts.map +0 -1
  80. package/dist/help-CgGP7hQU.mjs +0 -1229
  81. package/dist/help-CgGP7hQU.mjs.map +0 -1
  82. package/dist/types-BS7RP5Ls.d.mts.map +0 -1
package/src/formatter.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { camelToKebab } from './args.ts';
2
- import { createColorizer } from './colorizer.ts';
2
+ import { type ColorConfig, type ColorTheme, createColorizer } from './colorizer.ts';
3
3
 
4
4
  export type HelpFormat = 'text' | 'ansi' | 'console' | 'markdown' | 'html' | 'json';
5
5
  export type HelpDetail = 'minimal' | 'standard' | 'full';
@@ -17,6 +17,7 @@ export type HelpPositionalInfo = {
17
17
  optional: boolean;
18
18
  default?: unknown;
19
19
  type?: string;
20
+ enum?: string[];
20
21
  };
21
22
 
22
23
  /**
@@ -44,6 +45,8 @@ export type HelpArgumentInfo = {
44
45
  negatable?: boolean;
45
46
  /** Config file key that maps to this arg */
46
47
  configKey?: string;
48
+ /** Group name for organizing this option under a labeled section in help output */
49
+ group?: string;
47
50
  };
48
51
 
49
52
  /**
@@ -57,6 +60,8 @@ export type HelpSubcommandInfo = {
57
60
  deprecated?: boolean | string;
58
61
  hidden?: boolean;
59
62
  hasSubcommands?: boolean;
63
+ /** Group name for organizing this command under a labeled section in help output */
64
+ group?: string;
60
65
  };
61
66
 
62
67
  /**
@@ -102,6 +107,8 @@ export type HelpInfo = {
102
107
  arguments?: HelpArgumentInfo[];
103
108
  /** Built-in commands and flags (shown only for root command) */
104
109
  builtins?: HelpBuiltinInfo[];
110
+ /** Command-level usage examples (shown in help output) */
111
+ examples?: string[];
105
112
  /** Full help info for nested commands (used in 'full' detail mode) */
106
113
  nestedCommands?: HelpInfo[];
107
114
  };
@@ -132,6 +139,7 @@ type Styler = {
132
139
  type: (text: string) => string;
133
140
  description: (text: string) => string;
134
141
  label: (text: string) => string;
142
+ section: (text: string) => string;
135
143
  meta: (text: string) => string;
136
144
  example: (text: string) => string;
137
145
  exampleValue: (text: string) => string;
@@ -146,7 +154,6 @@ type LayoutConfig = {
146
154
  indent: (level: number) => string;
147
155
  join: (parts: string[]) => string;
148
156
  wrapDocument?: (content: string) => string;
149
- usageLabel: string;
150
157
  };
151
158
 
152
159
  // ============================================================================
@@ -160,6 +167,7 @@ function createTextStyler(): Styler {
160
167
  type: (text) => text,
161
168
  description: (text) => text,
162
169
  label: (text) => text,
170
+ section: (text) => text,
163
171
  meta: (text) => text,
164
172
  example: (text) => text,
165
173
  exampleValue: (text) => text,
@@ -167,14 +175,15 @@ function createTextStyler(): Styler {
167
175
  };
168
176
  }
169
177
 
170
- function createAnsiStyler(): Styler {
171
- const colorizer = createColorizer();
178
+ function createAnsiStyler(theme?: ColorTheme | ColorConfig): Styler {
179
+ const colorizer = createColorizer(theme);
172
180
  return {
173
181
  command: colorizer.command,
174
182
  arg: colorizer.arg,
175
183
  type: colorizer.type,
176
184
  description: colorizer.description,
177
185
  label: colorizer.label,
186
+ section: colorizer.label,
178
187
  meta: colorizer.meta,
179
188
  example: colorizer.example,
180
189
  exampleValue: colorizer.exampleValue,
@@ -182,30 +191,8 @@ function createAnsiStyler(): Styler {
182
191
  };
183
192
  }
184
193
 
185
- function createConsoleStyler(): Styler {
186
- const colors = {
187
- reset: '\x1b[0m',
188
- bold: '\x1b[1m',
189
- dim: '\x1b[2m',
190
- italic: '\x1b[3m',
191
- underline: '\x1b[4m',
192
- strikethrough: '\x1b[9m',
193
- cyan: '\x1b[36m',
194
- green: '\x1b[32m',
195
- yellow: '\x1b[33m',
196
- gray: '\x1b[90m',
197
- };
198
- return {
199
- command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
200
- arg: (text) => `${colors.green}${text}${colors.reset}`,
201
- type: (text) => `${colors.yellow}${text}${colors.reset}`,
202
- description: (text) => `${colors.dim}${text}${colors.reset}`,
203
- label: (text) => `${colors.bold}${text}${colors.reset}`,
204
- meta: (text) => `${colors.gray}${text}${colors.reset}`,
205
- example: (text) => `${colors.underline}${text}${colors.reset}`,
206
- exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
207
- deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`,
208
- };
194
+ function createConsoleStyler(theme?: ColorTheme | ColorConfig): Styler {
195
+ return createAnsiStyler(theme);
209
196
  }
210
197
 
211
198
  function createMarkdownStyler(): Styler {
@@ -214,7 +201,8 @@ function createMarkdownStyler(): Styler {
214
201
  arg: (text) => `\`${text}\``,
215
202
  type: (text) => `\`${text}\``,
216
203
  description: (text) => text,
217
- label: (text) => `### ${text}`,
204
+ label: (text) => `**${text}**`,
205
+ section: (text) => `### ${text}`,
218
206
  meta: (text) => `*${text}*`,
219
207
  example: (text) => `**${text}**`,
220
208
  exampleValue: (text) => `\`${text}\``,
@@ -232,7 +220,8 @@ function createHtmlStyler(): Styler {
232
220
  arg: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
233
221
  type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
234
222
  description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
235
- label: (text) => `<h3>${escapeHtml(text)}</h3>`,
223
+ label: (text) => `<strong>${escapeHtml(text)}</strong>`,
224
+ section: (text) => `<h3>${escapeHtml(text)}</h3>`,
236
225
  meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
237
226
  example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
238
227
  exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
@@ -249,7 +238,6 @@ function createTextLayout(): LayoutConfig {
249
238
  newline: '\n',
250
239
  indent: (level) => ' '.repeat(level),
251
240
  join: (parts) => parts.filter(Boolean).join(' '),
252
- usageLabel: 'Usage:',
253
241
  };
254
242
  }
255
243
 
@@ -262,7 +250,6 @@ function createMarkdownLayout(): LayoutConfig {
262
250
  return ' ';
263
251
  },
264
252
  join: (parts) => parts.filter(Boolean).join(' '),
265
- usageLabel: 'Usage:',
266
253
  };
267
254
  }
268
255
 
@@ -272,7 +259,6 @@ function createHtmlLayout(): LayoutConfig {
272
259
  indent: (level) => '&nbsp;&nbsp;'.repeat(level),
273
260
  join: (parts) => parts.filter(Boolean).join(' '),
274
261
  wrapDocument: (content) => `<div style="font-family: monospace; line-height: 1.6;">${content}</div>`,
275
- usageLabel: '<strong>Usage:</strong>',
276
262
  };
277
263
  }
278
264
 
@@ -283,8 +269,8 @@ function createHtmlLayout(): LayoutConfig {
283
269
  /**
284
270
  * Creates a formatter that uses the given styler and layout configuration.
285
271
  */
286
- function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter {
287
- const { newline, indent, join, wrapDocument, usageLabel } = layout;
272
+ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBuiltins?: boolean): Formatter {
273
+ const { newline, indent, join, wrapDocument } = layout;
288
274
 
289
275
  function formatUsageSection(info: HelpInfo): string[] {
290
276
  const usageParts: string[] = [styler.command(info.usage.command), info.usage.hasSubcommands ? styler.meta('[command]') : ''];
@@ -296,15 +282,13 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
296
282
  }
297
283
  }
298
284
  if (info.usage.hasArguments) usageParts.push(styler.meta('[options]'));
299
- return [`${usageLabel} ${join(usageParts)}`];
285
+ return [`${styler.label('Usage:')} ${join(usageParts)}`];
300
286
  }
301
287
 
302
288
  function formatSubcommandsSection(info: HelpInfo): string[] {
303
289
  const lines: string[] = [];
304
290
  const subcommands = info.subcommands!;
305
291
 
306
- lines.push(styler.label('Commands:'));
307
-
308
292
  const subcommandSuffix = (c: HelpSubcommandInfo) => (c.hasSubcommands ? ' <subcommand>' : '');
309
293
  const formatAliasParts = (c: HelpSubcommandInfo) => {
310
294
  if (!c.aliases?.length) return { plain: '', styled: '' };
@@ -322,40 +306,55 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
322
306
  }
323
307
  return { plain: parts.length ? ` ${parts.join(' ')}` : '', styled: styledParts.length ? ` ${styledParts.join(' ')}` : '' };
324
308
  };
309
+ // Column width is computed across all subcommands for consistent alignment
325
310
  const maxNameLength = Math.max(
326
311
  ...subcommands.map((c) => {
327
312
  return (c.name + subcommandSuffix(c) + formatAliasParts(c).plain).length;
328
313
  }),
329
314
  );
330
- for (const subCmd of subcommands) {
331
- const aliasParts = formatAliasParts(subCmd);
332
- const suffix = subcommandSuffix(subCmd);
333
- const commandDisplay = subCmd.name + suffix + aliasParts.plain;
334
- const padding = ' '.repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
335
- const isDeprecated = !!subCmd.deprecated;
336
- const isDefaultEntry = subCmd.name === '[default]';
337
- const commandName = isDeprecated
338
- ? styler.deprecated(commandDisplay)
339
- : (isDefaultEntry ? styler.meta(subCmd.name) : styler.command(subCmd.name)) +
340
- (suffix ? styler.meta(suffix) : '') +
341
- aliasParts.styled;
342
- const lineParts: string[] = [commandName, padding];
343
-
344
- // Use title if available, otherwise use description
345
- const displayText = subCmd.title ?? subCmd.description;
346
- if (displayText) {
347
- lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
348
- }
349
- if (isDeprecated) {
350
- const deprecatedMeta =
351
- typeof subCmd.deprecated === 'string' ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(' (deprecated)');
352
- lineParts.push(deprecatedMeta);
315
+
316
+ const grouped = Object.groupBy(subcommands, (c) => c.group ?? '');
317
+
318
+ const renderSubcommands = (cmds: HelpSubcommandInfo[]) => {
319
+ for (const subCmd of cmds) {
320
+ const aliasParts = formatAliasParts(subCmd);
321
+ const suffix = subcommandSuffix(subCmd);
322
+ const commandDisplay = subCmd.name + suffix + aliasParts.plain;
323
+ const padding = ' '.repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
324
+ const isDeprecated = !!subCmd.deprecated;
325
+ const isDefaultEntry = subCmd.name === '[default]';
326
+ const commandName = isDeprecated
327
+ ? styler.deprecated(commandDisplay)
328
+ : (isDefaultEntry ? styler.meta(subCmd.name) : styler.command(subCmd.name)) +
329
+ (suffix ? styler.meta(suffix) : '') +
330
+ aliasParts.styled;
331
+ const lineParts: string[] = [commandName, padding];
332
+
333
+ // Use title if available, otherwise use description
334
+ const displayText = subCmd.title ?? subCmd.description;
335
+ if (displayText) {
336
+ lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
337
+ }
338
+ if (isDeprecated) {
339
+ const deprecatedMeta =
340
+ typeof subCmd.deprecated === 'string' ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(' (deprecated)');
341
+ lineParts.push(deprecatedMeta);
342
+ }
343
+ lines.push(indent(1) + lineParts.join(''));
353
344
  }
354
- lines.push(indent(1) + lineParts.join(''));
345
+ };
346
+
347
+ for (const [key, items] of Object.entries(grouped)) {
348
+ if (lines.length > 0) lines.push('');
349
+ lines.push(styler.section(key ? `${key}:` : 'Commands:'));
350
+ renderSubcommands(items!);
355
351
  }
356
352
 
357
- lines.push('');
358
- lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
353
+ // Skip hint when builtins are present — the builtins section shows a combined hint
354
+ if (!info.builtins?.length) {
355
+ lines.push('');
356
+ lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
357
+ }
359
358
 
360
359
  return lines;
361
360
  }
@@ -364,7 +363,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
364
363
  const lines: string[] = [];
365
364
  const args = info.positionals!;
366
365
 
367
- lines.push(styler.label('Arguments:'));
366
+ lines.push(styler.section('Arguments:'));
368
367
 
369
368
  const maxNameLength = Math.min(32, Math.max(...args.map((a) => a.name.length)));
370
369
 
@@ -373,6 +372,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
373
372
  const descParts: string[] = [];
374
373
  if (arg.description) descParts.push(styler.description(arg.description));
375
374
  if (info.usage.stdinField === arg.name) descParts.push(styler.meta('(stdin)'));
375
+ if (arg.enum) descParts.push(styler.meta(`(choices: ${arg.enum.join(', ')})`));
376
376
  if (arg.default !== undefined) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
377
377
  lines.push(indent(1) + styler.arg(arg.name) + padding + join(descParts));
378
378
  }
@@ -384,8 +384,6 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
384
384
  const lines: string[] = [];
385
385
  const argList = info.arguments || [];
386
386
 
387
- lines.push(styler.label('Options:'));
388
-
389
387
  // Helper to check if a default value is meaningful (not empty string/array)
390
388
  const hasDefault = (value: unknown): boolean => {
391
389
  if (value === undefined) return false;
@@ -394,70 +392,94 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
394
392
  return true;
395
393
  };
396
394
 
397
- // Build left column (signature) for each arg to compute alignment
398
- const argColumns: { plain: string; styled: string; arg: HelpArgumentInfo }[] = argList.map((arg) => {
395
+ // Build columns: flags | names | type | description
396
+ const argColumns = argList.map((arg) => {
399
397
  // Promote kebab-case alias to primary display name if it exists
400
398
  const kebab = camelToKebab(arg.name);
401
399
  const primaryName = kebab && arg.aliases?.includes(kebab) ? kebab : arg.name;
402
400
  const remainingAliases = arg.aliases?.filter((a) => a !== primaryName);
403
- const argName = `--${primaryName}`;
404
- const flagNames = arg.flags?.length ? arg.flags.map((f) => `-${f}`).join(', ') : '';
405
- const aliasNames = remainingAliases?.length ? remainingAliases.map((a) => `--${a}`).join(', ') : '';
406
- const shortNames = [flagNames, aliasNames].filter(Boolean).join(', ');
407
- const fullArgName = shortNames ? `${argName}, ${shortNames}` : argName;
408
- const isDeprecated = !!arg.deprecated;
409
- const formattedArgName = isDeprecated ? styler.deprecated(fullArgName) : styler.arg(fullArgName);
410
401
 
411
- const plainParts: string[] = [fullArgName];
412
- const styledParts: string[] = [formattedArgName];
402
+ const flagsPlain = arg.flags?.length ? arg.flags.map((f) => `-${f}`).join(', ') : '';
403
+ const namesPlain = [`--${primaryName}`, ...(remainingAliases?.map((a) => `--${a}`) || [])].join(', ');
404
+ const typePlain = arg.type && arg.type !== 'boolean' ? (arg.optional ? `[${arg.type}]` : `<${arg.type}>`) : '';
413
405
 
414
- if (arg.type && arg.type !== 'boolean') {
415
- const typePart = arg.optional ? `[${arg.type}]` : `<${arg.type}>`;
416
- plainParts.push(typePart);
417
- styledParts.push(styler.type(typePart));
418
- }
419
- if (isDeprecated) {
420
- const deprecatedPart = typeof arg.deprecated === 'string' ? `(deprecated: ${arg.deprecated})` : '(deprecated)';
421
- plainParts.push(deprecatedPart);
422
- styledParts.push(styler.meta(deprecatedPart));
423
- }
406
+ const isDeprecated = !!arg.deprecated;
424
407
 
425
- return { plain: plainParts.join(' '), styled: join(styledParts), arg };
408
+ return { flagsPlain, namesPlain, typePlain, isDeprecated, arg };
426
409
  });
427
410
 
428
- const maxColumnWidth = Math.min(32, Math.max(...argColumns.map((c) => c.plain.length)));
429
-
430
- for (const { plain, styled, arg } of argColumns) {
431
- const padding = ' '.repeat(Math.max(2, maxColumnWidth - plain.length + 2));
432
-
433
- // Description followed by metadata (choices, default)
434
- const descParts: string[] = [];
435
- if (arg.description) descParts.push(styler.description(arg.description));
436
- if (info.usage.stdinField === arg.name) descParts.push(styler.meta('(stdin)'));
437
- if (arg.enum) descParts.push(styler.meta(`(choices: ${arg.enum.join(', ')})`));
438
- if (hasDefault(arg.default)) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
411
+ // Column widths are computed across all args (regardless of group) for consistent alignment
412
+ const maxFlagsWidth = Math.min(12, Math.max(0, ...argColumns.map((c) => c.flagsPlain.length)));
413
+ const maxNamesWidth = Math.min(32, Math.max(0, ...argColumns.map((c) => c.namesPlain.length)));
414
+ const maxTypeWidth = Math.min(16, Math.max(0, ...argColumns.map((c) => c.typePlain.length)));
415
+ const hasAnyFlags = maxFlagsWidth > 0;
416
+
417
+ // Split into ordered groups: ungrouped first as "Options:", then each group in first-seen order
418
+ const grouped = Object.groupBy(argColumns, (c) => c.arg.group ?? '');
419
+
420
+ const renderArgColumns = (columns: typeof argColumns) => {
421
+ for (const { flagsPlain, namesPlain, typePlain, isDeprecated, arg } of columns) {
422
+ const parts: string[] = [];
423
+
424
+ // Flags column
425
+ if (hasAnyFlags) {
426
+ const styledFlags = isDeprecated ? (flagsPlain ? styler.deprecated(flagsPlain) : '') : flagsPlain ? styler.arg(flagsPlain) : '';
427
+ const flagsPadding = ' '.repeat(Math.max(0, maxFlagsWidth - flagsPlain.length));
428
+ const separator = flagsPlain ? ', ' : ' ';
429
+ parts.push(styledFlags + flagsPadding + separator);
430
+ }
439
431
 
440
- lines.push(indent(1) + styled + padding + join(descParts));
432
+ // Names column
433
+ const styledNames = isDeprecated ? styler.deprecated(namesPlain) : styler.arg(namesPlain);
434
+ const namesPadding = ' '.repeat(Math.max(2, maxNamesWidth - namesPlain.length + 2));
435
+ parts.push(styledNames + namesPadding);
441
436
 
442
- // Environment variable line
443
- if (arg.env) {
444
- const envVars = typeof arg.env === 'string' ? [arg.env] : arg.env;
445
- const envParts: string[] = [styler.example('Env:'), styler.exampleValue(envVars.join(', '))];
446
- lines.push(indent(3) + join(envParts));
447
- }
437
+ // Type column
438
+ if (maxTypeWidth > 0) {
439
+ const styledType = typePlain ? styler.type(typePlain) : '';
440
+ const typePadding = ' '.repeat(Math.max(2, maxTypeWidth - typePlain.length + 2));
441
+ parts.push(styledType + typePadding);
442
+ }
448
443
 
449
- // Config key line
450
- if (arg.configKey) {
451
- const configParts: string[] = [styler.example('Config:'), styler.exampleValue(arg.configKey)];
452
- lines.push(indent(3) + join(configParts));
444
+ // Line 1: description
445
+ const descParts: string[] = [];
446
+ if (arg.description) descParts.push(isDeprecated ? styler.deprecated(arg.description) : styler.description(arg.description));
447
+ lines.push(indent(1) + parts.join('') + join(descParts));
448
+
449
+ // Line 2: deprecated (no reason), default, choices
450
+ const line2Parts: string[] = [];
451
+ if (isDeprecated && typeof arg.deprecated !== 'string') line2Parts.push(styler.meta('(deprecated)'));
452
+ if (hasDefault(arg.default)) line2Parts.push(styler.meta(`(default: ${String(arg.default)})`));
453
+ if (arg.enum) line2Parts.push(styler.meta(`(choices: ${arg.enum.join(', ')})`));
454
+ if (line2Parts.length > 0) lines.push(indent(3) + join(line2Parts));
455
+
456
+ // Line 3: deprecated (with reason), examples
457
+ const line3Parts: string[] = [];
458
+ if (isDeprecated && typeof arg.deprecated === 'string') line3Parts.push(styler.meta(`(deprecated: ${arg.deprecated})`));
459
+ if (arg.examples && arg.examples.length > 0) {
460
+ const exampleValues = arg.examples.map((example) => (typeof example === 'string' ? example : JSON.stringify(example))).join(', ');
461
+ line3Parts.push(styler.example('Example:'), styler.exampleValue(exampleValues));
462
+ }
463
+ if (line3Parts.length > 0) lines.push(indent(3) + join(line3Parts));
464
+
465
+ // Line 4: stdin, env, config
466
+ const line4Parts: string[] = [];
467
+ if (info.usage.stdinField === arg.name) line4Parts.push(styler.meta('(stdin)'));
468
+ if (arg.env) {
469
+ const envVars = typeof arg.env === 'string' ? [arg.env] : arg.env;
470
+ line4Parts.push(styler.example('Env:'), styler.exampleValue(envVars.join(', ')));
471
+ }
472
+ if (arg.configKey) {
473
+ line4Parts.push(styler.example('Config:'), styler.exampleValue(arg.configKey));
474
+ }
475
+ if (line4Parts.length > 0) lines.push(indent(3) + join(line4Parts));
453
476
  }
477
+ };
454
478
 
455
- // Examples line
456
- if (arg.examples && arg.examples.length > 0) {
457
- const exampleValues = arg.examples.map((example) => (typeof example === 'string' ? example : JSON.stringify(example))).join(', ');
458
- const exampleParts: string[] = [styler.example('Example:'), styler.exampleValue(exampleValues)];
459
- lines.push(indent(3) + join(exampleParts));
460
- }
479
+ for (const [key, items] of Object.entries(grouped)) {
480
+ if (lines.length > 0) lines.push('');
481
+ lines.push(styler.section(key ? `${key}:` : 'Options:'));
482
+ renderArgColumns(items!);
461
483
  }
462
484
 
463
485
  return lines;
@@ -467,7 +489,18 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
467
489
  const lines: string[] = [];
468
490
  const builtins = info.builtins!;
469
491
 
470
- lines.push(styler.label('Built-in:'));
492
+ if (!showAllBuiltins) {
493
+ // Collapsed summary: show selected builtins on a single line with a combined hint
494
+ const highlights = ['help [command]', 'version', '[command] --repl'];
495
+ lines.push(`${styler.label('Global:')} ${styler.meta(highlights.join(', '))}`);
496
+ const hint = info.usage.hasSubcommands
497
+ ? `Run "${info.name} [command] --help" for more information. Use "--all" for all global commands.`
498
+ : `Use "${info.name} help --all" for more information on global commands.`;
499
+ lines.push(styler.meta(hint));
500
+ return lines;
501
+ }
502
+
503
+ lines.push(styler.section('Global:'));
471
504
 
472
505
  // Compute max effective name length for alignment across main and sub entries
473
506
  const allLengths: number[] = [];
@@ -535,6 +568,15 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
535
568
  lines.push('');
536
569
  }
537
570
 
571
+ // Examples section (if present)
572
+ if (info.examples && info.examples.length > 0) {
573
+ lines.push(styler.section('Examples:'));
574
+ for (const ex of info.examples) {
575
+ lines.push(indent(1) + styler.meta('$ ') + styler.exampleValue(ex));
576
+ }
577
+ lines.push('');
578
+ }
579
+
538
580
  // Subcommands section
539
581
  if (info.subcommands && info.subcommands.length > 0) {
540
582
  lines.push(...formatSubcommandsSection(info));
@@ -564,7 +606,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
564
606
 
565
607
  // Nested commands section (full detail mode)
566
608
  if (info.nestedCommands?.length) {
567
- lines.push(styler.label('Subcommand Details:'));
609
+ lines.push(styler.section('Subcommand Details:'));
568
610
  lines.push('');
569
611
  for (const nestedCmd of info.nestedCommands) {
570
612
  lines.push(styler.meta('─'.repeat(60)));
@@ -626,12 +668,18 @@ function createMinimalFormatter(): Formatter {
626
668
  };
627
669
  }
628
670
 
629
- export function createFormatter(format: HelpFormat | 'auto', detail: HelpDetail = 'standard'): Formatter {
671
+ export function createFormatter(
672
+ format: HelpFormat | 'auto',
673
+ detail: HelpDetail = 'standard',
674
+ theme?: ColorTheme | ColorConfig,
675
+ all?: boolean,
676
+ ): Formatter {
630
677
  if (detail === 'minimal') return createMinimalFormatter();
631
678
  if (format === 'json') return createJsonFormatter();
632
- if (format === 'ansi' || (format === 'auto' && shouldUseAnsi())) return createGenericFormatter(createAnsiStyler(), createTextLayout());
633
- if (format === 'console') return createGenericFormatter(createConsoleStyler(), createTextLayout());
634
- if (format === 'markdown') return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout());
635
- if (format === 'html') return createGenericFormatter(createHtmlStyler(), createHtmlLayout());
636
- return createGenericFormatter(createTextStyler(), createTextLayout());
679
+ if (format === 'ansi' || (format === 'auto' && shouldUseAnsi()))
680
+ return createGenericFormatter(createAnsiStyler(theme), createTextLayout(), all);
681
+ if (format === 'console') return createGenericFormatter(createConsoleStyler(theme), createTextLayout(), all);
682
+ if (format === 'markdown') return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout(), all);
683
+ if (format === 'html') return createGenericFormatter(createHtmlStyler(), createHtmlLayout(), all);
684
+ return createGenericFormatter(createTextStyler(), createTextLayout(), all);
637
685
  }