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/create.ts
CHANGED
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
import type { Schema } from 'ai';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
coerceArgs,
|
|
5
|
+
detectUnknownArgs,
|
|
6
|
+
extractSchemaMetadata,
|
|
7
|
+
isArrayField,
|
|
8
|
+
isAsyncStreamField,
|
|
9
|
+
JSON_SCHEMA_OPTS,
|
|
10
|
+
parsePositionalConfig,
|
|
11
|
+
parseStdinConfig,
|
|
12
|
+
preprocessArgs,
|
|
13
|
+
} from './args.ts';
|
|
14
|
+
import { type ColorConfig, type ColorTheme, colorThemes } from './colorizer.ts';
|
|
4
15
|
import {
|
|
5
16
|
commandSymbol,
|
|
17
|
+
createLazyIndicator,
|
|
18
|
+
createProgress,
|
|
19
|
+
errorResult,
|
|
6
20
|
findCommandByName,
|
|
7
21
|
getCommandRuntime,
|
|
8
22
|
hasInteractiveConfig,
|
|
9
23
|
isAsyncBranded,
|
|
24
|
+
lazyResolver,
|
|
10
25
|
makeThenable,
|
|
11
26
|
mergeCommands,
|
|
12
27
|
noop,
|
|
28
|
+
noopIndicator,
|
|
13
29
|
outputValue,
|
|
14
30
|
repathCommandTree,
|
|
31
|
+
resolveAllCommands,
|
|
32
|
+
resolveCommand,
|
|
33
|
+
resolveProgressMessage,
|
|
15
34
|
runPluginChain,
|
|
16
35
|
suggestSimilar,
|
|
17
36
|
thenMaybe,
|
|
18
37
|
warnIfUnexpectedAsync,
|
|
38
|
+
withDrain,
|
|
39
|
+
withPromiseDrain,
|
|
19
40
|
wrapWithLifecycle,
|
|
20
41
|
} from './command-utils.ts';
|
|
21
42
|
import type { ShellType } from './completion.ts';
|
|
@@ -24,7 +45,8 @@ import { generateHelp } from './help.ts';
|
|
|
24
45
|
import { promptInteractiveFields } from './interactive.ts';
|
|
25
46
|
import { getNestedValue, parseCliInputToParts, setNestedValue } from './parse.ts';
|
|
26
47
|
import { createReplIterator } from './repl-loop.ts';
|
|
27
|
-
import { resolveStdin } from './runtime.ts';
|
|
48
|
+
import { type PadroneProgressIndicator, resolveStdin, resolveStdinAlways } from './runtime.ts';
|
|
49
|
+
import { createStdinStream } from './stream.ts';
|
|
28
50
|
import type {
|
|
29
51
|
AnyPadroneCommand,
|
|
30
52
|
AnyPadroneProgram,
|
|
@@ -65,11 +87,14 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
65
87
|
: inputCommand;
|
|
66
88
|
|
|
67
89
|
/** Creates the action context passed to command handlers. References `builder` which is defined later but only called at runtime. */
|
|
68
|
-
const createActionContext = (cmd: AnyPadroneCommand): PadroneActionContext =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
const createActionContext = (cmd: AnyPadroneCommand): PadroneActionContext => {
|
|
91
|
+
return {
|
|
92
|
+
runtime: getCommandRuntime(cmd),
|
|
93
|
+
command: cmd,
|
|
94
|
+
program: builder as any,
|
|
95
|
+
progress: noopIndicator,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
73
98
|
|
|
74
99
|
const find: AnyPadroneProgram['find'] = (command) => {
|
|
75
100
|
if (typeof command !== 'string') return findCommandByName(command.path, existingCommand.commands) as any;
|
|
@@ -136,7 +161,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
136
161
|
const arrayArguments = new Set<string>();
|
|
137
162
|
if (curCommand.argsSchema) {
|
|
138
163
|
try {
|
|
139
|
-
const jsonSchema = curCommand.argsSchema['~standard'].jsonSchema.input(
|
|
164
|
+
const jsonSchema = curCommand.argsSchema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
|
|
140
165
|
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
141
166
|
for (const [key, prop] of Object.entries(jsonSchema.properties as Record<string, any>)) {
|
|
142
167
|
if (prop?.type === 'array') arrayArguments.add(key);
|
|
@@ -352,16 +377,24 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
352
377
|
const stdinConfig = command.meta?.stdin;
|
|
353
378
|
if (!stdinConfig) return {};
|
|
354
379
|
|
|
355
|
-
const
|
|
380
|
+
const field = parseStdinConfig(stdinConfig);
|
|
356
381
|
|
|
357
382
|
// Skip if the field was already provided via CLI flags
|
|
358
383
|
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== undefined) return {};
|
|
359
384
|
|
|
360
385
|
const runtime = getCommandRuntime(existingCommand);
|
|
386
|
+
|
|
387
|
+
const streamInfo = isAsyncStreamField(command.argsSchema, field);
|
|
388
|
+
if (streamInfo) {
|
|
389
|
+
// Async stream: always resolve stdin (even on TTY) for interactive use
|
|
390
|
+
const stdinForStream = resolveStdinAlways(runtime as any);
|
|
391
|
+
return { [field]: createStdinStream(stdinForStream, streamInfo.itemSchema) };
|
|
392
|
+
}
|
|
393
|
+
|
|
361
394
|
const stdin = resolveStdin(runtime as any);
|
|
362
395
|
if (!stdin) return {};
|
|
363
396
|
|
|
364
|
-
if (
|
|
397
|
+
if (isArrayField(command.argsSchema, field)) {
|
|
365
398
|
return (async () => {
|
|
366
399
|
const lines: string[] = [];
|
|
367
400
|
for await (const line of stdin.lines()) {
|
|
@@ -499,10 +532,13 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
499
532
|
const checkBuiltinCommands = (
|
|
500
533
|
input: string | undefined,
|
|
501
534
|
):
|
|
502
|
-
| { type: 'help'; command?: AnyPadroneCommand; detail?: DetailLevel; format?: FormatLevel }
|
|
535
|
+
| { type: 'help'; command?: AnyPadroneCommand; detail?: DetailLevel; format?: FormatLevel; all?: boolean }
|
|
503
536
|
| { type: 'version' }
|
|
504
537
|
| { type: 'completion'; shell?: ShellType; setup?: boolean }
|
|
538
|
+
| { type: 'man'; setup?: boolean; remove?: boolean }
|
|
505
539
|
| { type: 'repl'; scope?: string }
|
|
540
|
+
| { type: 'mcp'; transport?: 'http' | 'stdio'; port?: number; host?: string; basePath?: string }
|
|
541
|
+
| { type: 'serve'; port?: number; host?: string; basePath?: string }
|
|
506
542
|
| null => {
|
|
507
543
|
if (!input) return null;
|
|
508
544
|
|
|
@@ -516,18 +552,21 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
516
552
|
// Check for --help, -h flags (these take precedence over commands)
|
|
517
553
|
const hasHelpFlag = args.some((p) => (p.type === 'named' && keyIs(p.key, 'help')) || (p.type === 'alias' && keyIs(p.key, 'h')));
|
|
518
554
|
|
|
519
|
-
// Extract detail level from --detail=<level> or -d <level>
|
|
555
|
+
// Extract detail level from --detail[=<level>] or -d [<level>]
|
|
556
|
+
// Bare --detail (no value) defaults to 'full'
|
|
520
557
|
const getDetailLevel = (): DetailLevel | undefined => {
|
|
521
558
|
for (const arg of args) {
|
|
522
|
-
if (arg.type === 'named' && keyIs(arg.key, 'detail')
|
|
523
|
-
if (arg.value === 'minimal' || arg.value === 'standard' || arg.value === 'full') {
|
|
559
|
+
if (arg.type === 'named' && keyIs(arg.key, 'detail')) {
|
|
560
|
+
if (typeof arg.value === 'string' && (arg.value === 'minimal' || arg.value === 'standard' || arg.value === 'full')) {
|
|
524
561
|
return arg.value;
|
|
525
562
|
}
|
|
563
|
+
return 'full';
|
|
526
564
|
}
|
|
527
|
-
if (arg.type === 'alias' && keyIs(arg.key, 'd')
|
|
528
|
-
if (arg.value === 'minimal' || arg.value === 'standard' || arg.value === 'full') {
|
|
565
|
+
if (arg.type === 'alias' && keyIs(arg.key, 'd')) {
|
|
566
|
+
if (typeof arg.value === 'string' && (arg.value === 'minimal' || arg.value === 'standard' || arg.value === 'full')) {
|
|
529
567
|
return arg.value;
|
|
530
568
|
}
|
|
569
|
+
return 'full';
|
|
531
570
|
}
|
|
532
571
|
}
|
|
533
572
|
return undefined;
|
|
@@ -553,6 +592,9 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
553
592
|
};
|
|
554
593
|
const format = getFormat();
|
|
555
594
|
|
|
595
|
+
// Check for --all flag (show all built-in help)
|
|
596
|
+
const hasAllFlag = args.some((p) => p.type === 'named' && keyIs(p.key, 'all'));
|
|
597
|
+
|
|
556
598
|
// Check for --version, -v, -V flags
|
|
557
599
|
const hasVersionFlag = args.some(
|
|
558
600
|
(p) => (p.type === 'named' && keyIs(p.key, 'version')) || (p.type === 'alias' && (keyIs(p.key, 'v') || keyIs(p.key, 'V'))),
|
|
@@ -573,7 +615,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
573
615
|
// help <command> - get help for specific command
|
|
574
616
|
const commandName = normalizedTerms.slice(1).join(' ');
|
|
575
617
|
const targetCommand = commandName ? findCommandByName(commandName, existingCommand.commands) : undefined;
|
|
576
|
-
return { type: 'help', command: targetCommand, detail, format };
|
|
618
|
+
return { type: 'help', command: targetCommand, detail, format, all: hasAllFlag || undefined };
|
|
577
619
|
}
|
|
578
620
|
if (!userHelpCommand && normalizedTerms.length > 0 && normalizedTerms[normalizedTerms.length - 1] === 'help') {
|
|
579
621
|
// <command> help - get help for specific command (trailing form)
|
|
@@ -590,7 +632,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
590
632
|
break;
|
|
591
633
|
}
|
|
592
634
|
}
|
|
593
|
-
return { type: 'help', command: targetCommand, detail, format };
|
|
635
|
+
return { type: 'help', command: targetCommand, detail, format, all: hasAllFlag || undefined };
|
|
594
636
|
}
|
|
595
637
|
|
|
596
638
|
// Check for 'version' command (only if user hasn't defined one)
|
|
@@ -607,13 +649,21 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
607
649
|
return { type: 'completion', shell, setup };
|
|
608
650
|
}
|
|
609
651
|
|
|
652
|
+
// Check for 'man' command (only if user hasn't defined one)
|
|
653
|
+
const userManCommand = findCommandByName('man', existingCommand.commands);
|
|
654
|
+
if (!userManCommand && normalizedTerms[0] === 'man') {
|
|
655
|
+
const setup = args.some((p) => p.type === 'named' && keyIs(p.key, 'setup'));
|
|
656
|
+
const remove = args.some((p) => p.type === 'named' && keyIs(p.key, 'remove'));
|
|
657
|
+
return { type: 'man', setup, remove };
|
|
658
|
+
}
|
|
659
|
+
|
|
610
660
|
// Handle help flag - find the command being requested
|
|
611
661
|
if (hasHelpFlag) {
|
|
612
662
|
// Filter out help-related terms and flags to find the target command
|
|
613
663
|
const commandTerms = normalizedTerms.filter((t) => t !== 'help');
|
|
614
664
|
const commandName = commandTerms.join(' ');
|
|
615
665
|
const targetCommand = commandName ? findCommandByName(commandName, existingCommand.commands) : undefined;
|
|
616
|
-
return { type: 'help', command: targetCommand, detail, format };
|
|
666
|
+
return { type: 'help', command: targetCommand, detail, format, all: hasAllFlag || undefined };
|
|
617
667
|
}
|
|
618
668
|
|
|
619
669
|
// Handle version flag (only for root command, i.e., no subcommand terms)
|
|
@@ -621,6 +671,32 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
621
671
|
return { type: 'version' };
|
|
622
672
|
}
|
|
623
673
|
|
|
674
|
+
// Check for 'mcp' command (only if user hasn't defined one)
|
|
675
|
+
const userMcpCommand = findCommandByName('mcp', existingCommand.commands);
|
|
676
|
+
if (!userMcpCommand && normalizedTerms[0] === 'mcp') {
|
|
677
|
+
const transportArg = normalizedTerms[1];
|
|
678
|
+
const transport = transportArg === 'stdio' || transportArg === 'http' ? transportArg : undefined;
|
|
679
|
+
const portArg = args.find((p) => p.type === 'named' && keyIs(p.key, 'port'));
|
|
680
|
+
const port = typeof portArg?.value === 'string' ? parseInt(portArg.value, 10) : undefined;
|
|
681
|
+
const hostArg = args.find((p) => p.type === 'named' && keyIs(p.key, 'host'));
|
|
682
|
+
const host = typeof hostArg?.value === 'string' ? hostArg.value : undefined;
|
|
683
|
+
const basePathArg = args.find((p) => p.type === 'named' && keyIs(p.key, 'base-path'));
|
|
684
|
+
const mcpBasePath = typeof basePathArg?.value === 'string' ? basePathArg.value : undefined;
|
|
685
|
+
return { type: 'mcp', transport, port: port && !Number.isNaN(port) ? port : undefined, host, basePath: mcpBasePath };
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Check for 'serve' command (only if user hasn't defined one)
|
|
689
|
+
const userServeCommand = findCommandByName('serve', existingCommand.commands);
|
|
690
|
+
if (!userServeCommand && normalizedTerms[0] === 'serve') {
|
|
691
|
+
const portArg = args.find((p) => p.type === 'named' && keyIs(p.key, 'port'));
|
|
692
|
+
const port = typeof portArg?.value === 'string' ? parseInt(portArg.value, 10) : undefined;
|
|
693
|
+
const hostArg = args.find((p) => p.type === 'named' && keyIs(p.key, 'host'));
|
|
694
|
+
const host = typeof hostArg?.value === 'string' ? hostArg.value : undefined;
|
|
695
|
+
const basePathArg = args.find((p) => p.type === 'named' && keyIs(p.key, 'base-path'));
|
|
696
|
+
const basePath = typeof basePathArg?.value === 'string' ? basePathArg.value : undefined;
|
|
697
|
+
return { type: 'serve', port: port && !Number.isNaN(port) ? port : undefined, host, basePath };
|
|
698
|
+
}
|
|
699
|
+
|
|
624
700
|
// Check for --repl flag
|
|
625
701
|
const hasReplFlag = args.some((p) => p.type === 'named' && keyIs(p.key, 'repl'));
|
|
626
702
|
if (hasReplFlag) {
|
|
@@ -651,6 +727,35 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
651
727
|
return undefined;
|
|
652
728
|
};
|
|
653
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Extract --color flag from input.
|
|
732
|
+
* - `--color` or `--color=true` → use default theme
|
|
733
|
+
* - `--color=false` or `--no-color` → disable colors (text format)
|
|
734
|
+
* - `--color=<theme>` → use the named theme
|
|
735
|
+
* Returns `undefined` if no --color flag is present.
|
|
736
|
+
*/
|
|
737
|
+
const extractColorFlag = (input: string | undefined): { theme?: ColorTheme | ColorConfig; disableColor?: boolean } | undefined => {
|
|
738
|
+
if (!input) return undefined;
|
|
739
|
+
|
|
740
|
+
const parts = parseCliInputToParts(input);
|
|
741
|
+
const args = parts.filter((p) => p.type === 'named');
|
|
742
|
+
const keyIs = (key: string[], name: string) => key.length === 1 && key[0] === name;
|
|
743
|
+
|
|
744
|
+
for (const arg of args) {
|
|
745
|
+
if (arg.type === 'named' && keyIs(arg.key, 'no-color')) {
|
|
746
|
+
return { disableColor: true };
|
|
747
|
+
}
|
|
748
|
+
if (arg.type === 'named' && keyIs(arg.key, 'color')) {
|
|
749
|
+
if (arg.negated) return { disableColor: true };
|
|
750
|
+
if (arg.value === undefined || arg.value === 'true') return { theme: 'default' };
|
|
751
|
+
if (arg.value === 'false') return { disableColor: true };
|
|
752
|
+
if (typeof arg.value === 'string' && arg.value in colorThemes) return { theme: arg.value as ColorTheme };
|
|
753
|
+
return undefined;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return undefined;
|
|
757
|
+
};
|
|
758
|
+
|
|
654
759
|
/**
|
|
655
760
|
* Core execution logic shared by eval() and cli().
|
|
656
761
|
* errorMode controls validation error behavior:
|
|
@@ -659,38 +764,51 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
659
764
|
*/
|
|
660
765
|
const execCommand = (resolvedInput: string | undefined, evalOptions?: PadroneEvalPreferences, errorMode: 'soft' | 'hard' = 'soft') => {
|
|
661
766
|
const baseRuntime = getCommandRuntime(existingCommand);
|
|
662
|
-
|
|
767
|
+
let runtime = evalOptions?.runtime
|
|
663
768
|
? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== undefined)))
|
|
664
769
|
: baseRuntime;
|
|
665
770
|
|
|
771
|
+
// Apply --color / --no-color flag to runtime
|
|
772
|
+
const colorFlag = extractColorFlag(resolvedInput);
|
|
773
|
+
if (colorFlag) {
|
|
774
|
+
runtime = {
|
|
775
|
+
...runtime,
|
|
776
|
+
...(colorFlag.disableColor ? { format: 'text' as const, theme: undefined } : { theme: colorFlag.theme }),
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
666
780
|
// Check for built-in help/version/completion commands and flags (bypass plugins)
|
|
667
781
|
const builtin = checkBuiltinCommands(resolvedInput);
|
|
668
782
|
|
|
669
783
|
if (builtin) {
|
|
670
784
|
if (builtin.type === 'help') {
|
|
785
|
+
resolveAllCommands(existingCommand);
|
|
671
786
|
const helpText = generateHelp(existingCommand, builtin.command ?? existingCommand, {
|
|
672
787
|
detail: builtin.detail,
|
|
673
788
|
format: builtin.format ?? runtime.format,
|
|
789
|
+
theme: runtime.theme,
|
|
790
|
+
all: builtin.all,
|
|
674
791
|
});
|
|
675
792
|
runtime.output(helpText);
|
|
676
|
-
return {
|
|
793
|
+
return withDrain({
|
|
677
794
|
command: existingCommand,
|
|
678
795
|
args: undefined,
|
|
679
796
|
result: helpText,
|
|
680
|
-
} as any;
|
|
797
|
+
}) as any;
|
|
681
798
|
}
|
|
682
799
|
|
|
683
800
|
if (builtin.type === 'version') {
|
|
684
801
|
const version = getVersion(existingCommand.version);
|
|
685
802
|
runtime.output(version);
|
|
686
|
-
return {
|
|
803
|
+
return withDrain({
|
|
687
804
|
command: existingCommand,
|
|
688
805
|
args: undefined,
|
|
689
806
|
result: version,
|
|
690
|
-
} as any;
|
|
807
|
+
}) as any;
|
|
691
808
|
}
|
|
692
809
|
|
|
693
810
|
if (builtin.type === 'completion') {
|
|
811
|
+
resolveAllCommands(existingCommand);
|
|
694
812
|
return import('./completion.ts').then(({ detectShell, generateCompletionOutput, setupCompletions }) => {
|
|
695
813
|
if (builtin.setup) {
|
|
696
814
|
const shell = builtin.shell ?? detectShell();
|
|
@@ -700,19 +818,57 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
700
818
|
const result = setupCompletions(existingCommand.name, shell);
|
|
701
819
|
const message = `${result.updated ? 'Updated' : 'Added'} ${existingCommand.name} completions in ${result.file}`;
|
|
702
820
|
runtime.output(message);
|
|
703
|
-
return {
|
|
821
|
+
return withDrain({
|
|
704
822
|
command: existingCommand,
|
|
705
823
|
args: undefined,
|
|
706
824
|
result: message,
|
|
707
|
-
};
|
|
825
|
+
});
|
|
708
826
|
}
|
|
709
827
|
const completionScript = generateCompletionOutput(existingCommand, builtin.shell);
|
|
710
828
|
runtime.output(completionScript);
|
|
711
|
-
return {
|
|
829
|
+
return withDrain({
|
|
712
830
|
command: existingCommand,
|
|
713
831
|
args: undefined,
|
|
714
832
|
result: completionScript,
|
|
715
|
-
};
|
|
833
|
+
});
|
|
834
|
+
}) as any;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (builtin.type === 'man') {
|
|
838
|
+
resolveAllCommands(existingCommand);
|
|
839
|
+
return import('./docs/index.ts').then(({ setupManPages, removeManPages, generateDocs }) => {
|
|
840
|
+
if (builtin.setup) {
|
|
841
|
+
const result = setupManPages(existingCommand);
|
|
842
|
+
const message = `${result.updated ? 'Updated' : 'Installed'} ${result.written.length} man page(s) in ${result.dir}`;
|
|
843
|
+
runtime.output(message);
|
|
844
|
+
return withDrain({
|
|
845
|
+
command: existingCommand,
|
|
846
|
+
args: undefined,
|
|
847
|
+
result: message,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
if (builtin.remove) {
|
|
851
|
+
const result = removeManPages(existingCommand);
|
|
852
|
+
const message =
|
|
853
|
+
result.removed.length > 0
|
|
854
|
+
? `Removed ${result.removed.length} man page(s) from ${result.dir}`
|
|
855
|
+
: 'No man pages found to remove.';
|
|
856
|
+
runtime.output(message);
|
|
857
|
+
return withDrain({
|
|
858
|
+
command: existingCommand,
|
|
859
|
+
args: undefined,
|
|
860
|
+
result: message,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
// Default: generate man page for the root command and print it
|
|
864
|
+
const result = generateDocs(existingCommand, { format: 'man' });
|
|
865
|
+
const manPage = result.pages[0]?.content ?? '';
|
|
866
|
+
runtime.output(manPage);
|
|
867
|
+
return withDrain({
|
|
868
|
+
command: existingCommand,
|
|
869
|
+
args: undefined,
|
|
870
|
+
result: manPage,
|
|
871
|
+
});
|
|
716
872
|
}) as any;
|
|
717
873
|
}
|
|
718
874
|
}
|
|
@@ -732,7 +888,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
732
888
|
const hasSubcommands = command.commands && command.commands.length > 0;
|
|
733
889
|
const hasSchema = command.argsSchema != null;
|
|
734
890
|
if (!command.action && (hasSubcommands || !hasSchema) && unmatchedTerms.length === 0) {
|
|
735
|
-
|
|
891
|
+
resolveAllCommands(existingCommand);
|
|
892
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format, theme: runtime.theme });
|
|
736
893
|
runtime.output(helpText);
|
|
737
894
|
return {
|
|
738
895
|
command: command,
|
|
@@ -785,7 +942,11 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
785
942
|
runtime.output(`\nAvailable commands: ${cmdList}`);
|
|
786
943
|
}
|
|
787
944
|
} else {
|
|
788
|
-
|
|
945
|
+
resolveAllCommands(existingCommand);
|
|
946
|
+
const helpText = generateHelp(existingCommand, isRootCommand ? existingCommand : command, {
|
|
947
|
+
format: runtime.format,
|
|
948
|
+
theme: runtime.theme,
|
|
949
|
+
});
|
|
789
950
|
runtime.error(helpText);
|
|
790
951
|
}
|
|
791
952
|
throw new RoutingError(errorMsg, { suggestions, command: command.path || command.name });
|
|
@@ -817,6 +978,40 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
817
978
|
} as any;
|
|
818
979
|
}
|
|
819
980
|
|
|
981
|
+
// ── Auto-progress: start before validation ───────────────────────
|
|
982
|
+
const progressConfig = command.progress;
|
|
983
|
+
if (progressConfig && runtime.progress) {
|
|
984
|
+
const isObj = typeof progressConfig === 'object';
|
|
985
|
+
const defaultMsg = typeof progressConfig === 'string' ? progressConfig : `Running ${command.name}...`;
|
|
986
|
+
const progressMsg = isObj ? (progressConfig.progress ?? defaultMsg) : defaultMsg;
|
|
987
|
+
const validationMsg = isObj ? (progressConfig.validation ?? '') : '';
|
|
988
|
+
state._progressSuccess = isObj ? progressConfig.success : undefined;
|
|
989
|
+
state._progressError = isObj ? progressConfig.error : undefined;
|
|
990
|
+
state._progressMsg = progressMsg;
|
|
991
|
+
state._progressValidationMsg = validationMsg || undefined;
|
|
992
|
+
const spinnerConfig = isObj ? progressConfig.spinner : undefined;
|
|
993
|
+
const progressOptions = spinnerConfig !== undefined ? { spinner: spinnerConfig } : undefined;
|
|
994
|
+
const indicator = createProgress(runtime, validationMsg || progressMsg, progressOptions);
|
|
995
|
+
state._progress = indicator;
|
|
996
|
+
|
|
997
|
+
const originalOutput = runtime.output;
|
|
998
|
+
const originalError = runtime.error;
|
|
999
|
+
runtime.output = (...args: unknown[]) => {
|
|
1000
|
+
indicator.pause();
|
|
1001
|
+
originalOutput(...args);
|
|
1002
|
+
indicator.resume();
|
|
1003
|
+
};
|
|
1004
|
+
runtime.error = (text: string) => {
|
|
1005
|
+
indicator.pause();
|
|
1006
|
+
originalError(text);
|
|
1007
|
+
indicator.resume();
|
|
1008
|
+
};
|
|
1009
|
+
state._restoreOutput = () => {
|
|
1010
|
+
runtime.output = originalOutput;
|
|
1011
|
+
runtime.error = originalError;
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
820
1015
|
// ── Phase 2: Validate ───────────────────────────────────────────
|
|
821
1016
|
const validateCtx: PluginValidateContext = {
|
|
822
1017
|
command,
|
|
@@ -839,6 +1034,10 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
839
1034
|
}
|
|
840
1035
|
}
|
|
841
1036
|
|
|
1037
|
+
// Strip --color / --no-color from rawArgs (handled globally)
|
|
1038
|
+
delete validateCtx.rawArgs.color;
|
|
1039
|
+
delete validateCtx.rawArgs['no-color'];
|
|
1040
|
+
|
|
842
1041
|
const runtimeDefault: boolean | undefined =
|
|
843
1042
|
runtime.interactive === 'forced' ? true : runtime.interactive === 'disabled' ? false : undefined;
|
|
844
1043
|
const effectiveInteractive: boolean | undefined = flagInteractive ?? evalOptions?.interactive ?? runtimeDefault;
|
|
@@ -928,17 +1127,24 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
928
1127
|
const stdinConfig = command.meta?.stdin;
|
|
929
1128
|
if (!stdinConfig) return {};
|
|
930
1129
|
|
|
931
|
-
const
|
|
1130
|
+
const field = parseStdinConfig(stdinConfig);
|
|
932
1131
|
|
|
933
1132
|
// Skip if the field was already provided via CLI flags (highest precedence)
|
|
934
1133
|
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== undefined) return {};
|
|
935
1134
|
|
|
1135
|
+
const streamInfo = isAsyncStreamField(command.argsSchema, field);
|
|
1136
|
+
if (streamInfo) {
|
|
1137
|
+
// Async stream: always resolve stdin (even on TTY) for interactive use
|
|
1138
|
+
const stdinForStream = resolveStdinAlways(runtime as any);
|
|
1139
|
+
return { [field]: createStdinStream(stdinForStream, streamInfo.itemSchema) };
|
|
1140
|
+
}
|
|
1141
|
+
|
|
936
1142
|
// Resolve stdin: use runtime's custom stdin, or default if piped.
|
|
937
1143
|
// Returns undefined when stdin is a TTY or unavailable.
|
|
938
1144
|
const stdin = resolveStdin(runtime as any);
|
|
939
1145
|
if (!stdin) return {};
|
|
940
1146
|
|
|
941
|
-
if (
|
|
1147
|
+
if (isArrayField(command.argsSchema, field)) {
|
|
942
1148
|
return (async () => {
|
|
943
1149
|
const lines: string[] = [];
|
|
944
1150
|
for await (const line of stdin.lines()) {
|
|
@@ -1057,7 +1263,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1057
1263
|
knownOptions = [];
|
|
1058
1264
|
if (command.argsSchema) {
|
|
1059
1265
|
try {
|
|
1060
|
-
const js = command.argsSchema['~standard'].jsonSchema.input(
|
|
1266
|
+
const js = command.argsSchema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
|
|
1061
1267
|
if (js.type === 'object' && js.properties) knownOptions = Object.keys(js.properties);
|
|
1062
1268
|
} catch {
|
|
1063
1269
|
/* ignore */
|
|
@@ -1082,7 +1288,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1082
1288
|
.join('\n');
|
|
1083
1289
|
|
|
1084
1290
|
if (errorMode === 'hard') {
|
|
1085
|
-
|
|
1291
|
+
resolveAllCommands(existingCommand);
|
|
1292
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format, theme: runtime.theme });
|
|
1086
1293
|
runtime.error(`Validation error:\n${issueMessages}`);
|
|
1087
1294
|
runtime.error(helpText);
|
|
1088
1295
|
throw new ValidationError(`Validation error:\n${issueMessages}`, v.argsResult.issues as any, {
|
|
@@ -1096,12 +1303,18 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1096
1303
|
}
|
|
1097
1304
|
|
|
1098
1305
|
// Soft mode: return result with issues, skip the action
|
|
1099
|
-
return {
|
|
1306
|
+
return withDrain({
|
|
1100
1307
|
command: command as any,
|
|
1101
1308
|
args: undefined,
|
|
1102
1309
|
argsResult: v.argsResult,
|
|
1103
1310
|
result: undefined,
|
|
1104
|
-
};
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Update auto-progress message from validation to execute phase
|
|
1315
|
+
const activeIndicator = state._progress as import('./runtime.ts').PadroneProgressIndicator | undefined;
|
|
1316
|
+
if (activeIndicator && state._progressMsg && state._progressValidationMsg) {
|
|
1317
|
+
activeIndicator.update(state._progressMsg as string);
|
|
1105
1318
|
}
|
|
1106
1319
|
|
|
1107
1320
|
const executeCtx: PluginExecuteContext = {
|
|
@@ -1112,7 +1325,11 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1112
1325
|
|
|
1113
1326
|
const coreExecute = (): PluginExecuteResult => {
|
|
1114
1327
|
const handler = command.action ?? noop;
|
|
1115
|
-
const ctx =
|
|
1328
|
+
const ctx: PadroneActionContext = {
|
|
1329
|
+
...createActionContext(command),
|
|
1330
|
+
runtime,
|
|
1331
|
+
progress: (state._progress as PadroneProgressIndicator) ?? createLazyIndicator(runtime, state),
|
|
1332
|
+
};
|
|
1116
1333
|
const result = handler(executeCtx.args as any, ctx);
|
|
1117
1334
|
return { result };
|
|
1118
1335
|
};
|
|
@@ -1120,21 +1337,62 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1120
1337
|
const executedOrPromise = runPluginChain('execute', commandPlugins, executeCtx, coreExecute);
|
|
1121
1338
|
|
|
1122
1339
|
return thenMaybe(executedOrPromise, (e) => {
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1340
|
+
const finalize = (result: unknown) => {
|
|
1341
|
+
// Clean up progress before auto-output so the spinner clears first
|
|
1342
|
+
const indicator = state._progress as import('./runtime.ts').PadroneProgressIndicator | undefined;
|
|
1343
|
+
if (indicator) {
|
|
1344
|
+
const hasProgressConfig = '_progressMsg' in state;
|
|
1345
|
+
if (!hasProgressConfig) {
|
|
1346
|
+
// Lazy/manual indicator: just stop silently
|
|
1347
|
+
indicator.stop();
|
|
1348
|
+
} else {
|
|
1349
|
+
const { message: successMsg, indicator: successIcon } = resolveProgressMessage(state._progressSuccess, result);
|
|
1350
|
+
indicator.succeed(successMsg, successIcon !== undefined ? { indicator: successIcon } : undefined);
|
|
1351
|
+
}
|
|
1352
|
+
(state._restoreOutput as (() => void) | undefined)?.();
|
|
1353
|
+
state._progress = undefined;
|
|
1354
|
+
state._restoreOutput = undefined;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
const commandResult = withDrain({
|
|
1358
|
+
command: command as any,
|
|
1359
|
+
args: v.args,
|
|
1360
|
+
argsResult: v.argsResult,
|
|
1361
|
+
result,
|
|
1362
|
+
});
|
|
1129
1363
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1364
|
+
if (command.autoOutput ?? evalOptions?.autoOutput ?? true) {
|
|
1365
|
+
const outputOrPromise = outputValue(result, runtime.output);
|
|
1366
|
+
if (outputOrPromise instanceof Promise) {
|
|
1367
|
+
return outputOrPromise.then(() => commandResult);
|
|
1368
|
+
}
|
|
1134
1369
|
}
|
|
1370
|
+
|
|
1371
|
+
return commandResult;
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
// If the action returned a Promise, wait for it before finalizing
|
|
1375
|
+
if (e.result instanceof Promise) {
|
|
1376
|
+
return e.result.then(finalize, (err: unknown) => {
|
|
1377
|
+
const indicator = state._progress as import('./runtime.ts').PadroneProgressIndicator | undefined;
|
|
1378
|
+
if (indicator) {
|
|
1379
|
+
const hasProgressConfig = '_progressMsg' in state;
|
|
1380
|
+
if (!hasProgressConfig) {
|
|
1381
|
+
indicator.stop();
|
|
1382
|
+
} else {
|
|
1383
|
+
const fallback = err instanceof Error ? err.message : String(err);
|
|
1384
|
+
const { message: errorMsg, indicator: errorIcon } = resolveProgressMessage(state._progressError, err, fallback);
|
|
1385
|
+
indicator.fail(errorMsg, errorIcon !== undefined ? { indicator: errorIcon } : undefined);
|
|
1386
|
+
}
|
|
1387
|
+
(state._restoreOutput as (() => void) | undefined)?.();
|
|
1388
|
+
state._progress = undefined;
|
|
1389
|
+
state._restoreOutput = undefined;
|
|
1390
|
+
}
|
|
1391
|
+
throw err;
|
|
1392
|
+
});
|
|
1135
1393
|
}
|
|
1136
1394
|
|
|
1137
|
-
return
|
|
1395
|
+
return finalize(e.result);
|
|
1138
1396
|
});
|
|
1139
1397
|
};
|
|
1140
1398
|
|
|
@@ -1144,16 +1402,24 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1144
1402
|
return thenMaybe(parsedOrPromise, continueAfterParse) as any;
|
|
1145
1403
|
};
|
|
1146
1404
|
|
|
1147
|
-
return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) =>
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1405
|
+
return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) =>
|
|
1406
|
+
withDrain({
|
|
1407
|
+
command: existingCommand,
|
|
1408
|
+
args: undefined,
|
|
1409
|
+
argsResult: undefined,
|
|
1410
|
+
result,
|
|
1411
|
+
}),
|
|
1412
|
+
) as any;
|
|
1153
1413
|
};
|
|
1154
1414
|
|
|
1155
1415
|
const evalCommand: AnyPadroneProgram['eval'] = (input, evalOptions) => {
|
|
1156
|
-
|
|
1416
|
+
try {
|
|
1417
|
+
const result = execCommand(input as string, evalOptions, 'soft');
|
|
1418
|
+
if (result instanceof Promise) return withPromiseDrain(result.catch((err: unknown) => errorResult(err))) as any;
|
|
1419
|
+
return makeThenable(result);
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
return makeThenable(errorResult(err)) as any;
|
|
1422
|
+
}
|
|
1157
1423
|
};
|
|
1158
1424
|
|
|
1159
1425
|
/**
|
|
@@ -1166,8 +1432,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1166
1432
|
* `.use()` was called on the program). We substitute `programRoot` for the
|
|
1167
1433
|
* top of the chain to ensure program-level plugins are always included.
|
|
1168
1434
|
*/
|
|
1169
|
-
const collectPlugins = (cmd: AnyPadroneCommand): PadronePlugin[] => {
|
|
1170
|
-
const chain: PadronePlugin[][] = [];
|
|
1435
|
+
const collectPlugins = (cmd: AnyPadroneCommand): PadronePlugin<any, any>[] => {
|
|
1436
|
+
const chain: PadronePlugin<any, any>[][] = [];
|
|
1171
1437
|
let current: AnyPadroneCommand | undefined = cmd;
|
|
1172
1438
|
while (current) {
|
|
1173
1439
|
// If this is the root (no parent), use existingCommand's plugins instead
|
|
@@ -1183,94 +1449,143 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1183
1449
|
};
|
|
1184
1450
|
|
|
1185
1451
|
// Forward declaration — assigned by the repl method in the return object, used by cli() for --repl.
|
|
1186
|
-
|
|
1452
|
+
const replFn = (options?: PadroneReplPreferences) => {
|
|
1453
|
+
return createReplIterator({ existingCommand, evalCommand, replActiveRef }, options);
|
|
1454
|
+
};
|
|
1187
1455
|
const replActiveRef = { value: false };
|
|
1188
1456
|
|
|
1189
1457
|
const cli: AnyPadroneProgram['cli'] = (cliOptions) => {
|
|
1190
|
-
|
|
1191
|
-
|
|
1458
|
+
try {
|
|
1459
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
1460
|
+
const resolvedInput = (runtime.argv().join(' ') || undefined) as string | undefined;
|
|
1192
1461
|
|
|
1193
|
-
|
|
1194
|
-
if (cliOptions?.repl !== false) {
|
|
1462
|
+
// Check for --repl flag and mcp command before normal execution
|
|
1195
1463
|
const builtin = checkBuiltinCommands(resolvedInput);
|
|
1196
|
-
|
|
1464
|
+
|
|
1465
|
+
if (cliOptions?.repl !== false && builtin?.type === 'repl') {
|
|
1197
1466
|
const replPrefs: PadroneReplPreferences = {
|
|
1198
1467
|
...(typeof cliOptions?.repl === 'object' ? cliOptions.repl : {}),
|
|
1199
1468
|
scope: builtin.scope,
|
|
1200
1469
|
autoOutput: (typeof cliOptions?.repl === 'object' ? cliOptions.repl.autoOutput : undefined) ?? cliOptions?.autoOutput,
|
|
1201
1470
|
};
|
|
1471
|
+
const repl = replFn(replPrefs);
|
|
1202
1472
|
const drainRepl = async () => {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
}
|
|
1206
|
-
return { command: existingCommand, args: undefined, result: undefined } as any;
|
|
1473
|
+
const { value } = await repl.drain();
|
|
1474
|
+
return withDrain({ command: existingCommand, args: undefined, result: value }) as any;
|
|
1207
1475
|
};
|
|
1208
|
-
return drainRepl() as any;
|
|
1476
|
+
return withPromiseDrain(drainRepl()) as any;
|
|
1209
1477
|
}
|
|
1210
|
-
}
|
|
1211
1478
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1479
|
+
if (cliOptions?.mcp !== false && builtin?.type === 'mcp') {
|
|
1480
|
+
const basePrefs = typeof cliOptions?.mcp === 'object' ? cliOptions.mcp : {};
|
|
1481
|
+
const mcpPrefs = {
|
|
1482
|
+
...basePrefs,
|
|
1483
|
+
transport: builtin.transport ?? basePrefs.transport,
|
|
1484
|
+
port: builtin.port ?? basePrefs.port,
|
|
1485
|
+
host: builtin.host ?? basePrefs.host,
|
|
1486
|
+
basePath: builtin.basePath ?? basePrefs.basePath,
|
|
1487
|
+
};
|
|
1488
|
+
const startMcp = async () => {
|
|
1489
|
+
const { startMcpServer } = await import('./mcp.ts');
|
|
1490
|
+
await startMcpServer(builder as any, existingCommand, evalCommand, mcpPrefs);
|
|
1491
|
+
return withDrain({ command: existingCommand, args: undefined, result: undefined }) as any;
|
|
1492
|
+
};
|
|
1493
|
+
return withPromiseDrain(startMcp()) as any;
|
|
1224
1494
|
}
|
|
1225
|
-
}
|
|
1226
1495
|
|
|
1227
|
-
|
|
1496
|
+
if (cliOptions?.serve !== false && builtin?.type === 'serve') {
|
|
1497
|
+
const basePrefs = typeof cliOptions?.serve === 'object' ? cliOptions.serve : {};
|
|
1498
|
+
const servePrefs = {
|
|
1499
|
+
...basePrefs,
|
|
1500
|
+
port: builtin.port ?? basePrefs.port,
|
|
1501
|
+
host: builtin.host ?? basePrefs.host,
|
|
1502
|
+
basePath: builtin.basePath ?? basePrefs.basePath,
|
|
1503
|
+
};
|
|
1504
|
+
const startServe = async () => {
|
|
1505
|
+
const { startServeServer } = await import('./serve.ts');
|
|
1506
|
+
await startServeServer(builder as any, existingCommand, evalCommand, servePrefs);
|
|
1507
|
+
return withDrain({ command: existingCommand, args: undefined, result: undefined }) as any;
|
|
1508
|
+
};
|
|
1509
|
+
return withPromiseDrain(startServe()) as any;
|
|
1510
|
+
}
|
|
1228
1511
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
if (
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1512
|
+
// Start background update check (non-blocking)
|
|
1513
|
+
let updateCheckPromise: Promise<(() => void) | undefined> | undefined;
|
|
1514
|
+
if (existingCommand.updateCheck) {
|
|
1515
|
+
// Respect --no-update-check flag
|
|
1516
|
+
const hasNoUpdateCheckFlag =
|
|
1517
|
+
resolvedInput &&
|
|
1518
|
+
parseCliInputToParts(resolvedInput).some((p) => p.type === 'named' && p.key.length === 1 && p.key[0] === 'no-update-check');
|
|
1519
|
+
if (!hasNoUpdateCheckFlag) {
|
|
1520
|
+
const currentVersion = getVersion(existingCommand.version);
|
|
1521
|
+
updateCheckPromise = import('./update-check.ts').then(({ createUpdateChecker }) =>
|
|
1522
|
+
createUpdateChecker(existingCommand.name, currentVersion, existingCommand.updateCheck!, runtime),
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const result = execCommand(resolvedInput, cliOptions, 'hard');
|
|
1528
|
+
|
|
1529
|
+
// Show update notification after command output
|
|
1530
|
+
if (updateCheckPromise) {
|
|
1531
|
+
if (result instanceof Promise) {
|
|
1532
|
+
return withPromiseDrain(
|
|
1533
|
+
result
|
|
1534
|
+
.then(async (r) => {
|
|
1535
|
+
const showUpdateNotification = await updateCheckPromise;
|
|
1536
|
+
showUpdateNotification?.();
|
|
1537
|
+
return r;
|
|
1538
|
+
})
|
|
1539
|
+
.catch((err: unknown) => errorResult(err)),
|
|
1540
|
+
) as any;
|
|
1541
|
+
}
|
|
1542
|
+
// For sync results, schedule notification for next tick (non-blocking)
|
|
1543
|
+
updateCheckPromise.then((show) => show?.());
|
|
1237
1544
|
}
|
|
1238
|
-
// For sync results, schedule notification for next tick (non-blocking)
|
|
1239
|
-
updateCheckPromise.then((show) => show?.());
|
|
1240
|
-
}
|
|
1241
1545
|
|
|
1242
|
-
|
|
1546
|
+
if (result instanceof Promise) return withPromiseDrain(result.catch((err: unknown) => errorResult(err))) as any;
|
|
1547
|
+
return makeThenable(result);
|
|
1548
|
+
} catch (err) {
|
|
1549
|
+
return makeThenable(errorResult(err)) as any;
|
|
1550
|
+
}
|
|
1243
1551
|
};
|
|
1244
1552
|
|
|
1245
1553
|
const run: AnyPadroneProgram['run'] = (command, args) => {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1554
|
+
try {
|
|
1555
|
+
const commandObj =
|
|
1556
|
+
typeof command === 'string' ? findCommandByName(command, existingCommand.commands) : (command as AnyPadroneCommand);
|
|
1557
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ''}" not found`);
|
|
1558
|
+
if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
|
|
1249
1559
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1560
|
+
const state: Record<string, unknown> = {};
|
|
1561
|
+
const executeCtx: PluginExecuteContext = { command: commandObj, args, state };
|
|
1252
1562
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1563
|
+
const coreExecute = (): PluginExecuteResult => {
|
|
1564
|
+
const result = commandObj.action!(executeCtx.args as any, createActionContext(commandObj));
|
|
1565
|
+
return { result };
|
|
1566
|
+
};
|
|
1257
1567
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1568
|
+
const commandObjPlugins = collectPlugins(commandObj);
|
|
1569
|
+
const executedOrPromise = runPluginChain('execute', commandObjPlugins, executeCtx, coreExecute);
|
|
1260
1570
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1571
|
+
const toResult = (e: PluginExecuteResult) =>
|
|
1572
|
+
withDrain({
|
|
1573
|
+
command: commandObj as any,
|
|
1574
|
+
args: args as any,
|
|
1575
|
+
result: e.result,
|
|
1576
|
+
});
|
|
1266
1577
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1578
|
+
if (executedOrPromise instanceof Promise) {
|
|
1579
|
+
return executedOrPromise.then(toResult).catch((err: unknown) => errorResult(err, { command: commandObj, args })) as any;
|
|
1580
|
+
}
|
|
1581
|
+
return toResult(executedOrPromise);
|
|
1582
|
+
} catch (err) {
|
|
1583
|
+
return errorResult(err) as any;
|
|
1269
1584
|
}
|
|
1270
|
-
return toResult(executedOrPromise);
|
|
1271
1585
|
};
|
|
1272
1586
|
|
|
1273
1587
|
const tool: AnyPadroneProgram['tool'] = () => {
|
|
1588
|
+
resolveAllCommands(existingCommand);
|
|
1274
1589
|
const helpText = generateHelp(existingCommand, undefined, { format: 'text' });
|
|
1275
1590
|
|
|
1276
1591
|
const description = `Run a command. Pass the full command string including arguments. Use "help <command>" for detailed usage.\n\n${helpText}`;
|
|
@@ -1299,7 +1614,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1299
1614
|
needsApproval: async (input) => {
|
|
1300
1615
|
const parsed = await parse(input.command);
|
|
1301
1616
|
if (typeof parsed.command.needsApproval === 'function') return parsed.command.needsApproval(parsed.args);
|
|
1302
|
-
return !!parsed.command.needsApproval;
|
|
1617
|
+
if (parsed.command.needsApproval != null) return !!parsed.command.needsApproval;
|
|
1618
|
+
return !!parsed.command.mutation;
|
|
1303
1619
|
},
|
|
1304
1620
|
execute: async (input) => {
|
|
1305
1621
|
const output: string[] = [];
|
|
@@ -1345,6 +1661,10 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1345
1661
|
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedEnv);
|
|
1346
1662
|
return createPadroneBuilder({ ...existingCommand, envSchema: resolvedEnv as any, isAsync }) as any;
|
|
1347
1663
|
},
|
|
1664
|
+
progress(config = true) {
|
|
1665
|
+
const progress = typeof config === 'boolean' || typeof config === 'string' ? config : { ...config };
|
|
1666
|
+
return createPadroneBuilder({ ...existingCommand, progress }) as any;
|
|
1667
|
+
},
|
|
1348
1668
|
action(handler = noop) {
|
|
1349
1669
|
const baseHandler = existingCommand.action ?? noop;
|
|
1350
1670
|
return createPadroneBuilder({
|
|
@@ -1364,6 +1684,9 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1364
1684
|
// Check if a command with this name already exists (override case)
|
|
1365
1685
|
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name) as AnyPadroneCommand | undefined;
|
|
1366
1686
|
|
|
1687
|
+
// For override case, resolve the existing lazy command first so the builder starts with full state
|
|
1688
|
+
if (existingSubcommand) resolveCommand(existingSubcommand);
|
|
1689
|
+
|
|
1367
1690
|
const initialCommand: AnyPadroneCommand = existingSubcommand
|
|
1368
1691
|
? { ...existingSubcommand, aliases: aliases ?? existingSubcommand.aliases, parent: existingCommand }
|
|
1369
1692
|
: ({
|
|
@@ -1374,21 +1697,33 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1374
1697
|
'~types': {} as any,
|
|
1375
1698
|
} satisfies PadroneCommand);
|
|
1376
1699
|
|
|
1377
|
-
|
|
1700
|
+
// Lazy initialization: defer builderFn invocation until the command is actually needed
|
|
1701
|
+
if (builderFn) {
|
|
1702
|
+
const lazyCmd: AnyPadroneCommand = { ...initialCommand };
|
|
1703
|
+
(lazyCmd as any)[lazyResolver] = (target: AnyPadroneCommand) => {
|
|
1704
|
+
const builder = createPadroneBuilder(target);
|
|
1705
|
+
const commandObj = ((builderFn(builder as any) as unknown as typeof builder)?.[commandSymbol] as AnyPadroneCommand) ?? target;
|
|
1706
|
+
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, commandObj) : commandObj;
|
|
1707
|
+
Object.assign(target, mergedCommandObj);
|
|
1708
|
+
};
|
|
1378
1709
|
|
|
1379
|
-
|
|
1380
|
-
((
|
|
1710
|
+
const commands = existingCommand.commands || [];
|
|
1711
|
+
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1712
|
+
const updatedCommands =
|
|
1713
|
+
existingIndex >= 0
|
|
1714
|
+
? [...commands.slice(0, existingIndex), lazyCmd, ...commands.slice(existingIndex + 1)]
|
|
1715
|
+
: [...commands, lazyCmd];
|
|
1381
1716
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1717
|
+
return createPadroneBuilder({ ...existingCommand, commands: updatedCommands }) as any;
|
|
1718
|
+
}
|
|
1384
1719
|
|
|
1385
|
-
//
|
|
1720
|
+
// No builderFn: use the initial command as-is (no lazy resolution needed)
|
|
1386
1721
|
const commands = existingCommand.commands || [];
|
|
1387
1722
|
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1388
1723
|
const updatedCommands =
|
|
1389
1724
|
existingIndex >= 0
|
|
1390
|
-
? [...commands.slice(0, existingIndex),
|
|
1391
|
-
: [...commands,
|
|
1725
|
+
? [...commands.slice(0, existingIndex), initialCommand, ...commands.slice(existingIndex + 1)]
|
|
1726
|
+
: [...commands, initialCommand];
|
|
1392
1727
|
|
|
1393
1728
|
return createPadroneBuilder({ ...existingCommand, commands: updatedCommands }) as any;
|
|
1394
1729
|
},
|
|
@@ -1419,7 +1754,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1419
1754
|
return createPadroneBuilder({ ...existingCommand, commands: updatedCommands }) as any;
|
|
1420
1755
|
},
|
|
1421
1756
|
|
|
1422
|
-
use(plugin: PadronePlugin) {
|
|
1757
|
+
use(plugin: PadronePlugin<any, any>) {
|
|
1423
1758
|
return createPadroneBuilder({
|
|
1424
1759
|
...existingCommand,
|
|
1425
1760
|
plugins: [...(existingCommand.plugins ?? []), plugin],
|
|
@@ -1438,11 +1773,10 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1438
1773
|
cli,
|
|
1439
1774
|
tool,
|
|
1440
1775
|
|
|
1441
|
-
repl:
|
|
1442
|
-
return createReplIterator({ existingCommand, evalCommand, replActiveRef }, options);
|
|
1443
|
-
}),
|
|
1776
|
+
repl: replFn,
|
|
1444
1777
|
|
|
1445
1778
|
api() {
|
|
1779
|
+
resolveAllCommands(existingCommand);
|
|
1446
1780
|
function buildApi(command: AnyPadroneCommand) {
|
|
1447
1781
|
const runCommand = ((args) => run(command, args).result) as PadroneAPI<AnyPadroneCommand>;
|
|
1448
1782
|
if (!command.commands) return runCommand;
|
|
@@ -1454,6 +1788,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1454
1788
|
},
|
|
1455
1789
|
|
|
1456
1790
|
help(command, prefs) {
|
|
1791
|
+
resolveAllCommands(existingCommand);
|
|
1457
1792
|
const commandObj = !command
|
|
1458
1793
|
? existingCommand
|
|
1459
1794
|
: typeof command === 'string'
|
|
@@ -1461,14 +1796,31 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1461
1796
|
: (command as AnyPadroneCommand);
|
|
1462
1797
|
if (!commandObj) throw new RoutingError(`Command "${command ?? ''}" not found`);
|
|
1463
1798
|
const runtime = getCommandRuntime(existingCommand);
|
|
1464
|
-
return generateHelp(existingCommand, commandObj, {
|
|
1799
|
+
return generateHelp(existingCommand, commandObj, {
|
|
1800
|
+
...prefs,
|
|
1801
|
+
format: prefs?.format ?? runtime.format,
|
|
1802
|
+
theme: prefs?.theme ?? runtime.theme,
|
|
1803
|
+
});
|
|
1465
1804
|
},
|
|
1466
1805
|
|
|
1467
1806
|
async completion(shell) {
|
|
1807
|
+
resolveAllCommands(existingCommand);
|
|
1468
1808
|
const { generateCompletionOutput } = await import('./completion.ts');
|
|
1469
1809
|
return generateCompletionOutput(existingCommand, shell as ShellType | undefined);
|
|
1470
1810
|
},
|
|
1471
1811
|
|
|
1812
|
+
async mcp(prefs) {
|
|
1813
|
+
resolveAllCommands(existingCommand);
|
|
1814
|
+
const { startMcpServer } = await import('./mcp.ts');
|
|
1815
|
+
return startMcpServer(builder as any, existingCommand, evalCommand, prefs);
|
|
1816
|
+
},
|
|
1817
|
+
|
|
1818
|
+
async serve(prefs) {
|
|
1819
|
+
resolveAllCommands(existingCommand);
|
|
1820
|
+
const { startServeServer } = await import('./serve.ts');
|
|
1821
|
+
return startServeServer(builder as any, existingCommand, evalCommand, prefs);
|
|
1822
|
+
},
|
|
1823
|
+
|
|
1472
1824
|
'~types': {} as any,
|
|
1473
1825
|
|
|
1474
1826
|
[commandSymbol]: existingCommand,
|