padrone 1.4.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.
- package/CHANGELOG.md +79 -0
- package/README.md +105 -284
- package/dist/{args-CVDbyyzG.mjs → args-D5PNDyNu.mjs} +41 -18
- package/dist/args-D5PNDyNu.mjs.map +1 -0
- package/dist/chunk-CjcI7cDX.mjs +15 -0
- package/dist/codegen/index.d.mts +28 -3
- package/dist/codegen/index.d.mts.map +1 -1
- package/dist/codegen/index.mjs +169 -19
- package/dist/codegen/index.mjs.map +1 -1
- package/dist/command-utils-B1D-HqCd.mjs +1117 -0
- package/dist/command-utils-B1D-HqCd.mjs.map +1 -0
- package/dist/completion.d.mts +1 -1
- package/dist/completion.d.mts.map +1 -1
- package/dist/completion.mjs +77 -29
- package/dist/completion.mjs.map +1 -1
- package/dist/docs/index.d.mts +22 -2
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +94 -7
- package/dist/docs/index.mjs.map +1 -1
- package/dist/errors-BiVrBgi6.mjs +114 -0
- package/dist/errors-BiVrBgi6.mjs.map +1 -0
- package/dist/{formatter-ClUK5hcQ.d.mts → formatter-DtHzbP22.d.mts} +34 -5
- package/dist/formatter-DtHzbP22.d.mts.map +1 -0
- package/dist/help-bbmu9-qd.mjs +735 -0
- package/dist/help-bbmu9-qd.mjs.map +1 -0
- package/dist/index.d.mts +32 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +493 -265
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-mLWIdUIu.mjs +379 -0
- package/dist/mcp-mLWIdUIu.mjs.map +1 -0
- package/dist/serve-B0u43DK7.mjs +404 -0
- package/dist/serve-B0u43DK7.mjs.map +1 -0
- package/dist/stream-BcC146Ud.mjs +56 -0
- package/dist/stream-BcC146Ud.mjs.map +1 -0
- package/dist/test.d.mts +1 -1
- package/dist/test.mjs +4 -15
- package/dist/test.mjs.map +1 -1
- package/dist/{types-DjIdJN5G.d.mts → types-Ch8Mk6Qb.d.mts} +310 -62
- package/dist/types-Ch8Mk6Qb.d.mts.map +1 -0
- package/dist/{update-check-EbNDkzyV.mjs → update-check-CFX1FV3v.mjs} +2 -2
- package/dist/{update-check-EbNDkzyV.mjs.map → update-check-CFX1FV3v.mjs.map} +1 -1
- package/dist/zod.d.mts +32 -0
- package/dist/zod.d.mts.map +1 -0
- package/dist/zod.mjs +50 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +10 -2
- package/src/args.ts +68 -40
- package/src/cli/docs.ts +1 -7
- package/src/cli/doctor.ts +195 -10
- package/src/cli/index.ts +1 -1
- package/src/cli/init.ts +2 -3
- package/src/cli/link.ts +2 -2
- package/src/codegen/discovery.ts +80 -28
- package/src/codegen/index.ts +2 -1
- package/src/codegen/parsers/bash.ts +179 -0
- package/src/codegen/schema-to-code.ts +2 -1
- package/src/colorizer.ts +126 -13
- package/src/command-utils.ts +380 -30
- package/src/completion.ts +120 -47
- package/src/create.ts +480 -128
- package/src/docs/index.ts +122 -8
- package/src/formatter.ts +171 -125
- package/src/help.ts +45 -12
- package/src/index.ts +29 -1
- package/src/interactive.ts +45 -4
- package/src/mcp.ts +390 -0
- package/src/repl-loop.ts +16 -3
- package/src/runtime.ts +195 -2
- package/src/serve.ts +442 -0
- package/src/stream.ts +75 -0
- package/src/test.ts +7 -16
- package/src/type-utils.ts +28 -4
- package/src/types.ts +212 -30
- package/src/wrap.ts +23 -25
- package/src/zod.ts +50 -0
- package/dist/args-CVDbyyzG.mjs.map +0 -1
- package/dist/chunk-y_GBKt04.mjs +0 -5
- package/dist/formatter-ClUK5hcQ.d.mts.map +0 -1
- package/dist/help-CcBe91bV.mjs +0 -1254
- package/dist/help-CcBe91bV.mjs.map +0 -1
- package/dist/types-DjIdJN5G.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';
|
|
@@ -45,6 +45,8 @@ export type HelpArgumentInfo = {
|
|
|
45
45
|
negatable?: boolean;
|
|
46
46
|
/** Config file key that maps to this arg */
|
|
47
47
|
configKey?: string;
|
|
48
|
+
/** Group name for organizing this option under a labeled section in help output */
|
|
49
|
+
group?: string;
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -58,6 +60,8 @@ export type HelpSubcommandInfo = {
|
|
|
58
60
|
deprecated?: boolean | string;
|
|
59
61
|
hidden?: boolean;
|
|
60
62
|
hasSubcommands?: boolean;
|
|
63
|
+
/** Group name for organizing this command under a labeled section in help output */
|
|
64
|
+
group?: string;
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
/**
|
|
@@ -103,6 +107,8 @@ export type HelpInfo = {
|
|
|
103
107
|
arguments?: HelpArgumentInfo[];
|
|
104
108
|
/** Built-in commands and flags (shown only for root command) */
|
|
105
109
|
builtins?: HelpBuiltinInfo[];
|
|
110
|
+
/** Command-level usage examples (shown in help output) */
|
|
111
|
+
examples?: string[];
|
|
106
112
|
/** Full help info for nested commands (used in 'full' detail mode) */
|
|
107
113
|
nestedCommands?: HelpInfo[];
|
|
108
114
|
};
|
|
@@ -133,6 +139,7 @@ type Styler = {
|
|
|
133
139
|
type: (text: string) => string;
|
|
134
140
|
description: (text: string) => string;
|
|
135
141
|
label: (text: string) => string;
|
|
142
|
+
section: (text: string) => string;
|
|
136
143
|
meta: (text: string) => string;
|
|
137
144
|
example: (text: string) => string;
|
|
138
145
|
exampleValue: (text: string) => string;
|
|
@@ -147,7 +154,6 @@ type LayoutConfig = {
|
|
|
147
154
|
indent: (level: number) => string;
|
|
148
155
|
join: (parts: string[]) => string;
|
|
149
156
|
wrapDocument?: (content: string) => string;
|
|
150
|
-
usageLabel: string;
|
|
151
157
|
};
|
|
152
158
|
|
|
153
159
|
// ============================================================================
|
|
@@ -161,6 +167,7 @@ function createTextStyler(): Styler {
|
|
|
161
167
|
type: (text) => text,
|
|
162
168
|
description: (text) => text,
|
|
163
169
|
label: (text) => text,
|
|
170
|
+
section: (text) => text,
|
|
164
171
|
meta: (text) => text,
|
|
165
172
|
example: (text) => text,
|
|
166
173
|
exampleValue: (text) => text,
|
|
@@ -168,14 +175,15 @@ function createTextStyler(): Styler {
|
|
|
168
175
|
};
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
function createAnsiStyler(): Styler {
|
|
172
|
-
const colorizer = createColorizer();
|
|
178
|
+
function createAnsiStyler(theme?: ColorTheme | ColorConfig): Styler {
|
|
179
|
+
const colorizer = createColorizer(theme);
|
|
173
180
|
return {
|
|
174
181
|
command: colorizer.command,
|
|
175
182
|
arg: colorizer.arg,
|
|
176
183
|
type: colorizer.type,
|
|
177
184
|
description: colorizer.description,
|
|
178
185
|
label: colorizer.label,
|
|
186
|
+
section: colorizer.label,
|
|
179
187
|
meta: colorizer.meta,
|
|
180
188
|
example: colorizer.example,
|
|
181
189
|
exampleValue: colorizer.exampleValue,
|
|
@@ -183,30 +191,8 @@ function createAnsiStyler(): Styler {
|
|
|
183
191
|
};
|
|
184
192
|
}
|
|
185
193
|
|
|
186
|
-
function createConsoleStyler(): Styler {
|
|
187
|
-
|
|
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
|
-
};
|
|
194
|
+
function createConsoleStyler(theme?: ColorTheme | ColorConfig): Styler {
|
|
195
|
+
return createAnsiStyler(theme);
|
|
210
196
|
}
|
|
211
197
|
|
|
212
198
|
function createMarkdownStyler(): Styler {
|
|
@@ -215,7 +201,8 @@ function createMarkdownStyler(): Styler {
|
|
|
215
201
|
arg: (text) => `\`${text}\``,
|
|
216
202
|
type: (text) => `\`${text}\``,
|
|
217
203
|
description: (text) => text,
|
|
218
|
-
label: (text) =>
|
|
204
|
+
label: (text) => `**${text}**`,
|
|
205
|
+
section: (text) => `### ${text}`,
|
|
219
206
|
meta: (text) => `*${text}*`,
|
|
220
207
|
example: (text) => `**${text}**`,
|
|
221
208
|
exampleValue: (text) => `\`${text}\``,
|
|
@@ -233,7 +220,8 @@ function createHtmlStyler(): Styler {
|
|
|
233
220
|
arg: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
|
|
234
221
|
type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
|
|
235
222
|
description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
|
|
236
|
-
label: (text) => `<
|
|
223
|
+
label: (text) => `<strong>${escapeHtml(text)}</strong>`,
|
|
224
|
+
section: (text) => `<h3>${escapeHtml(text)}</h3>`,
|
|
237
225
|
meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
|
|
238
226
|
example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
|
|
239
227
|
exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
|
|
@@ -250,7 +238,6 @@ function createTextLayout(): LayoutConfig {
|
|
|
250
238
|
newline: '\n',
|
|
251
239
|
indent: (level) => ' '.repeat(level),
|
|
252
240
|
join: (parts) => parts.filter(Boolean).join(' '),
|
|
253
|
-
usageLabel: 'Usage:',
|
|
254
241
|
};
|
|
255
242
|
}
|
|
256
243
|
|
|
@@ -263,7 +250,6 @@ function createMarkdownLayout(): LayoutConfig {
|
|
|
263
250
|
return ' ';
|
|
264
251
|
},
|
|
265
252
|
join: (parts) => parts.filter(Boolean).join(' '),
|
|
266
|
-
usageLabel: 'Usage:',
|
|
267
253
|
};
|
|
268
254
|
}
|
|
269
255
|
|
|
@@ -273,7 +259,6 @@ function createHtmlLayout(): LayoutConfig {
|
|
|
273
259
|
indent: (level) => ' '.repeat(level),
|
|
274
260
|
join: (parts) => parts.filter(Boolean).join(' '),
|
|
275
261
|
wrapDocument: (content) => `<div style="font-family: monospace; line-height: 1.6;">${content}</div>`,
|
|
276
|
-
usageLabel: '<strong>Usage:</strong>',
|
|
277
262
|
};
|
|
278
263
|
}
|
|
279
264
|
|
|
@@ -284,8 +269,8 @@ function createHtmlLayout(): LayoutConfig {
|
|
|
284
269
|
/**
|
|
285
270
|
* Creates a formatter that uses the given styler and layout configuration.
|
|
286
271
|
*/
|
|
287
|
-
function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter {
|
|
288
|
-
const { newline, indent, join, wrapDocument
|
|
272
|
+
function createGenericFormatter(styler: Styler, layout: LayoutConfig, showAllBuiltins?: boolean): Formatter {
|
|
273
|
+
const { newline, indent, join, wrapDocument } = layout;
|
|
289
274
|
|
|
290
275
|
function formatUsageSection(info: HelpInfo): string[] {
|
|
291
276
|
const usageParts: string[] = [styler.command(info.usage.command), info.usage.hasSubcommands ? styler.meta('[command]') : ''];
|
|
@@ -297,15 +282,13 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
297
282
|
}
|
|
298
283
|
}
|
|
299
284
|
if (info.usage.hasArguments) usageParts.push(styler.meta('[options]'));
|
|
300
|
-
return [`${
|
|
285
|
+
return [`${styler.label('Usage:')} ${join(usageParts)}`];
|
|
301
286
|
}
|
|
302
287
|
|
|
303
288
|
function formatSubcommandsSection(info: HelpInfo): string[] {
|
|
304
289
|
const lines: string[] = [];
|
|
305
290
|
const subcommands = info.subcommands!;
|
|
306
291
|
|
|
307
|
-
lines.push(styler.label('Commands:'));
|
|
308
|
-
|
|
309
292
|
const subcommandSuffix = (c: HelpSubcommandInfo) => (c.hasSubcommands ? ' <subcommand>' : '');
|
|
310
293
|
const formatAliasParts = (c: HelpSubcommandInfo) => {
|
|
311
294
|
if (!c.aliases?.length) return { plain: '', styled: '' };
|
|
@@ -323,40 +306,55 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
323
306
|
}
|
|
324
307
|
return { plain: parts.length ? ` ${parts.join(' ')}` : '', styled: styledParts.length ? ` ${styledParts.join(' ')}` : '' };
|
|
325
308
|
};
|
|
309
|
+
// Column width is computed across all subcommands for consistent alignment
|
|
326
310
|
const maxNameLength = Math.max(
|
|
327
311
|
...subcommands.map((c) => {
|
|
328
312
|
return (c.name + subcommandSuffix(c) + formatAliasParts(c).plain).length;
|
|
329
313
|
}),
|
|
330
314
|
);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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(''));
|
|
354
344
|
}
|
|
355
|
-
|
|
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!);
|
|
356
351
|
}
|
|
357
352
|
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
}
|
|
360
358
|
|
|
361
359
|
return lines;
|
|
362
360
|
}
|
|
@@ -365,7 +363,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
365
363
|
const lines: string[] = [];
|
|
366
364
|
const args = info.positionals!;
|
|
367
365
|
|
|
368
|
-
lines.push(styler.
|
|
366
|
+
lines.push(styler.section('Arguments:'));
|
|
369
367
|
|
|
370
368
|
const maxNameLength = Math.min(32, Math.max(...args.map((a) => a.name.length)));
|
|
371
369
|
|
|
@@ -386,8 +384,6 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
386
384
|
const lines: string[] = [];
|
|
387
385
|
const argList = info.arguments || [];
|
|
388
386
|
|
|
389
|
-
lines.push(styler.label('Options:'));
|
|
390
|
-
|
|
391
387
|
// Helper to check if a default value is meaningful (not empty string/array)
|
|
392
388
|
const hasDefault = (value: unknown): boolean => {
|
|
393
389
|
if (value === undefined) return false;
|
|
@@ -396,70 +392,94 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
396
392
|
return true;
|
|
397
393
|
};
|
|
398
394
|
|
|
399
|
-
// Build
|
|
400
|
-
const argColumns
|
|
395
|
+
// Build columns: flags | names | type | description
|
|
396
|
+
const argColumns = argList.map((arg) => {
|
|
401
397
|
// Promote kebab-case alias to primary display name if it exists
|
|
402
398
|
const kebab = camelToKebab(arg.name);
|
|
403
399
|
const primaryName = kebab && arg.aliases?.includes(kebab) ? kebab : arg.name;
|
|
404
400
|
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
401
|
|
|
413
|
-
const
|
|
414
|
-
const
|
|
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}>`) : '';
|
|
415
405
|
|
|
416
|
-
|
|
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
|
-
}
|
|
406
|
+
const isDeprecated = !!arg.deprecated;
|
|
426
407
|
|
|
427
|
-
return {
|
|
408
|
+
return { flagsPlain, namesPlain, typePlain, isDeprecated, arg };
|
|
428
409
|
});
|
|
429
410
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
}
|
|
441
431
|
|
|
442
|
-
|
|
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);
|
|
443
436
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
+
}
|
|
450
443
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
lines.push(indent(
|
|
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));
|
|
455
476
|
}
|
|
477
|
+
};
|
|
456
478
|
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
lines.push(indent(3) + join(exampleParts));
|
|
462
|
-
}
|
|
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!);
|
|
463
483
|
}
|
|
464
484
|
|
|
465
485
|
return lines;
|
|
@@ -469,7 +489,18 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
469
489
|
const lines: string[] = [];
|
|
470
490
|
const builtins = info.builtins!;
|
|
471
491
|
|
|
472
|
-
|
|
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:'));
|
|
473
504
|
|
|
474
505
|
// Compute max effective name length for alignment across main and sub entries
|
|
475
506
|
const allLengths: number[] = [];
|
|
@@ -537,6 +568,15 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
537
568
|
lines.push('');
|
|
538
569
|
}
|
|
539
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
|
+
|
|
540
580
|
// Subcommands section
|
|
541
581
|
if (info.subcommands && info.subcommands.length > 0) {
|
|
542
582
|
lines.push(...formatSubcommandsSection(info));
|
|
@@ -566,7 +606,7 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
566
606
|
|
|
567
607
|
// Nested commands section (full detail mode)
|
|
568
608
|
if (info.nestedCommands?.length) {
|
|
569
|
-
lines.push(styler.
|
|
609
|
+
lines.push(styler.section('Subcommand Details:'));
|
|
570
610
|
lines.push('');
|
|
571
611
|
for (const nestedCmd of info.nestedCommands) {
|
|
572
612
|
lines.push(styler.meta('─'.repeat(60)));
|
|
@@ -628,12 +668,18 @@ function createMinimalFormatter(): Formatter {
|
|
|
628
668
|
};
|
|
629
669
|
}
|
|
630
670
|
|
|
631
|
-
export function createFormatter(
|
|
671
|
+
export function createFormatter(
|
|
672
|
+
format: HelpFormat | 'auto',
|
|
673
|
+
detail: HelpDetail = 'standard',
|
|
674
|
+
theme?: ColorTheme | ColorConfig,
|
|
675
|
+
all?: boolean,
|
|
676
|
+
): Formatter {
|
|
632
677
|
if (detail === 'minimal') return createMinimalFormatter();
|
|
633
678
|
if (format === 'json') return createJsonFormatter();
|
|
634
|
-
if (format === 'ansi' || (format === 'auto' && shouldUseAnsi()))
|
|
635
|
-
|
|
636
|
-
if (format === '
|
|
637
|
-
if (format === '
|
|
638
|
-
return createGenericFormatter(
|
|
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);
|
|
639
685
|
}
|
package/src/help.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StandardJSONSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
import { extractSchemaMetadata, type PadroneArgsSchemaMeta, parsePositionalConfig, parseStdinConfig } from './args.ts';
|
|
2
|
+
import { extractSchemaMetadata, JSON_SCHEMA_OPTS, type PadroneArgsSchemaMeta, parsePositionalConfig, parseStdinConfig } from './args.ts';
|
|
3
|
+
import type { ColorConfig, ColorTheme } from './colorizer.ts';
|
|
3
4
|
import { findCommandByName } from './command-utils.ts';
|
|
4
5
|
import {
|
|
5
6
|
createFormatter,
|
|
@@ -16,6 +17,9 @@ import { getRootCommand } from './utils.ts';
|
|
|
16
17
|
export type HelpPreferences = {
|
|
17
18
|
format?: HelpFormat | 'auto';
|
|
18
19
|
detail?: HelpDetail;
|
|
20
|
+
theme?: ColorTheme | ColorConfig;
|
|
21
|
+
/** Show all global commands and flags in full detail */
|
|
22
|
+
all?: boolean;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
/**
|
|
@@ -35,7 +39,7 @@ function extractPositionalArgsInfo(
|
|
|
35
39
|
const positionalConfig = parsePositionalConfig(meta.positional);
|
|
36
40
|
|
|
37
41
|
try {
|
|
38
|
-
const jsonSchema = schema['~standard'].jsonSchema.input(
|
|
42
|
+
const jsonSchema = schema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
|
|
39
43
|
|
|
40
44
|
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
41
45
|
const properties = jsonSchema.properties as Record<string, any>;
|
|
@@ -75,7 +79,7 @@ function extractArgsInfo(schema: StandardJSONSchemaV1, meta?: PadroneArgsSchemaM
|
|
|
75
79
|
const argsMeta = meta?.fields;
|
|
76
80
|
|
|
77
81
|
try {
|
|
78
|
-
const jsonSchema = schema['~standard'].jsonSchema.input(
|
|
82
|
+
const jsonSchema = schema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
|
|
79
83
|
|
|
80
84
|
// Handle object: z.object({ key: z.string(), ... })
|
|
81
85
|
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
@@ -134,6 +138,7 @@ function extractArgsInfo(schema: StandardJSONSchemaV1, meta?: PadroneArgsSchemaM
|
|
|
134
138
|
examples: optMeta?.examples ?? prop?.examples,
|
|
135
139
|
variadic: propType === 'array',
|
|
136
140
|
negatable: isNegatable,
|
|
141
|
+
group: optMeta?.group,
|
|
137
142
|
});
|
|
138
143
|
}
|
|
139
144
|
}
|
|
@@ -154,7 +159,7 @@ function extractArgsInfo(schema: StandardJSONSchemaV1, meta?: PadroneArgsSchemaM
|
|
|
154
159
|
* @param cmd - The command to build help info for
|
|
155
160
|
* @param detail - The level of detail ('minimal', 'standard', or 'full')
|
|
156
161
|
*/
|
|
157
|
-
export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['detail'] = 'standard'): HelpInfo {
|
|
162
|
+
export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['detail'] = 'standard', all?: boolean): HelpInfo {
|
|
158
163
|
const rootCmd = getRootCommand(cmd);
|
|
159
164
|
// A command is a "default" command if its name is '' or it has '' as an alias
|
|
160
165
|
const isDefaultCommand = cmd.parent && (!cmd.name || cmd.aliases?.includes(''));
|
|
@@ -176,6 +181,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
176
181
|
name: commandName,
|
|
177
182
|
title: cmd.title,
|
|
178
183
|
description: cmd.description,
|
|
184
|
+
examples: cmd.examples,
|
|
179
185
|
aliases: displayAliases,
|
|
180
186
|
deprecated: cmd.deprecated,
|
|
181
187
|
hidden: cmd.hidden,
|
|
@@ -184,7 +190,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
184
190
|
hasSubcommands: !!(cmd.commands && cmd.commands.length > 0),
|
|
185
191
|
hasPositionals,
|
|
186
192
|
hasArguments: false, // updated below after extracting arguments
|
|
187
|
-
stdinField: cmd.meta?.stdin ? parseStdinConfig(cmd.meta.stdin)
|
|
193
|
+
stdinField: cmd.meta?.stdin ? parseStdinConfig(cmd.meta.stdin) : undefined,
|
|
188
194
|
},
|
|
189
195
|
};
|
|
190
196
|
|
|
@@ -222,6 +228,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
222
228
|
aliases: displayAliases?.length ? displayAliases : undefined,
|
|
223
229
|
deprecated: c.deprecated,
|
|
224
230
|
hidden: c.hidden,
|
|
231
|
+
group: c.group,
|
|
225
232
|
},
|
|
226
233
|
{
|
|
227
234
|
name: displayName,
|
|
@@ -230,6 +237,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
230
237
|
deprecated: c.deprecated,
|
|
231
238
|
hidden: c.hidden,
|
|
232
239
|
hasSubcommands: true,
|
|
240
|
+
group: c.group,
|
|
233
241
|
},
|
|
234
242
|
];
|
|
235
243
|
}
|
|
@@ -243,6 +251,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
243
251
|
deprecated: c.deprecated,
|
|
244
252
|
hidden: c.hidden,
|
|
245
253
|
hasSubcommands,
|
|
254
|
+
group: c.group,
|
|
246
255
|
},
|
|
247
256
|
];
|
|
248
257
|
}),
|
|
@@ -285,40 +294,64 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
296
|
|
|
288
|
-
// Add
|
|
289
|
-
if (!cmd.parent) {
|
|
297
|
+
// Add global commands/flags (root command by default, all commands when --all is passed)
|
|
298
|
+
if (!cmd.parent || all) {
|
|
290
299
|
const builtins: HelpInfo['builtins'] = [];
|
|
291
300
|
|
|
292
|
-
if (!findCommandByName('help',
|
|
301
|
+
if (!findCommandByName('help', rootCmd.commands)) {
|
|
293
302
|
builtins.push({
|
|
294
303
|
name: 'help [command], -h, --help',
|
|
295
304
|
description: 'Show help for a command',
|
|
296
305
|
sub: [
|
|
306
|
+
{ name: '--all', description: 'Show all global commands and flags' },
|
|
297
307
|
{ name: '--detail <level>', description: 'Detail level (minimal, standard, full)' },
|
|
298
308
|
{ name: '--format <format>', description: 'Output format (text, ansi, json, markdown, html)' },
|
|
299
309
|
],
|
|
300
310
|
});
|
|
301
311
|
}
|
|
302
312
|
|
|
303
|
-
if (!findCommandByName('version',
|
|
313
|
+
if (!findCommandByName('version', rootCmd.commands)) {
|
|
304
314
|
builtins.push({
|
|
305
315
|
name: 'version, -v, --version',
|
|
306
316
|
description: 'Show version information',
|
|
307
317
|
});
|
|
308
318
|
}
|
|
309
319
|
|
|
310
|
-
if (!findCommandByName('completion',
|
|
320
|
+
if (!findCommandByName('completion', rootCmd.commands)) {
|
|
311
321
|
builtins.push({
|
|
312
322
|
name: 'completion [shell]',
|
|
313
323
|
description: 'Generate shell completions (bash, zsh, fish, powershell)',
|
|
314
324
|
});
|
|
315
325
|
}
|
|
316
326
|
|
|
327
|
+
if (!findCommandByName('man', rootCmd.commands)) {
|
|
328
|
+
builtins.push({
|
|
329
|
+
name: 'man',
|
|
330
|
+
description: 'Show or install man pages (--setup to install, --remove to uninstall) (experimental)',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
317
334
|
builtins.push({
|
|
318
335
|
name: '[command] --repl',
|
|
319
336
|
description: 'Start interactive REPL scoped to a command',
|
|
320
337
|
});
|
|
321
338
|
|
|
339
|
+
if (!findCommandByName('mcp', rootCmd.commands)) {
|
|
340
|
+
builtins.push({
|
|
341
|
+
name: 'mcp [http|stdio]',
|
|
342
|
+
description: 'Start a Model Context Protocol server to expose commands as AI tools (experimental)',
|
|
343
|
+
sub: [
|
|
344
|
+
{ name: '--port <port>', description: 'HTTP port (default: 3000)' },
|
|
345
|
+
{ name: '--host <host>', description: 'HTTP host (default: 127.0.0.1)' },
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
builtins.push({
|
|
351
|
+
name: '--color [theme], --no-color',
|
|
352
|
+
description: 'Set color theme (default, ocean, warm, monochrome) or disable colors',
|
|
353
|
+
});
|
|
354
|
+
|
|
322
355
|
if (builtins.length > 0) {
|
|
323
356
|
helpInfo.builtins = builtins;
|
|
324
357
|
}
|
|
@@ -332,7 +365,7 @@ export function getHelpInfo(cmd: AnyPadroneCommand, detail: HelpPreferences['det
|
|
|
332
365
|
// ============================================================================
|
|
333
366
|
|
|
334
367
|
export function generateHelp(rootCommand: AnyPadroneCommand, commandObj: AnyPadroneCommand = rootCommand, prefs?: HelpPreferences): string {
|
|
335
|
-
const helpInfo = getHelpInfo(commandObj, prefs?.detail);
|
|
336
|
-
const formatter = createFormatter(prefs?.format ?? 'auto', prefs?.detail);
|
|
368
|
+
const helpInfo = getHelpInfo(commandObj, prefs?.detail, prefs?.all);
|
|
369
|
+
const formatter = createFormatter(prefs?.format ?? 'auto', prefs?.detail, prefs?.theme, prefs?.all);
|
|
337
370
|
return formatter.format(helpInfo);
|
|
338
371
|
}
|