padrone 1.5.0 → 1.7.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 +44 -0
  2. package/README.md +15 -11
  3. package/dist/{args-D5PNDyNu.mjs → args-Cnq0nwSM.mjs} +91 -41
  4. package/dist/args-Cnq0nwSM.mjs.map +1 -0
  5. package/dist/codegen/index.mjs +4 -4
  6. package/dist/codegen/index.mjs.map +1 -1
  7. package/dist/commands-B_gufyR9.mjs +514 -0
  8. package/dist/commands-B_gufyR9.mjs.map +1 -0
  9. package/dist/{completion.mjs → completion-BEuflbDO.mjs} +12 -82
  10. package/dist/completion-BEuflbDO.mjs.map +1 -0
  11. package/dist/docs/index.d.mts +4 -4
  12. package/dist/docs/index.d.mts.map +1 -1
  13. package/dist/docs/index.mjs +10 -12
  14. package/dist/docs/index.mjs.map +1 -1
  15. package/dist/{errors-BiVrBgi6.mjs → errors-DA4KzK1M.mjs} +26 -3
  16. package/dist/errors-DA4KzK1M.mjs.map +1 -0
  17. package/dist/{formatter-DtHzbP22.d.mts → formatter-DrvhDMrq.d.mts} +3 -3
  18. package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
  19. package/dist/{help-bbmu9-qd.mjs → help-BtxLgrF_.mjs} +190 -43
  20. package/dist/help-BtxLgrF_.mjs.map +1 -0
  21. package/dist/{types-Ch8Mk6Qb.d.mts → index-D6-7dz0l.d.mts} +634 -745
  22. package/dist/index-D6-7dz0l.d.mts.map +1 -0
  23. package/dist/index.d.mts +869 -36
  24. package/dist/index.d.mts.map +1 -1
  25. package/dist/index.mjs +3884 -1699
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/{mcp-mLWIdUIu.mjs → mcp-6-Jw4Bpq.mjs} +13 -15
  28. package/dist/mcp-6-Jw4Bpq.mjs.map +1 -0
  29. package/dist/{serve-B0u43DK7.mjs → serve-YVTPzBCl.mjs} +12 -14
  30. package/dist/serve-YVTPzBCl.mjs.map +1 -0
  31. package/dist/{stream-BcC146Ud.mjs → stream-DC4H8YTx.mjs} +24 -3
  32. package/dist/stream-DC4H8YTx.mjs.map +1 -0
  33. package/dist/test.d.mts +5 -8
  34. package/dist/test.d.mts.map +1 -1
  35. package/dist/test.mjs +2 -13
  36. package/dist/test.mjs.map +1 -1
  37. package/dist/{update-check-CFX1FV3v.mjs → update-check-CZ2VqjnV.mjs} +16 -17
  38. package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
  39. package/dist/zod.d.mts +2 -2
  40. package/dist/zod.d.mts.map +1 -1
  41. package/dist/zod.mjs +2 -2
  42. package/dist/zod.mjs.map +1 -1
  43. package/package.json +15 -12
  44. package/src/cli/completions.ts +14 -11
  45. package/src/cli/docs.ts +13 -10
  46. package/src/cli/doctor.ts +22 -18
  47. package/src/cli/index.ts +28 -82
  48. package/src/cli/init.ts +10 -7
  49. package/src/cli/link.ts +20 -16
  50. package/src/cli/wrap.ts +14 -11
  51. package/src/codegen/schema-to-code.ts +2 -2
  52. package/src/{args.ts → core/args.ts} +32 -225
  53. package/src/core/commands.ts +373 -0
  54. package/src/core/create.ts +301 -0
  55. package/src/core/default-runtime.ts +239 -0
  56. package/src/{errors.ts → core/errors.ts} +22 -0
  57. package/src/core/exec.ts +259 -0
  58. package/src/core/interceptors.ts +302 -0
  59. package/src/{parse.ts → core/parse.ts} +36 -89
  60. package/src/core/program-methods.ts +301 -0
  61. package/src/core/results.ts +229 -0
  62. package/src/core/runtime.ts +246 -0
  63. package/src/core/validate.ts +247 -0
  64. package/src/docs/index.ts +12 -13
  65. package/src/extension/auto-output.ts +146 -0
  66. package/src/extension/color.ts +38 -0
  67. package/src/extension/completion.ts +49 -0
  68. package/src/extension/config.ts +262 -0
  69. package/src/extension/env.ts +101 -0
  70. package/src/extension/help.ts +192 -0
  71. package/src/extension/index.ts +44 -0
  72. package/src/extension/ink.ts +93 -0
  73. package/src/extension/interactive.ts +106 -0
  74. package/src/extension/logger.ts +262 -0
  75. package/src/extension/man.ts +51 -0
  76. package/src/extension/mcp.ts +52 -0
  77. package/src/extension/progress-renderer.ts +338 -0
  78. package/src/extension/progress.ts +299 -0
  79. package/src/extension/repl.ts +94 -0
  80. package/src/extension/serve.ts +48 -0
  81. package/src/extension/signal.ts +87 -0
  82. package/src/extension/stdin.ts +62 -0
  83. package/src/extension/suggestions.ts +114 -0
  84. package/src/extension/timing.ts +81 -0
  85. package/src/extension/tracing.ts +175 -0
  86. package/src/extension/update-check.ts +77 -0
  87. package/src/extension/utils.ts +51 -0
  88. package/src/extension/version.ts +63 -0
  89. package/src/{completion.ts → feature/completion.ts} +12 -12
  90. package/src/{interactive.ts → feature/interactive.ts} +4 -4
  91. package/src/{mcp.ts → feature/mcp.ts} +12 -15
  92. package/src/{repl-loop.ts → feature/repl-loop.ts} +10 -13
  93. package/src/{serve.ts → feature/serve.ts} +11 -15
  94. package/src/feature/test.ts +262 -0
  95. package/src/{update-check.ts → feature/update-check.ts} +16 -16
  96. package/src/{wrap.ts → feature/wrap.ts} +10 -8
  97. package/src/index.ts +115 -30
  98. package/src/{formatter.ts → output/formatter.ts} +124 -176
  99. package/src/{help.ts → output/help.ts} +22 -8
  100. package/src/output/output-indicator.ts +87 -0
  101. package/src/output/primitives.ts +335 -0
  102. package/src/output/styling.ts +221 -0
  103. package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
  104. package/src/schema/zod.ts +50 -0
  105. package/src/test.ts +2 -276
  106. package/src/types/args-meta.ts +151 -0
  107. package/src/types/builder.ts +718 -0
  108. package/src/types/command.ts +157 -0
  109. package/src/types/index.ts +60 -0
  110. package/src/types/interceptor.ts +296 -0
  111. package/src/types/preferences.ts +83 -0
  112. package/src/types/result.ts +71 -0
  113. package/src/types/schema.ts +19 -0
  114. package/src/util/dotenv.ts +244 -0
  115. package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
  116. package/src/{stream.ts → util/stream.ts} +27 -1
  117. package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
  118. package/src/{type-utils.ts → util/type-utils.ts} +71 -33
  119. package/src/util/utils.ts +51 -0
  120. package/src/zod.ts +1 -50
  121. package/dist/args-D5PNDyNu.mjs.map +0 -1
  122. package/dist/chunk-CjcI7cDX.mjs +0 -15
  123. package/dist/command-utils-B1D-HqCd.mjs +0 -1117
  124. package/dist/command-utils-B1D-HqCd.mjs.map +0 -1
  125. package/dist/completion.d.mts +0 -64
  126. package/dist/completion.d.mts.map +0 -1
  127. package/dist/completion.mjs.map +0 -1
  128. package/dist/errors-BiVrBgi6.mjs.map +0 -1
  129. package/dist/formatter-DtHzbP22.d.mts.map +0 -1
  130. package/dist/help-bbmu9-qd.mjs.map +0 -1
  131. package/dist/mcp-mLWIdUIu.mjs.map +0 -1
  132. package/dist/serve-B0u43DK7.mjs.map +0 -1
  133. package/dist/stream-BcC146Ud.mjs.map +0 -1
  134. package/dist/types-Ch8Mk6Qb.d.mts.map +0 -1
  135. package/dist/update-check-CFX1FV3v.mjs.map +0 -1
  136. package/src/command-utils.ts +0 -882
  137. package/src/create.ts +0 -1829
  138. package/src/runtime.ts +0 -497
  139. package/src/types.ts +0 -1291
  140. package/src/utils.ts +0 -140
  141. /package/src/{colorizer.ts → output/colorizer.ts} +0 -0
@@ -1,5 +1,20 @@
1
- import { camelToKebab } from './args.ts';
2
- import { type ColorConfig, type ColorTheme, createColorizer } from './colorizer.ts';
1
+ import { camelToKebab } from '../util/shell-utils.ts';
2
+ import type { ColorConfig, ColorTheme } from './colorizer.ts';
3
+ import {
4
+ createAnsiStyler,
5
+ createConsoleStyler,
6
+ createHtmlLayout,
7
+ createHtmlStyler,
8
+ createMarkdownLayout,
9
+ createMarkdownStyler,
10
+ createTextLayout,
11
+ createTextStyler,
12
+ DEFAULT_TERMINAL_WIDTH,
13
+ type LayoutConfig,
14
+ type Styler,
15
+ shouldUseAnsi,
16
+ wrapText,
17
+ } from './styling.ts';
3
18
 
4
19
  export type HelpFormat = 'text' | 'ansi' | 'console' | 'markdown' | 'html' | 'json';
5
20
  export type HelpDetail = 'minimal' | 'standard' | 'full';
@@ -125,143 +140,6 @@ export type Formatter = {
125
140
  format: (info: HelpInfo) => string;
126
141
  };
127
142
 
128
- // ============================================================================
129
- // Internal Styling Types
130
- // ============================================================================
131
-
132
- /**
133
- * Internal styling functions used by formatters.
134
- * These handle the visual styling of individual text elements.
135
- */
136
- type Styler = {
137
- command: (text: string) => string;
138
- arg: (text: string) => string;
139
- type: (text: string) => string;
140
- description: (text: string) => string;
141
- label: (text: string) => string;
142
- section: (text: string) => string;
143
- meta: (text: string) => string;
144
- example: (text: string) => string;
145
- exampleValue: (text: string) => string;
146
- deprecated: (text: string) => string;
147
- };
148
-
149
- /**
150
- * Layout configuration for formatters.
151
- */
152
- type LayoutConfig = {
153
- newline: string;
154
- indent: (level: number) => string;
155
- join: (parts: string[]) => string;
156
- wrapDocument?: (content: string) => string;
157
- };
158
-
159
- // ============================================================================
160
- // Styler Factories
161
- // ============================================================================
162
-
163
- function createTextStyler(): Styler {
164
- return {
165
- command: (text) => text,
166
- arg: (text) => text,
167
- type: (text) => text,
168
- description: (text) => text,
169
- label: (text) => text,
170
- section: (text) => text,
171
- meta: (text) => text,
172
- example: (text) => text,
173
- exampleValue: (text) => text,
174
- deprecated: (text) => text,
175
- };
176
- }
177
-
178
- function createAnsiStyler(theme?: ColorTheme | ColorConfig): Styler {
179
- const colorizer = createColorizer(theme);
180
- return {
181
- command: colorizer.command,
182
- arg: colorizer.arg,
183
- type: colorizer.type,
184
- description: colorizer.description,
185
- label: colorizer.label,
186
- section: colorizer.label,
187
- meta: colorizer.meta,
188
- example: colorizer.example,
189
- exampleValue: colorizer.exampleValue,
190
- deprecated: colorizer.deprecated,
191
- };
192
- }
193
-
194
- function createConsoleStyler(theme?: ColorTheme | ColorConfig): Styler {
195
- return createAnsiStyler(theme);
196
- }
197
-
198
- function createMarkdownStyler(): Styler {
199
- return {
200
- command: (text) => `**${text}**`,
201
- arg: (text) => `\`${text}\``,
202
- type: (text) => `\`${text}\``,
203
- description: (text) => text,
204
- label: (text) => `**${text}**`,
205
- section: (text) => `### ${text}`,
206
- meta: (text) => `*${text}*`,
207
- example: (text) => `**${text}**`,
208
- exampleValue: (text) => `\`${text}\``,
209
- deprecated: (text) => `~~${text}~~`,
210
- };
211
- }
212
-
213
- function escapeHtml(text: string): string {
214
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
215
- }
216
-
217
- function createHtmlStyler(): Styler {
218
- return {
219
- command: (text) => `<strong style="color: #00bcd4;">${escapeHtml(text)}</strong>`,
220
- arg: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
221
- type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
222
- description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
223
- label: (text) => `<strong>${escapeHtml(text)}</strong>`,
224
- section: (text) => `<h3>${escapeHtml(text)}</h3>`,
225
- meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
226
- example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
227
- exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
228
- deprecated: (text) => `<del style="color: #999;">${escapeHtml(text)}</del>`,
229
- };
230
- }
231
-
232
- // ============================================================================
233
- // Layout Configurations
234
- // ============================================================================
235
-
236
- function createTextLayout(): LayoutConfig {
237
- return {
238
- newline: '\n',
239
- indent: (level) => ' '.repeat(level),
240
- join: (parts) => parts.filter(Boolean).join(' '),
241
- };
242
- }
243
-
244
- function createMarkdownLayout(): LayoutConfig {
245
- return {
246
- newline: '\n\n',
247
- indent: (level) => {
248
- if (level === 0) return '';
249
- if (level === 1) return ' ';
250
- return ' ';
251
- },
252
- join: (parts) => parts.filter(Boolean).join(' '),
253
- };
254
- }
255
-
256
- function createHtmlLayout(): LayoutConfig {
257
- return {
258
- newline: '<br>',
259
- indent: (level) => '&nbsp;&nbsp;'.repeat(level),
260
- join: (parts) => parts.filter(Boolean).join(' '),
261
- wrapDocument: (content) => `<div style="font-family: monospace; line-height: 1.6;">${content}</div>`,
262
- };
263
- }
264
-
265
143
  // ============================================================================
266
144
  // Generic Formatter Implementation
267
145
  // ============================================================================
@@ -269,7 +147,7 @@ function createHtmlLayout(): LayoutConfig {
269
147
  /**
270
148
  * Creates a formatter that uses the given styler and layout configuration.
271
149
  */
272
- function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBuiltins?: boolean): Formatter {
150
+ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBuiltins?: boolean, terminalWidth?: number): Formatter {
273
151
  const { newline, indent, join, wrapDocument } = layout;
274
152
 
275
153
  function formatUsageSection(info: HelpInfo): string[] {
@@ -366,15 +244,53 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBui
366
244
  lines.push(styler.section('Arguments:'));
367
245
 
368
246
  const maxNameLength = Math.min(32, Math.max(...args.map((a) => a.name.length)));
247
+ const descCol = 2 + maxNameLength + 2;
248
+ const posAvailWidth = terminalWidth ? terminalWidth - descCol : undefined;
249
+ const descColPad = ' '.repeat(descCol);
369
250
 
370
251
  for (const arg of args) {
371
252
  const padding = ' '.repeat(Math.max(2, maxNameLength - arg.name.length + 2));
372
- const descParts: string[] = [];
373
- if (arg.description) descParts.push(styler.description(arg.description));
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
- if (arg.default !== undefined) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
377
- lines.push(indent(1) + styler.arg(arg.name) + padding + join(descParts));
253
+ const prefix = indent(1) + styler.arg(arg.name) + padding;
254
+
255
+ const descPlain = arg.description ?? '';
256
+ const styledDesc = descPlain ? styler.description(descPlain) : '';
257
+
258
+ const metaParts: string[] = [];
259
+ const styledMetaParts: string[] = [];
260
+ if (info.usage.stdinField === arg.name) {
261
+ metaParts.push('(stdin)');
262
+ styledMetaParts.push(styler.meta('(stdin)'));
263
+ }
264
+ if (arg.enum) {
265
+ const text = `(choices: ${arg.enum.join(', ')})`;
266
+ metaParts.push(text);
267
+ styledMetaParts.push(styler.meta(text));
268
+ }
269
+ if (arg.default !== undefined) {
270
+ const text = `(default: ${String(arg.default)})`;
271
+ metaParts.push(text);
272
+ styledMetaParts.push(styler.meta(text));
273
+ }
274
+
275
+ const metaStyled = join(styledMetaParts);
276
+
277
+ if (posAvailWidth && posAvailWidth > 0) {
278
+ const metaPlain = metaParts.join(' ');
279
+ const fullPlain = [descPlain, metaPlain].filter(Boolean).join(' ');
280
+ if (fullPlain.length <= posAvailWidth) {
281
+ lines.push(prefix + [styledDesc, metaStyled].filter(Boolean).join(' '));
282
+ } else if (!descPlain || descPlain.length <= posAvailWidth) {
283
+ lines.push(prefix + styledDesc);
284
+ if (metaStyled) lines.push(descColPad + metaStyled);
285
+ } else {
286
+ const wrapped = wrapText(descPlain, posAvailWidth);
287
+ lines.push(prefix + styler.description(wrapped[0]!));
288
+ for (const wline of wrapped.slice(1)) lines.push(descColPad + styler.description(wline));
289
+ if (metaStyled) lines.push(descColPad + metaStyled);
290
+ }
291
+ } else {
292
+ lines.push(prefix + join([styledDesc, metaStyled]));
293
+ }
378
294
  }
379
295
 
380
296
  return lines;
@@ -413,6 +329,9 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBui
413
329
  const maxNamesWidth = Math.min(32, Math.max(0, ...argColumns.map((c) => c.namesPlain.length)));
414
330
  const maxTypeWidth = Math.min(16, Math.max(0, ...argColumns.map((c) => c.typePlain.length)));
415
331
  const hasAnyFlags = maxFlagsWidth > 0;
332
+ const descCol = 2 + (hasAnyFlags ? maxFlagsWidth + 2 : 0) + maxNamesWidth + 2 + (maxTypeWidth > 0 ? maxTypeWidth + 2 : 0);
333
+ const argAvailWidth = terminalWidth ? terminalWidth - descCol : undefined;
334
+ const descColPad = ' '.repeat(descCol);
416
335
 
417
336
  // Split into ordered groups: ungrouped first as "Options:", then each group in first-seen order
418
337
  const grouped = Object.groupBy(argColumns, (c) => c.arg.group ?? '');
@@ -441,28 +360,65 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBui
441
360
  parts.push(styledType + typePadding);
442
361
  }
443
362
 
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));
363
+ const prefix = indent(1) + parts.join('');
364
+ const contPad = argAvailWidth ? descColPad : indent(3);
448
365
 
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));
366
+ // Build inline meta (deprecated no-reason, default, choices)
367
+ const inlineMeta: string[] = [];
368
+ const styledInlineMeta: string[] = [];
369
+ if (isDeprecated && typeof arg.deprecated !== 'string') {
370
+ inlineMeta.push('(deprecated)');
371
+ styledInlineMeta.push(styler.meta('(deprecated)'));
372
+ }
373
+ if (hasDefault(arg.default)) {
374
+ const text = `(default: ${String(arg.default)})`;
375
+ inlineMeta.push(text);
376
+ styledInlineMeta.push(styler.meta(text));
377
+ }
378
+ if (arg.enum) {
379
+ const text = `(choices: ${arg.enum.join(', ')})`;
380
+ inlineMeta.push(text);
381
+ styledInlineMeta.push(styler.meta(text));
382
+ }
383
+
384
+ const descPlain = arg.description ?? '';
385
+ const styledDesc = descPlain ? (isDeprecated ? styler.deprecated(descPlain) : styler.description(descPlain)) : '';
386
+ const metaStyled = join(styledInlineMeta);
387
+
388
+ if (argAvailWidth && argAvailWidth > 0) {
389
+ // Terminal-width-aware: try to fit description + meta on one line
390
+ const metaPlain = inlineMeta.join(' ');
391
+ const fullPlain = [descPlain, metaPlain].filter(Boolean).join(' ');
392
+ if (fullPlain.length <= argAvailWidth) {
393
+ lines.push(prefix + [styledDesc, metaStyled].filter(Boolean).join(' '));
394
+ } else if (!descPlain || descPlain.length <= argAvailWidth) {
395
+ lines.push(prefix + styledDesc);
396
+ if (metaStyled) lines.push(descColPad + metaStyled);
397
+ } else {
398
+ const wrapped = wrapText(descPlain, argAvailWidth);
399
+ const styleFn = isDeprecated ? styler.deprecated : styler.description;
400
+ lines.push(prefix + styleFn(wrapped[0]!));
401
+ for (const wline of wrapped.slice(1)) lines.push(descColPad + styleFn(wline));
402
+ if (metaStyled) lines.push(descColPad + metaStyled);
403
+ }
404
+ } else {
405
+ // No terminal width (markdown/html): description on line 1, meta on line 2
406
+ const descParts: string[] = [];
407
+ if (styledDesc) descParts.push(styledDesc);
408
+ lines.push(prefix + join(descParts));
409
+ if (styledInlineMeta.length > 0) lines.push(indent(3) + metaStyled);
410
+ }
455
411
 
456
- // Line 3: deprecated (with reason), examples
412
+ // Deprecated (with reason), examples — always on separate line
457
413
  const line3Parts: string[] = [];
458
414
  if (isDeprecated && typeof arg.deprecated === 'string') line3Parts.push(styler.meta(`(deprecated: ${arg.deprecated})`));
459
415
  if (arg.examples && arg.examples.length > 0) {
460
416
  const exampleValues = arg.examples.map((example) => (typeof example === 'string' ? example : JSON.stringify(example))).join(', ');
461
417
  line3Parts.push(styler.example('Example:'), styler.exampleValue(exampleValues));
462
418
  }
463
- if (line3Parts.length > 0) lines.push(indent(3) + join(line3Parts));
419
+ if (line3Parts.length > 0) lines.push(contPad + join(line3Parts));
464
420
 
465
- // Line 4: stdin, env, config
421
+ // stdin, env, config — always on separate line
466
422
  const line4Parts: string[] = [];
467
423
  if (info.usage.stdinField === arg.name) line4Parts.push(styler.meta('(stdin)'));
468
424
  if (arg.env) {
@@ -472,7 +428,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBui
472
428
  if (arg.configKey) {
473
429
  line4Parts.push(styler.example('Config:'), styler.exampleValue(arg.configKey));
474
430
  }
475
- if (line4Parts.length > 0) lines.push(indent(3) + join(line4Parts));
431
+ if (line4Parts.length > 0) lines.push(contPad + join(line4Parts));
476
432
  }
477
433
  };
478
434
 
@@ -632,18 +588,6 @@ function createJsonFormatter(): Formatter {
632
588
  };
633
589
  }
634
590
 
635
- // ============================================================================
636
- // Formatter Factory
637
- // ============================================================================
638
-
639
- function shouldUseAnsi(): boolean {
640
- if (typeof process === 'undefined') return false;
641
- if (process.env.NO_COLOR) return false;
642
- if (process.env.CI) return false;
643
- if (process.stdout && typeof process.stdout.isTTY === 'boolean') return process.stdout.isTTY;
644
- return false;
645
- }
646
-
647
591
  // ============================================================================
648
592
  // Minimal Formatter
649
593
  // ============================================================================
@@ -673,13 +617,17 @@ export function createFormatter(
673
617
  detail: HelpDetail = 'standard',
674
618
  theme?: ColorTheme | ColorConfig,
675
619
  all?: boolean,
620
+ width?: number,
621
+ terminal?: { columns?: number; isTTY?: boolean },
622
+ env?: Record<string, string | undefined>,
676
623
  ): Formatter {
677
624
  if (detail === 'minimal') return createMinimalFormatter();
678
625
  if (format === 'json') return createJsonFormatter();
679
- if (format === 'ansi' || (format === 'auto' && shouldUseAnsi()))
680
- return createGenericFormatter(createAnsiStyler(theme), createTextLayout(), all);
681
- if (format === 'console') return createGenericFormatter(createConsoleStyler(theme), createTextLayout(), all);
626
+ const tw = format === 'markdown' || format === 'html' ? undefined : (width ?? terminal?.columns ?? DEFAULT_TERMINAL_WIDTH);
627
+ if (format === 'ansi' || (format === 'auto' && shouldUseAnsi(env, terminal?.isTTY)))
628
+ return createGenericFormatter(createAnsiStyler(theme), createTextLayout(), all, tw);
629
+ if (format === 'console') return createGenericFormatter(createConsoleStyler(theme), createTextLayout(), all, tw);
682
630
  if (format === 'markdown') return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout(), all);
683
631
  if (format === 'html') return createGenericFormatter(createHtmlStyler(), createHtmlLayout(), all);
684
- return createGenericFormatter(createTextStyler(), createTextLayout(), all);
632
+ return createGenericFormatter(createTextStyler(), createTextLayout(), all, tw);
685
633
  }
@@ -1,7 +1,9 @@
1
1
  import type { StandardJSONSchemaV1 } from '@standard-schema/spec';
2
- import { extractSchemaMetadata, JSON_SCHEMA_OPTS, type PadroneArgsSchemaMeta, parsePositionalConfig, parseStdinConfig } from './args.ts';
2
+ import { extractSchemaMetadata, getJsonSchema, type PadroneArgsSchemaMeta, parsePositionalConfig } from '../core/args.ts';
3
+ import { findCommandByName } from '../core/commands.ts';
4
+ import type { AnyPadroneCommand } from '../types/index.ts';
5
+ import { getRootCommand } from '../util/utils.ts';
3
6
  import type { ColorConfig, ColorTheme } from './colorizer.ts';
4
- import { findCommandByName } from './command-utils.ts';
5
7
  import {
6
8
  createFormatter,
7
9
  type HelpArgumentInfo,
@@ -11,8 +13,6 @@ import {
11
13
  type HelpPositionalInfo,
12
14
  type HelpSubcommandInfo,
13
15
  } from './formatter.ts';
14
- import type { AnyPadroneCommand } from './types.ts';
15
- import { getRootCommand } from './utils.ts';
16
16
 
17
17
  export type HelpPreferences = {
18
18
  format?: HelpFormat | 'auto';
@@ -20,6 +20,12 @@ export type HelpPreferences = {
20
20
  theme?: ColorTheme | ColorConfig;
21
21
  /** Show all global commands and flags in full detail */
22
22
  all?: boolean;
23
+ /** Terminal width for text wrapping. Defaults to terminal columns or 80. */
24
+ width?: number;
25
+ /** Terminal capabilities for auto-detection of ANSI and width. */
26
+ terminal?: { columns?: number; isTTY?: boolean };
27
+ /** Environment variables for auto-detection (e.g., NO_COLOR, CI). */
28
+ env?: Record<string, string | undefined>;
23
29
  };
24
30
 
25
31
  /**
@@ -39,7 +45,7 @@ function extractPositionalArgsInfo(
39
45
  const positionalConfig = parsePositionalConfig(meta.positional);
40
46
 
41
47
  try {
42
- const jsonSchema = schema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
48
+ const jsonSchema = getJsonSchema(schema) as Record<string, any>;
43
49
 
44
50
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
45
51
  const properties = jsonSchema.properties as Record<string, any>;
@@ -79,7 +85,7 @@ function extractArgsInfo(schema: StandardJSONSchemaV1, meta?: PadroneArgsSchemaM
79
85
  const argsMeta = meta?.fields;
80
86
 
81
87
  try {
82
- const jsonSchema = schema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
88
+ const jsonSchema = getJsonSchema(schema) as Record<string, any>;
83
89
 
84
90
  // Handle object: z.object({ key: z.string(), ... })
85
91
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
@@ -190,7 +196,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
190
196
  hasSubcommands: !!(cmd.commands && cmd.commands.length > 0),
191
197
  hasPositionals,
192
198
  hasArguments: false, // updated below after extracting arguments
193
- stdinField: cmd.meta?.stdin ? parseStdinConfig(cmd.meta.stdin) : undefined,
199
+ stdinField: cmd.meta?.stdin,
194
200
  },
195
201
  };
196
202
 
@@ -366,6 +372,14 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
366
372
 
367
373
  export function generateHelp(rootCommand: AnyPadroneCommand, commandObj: AnyPadroneCommand = rootCommand, prefs?: HelpPreferences): string {
368
374
  const helpInfo = getHelpInfo(commandObj, prefs?.detail, prefs?.all);
369
- const formatter = createFormatter(prefs?.format ?? 'auto', prefs?.detail, prefs?.theme, prefs?.all);
375
+ const formatter = createFormatter(
376
+ prefs?.format ?? 'auto',
377
+ prefs?.detail,
378
+ prefs?.theme,
379
+ prefs?.all,
380
+ prefs?.width,
381
+ prefs?.terminal,
382
+ prefs?.env,
383
+ );
370
384
  return formatter.format(helpInfo);
371
385
  }
@@ -0,0 +1,87 @@
1
+ import type { KeyValueOptions, ListItem, ListOptions, TableOptions, TreeNode, TreeOptions } from './primitives.ts';
2
+ import { renderKeyValue, renderList, renderTable, renderTree } from './primitives.ts';
3
+ import type { OutputContext } from './styling.ts';
4
+
5
+ /**
6
+ * Runtime output helper injected into action context as `ctx.context.output`.
7
+ * Provides format-aware output primitives (table, tree, list, key-value).
8
+ *
9
+ * Each method renders data using the resolved format (ANSI, text, JSON, markdown, HTML)
10
+ * and writes it to the runtime's output function.
11
+ */
12
+ export type PadroneOutputIndicator = {
13
+ /** Render data as a table. */
14
+ table(data: Record<string, unknown>[], options?: TableOptions): void;
15
+ /** Render data as a tree. */
16
+ tree(data: TreeNode | TreeNode[], options?: TreeOptions): void;
17
+ /** Render data as a list. */
18
+ list(data: ListItem[], options?: ListOptions): void;
19
+ /** Render data as aligned key-value pairs. */
20
+ kv(data: Record<string, unknown>, options?: KeyValueOptions): void;
21
+ /** Write raw output (same as runtime.output but sets the "already called" flag). */
22
+ raw(...args: unknown[]): void;
23
+ /** Whether any output method has been called. */
24
+ readonly called: boolean;
25
+ };
26
+
27
+ /** Declarative output configuration for a command. */
28
+ export type OutputPrimitiveType = 'table' | 'tree' | 'list' | 'kv' | 'json';
29
+ export type OutputConfig =
30
+ | OutputPrimitiveType
31
+ | { type: OutputPrimitiveType; options?: TableOptions | TreeOptions | ListOptions | KeyValueOptions };
32
+
33
+ /** Create an output indicator that renders through the given output function and format context. */
34
+ export function createOutputIndicator(outputFn: (...args: unknown[]) => void, ctx: OutputContext): PadroneOutputIndicator {
35
+ let _called = false;
36
+
37
+ const emit = (rendered: string) => {
38
+ _called = true;
39
+ outputFn(rendered);
40
+ };
41
+
42
+ return {
43
+ table(data, options) {
44
+ emit(renderTable(data, options, ctx));
45
+ },
46
+ tree(data, options) {
47
+ emit(renderTree(data, options, ctx));
48
+ },
49
+ list(data, options) {
50
+ emit(renderList(data, options, ctx));
51
+ },
52
+ kv(data, options) {
53
+ emit(renderKeyValue(data, options, ctx));
54
+ },
55
+ raw(...args) {
56
+ _called = true;
57
+ outputFn(...args);
58
+ },
59
+ get called() {
60
+ return _called;
61
+ },
62
+ };
63
+ }
64
+
65
+ /** Format a return value using a declarative output config. */
66
+ export function formatDeclarativeOutput(value: unknown, config: OutputConfig, ctx: OutputContext): string | undefined {
67
+ const type = typeof config === 'string' ? config : config.type;
68
+ const options = typeof config === 'object' ? config.options : undefined;
69
+
70
+ switch (type) {
71
+ case 'table':
72
+ if (!Array.isArray(value)) return undefined;
73
+ return renderTable(value as Record<string, unknown>[], options as TableOptions | undefined, ctx);
74
+ case 'tree':
75
+ return renderTree(value as TreeNode | TreeNode[], options as TreeOptions | undefined, ctx);
76
+ case 'list':
77
+ if (!Array.isArray(value)) return undefined;
78
+ return renderList(value as ListItem[], options as ListOptions | undefined, ctx);
79
+ case 'kv':
80
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;
81
+ return renderKeyValue(value as Record<string, unknown>, options as KeyValueOptions | undefined, ctx);
82
+ case 'json':
83
+ return JSON.stringify(value, null, 2);
84
+ default:
85
+ return undefined;
86
+ }
87
+ }