padrone 1.4.0 → 1.6.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 (141) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +108 -283
  3. package/dist/args-Cnq0nwSM.mjs +272 -0
  4. package/dist/args-Cnq0nwSM.mjs.map +1 -0
  5. package/dist/codegen/index.d.mts +28 -3
  6. package/dist/codegen/index.d.mts.map +1 -1
  7. package/dist/codegen/index.mjs +169 -19
  8. package/dist/codegen/index.mjs.map +1 -1
  9. package/dist/commands-B_gufyR9.mjs +514 -0
  10. package/dist/commands-B_gufyR9.mjs.map +1 -0
  11. package/dist/{completion.mjs → completion-BEuflbDO.mjs} +86 -108
  12. package/dist/completion-BEuflbDO.mjs.map +1 -0
  13. package/dist/docs/index.d.mts +22 -2
  14. package/dist/docs/index.d.mts.map +1 -1
  15. package/dist/docs/index.mjs +92 -7
  16. package/dist/docs/index.mjs.map +1 -1
  17. package/dist/errors-CL63UOzt.mjs +137 -0
  18. package/dist/errors-CL63UOzt.mjs.map +1 -0
  19. package/dist/{formatter-ClUK5hcQ.d.mts → formatter-DrvhDMrq.d.mts} +35 -6
  20. package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
  21. package/dist/help-B5Kk83of.mjs +849 -0
  22. package/dist/help-B5Kk83of.mjs.map +1 -0
  23. package/dist/index-BaU3X6dY.d.mts +1178 -0
  24. package/dist/index-BaU3X6dY.d.mts.map +1 -0
  25. package/dist/index.d.mts +763 -36
  26. package/dist/index.d.mts.map +1 -1
  27. package/dist/index.mjs +3608 -1534
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/mcp-BM-d0nZi.mjs +377 -0
  30. package/dist/mcp-BM-d0nZi.mjs.map +1 -0
  31. package/dist/serve-Bk0JUlCj.mjs +402 -0
  32. package/dist/serve-Bk0JUlCj.mjs.map +1 -0
  33. package/dist/stream-DC4H8YTx.mjs +77 -0
  34. package/dist/stream-DC4H8YTx.mjs.map +1 -0
  35. package/dist/test.d.mts +5 -8
  36. package/dist/test.d.mts.map +1 -1
  37. package/dist/test.mjs +5 -27
  38. package/dist/test.mjs.map +1 -1
  39. package/dist/{update-check-EbNDkzyV.mjs → update-check-CZ2VqjnV.mjs} +16 -17
  40. package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
  41. package/dist/zod.d.mts +32 -0
  42. package/dist/zod.d.mts.map +1 -0
  43. package/dist/zod.mjs +50 -0
  44. package/dist/zod.mjs.map +1 -0
  45. package/package.json +20 -9
  46. package/src/cli/completions.ts +14 -11
  47. package/src/cli/docs.ts +13 -16
  48. package/src/cli/doctor.ts +213 -24
  49. package/src/cli/index.ts +28 -82
  50. package/src/cli/init.ts +12 -10
  51. package/src/cli/link.ts +22 -18
  52. package/src/cli/wrap.ts +14 -11
  53. package/src/codegen/discovery.ts +80 -28
  54. package/src/codegen/index.ts +2 -1
  55. package/src/codegen/parsers/bash.ts +179 -0
  56. package/src/codegen/schema-to-code.ts +2 -1
  57. package/src/core/args.ts +296 -0
  58. package/src/core/commands.ts +373 -0
  59. package/src/core/create.ts +268 -0
  60. package/src/{runtime.ts → core/default-runtime.ts} +70 -135
  61. package/src/{errors.ts → core/errors.ts} +22 -0
  62. package/src/core/exec.ts +259 -0
  63. package/src/core/interceptors.ts +302 -0
  64. package/src/{parse.ts → core/parse.ts} +36 -89
  65. package/src/core/program-methods.ts +301 -0
  66. package/src/core/results.ts +229 -0
  67. package/src/core/runtime.ts +246 -0
  68. package/src/core/validate.ts +247 -0
  69. package/src/docs/index.ts +124 -11
  70. package/src/extension/auto-output.ts +95 -0
  71. package/src/extension/color.ts +38 -0
  72. package/src/extension/completion.ts +49 -0
  73. package/src/extension/config.ts +262 -0
  74. package/src/extension/env.ts +101 -0
  75. package/src/extension/help.ts +192 -0
  76. package/src/extension/index.ts +43 -0
  77. package/src/extension/ink.ts +93 -0
  78. package/src/extension/interactive.ts +106 -0
  79. package/src/extension/logger.ts +214 -0
  80. package/src/extension/man.ts +51 -0
  81. package/src/extension/mcp.ts +52 -0
  82. package/src/extension/progress-renderer.ts +338 -0
  83. package/src/extension/progress.ts +299 -0
  84. package/src/extension/repl.ts +94 -0
  85. package/src/extension/serve.ts +48 -0
  86. package/src/extension/signal.ts +87 -0
  87. package/src/extension/stdin.ts +62 -0
  88. package/src/extension/suggestions.ts +114 -0
  89. package/src/extension/timing.ts +81 -0
  90. package/src/extension/tracing.ts +175 -0
  91. package/src/extension/update-check.ts +77 -0
  92. package/src/extension/utils.ts +51 -0
  93. package/src/extension/version.ts +63 -0
  94. package/src/{completion.ts → feature/completion.ts} +130 -57
  95. package/src/{interactive.ts → feature/interactive.ts} +47 -6
  96. package/src/feature/mcp.ts +387 -0
  97. package/src/{repl-loop.ts → feature/repl-loop.ts} +26 -16
  98. package/src/feature/serve.ts +438 -0
  99. package/src/feature/test.ts +262 -0
  100. package/src/{update-check.ts → feature/update-check.ts} +16 -16
  101. package/src/{wrap.ts → feature/wrap.ts} +27 -27
  102. package/src/index.ts +120 -11
  103. package/src/output/colorizer.ts +154 -0
  104. package/src/{formatter.ts → output/formatter.ts} +281 -135
  105. package/src/{help.ts → output/help.ts} +62 -15
  106. package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
  107. package/src/schema/zod.ts +50 -0
  108. package/src/test.ts +2 -285
  109. package/src/types/args-meta.ts +151 -0
  110. package/src/types/builder.ts +697 -0
  111. package/src/types/command.ts +157 -0
  112. package/src/types/index.ts +59 -0
  113. package/src/types/interceptor.ts +296 -0
  114. package/src/types/preferences.ts +83 -0
  115. package/src/types/result.ts +71 -0
  116. package/src/types/schema.ts +19 -0
  117. package/src/util/dotenv.ts +244 -0
  118. package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
  119. package/src/util/stream.ts +101 -0
  120. package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
  121. package/src/{type-utils.ts → util/type-utils.ts} +99 -37
  122. package/src/util/utils.ts +51 -0
  123. package/src/zod.ts +1 -0
  124. package/dist/args-CVDbyyzG.mjs +0 -199
  125. package/dist/args-CVDbyyzG.mjs.map +0 -1
  126. package/dist/chunk-y_GBKt04.mjs +0 -5
  127. package/dist/completion.d.mts +0 -64
  128. package/dist/completion.d.mts.map +0 -1
  129. package/dist/completion.mjs.map +0 -1
  130. package/dist/formatter-ClUK5hcQ.d.mts.map +0 -1
  131. package/dist/help-CcBe91bV.mjs +0 -1254
  132. package/dist/help-CcBe91bV.mjs.map +0 -1
  133. package/dist/types-DjIdJN5G.d.mts +0 -1059
  134. package/dist/types-DjIdJN5G.d.mts.map +0 -1
  135. package/dist/update-check-EbNDkzyV.mjs.map +0 -1
  136. package/src/args.ts +0 -461
  137. package/src/colorizer.ts +0 -41
  138. package/src/command-utils.ts +0 -532
  139. package/src/create.ts +0 -1477
  140. package/src/types.ts +0 -1109
  141. package/src/utils.ts +0 -140
@@ -1,5 +1,24 @@
1
- import { camelToKebab } from './args.ts';
2
- import { createColorizer } from './colorizer.ts';
1
+ import { camelToKebab } from '../util/shell-utils.ts';
2
+ import { type ColorConfig, type ColorTheme, createColorizer } from './colorizer.ts';
3
+
4
+ const DEFAULT_TERMINAL_WIDTH = 80;
5
+
6
+ function wrapText(text: string, maxWidth: number): string[] {
7
+ if (maxWidth <= 0 || text.length <= maxWidth) return [text];
8
+ const words = text.split(' ');
9
+ const lines: string[] = [];
10
+ let current = '';
11
+ for (const word of words) {
12
+ if (current && current.length + 1 + word.length > maxWidth) {
13
+ lines.push(current);
14
+ current = word;
15
+ } else {
16
+ current = current ? `${current} ${word}` : word;
17
+ }
18
+ }
19
+ if (current) lines.push(current);
20
+ return lines.length > 0 ? lines : [text];
21
+ }
3
22
 
4
23
  export type HelpFormat = 'text' | 'ansi' | 'console' | 'markdown' | 'html' | 'json';
5
24
  export type HelpDetail = 'minimal' | 'standard' | 'full';
@@ -45,6 +64,8 @@ export type HelpArgumentInfo = {
45
64
  negatable?: boolean;
46
65
  /** Config file key that maps to this arg */
47
66
  configKey?: string;
67
+ /** Group name for organizing this option under a labeled section in help output */
68
+ group?: string;
48
69
  };
49
70
 
50
71
  /**
@@ -58,6 +79,8 @@ export type HelpSubcommandInfo = {
58
79
  deprecated?: boolean | string;
59
80
  hidden?: boolean;
60
81
  hasSubcommands?: boolean;
82
+ /** Group name for organizing this command under a labeled section in help output */
83
+ group?: string;
61
84
  };
62
85
 
63
86
  /**
@@ -103,6 +126,8 @@ export type HelpInfo = {
103
126
  arguments?: HelpArgumentInfo[];
104
127
  /** Built-in commands and flags (shown only for root command) */
105
128
  builtins?: HelpBuiltinInfo[];
129
+ /** Command-level usage examples (shown in help output) */
130
+ examples?: string[];
106
131
  /** Full help info for nested commands (used in 'full' detail mode) */
107
132
  nestedCommands?: HelpInfo[];
108
133
  };
@@ -133,6 +158,7 @@ type Styler = {
133
158
  type: (text: string) => string;
134
159
  description: (text: string) => string;
135
160
  label: (text: string) => string;
161
+ section: (text: string) => string;
136
162
  meta: (text: string) => string;
137
163
  example: (text: string) => string;
138
164
  exampleValue: (text: string) => string;
@@ -147,7 +173,6 @@ type LayoutConfig = {
147
173
  indent: (level: number) => string;
148
174
  join: (parts: string[]) => string;
149
175
  wrapDocument?: (content: string) => string;
150
- usageLabel: string;
151
176
  };
152
177
 
153
178
  // ============================================================================
@@ -161,6 +186,7 @@ function createTextStyler(): Styler {
161
186
  type: (text) => text,
162
187
  description: (text) => text,
163
188
  label: (text) => text,
189
+ section: (text) => text,
164
190
  meta: (text) => text,
165
191
  example: (text) => text,
166
192
  exampleValue: (text) => text,
@@ -168,14 +194,15 @@ function createTextStyler(): Styler {
168
194
  };
169
195
  }
170
196
 
171
- function createAnsiStyler(): Styler {
172
- const colorizer = createColorizer();
197
+ function createAnsiStyler(theme?: ColorTheme | ColorConfig): Styler {
198
+ const colorizer = createColorizer(theme);
173
199
  return {
174
200
  command: colorizer.command,
175
201
  arg: colorizer.arg,
176
202
  type: colorizer.type,
177
203
  description: colorizer.description,
178
204
  label: colorizer.label,
205
+ section: colorizer.label,
179
206
  meta: colorizer.meta,
180
207
  example: colorizer.example,
181
208
  exampleValue: colorizer.exampleValue,
@@ -183,30 +210,8 @@ function createAnsiStyler(): Styler {
183
210
  };
184
211
  }
185
212
 
186
- function createConsoleStyler(): Styler {
187
- const colors = {
188
- reset: '\x1b[0m',
189
- bold: '\x1b[1m',
190
- dim: '\x1b[2m',
191
- italic: '\x1b[3m',
192
- underline: '\x1b[4m',
193
- strikethrough: '\x1b[9m',
194
- cyan: '\x1b[36m',
195
- green: '\x1b[32m',
196
- yellow: '\x1b[33m',
197
- gray: '\x1b[90m',
198
- };
199
- return {
200
- command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
201
- arg: (text) => `${colors.green}${text}${colors.reset}`,
202
- type: (text) => `${colors.yellow}${text}${colors.reset}`,
203
- description: (text) => `${colors.dim}${text}${colors.reset}`,
204
- label: (text) => `${colors.bold}${text}${colors.reset}`,
205
- meta: (text) => `${colors.gray}${text}${colors.reset}`,
206
- example: (text) => `${colors.underline}${text}${colors.reset}`,
207
- exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
208
- deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`,
209
- };
213
+ function createConsoleStyler(theme?: ColorTheme | ColorConfig): Styler {
214
+ return createAnsiStyler(theme);
210
215
  }
211
216
 
212
217
  function createMarkdownStyler(): Styler {
@@ -215,7 +220,8 @@ function createMarkdownStyler(): Styler {
215
220
  arg: (text) => `\`${text}\``,
216
221
  type: (text) => `\`${text}\``,
217
222
  description: (text) => text,
218
- label: (text) => `### ${text}`,
223
+ label: (text) => `**${text}**`,
224
+ section: (text) => `### ${text}`,
219
225
  meta: (text) => `*${text}*`,
220
226
  example: (text) => `**${text}**`,
221
227
  exampleValue: (text) => `\`${text}\``,
@@ -233,7 +239,8 @@ function createHtmlStyler(): Styler {
233
239
  arg: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
234
240
  type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
235
241
  description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
236
- label: (text) => `<h3>${escapeHtml(text)}</h3>`,
242
+ label: (text) => `<strong>${escapeHtml(text)}</strong>`,
243
+ section: (text) => `<h3>${escapeHtml(text)}</h3>`,
237
244
  meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
238
245
  example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
239
246
  exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
@@ -250,7 +257,6 @@ function createTextLayout(): LayoutConfig {
250
257
  newline: '\n',
251
258
  indent: (level) => ' '.repeat(level),
252
259
  join: (parts) => parts.filter(Boolean).join(' '),
253
- usageLabel: 'Usage:',
254
260
  };
255
261
  }
256
262
 
@@ -263,7 +269,6 @@ function createMarkdownLayout(): LayoutConfig {
263
269
  return ' ';
264
270
  },
265
271
  join: (parts) => parts.filter(Boolean).join(' '),
266
- usageLabel: 'Usage:',
267
272
  };
268
273
  }
269
274
 
@@ -273,7 +278,6 @@ function createHtmlLayout(): LayoutConfig {
273
278
  indent: (level) => '&nbsp;&nbsp;'.repeat(level),
274
279
  join: (parts) => parts.filter(Boolean).join(' '),
275
280
  wrapDocument: (content) => `<div style="font-family: monospace; line-height: 1.6;">${content}</div>`,
276
- usageLabel: '<strong>Usage:</strong>',
277
281
  };
278
282
  }
279
283
 
@@ -284,8 +288,8 @@ function createHtmlLayout(): LayoutConfig {
284
288
  /**
285
289
  * Creates a formatter that uses the given styler and layout configuration.
286
290
  */
287
- function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter {
288
- const { newline, indent, join, wrapDocument, usageLabel } = layout;
291
+ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBuiltins?: boolean, terminalWidth?: number): Formatter {
292
+ const { newline, indent, join, wrapDocument } = layout;
289
293
 
290
294
  function formatUsageSection(info: HelpInfo): string[] {
291
295
  const usageParts: string[] = [styler.command(info.usage.command), info.usage.hasSubcommands ? styler.meta('[command]') : ''];
@@ -297,15 +301,13 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
297
301
  }
298
302
  }
299
303
  if (info.usage.hasArguments) usageParts.push(styler.meta('[options]'));
300
- return [`${usageLabel} ${join(usageParts)}`];
304
+ return [`${styler.label('Usage:')} ${join(usageParts)}`];
301
305
  }
302
306
 
303
307
  function formatSubcommandsSection(info: HelpInfo): string[] {
304
308
  const lines: string[] = [];
305
309
  const subcommands = info.subcommands!;
306
310
 
307
- lines.push(styler.label('Commands:'));
308
-
309
311
  const subcommandSuffix = (c: HelpSubcommandInfo) => (c.hasSubcommands ? ' <subcommand>' : '');
310
312
  const formatAliasParts = (c: HelpSubcommandInfo) => {
311
313
  if (!c.aliases?.length) return { plain: '', styled: '' };
@@ -323,40 +325,55 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
323
325
  }
324
326
  return { plain: parts.length ? ` ${parts.join(' ')}` : '', styled: styledParts.length ? ` ${styledParts.join(' ')}` : '' };
325
327
  };
328
+ // Column width is computed across all subcommands for consistent alignment
326
329
  const maxNameLength = Math.max(
327
330
  ...subcommands.map((c) => {
328
331
  return (c.name + subcommandSuffix(c) + formatAliasParts(c).plain).length;
329
332
  }),
330
333
  );
331
- for (const subCmd of subcommands) {
332
- const aliasParts = formatAliasParts(subCmd);
333
- const suffix = subcommandSuffix(subCmd);
334
- const commandDisplay = subCmd.name + suffix + aliasParts.plain;
335
- const padding = ' '.repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
336
- const isDeprecated = !!subCmd.deprecated;
337
- const isDefaultEntry = subCmd.name === '[default]';
338
- const commandName = isDeprecated
339
- ? styler.deprecated(commandDisplay)
340
- : (isDefaultEntry ? styler.meta(subCmd.name) : styler.command(subCmd.name)) +
341
- (suffix ? styler.meta(suffix) : '') +
342
- aliasParts.styled;
343
- const lineParts: string[] = [commandName, padding];
344
-
345
- // Use title if available, otherwise use description
346
- const displayText = subCmd.title ?? subCmd.description;
347
- if (displayText) {
348
- lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
349
- }
350
- if (isDeprecated) {
351
- const deprecatedMeta =
352
- typeof subCmd.deprecated === 'string' ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(' (deprecated)');
353
- lineParts.push(deprecatedMeta);
334
+
335
+ const grouped = Object.groupBy(subcommands, (c) => c.group ?? '');
336
+
337
+ const renderSubcommands = (cmds: HelpSubcommandInfo[]) => {
338
+ for (const subCmd of cmds) {
339
+ const aliasParts = formatAliasParts(subCmd);
340
+ const suffix = subcommandSuffix(subCmd);
341
+ const commandDisplay = subCmd.name + suffix + aliasParts.plain;
342
+ const padding = ' '.repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
343
+ const isDeprecated = !!subCmd.deprecated;
344
+ const isDefaultEntry = subCmd.name === '[default]';
345
+ const commandName = isDeprecated
346
+ ? styler.deprecated(commandDisplay)
347
+ : (isDefaultEntry ? styler.meta(subCmd.name) : styler.command(subCmd.name)) +
348
+ (suffix ? styler.meta(suffix) : '') +
349
+ aliasParts.styled;
350
+ const lineParts: string[] = [commandName, padding];
351
+
352
+ // Use title if available, otherwise use description
353
+ const displayText = subCmd.title ?? subCmd.description;
354
+ if (displayText) {
355
+ lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
356
+ }
357
+ if (isDeprecated) {
358
+ const deprecatedMeta =
359
+ typeof subCmd.deprecated === 'string' ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(' (deprecated)');
360
+ lineParts.push(deprecatedMeta);
361
+ }
362
+ lines.push(indent(1) + lineParts.join(''));
354
363
  }
355
- lines.push(indent(1) + lineParts.join(''));
364
+ };
365
+
366
+ for (const [key, items] of Object.entries(grouped)) {
367
+ if (lines.length > 0) lines.push('');
368
+ lines.push(styler.section(key ? `${key}:` : 'Commands:'));
369
+ renderSubcommands(items!);
356
370
  }
357
371
 
358
- lines.push('');
359
- lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
372
+ // Skip hint when builtins are present — the builtins section shows a combined hint
373
+ if (!info.builtins?.length) {
374
+ lines.push('');
375
+ lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
376
+ }
360
377
 
361
378
  return lines;
362
379
  }
@@ -365,18 +382,56 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
365
382
  const lines: string[] = [];
366
383
  const args = info.positionals!;
367
384
 
368
- lines.push(styler.label('Arguments:'));
385
+ lines.push(styler.section('Arguments:'));
369
386
 
370
387
  const maxNameLength = Math.min(32, Math.max(...args.map((a) => a.name.length)));
388
+ const descCol = 2 + maxNameLength + 2;
389
+ const posAvailWidth = terminalWidth ? terminalWidth - descCol : undefined;
390
+ const descColPad = ' '.repeat(descCol);
371
391
 
372
392
  for (const arg of args) {
373
393
  const padding = ' '.repeat(Math.max(2, maxNameLength - arg.name.length + 2));
374
- const descParts: string[] = [];
375
- if (arg.description) descParts.push(styler.description(arg.description));
376
- if (info.usage.stdinField === arg.name) descParts.push(styler.meta('(stdin)'));
377
- if (arg.enum) descParts.push(styler.meta(`(choices: ${arg.enum.join(', ')})`));
378
- if (arg.default !== undefined) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
379
- lines.push(indent(1) + styler.arg(arg.name) + padding + join(descParts));
394
+ const prefix = indent(1) + styler.arg(arg.name) + padding;
395
+
396
+ const descPlain = arg.description ?? '';
397
+ const styledDesc = descPlain ? styler.description(descPlain) : '';
398
+
399
+ const metaParts: string[] = [];
400
+ const styledMetaParts: string[] = [];
401
+ if (info.usage.stdinField === arg.name) {
402
+ metaParts.push('(stdin)');
403
+ styledMetaParts.push(styler.meta('(stdin)'));
404
+ }
405
+ if (arg.enum) {
406
+ const text = `(choices: ${arg.enum.join(', ')})`;
407
+ metaParts.push(text);
408
+ styledMetaParts.push(styler.meta(text));
409
+ }
410
+ if (arg.default !== undefined) {
411
+ const text = `(default: ${String(arg.default)})`;
412
+ metaParts.push(text);
413
+ styledMetaParts.push(styler.meta(text));
414
+ }
415
+
416
+ const metaStyled = join(styledMetaParts);
417
+
418
+ if (posAvailWidth && posAvailWidth > 0) {
419
+ const metaPlain = metaParts.join(' ');
420
+ const fullPlain = [descPlain, metaPlain].filter(Boolean).join(' ');
421
+ if (fullPlain.length <= posAvailWidth) {
422
+ lines.push(prefix + [styledDesc, metaStyled].filter(Boolean).join(' '));
423
+ } else if (!descPlain || descPlain.length <= posAvailWidth) {
424
+ lines.push(prefix + styledDesc);
425
+ if (metaStyled) lines.push(descColPad + metaStyled);
426
+ } else {
427
+ const wrapped = wrapText(descPlain, posAvailWidth);
428
+ lines.push(prefix + styler.description(wrapped[0]!));
429
+ for (const wline of wrapped.slice(1)) lines.push(descColPad + styler.description(wline));
430
+ if (metaStyled) lines.push(descColPad + metaStyled);
431
+ }
432
+ } else {
433
+ lines.push(prefix + join([styledDesc, metaStyled]));
434
+ }
380
435
  }
381
436
 
382
437
  return lines;
@@ -386,8 +441,6 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
386
441
  const lines: string[] = [];
387
442
  const argList = info.arguments || [];
388
443
 
389
- lines.push(styler.label('Options:'));
390
-
391
444
  // Helper to check if a default value is meaningful (not empty string/array)
392
445
  const hasDefault = (value: unknown): boolean => {
393
446
  if (value === undefined) return false;
@@ -396,70 +449,134 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
396
449
  return true;
397
450
  };
398
451
 
399
- // Build left column (signature) for each arg to compute alignment
400
- const argColumns: { plain: string; styled: string; arg: HelpArgumentInfo }[] = argList.map((arg) => {
452
+ // Build columns: flags | names | type | description
453
+ const argColumns = argList.map((arg) => {
401
454
  // Promote kebab-case alias to primary display name if it exists
402
455
  const kebab = camelToKebab(arg.name);
403
456
  const primaryName = kebab && arg.aliases?.includes(kebab) ? kebab : arg.name;
404
457
  const remainingAliases = arg.aliases?.filter((a) => a !== primaryName);
405
- const argName = `--${primaryName}`;
406
- const flagNames = arg.flags?.length ? arg.flags.map((f) => `-${f}`).join(', ') : '';
407
- const aliasNames = remainingAliases?.length ? remainingAliases.map((a) => `--${a}`).join(', ') : '';
408
- const shortNames = [flagNames, aliasNames].filter(Boolean).join(', ');
409
- const fullArgName = shortNames ? `${argName}, ${shortNames}` : argName;
410
- const isDeprecated = !!arg.deprecated;
411
- const formattedArgName = isDeprecated ? styler.deprecated(fullArgName) : styler.arg(fullArgName);
412
458
 
413
- const plainParts: string[] = [fullArgName];
414
- const styledParts: string[] = [formattedArgName];
459
+ const flagsPlain = arg.flags?.length ? arg.flags.map((f) => `-${f}`).join(', ') : '';
460
+ const namesPlain = [`--${primaryName}`, ...(remainingAliases?.map((a) => `--${a}`) || [])].join(', ');
461
+ const typePlain = arg.type && arg.type !== 'boolean' ? (arg.optional ? `[${arg.type}]` : `<${arg.type}>`) : '';
415
462
 
416
- if (arg.type && arg.type !== 'boolean') {
417
- const typePart = arg.optional ? `[${arg.type}]` : `<${arg.type}>`;
418
- plainParts.push(typePart);
419
- styledParts.push(styler.type(typePart));
420
- }
421
- if (isDeprecated) {
422
- const deprecatedPart = typeof arg.deprecated === 'string' ? `(deprecated: ${arg.deprecated})` : '(deprecated)';
423
- plainParts.push(deprecatedPart);
424
- styledParts.push(styler.meta(deprecatedPart));
425
- }
463
+ const isDeprecated = !!arg.deprecated;
426
464
 
427
- return { plain: plainParts.join(' '), styled: join(styledParts), arg };
465
+ return { flagsPlain, namesPlain, typePlain, isDeprecated, arg };
428
466
  });
429
467
 
430
- const maxColumnWidth = Math.min(32, Math.max(...argColumns.map((c) => c.plain.length)));
468
+ // Column widths are computed across all args (regardless of group) for consistent alignment
469
+ const maxFlagsWidth = Math.min(12, Math.max(0, ...argColumns.map((c) => c.flagsPlain.length)));
470
+ const maxNamesWidth = Math.min(32, Math.max(0, ...argColumns.map((c) => c.namesPlain.length)));
471
+ const maxTypeWidth = Math.min(16, Math.max(0, ...argColumns.map((c) => c.typePlain.length)));
472
+ const hasAnyFlags = maxFlagsWidth > 0;
473
+ const descCol = 2 + (hasAnyFlags ? maxFlagsWidth + 2 : 0) + maxNamesWidth + 2 + (maxTypeWidth > 0 ? maxTypeWidth + 2 : 0);
474
+ const argAvailWidth = terminalWidth ? terminalWidth - descCol : undefined;
475
+ const descColPad = ' '.repeat(descCol);
476
+
477
+ // Split into ordered groups: ungrouped first as "Options:", then each group in first-seen order
478
+ const grouped = Object.groupBy(argColumns, (c) => c.arg.group ?? '');
479
+
480
+ const renderArgColumns = (columns: typeof argColumns) => {
481
+ for (const { flagsPlain, namesPlain, typePlain, isDeprecated, arg } of columns) {
482
+ const parts: string[] = [];
483
+
484
+ // Flags column
485
+ if (hasAnyFlags) {
486
+ const styledFlags = isDeprecated ? (flagsPlain ? styler.deprecated(flagsPlain) : '') : flagsPlain ? styler.arg(flagsPlain) : '';
487
+ const flagsPadding = ' '.repeat(Math.max(0, maxFlagsWidth - flagsPlain.length));
488
+ const separator = flagsPlain ? ', ' : ' ';
489
+ parts.push(styledFlags + flagsPadding + separator);
490
+ }
431
491
 
432
- for (const { plain, styled, arg } of argColumns) {
433
- const padding = ' '.repeat(Math.max(2, maxColumnWidth - plain.length + 2));
492
+ // Names column
493
+ const styledNames = isDeprecated ? styler.deprecated(namesPlain) : styler.arg(namesPlain);
494
+ const namesPadding = ' '.repeat(Math.max(2, maxNamesWidth - namesPlain.length + 2));
495
+ parts.push(styledNames + namesPadding);
434
496
 
435
- // Description followed by metadata (choices, default)
436
- const descParts: string[] = [];
437
- if (arg.description) descParts.push(styler.description(arg.description));
438
- if (info.usage.stdinField === arg.name) descParts.push(styler.meta('(stdin)'));
439
- if (arg.enum) descParts.push(styler.meta(`(choices: ${arg.enum.join(', ')})`));
440
- if (hasDefault(arg.default)) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
497
+ // Type column
498
+ if (maxTypeWidth > 0) {
499
+ const styledType = typePlain ? styler.type(typePlain) : '';
500
+ const typePadding = ' '.repeat(Math.max(2, maxTypeWidth - typePlain.length + 2));
501
+ parts.push(styledType + typePadding);
502
+ }
441
503
 
442
- lines.push(indent(1) + styled + padding + join(descParts));
504
+ const prefix = indent(1) + parts.join('');
505
+ const contPad = argAvailWidth ? descColPad : indent(3);
443
506
 
444
- // Environment variable line
445
- if (arg.env) {
446
- const envVars = typeof arg.env === 'string' ? [arg.env] : arg.env;
447
- const envParts: string[] = [styler.example('Env:'), styler.exampleValue(envVars.join(', '))];
448
- lines.push(indent(3) + join(envParts));
449
- }
507
+ // Build inline meta (deprecated no-reason, default, choices)
508
+ const inlineMeta: string[] = [];
509
+ const styledInlineMeta: string[] = [];
510
+ if (isDeprecated && typeof arg.deprecated !== 'string') {
511
+ inlineMeta.push('(deprecated)');
512
+ styledInlineMeta.push(styler.meta('(deprecated)'));
513
+ }
514
+ if (hasDefault(arg.default)) {
515
+ const text = `(default: ${String(arg.default)})`;
516
+ inlineMeta.push(text);
517
+ styledInlineMeta.push(styler.meta(text));
518
+ }
519
+ if (arg.enum) {
520
+ const text = `(choices: ${arg.enum.join(', ')})`;
521
+ inlineMeta.push(text);
522
+ styledInlineMeta.push(styler.meta(text));
523
+ }
450
524
 
451
- // Config key line
452
- if (arg.configKey) {
453
- const configParts: string[] = [styler.example('Config:'), styler.exampleValue(arg.configKey)];
454
- lines.push(indent(3) + join(configParts));
455
- }
525
+ const descPlain = arg.description ?? '';
526
+ const styledDesc = descPlain ? (isDeprecated ? styler.deprecated(descPlain) : styler.description(descPlain)) : '';
527
+ const metaStyled = join(styledInlineMeta);
528
+
529
+ if (argAvailWidth && argAvailWidth > 0) {
530
+ // Terminal-width-aware: try to fit description + meta on one line
531
+ const metaPlain = inlineMeta.join(' ');
532
+ const fullPlain = [descPlain, metaPlain].filter(Boolean).join(' ');
533
+ if (fullPlain.length <= argAvailWidth) {
534
+ lines.push(prefix + [styledDesc, metaStyled].filter(Boolean).join(' '));
535
+ } else if (!descPlain || descPlain.length <= argAvailWidth) {
536
+ lines.push(prefix + styledDesc);
537
+ if (metaStyled) lines.push(descColPad + metaStyled);
538
+ } else {
539
+ const wrapped = wrapText(descPlain, argAvailWidth);
540
+ const styleFn = isDeprecated ? styler.deprecated : styler.description;
541
+ lines.push(prefix + styleFn(wrapped[0]!));
542
+ for (const wline of wrapped.slice(1)) lines.push(descColPad + styleFn(wline));
543
+ if (metaStyled) lines.push(descColPad + metaStyled);
544
+ }
545
+ } else {
546
+ // No terminal width (markdown/html): description on line 1, meta on line 2
547
+ const descParts: string[] = [];
548
+ if (styledDesc) descParts.push(styledDesc);
549
+ lines.push(prefix + join(descParts));
550
+ if (styledInlineMeta.length > 0) lines.push(indent(3) + metaStyled);
551
+ }
456
552
 
457
- // Examples line
458
- if (arg.examples && arg.examples.length > 0) {
459
- const exampleValues = arg.examples.map((example) => (typeof example === 'string' ? example : JSON.stringify(example))).join(', ');
460
- const exampleParts: string[] = [styler.example('Example:'), styler.exampleValue(exampleValues)];
461
- lines.push(indent(3) + join(exampleParts));
553
+ // Deprecated (with reason), examples — always on separate line
554
+ const line3Parts: string[] = [];
555
+ if (isDeprecated && typeof arg.deprecated === 'string') line3Parts.push(styler.meta(`(deprecated: ${arg.deprecated})`));
556
+ if (arg.examples && arg.examples.length > 0) {
557
+ const exampleValues = arg.examples.map((example) => (typeof example === 'string' ? example : JSON.stringify(example))).join(', ');
558
+ line3Parts.push(styler.example('Example:'), styler.exampleValue(exampleValues));
559
+ }
560
+ if (line3Parts.length > 0) lines.push(contPad + join(line3Parts));
561
+
562
+ // stdin, env, config — always on separate line
563
+ const line4Parts: string[] = [];
564
+ if (info.usage.stdinField === arg.name) line4Parts.push(styler.meta('(stdin)'));
565
+ if (arg.env) {
566
+ const envVars = typeof arg.env === 'string' ? [arg.env] : arg.env;
567
+ line4Parts.push(styler.example('Env:'), styler.exampleValue(envVars.join(', ')));
568
+ }
569
+ if (arg.configKey) {
570
+ line4Parts.push(styler.example('Config:'), styler.exampleValue(arg.configKey));
571
+ }
572
+ if (line4Parts.length > 0) lines.push(contPad + join(line4Parts));
462
573
  }
574
+ };
575
+
576
+ for (const [key, items] of Object.entries(grouped)) {
577
+ if (lines.length > 0) lines.push('');
578
+ lines.push(styler.section(key ? `${key}:` : 'Options:'));
579
+ renderArgColumns(items!);
463
580
  }
464
581
 
465
582
  return lines;
@@ -469,7 +586,18 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
469
586
  const lines: string[] = [];
470
587
  const builtins = info.builtins!;
471
588
 
472
- lines.push(styler.label('Built-in:'));
589
+ if (!showAllBuiltins) {
590
+ // Collapsed summary: show selected builtins on a single line with a combined hint
591
+ const highlights = ['help [command]', 'version', '[command] --repl'];
592
+ lines.push(`${styler.label('Global:')} ${styler.meta(highlights.join(', '))}`);
593
+ const hint = info.usage.hasSubcommands
594
+ ? `Run "${info.name} [command] --help" for more information. Use "--all" for all global commands.`
595
+ : `Use "${info.name} help --all" for more information on global commands.`;
596
+ lines.push(styler.meta(hint));
597
+ return lines;
598
+ }
599
+
600
+ lines.push(styler.section('Global:'));
473
601
 
474
602
  // Compute max effective name length for alignment across main and sub entries
475
603
  const allLengths: number[] = [];
@@ -537,6 +665,15 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
537
665
  lines.push('');
538
666
  }
539
667
 
668
+ // Examples section (if present)
669
+ if (info.examples && info.examples.length > 0) {
670
+ lines.push(styler.section('Examples:'));
671
+ for (const ex of info.examples) {
672
+ lines.push(indent(1) + styler.meta('$ ') + styler.exampleValue(ex));
673
+ }
674
+ lines.push('');
675
+ }
676
+
540
677
  // Subcommands section
541
678
  if (info.subcommands && info.subcommands.length > 0) {
542
679
  lines.push(...formatSubcommandsSection(info));
@@ -566,7 +703,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
566
703
 
567
704
  // Nested commands section (full detail mode)
568
705
  if (info.nestedCommands?.length) {
569
- lines.push(styler.label('Subcommand Details:'));
706
+ lines.push(styler.section('Subcommand Details:'));
570
707
  lines.push('');
571
708
  for (const nestedCmd of info.nestedCommands) {
572
709
  lines.push(styler.meta('─'.repeat(60)));
@@ -596,11 +733,10 @@ function createJsonFormatter(): Formatter {
596
733
  // Formatter Factory
597
734
  // ============================================================================
598
735
 
599
- function shouldUseAnsi(): boolean {
600
- if (typeof process === 'undefined') return false;
601
- if (process.env.NO_COLOR) return false;
602
- if (process.env.CI) return false;
603
- if (process.stdout && typeof process.stdout.isTTY === 'boolean') return process.stdout.isTTY;
736
+ function shouldUseAnsi(env?: Record<string, string | undefined>, isTTY?: boolean): boolean {
737
+ if (env?.NO_COLOR) return false;
738
+ if (env?.CI) return false;
739
+ if (typeof isTTY === 'boolean') return isTTY;
604
740
  return false;
605
741
  }
606
742
 
@@ -628,12 +764,22 @@ function createMinimalFormatter(): Formatter {
628
764
  };
629
765
  }
630
766
 
631
- export function createFormatter(format: HelpFormat | 'auto', detail: HelpDetail = 'standard'): Formatter {
767
+ export function createFormatter(
768
+ format: HelpFormat | 'auto',
769
+ detail: HelpDetail = 'standard',
770
+ theme?: ColorTheme | ColorConfig,
771
+ all?: boolean,
772
+ width?: number,
773
+ terminal?: { columns?: number; isTTY?: boolean },
774
+ env?: Record<string, string | undefined>,
775
+ ): Formatter {
632
776
  if (detail === 'minimal') return createMinimalFormatter();
633
777
  if (format === 'json') return createJsonFormatter();
634
- if (format === 'ansi' || (format === 'auto' && shouldUseAnsi())) return createGenericFormatter(createAnsiStyler(), createTextLayout());
635
- if (format === 'console') return createGenericFormatter(createConsoleStyler(), createTextLayout());
636
- if (format === 'markdown') return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout());
637
- if (format === 'html') return createGenericFormatter(createHtmlStyler(), createHtmlLayout());
638
- return createGenericFormatter(createTextStyler(), createTextLayout());
778
+ const tw = format === 'markdown' || format === 'html' ? undefined : (width ?? terminal?.columns ?? DEFAULT_TERMINAL_WIDTH);
779
+ if (format === 'ansi' || (format === 'auto' && shouldUseAnsi(env, terminal?.isTTY)))
780
+ return createGenericFormatter(createAnsiStyler(theme), createTextLayout(), all, tw);
781
+ if (format === 'console') return createGenericFormatter(createConsoleStyler(theme), createTextLayout(), all, tw);
782
+ if (format === 'markdown') return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout(), all);
783
+ if (format === 'html') return createGenericFormatter(createHtmlStyler(), createHtmlLayout(), all);
784
+ return createGenericFormatter(createTextStyler(), createTextLayout(), all, tw);
639
785
  }