padrone 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +105 -284
- package/dist/{args-DFEI7_G_.mjs → args-D5PNDyNu.mjs} +46 -21
- 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-XroimS3Q.d.mts → formatter-DtHzbP22.d.mts} +35 -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 +495 -267
- 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-BS7RP5Ls.d.mts → types-Ch8Mk6Qb.d.mts} +311 -63
- 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 +76 -44
- 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 +401 -23
- package/src/completion.ts +120 -47
- package/src/create.ts +483 -130
- package/src/docs/index.ts +122 -8
- package/src/formatter.ts +173 -125
- package/src/help.ts +46 -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-DFEI7_G_.mjs.map +0 -1
- package/dist/chunk-y_GBKt04.mjs +0 -5
- package/dist/formatter-XroimS3Q.d.mts.map +0 -1
- package/dist/help-CgGP7hQU.mjs +0 -1229
- package/dist/help-CgGP7hQU.mjs.map +0 -1
- package/dist/types-BS7RP5Ls.d.mts.map +0 -1
package/src/create.ts
CHANGED
|
@@ -1,20 +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,
|
|
25
|
+
makeThenable,
|
|
10
26
|
mergeCommands,
|
|
11
27
|
noop,
|
|
28
|
+
noopIndicator,
|
|
12
29
|
outputValue,
|
|
13
30
|
repathCommandTree,
|
|
31
|
+
resolveAllCommands,
|
|
32
|
+
resolveCommand,
|
|
33
|
+
resolveProgressMessage,
|
|
14
34
|
runPluginChain,
|
|
15
35
|
suggestSimilar,
|
|
16
36
|
thenMaybe,
|
|
17
37
|
warnIfUnexpectedAsync,
|
|
38
|
+
withDrain,
|
|
39
|
+
withPromiseDrain,
|
|
18
40
|
wrapWithLifecycle,
|
|
19
41
|
} from './command-utils.ts';
|
|
20
42
|
import type { ShellType } from './completion.ts';
|
|
@@ -23,7 +45,8 @@ import { generateHelp } from './help.ts';
|
|
|
23
45
|
import { promptInteractiveFields } from './interactive.ts';
|
|
24
46
|
import { getNestedValue, parseCliInputToParts, setNestedValue } from './parse.ts';
|
|
25
47
|
import { createReplIterator } from './repl-loop.ts';
|
|
26
|
-
import { resolveStdin } from './runtime.ts';
|
|
48
|
+
import { type PadroneProgressIndicator, resolveStdin, resolveStdinAlways } from './runtime.ts';
|
|
49
|
+
import { createStdinStream } from './stream.ts';
|
|
27
50
|
import type {
|
|
28
51
|
AnyPadroneCommand,
|
|
29
52
|
AnyPadroneProgram,
|
|
@@ -64,11 +87,14 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
64
87
|
: inputCommand;
|
|
65
88
|
|
|
66
89
|
/** Creates the action context passed to command handlers. References `builder` which is defined later but only called at runtime. */
|
|
67
|
-
const createActionContext = (cmd: AnyPadroneCommand): PadroneActionContext =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
};
|
|
72
98
|
|
|
73
99
|
const find: AnyPadroneProgram['find'] = (command) => {
|
|
74
100
|
if (typeof command !== 'string') return findCommandByName(command.path, existingCommand.commands) as any;
|
|
@@ -135,7 +161,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
135
161
|
const arrayArguments = new Set<string>();
|
|
136
162
|
if (curCommand.argsSchema) {
|
|
137
163
|
try {
|
|
138
|
-
const jsonSchema = curCommand.argsSchema['~standard'].jsonSchema.input(
|
|
164
|
+
const jsonSchema = curCommand.argsSchema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
|
|
139
165
|
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
140
166
|
for (const [key, prop] of Object.entries(jsonSchema.properties as Record<string, any>)) {
|
|
141
167
|
if (prop?.type === 'array') arrayArguments.add(key);
|
|
@@ -351,16 +377,24 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
351
377
|
const stdinConfig = command.meta?.stdin;
|
|
352
378
|
if (!stdinConfig) return {};
|
|
353
379
|
|
|
354
|
-
const
|
|
380
|
+
const field = parseStdinConfig(stdinConfig);
|
|
355
381
|
|
|
356
382
|
// Skip if the field was already provided via CLI flags
|
|
357
383
|
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== undefined) return {};
|
|
358
384
|
|
|
359
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
|
+
|
|
360
394
|
const stdin = resolveStdin(runtime as any);
|
|
361
395
|
if (!stdin) return {};
|
|
362
396
|
|
|
363
|
-
if (
|
|
397
|
+
if (isArrayField(command.argsSchema, field)) {
|
|
364
398
|
return (async () => {
|
|
365
399
|
const lines: string[] = [];
|
|
366
400
|
for await (const line of stdin.lines()) {
|
|
@@ -417,7 +451,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
417
451
|
);
|
|
418
452
|
};
|
|
419
453
|
|
|
420
|
-
return thenMaybe(parsedOrPromise, continueAfterParse) as any;
|
|
454
|
+
return makeThenable(thenMaybe(parsedOrPromise, continueAfterParse)) as any;
|
|
421
455
|
};
|
|
422
456
|
|
|
423
457
|
const stringify: AnyPadroneProgram['stringify'] = (command = '' as any, args) => {
|
|
@@ -498,10 +532,13 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
498
532
|
const checkBuiltinCommands = (
|
|
499
533
|
input: string | undefined,
|
|
500
534
|
):
|
|
501
|
-
| { type: 'help'; command?: AnyPadroneCommand; detail?: DetailLevel; format?: FormatLevel }
|
|
535
|
+
| { type: 'help'; command?: AnyPadroneCommand; detail?: DetailLevel; format?: FormatLevel; all?: boolean }
|
|
502
536
|
| { type: 'version' }
|
|
503
537
|
| { type: 'completion'; shell?: ShellType; setup?: boolean }
|
|
538
|
+
| { type: 'man'; setup?: boolean; remove?: boolean }
|
|
504
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 }
|
|
505
542
|
| null => {
|
|
506
543
|
if (!input) return null;
|
|
507
544
|
|
|
@@ -515,18 +552,21 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
515
552
|
// Check for --help, -h flags (these take precedence over commands)
|
|
516
553
|
const hasHelpFlag = args.some((p) => (p.type === 'named' && keyIs(p.key, 'help')) || (p.type === 'alias' && keyIs(p.key, 'h')));
|
|
517
554
|
|
|
518
|
-
// 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'
|
|
519
557
|
const getDetailLevel = (): DetailLevel | undefined => {
|
|
520
558
|
for (const arg of args) {
|
|
521
|
-
if (arg.type === 'named' && keyIs(arg.key, 'detail')
|
|
522
|
-
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')) {
|
|
523
561
|
return arg.value;
|
|
524
562
|
}
|
|
563
|
+
return 'full';
|
|
525
564
|
}
|
|
526
|
-
if (arg.type === 'alias' && keyIs(arg.key, 'd')
|
|
527
|
-
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')) {
|
|
528
567
|
return arg.value;
|
|
529
568
|
}
|
|
569
|
+
return 'full';
|
|
530
570
|
}
|
|
531
571
|
}
|
|
532
572
|
return undefined;
|
|
@@ -552,6 +592,9 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
552
592
|
};
|
|
553
593
|
const format = getFormat();
|
|
554
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
|
+
|
|
555
598
|
// Check for --version, -v, -V flags
|
|
556
599
|
const hasVersionFlag = args.some(
|
|
557
600
|
(p) => (p.type === 'named' && keyIs(p.key, 'version')) || (p.type === 'alias' && (keyIs(p.key, 'v') || keyIs(p.key, 'V'))),
|
|
@@ -572,7 +615,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
572
615
|
// help <command> - get help for specific command
|
|
573
616
|
const commandName = normalizedTerms.slice(1).join(' ');
|
|
574
617
|
const targetCommand = commandName ? findCommandByName(commandName, existingCommand.commands) : undefined;
|
|
575
|
-
return { type: 'help', command: targetCommand, detail, format };
|
|
618
|
+
return { type: 'help', command: targetCommand, detail, format, all: hasAllFlag || undefined };
|
|
576
619
|
}
|
|
577
620
|
if (!userHelpCommand && normalizedTerms.length > 0 && normalizedTerms[normalizedTerms.length - 1] === 'help') {
|
|
578
621
|
// <command> help - get help for specific command (trailing form)
|
|
@@ -589,7 +632,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
589
632
|
break;
|
|
590
633
|
}
|
|
591
634
|
}
|
|
592
|
-
return { type: 'help', command: targetCommand, detail, format };
|
|
635
|
+
return { type: 'help', command: targetCommand, detail, format, all: hasAllFlag || undefined };
|
|
593
636
|
}
|
|
594
637
|
|
|
595
638
|
// Check for 'version' command (only if user hasn't defined one)
|
|
@@ -606,13 +649,21 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
606
649
|
return { type: 'completion', shell, setup };
|
|
607
650
|
}
|
|
608
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
|
+
|
|
609
660
|
// Handle help flag - find the command being requested
|
|
610
661
|
if (hasHelpFlag) {
|
|
611
662
|
// Filter out help-related terms and flags to find the target command
|
|
612
663
|
const commandTerms = normalizedTerms.filter((t) => t !== 'help');
|
|
613
664
|
const commandName = commandTerms.join(' ');
|
|
614
665
|
const targetCommand = commandName ? findCommandByName(commandName, existingCommand.commands) : undefined;
|
|
615
|
-
return { type: 'help', command: targetCommand, detail, format };
|
|
666
|
+
return { type: 'help', command: targetCommand, detail, format, all: hasAllFlag || undefined };
|
|
616
667
|
}
|
|
617
668
|
|
|
618
669
|
// Handle version flag (only for root command, i.e., no subcommand terms)
|
|
@@ -620,6 +671,32 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
620
671
|
return { type: 'version' };
|
|
621
672
|
}
|
|
622
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
|
+
|
|
623
700
|
// Check for --repl flag
|
|
624
701
|
const hasReplFlag = args.some((p) => p.type === 'named' && keyIs(p.key, 'repl'));
|
|
625
702
|
if (hasReplFlag) {
|
|
@@ -650,6 +727,35 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
650
727
|
return undefined;
|
|
651
728
|
};
|
|
652
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
|
+
|
|
653
759
|
/**
|
|
654
760
|
* Core execution logic shared by eval() and cli().
|
|
655
761
|
* errorMode controls validation error behavior:
|
|
@@ -658,38 +764,51 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
658
764
|
*/
|
|
659
765
|
const execCommand = (resolvedInput: string | undefined, evalOptions?: PadroneEvalPreferences, errorMode: 'soft' | 'hard' = 'soft') => {
|
|
660
766
|
const baseRuntime = getCommandRuntime(existingCommand);
|
|
661
|
-
|
|
767
|
+
let runtime = evalOptions?.runtime
|
|
662
768
|
? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== undefined)))
|
|
663
769
|
: baseRuntime;
|
|
664
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
|
+
|
|
665
780
|
// Check for built-in help/version/completion commands and flags (bypass plugins)
|
|
666
781
|
const builtin = checkBuiltinCommands(resolvedInput);
|
|
667
782
|
|
|
668
783
|
if (builtin) {
|
|
669
784
|
if (builtin.type === 'help') {
|
|
785
|
+
resolveAllCommands(existingCommand);
|
|
670
786
|
const helpText = generateHelp(existingCommand, builtin.command ?? existingCommand, {
|
|
671
787
|
detail: builtin.detail,
|
|
672
788
|
format: builtin.format ?? runtime.format,
|
|
789
|
+
theme: runtime.theme,
|
|
790
|
+
all: builtin.all,
|
|
673
791
|
});
|
|
674
792
|
runtime.output(helpText);
|
|
675
|
-
return {
|
|
793
|
+
return withDrain({
|
|
676
794
|
command: existingCommand,
|
|
677
795
|
args: undefined,
|
|
678
796
|
result: helpText,
|
|
679
|
-
} as any;
|
|
797
|
+
}) as any;
|
|
680
798
|
}
|
|
681
799
|
|
|
682
800
|
if (builtin.type === 'version') {
|
|
683
801
|
const version = getVersion(existingCommand.version);
|
|
684
802
|
runtime.output(version);
|
|
685
|
-
return {
|
|
803
|
+
return withDrain({
|
|
686
804
|
command: existingCommand,
|
|
687
805
|
args: undefined,
|
|
688
806
|
result: version,
|
|
689
|
-
} as any;
|
|
807
|
+
}) as any;
|
|
690
808
|
}
|
|
691
809
|
|
|
692
810
|
if (builtin.type === 'completion') {
|
|
811
|
+
resolveAllCommands(existingCommand);
|
|
693
812
|
return import('./completion.ts').then(({ detectShell, generateCompletionOutput, setupCompletions }) => {
|
|
694
813
|
if (builtin.setup) {
|
|
695
814
|
const shell = builtin.shell ?? detectShell();
|
|
@@ -699,19 +818,57 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
699
818
|
const result = setupCompletions(existingCommand.name, shell);
|
|
700
819
|
const message = `${result.updated ? 'Updated' : 'Added'} ${existingCommand.name} completions in ${result.file}`;
|
|
701
820
|
runtime.output(message);
|
|
702
|
-
return {
|
|
821
|
+
return withDrain({
|
|
703
822
|
command: existingCommand,
|
|
704
823
|
args: undefined,
|
|
705
824
|
result: message,
|
|
706
|
-
};
|
|
825
|
+
});
|
|
707
826
|
}
|
|
708
827
|
const completionScript = generateCompletionOutput(existingCommand, builtin.shell);
|
|
709
828
|
runtime.output(completionScript);
|
|
710
|
-
return {
|
|
829
|
+
return withDrain({
|
|
711
830
|
command: existingCommand,
|
|
712
831
|
args: undefined,
|
|
713
832
|
result: completionScript,
|
|
714
|
-
};
|
|
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
|
+
});
|
|
715
872
|
}) as any;
|
|
716
873
|
}
|
|
717
874
|
}
|
|
@@ -731,7 +888,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
731
888
|
const hasSubcommands = command.commands && command.commands.length > 0;
|
|
732
889
|
const hasSchema = command.argsSchema != null;
|
|
733
890
|
if (!command.action && (hasSubcommands || !hasSchema) && unmatchedTerms.length === 0) {
|
|
734
|
-
|
|
891
|
+
resolveAllCommands(existingCommand);
|
|
892
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format, theme: runtime.theme });
|
|
735
893
|
runtime.output(helpText);
|
|
736
894
|
return {
|
|
737
895
|
command: command,
|
|
@@ -784,7 +942,11 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
784
942
|
runtime.output(`\nAvailable commands: ${cmdList}`);
|
|
785
943
|
}
|
|
786
944
|
} else {
|
|
787
|
-
|
|
945
|
+
resolveAllCommands(existingCommand);
|
|
946
|
+
const helpText = generateHelp(existingCommand, isRootCommand ? existingCommand : command, {
|
|
947
|
+
format: runtime.format,
|
|
948
|
+
theme: runtime.theme,
|
|
949
|
+
});
|
|
788
950
|
runtime.error(helpText);
|
|
789
951
|
}
|
|
790
952
|
throw new RoutingError(errorMsg, { suggestions, command: command.path || command.name });
|
|
@@ -816,6 +978,40 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
816
978
|
} as any;
|
|
817
979
|
}
|
|
818
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
|
+
|
|
819
1015
|
// ── Phase 2: Validate ───────────────────────────────────────────
|
|
820
1016
|
const validateCtx: PluginValidateContext = {
|
|
821
1017
|
command,
|
|
@@ -838,6 +1034,10 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
838
1034
|
}
|
|
839
1035
|
}
|
|
840
1036
|
|
|
1037
|
+
// Strip --color / --no-color from rawArgs (handled globally)
|
|
1038
|
+
delete validateCtx.rawArgs.color;
|
|
1039
|
+
delete validateCtx.rawArgs['no-color'];
|
|
1040
|
+
|
|
841
1041
|
const runtimeDefault: boolean | undefined =
|
|
842
1042
|
runtime.interactive === 'forced' ? true : runtime.interactive === 'disabled' ? false : undefined;
|
|
843
1043
|
const effectiveInteractive: boolean | undefined = flagInteractive ?? evalOptions?.interactive ?? runtimeDefault;
|
|
@@ -927,17 +1127,24 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
927
1127
|
const stdinConfig = command.meta?.stdin;
|
|
928
1128
|
if (!stdinConfig) return {};
|
|
929
1129
|
|
|
930
|
-
const
|
|
1130
|
+
const field = parseStdinConfig(stdinConfig);
|
|
931
1131
|
|
|
932
1132
|
// Skip if the field was already provided via CLI flags (highest precedence)
|
|
933
1133
|
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== undefined) return {};
|
|
934
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
|
+
|
|
935
1142
|
// Resolve stdin: use runtime's custom stdin, or default if piped.
|
|
936
1143
|
// Returns undefined when stdin is a TTY or unavailable.
|
|
937
1144
|
const stdin = resolveStdin(runtime as any);
|
|
938
1145
|
if (!stdin) return {};
|
|
939
1146
|
|
|
940
|
-
if (
|
|
1147
|
+
if (isArrayField(command.argsSchema, field)) {
|
|
941
1148
|
return (async () => {
|
|
942
1149
|
const lines: string[] = [];
|
|
943
1150
|
for await (const line of stdin.lines()) {
|
|
@@ -1056,7 +1263,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1056
1263
|
knownOptions = [];
|
|
1057
1264
|
if (command.argsSchema) {
|
|
1058
1265
|
try {
|
|
1059
|
-
const js = command.argsSchema['~standard'].jsonSchema.input(
|
|
1266
|
+
const js = command.argsSchema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
|
|
1060
1267
|
if (js.type === 'object' && js.properties) knownOptions = Object.keys(js.properties);
|
|
1061
1268
|
} catch {
|
|
1062
1269
|
/* ignore */
|
|
@@ -1081,7 +1288,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1081
1288
|
.join('\n');
|
|
1082
1289
|
|
|
1083
1290
|
if (errorMode === 'hard') {
|
|
1084
|
-
|
|
1291
|
+
resolveAllCommands(existingCommand);
|
|
1292
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format, theme: runtime.theme });
|
|
1085
1293
|
runtime.error(`Validation error:\n${issueMessages}`);
|
|
1086
1294
|
runtime.error(helpText);
|
|
1087
1295
|
throw new ValidationError(`Validation error:\n${issueMessages}`, v.argsResult.issues as any, {
|
|
@@ -1095,12 +1303,18 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1095
1303
|
}
|
|
1096
1304
|
|
|
1097
1305
|
// Soft mode: return result with issues, skip the action
|
|
1098
|
-
return {
|
|
1306
|
+
return withDrain({
|
|
1099
1307
|
command: command as any,
|
|
1100
1308
|
args: undefined,
|
|
1101
1309
|
argsResult: v.argsResult,
|
|
1102
1310
|
result: undefined,
|
|
1103
|
-
};
|
|
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);
|
|
1104
1318
|
}
|
|
1105
1319
|
|
|
1106
1320
|
const executeCtx: PluginExecuteContext = {
|
|
@@ -1111,7 +1325,11 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1111
1325
|
|
|
1112
1326
|
const coreExecute = (): PluginExecuteResult => {
|
|
1113
1327
|
const handler = command.action ?? noop;
|
|
1114
|
-
const ctx =
|
|
1328
|
+
const ctx: PadroneActionContext = {
|
|
1329
|
+
...createActionContext(command),
|
|
1330
|
+
runtime,
|
|
1331
|
+
progress: (state._progress as PadroneProgressIndicator) ?? createLazyIndicator(runtime, state),
|
|
1332
|
+
};
|
|
1115
1333
|
const result = handler(executeCtx.args as any, ctx);
|
|
1116
1334
|
return { result };
|
|
1117
1335
|
};
|
|
@@ -1119,40 +1337,89 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1119
1337
|
const executedOrPromise = runPluginChain('execute', commandPlugins, executeCtx, coreExecute);
|
|
1120
1338
|
|
|
1121
1339
|
return thenMaybe(executedOrPromise, (e) => {
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
+
});
|
|
1128
1363
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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
|
+
}
|
|
1133
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
|
+
});
|
|
1134
1393
|
}
|
|
1135
1394
|
|
|
1136
|
-
return
|
|
1395
|
+
return finalize(e.result);
|
|
1137
1396
|
});
|
|
1138
1397
|
};
|
|
1139
1398
|
|
|
1140
|
-
return warnIfUnexpectedAsync(
|
|
1399
|
+
return thenMaybe(warnIfUnexpectedAsync(validatedOrPromise, command), continueAfterValidate) as any;
|
|
1141
1400
|
};
|
|
1142
1401
|
|
|
1143
1402
|
return thenMaybe(parsedOrPromise, continueAfterParse) as any;
|
|
1144
1403
|
};
|
|
1145
1404
|
|
|
1146
|
-
return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) =>
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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;
|
|
1152
1413
|
};
|
|
1153
1414
|
|
|
1154
1415
|
const evalCommand: AnyPadroneProgram['eval'] = (input, evalOptions) => {
|
|
1155
|
-
|
|
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
|
+
}
|
|
1156
1423
|
};
|
|
1157
1424
|
|
|
1158
1425
|
/**
|
|
@@ -1165,8 +1432,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1165
1432
|
* `.use()` was called on the program). We substitute `programRoot` for the
|
|
1166
1433
|
* top of the chain to ensure program-level plugins are always included.
|
|
1167
1434
|
*/
|
|
1168
|
-
const collectPlugins = (cmd: AnyPadroneCommand): PadronePlugin[] => {
|
|
1169
|
-
const chain: PadronePlugin[][] = [];
|
|
1435
|
+
const collectPlugins = (cmd: AnyPadroneCommand): PadronePlugin<any, any>[] => {
|
|
1436
|
+
const chain: PadronePlugin<any, any>[][] = [];
|
|
1170
1437
|
let current: AnyPadroneCommand | undefined = cmd;
|
|
1171
1438
|
while (current) {
|
|
1172
1439
|
// If this is the root (no parent), use existingCommand's plugins instead
|
|
@@ -1182,94 +1449,143 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1182
1449
|
};
|
|
1183
1450
|
|
|
1184
1451
|
// Forward declaration — assigned by the repl method in the return object, used by cli() for --repl.
|
|
1185
|
-
|
|
1452
|
+
const replFn = (options?: PadroneReplPreferences) => {
|
|
1453
|
+
return createReplIterator({ existingCommand, evalCommand, replActiveRef }, options);
|
|
1454
|
+
};
|
|
1186
1455
|
const replActiveRef = { value: false };
|
|
1187
1456
|
|
|
1188
1457
|
const cli: AnyPadroneProgram['cli'] = (cliOptions) => {
|
|
1189
|
-
|
|
1190
|
-
|
|
1458
|
+
try {
|
|
1459
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
1460
|
+
const resolvedInput = (runtime.argv().join(' ') || undefined) as string | undefined;
|
|
1191
1461
|
|
|
1192
|
-
|
|
1193
|
-
if (cliOptions?.repl !== false) {
|
|
1462
|
+
// Check for --repl flag and mcp command before normal execution
|
|
1194
1463
|
const builtin = checkBuiltinCommands(resolvedInput);
|
|
1195
|
-
|
|
1464
|
+
|
|
1465
|
+
if (cliOptions?.repl !== false && builtin?.type === 'repl') {
|
|
1196
1466
|
const replPrefs: PadroneReplPreferences = {
|
|
1197
1467
|
...(typeof cliOptions?.repl === 'object' ? cliOptions.repl : {}),
|
|
1198
1468
|
scope: builtin.scope,
|
|
1199
1469
|
autoOutput: (typeof cliOptions?.repl === 'object' ? cliOptions.repl.autoOutput : undefined) ?? cliOptions?.autoOutput,
|
|
1200
1470
|
};
|
|
1471
|
+
const repl = replFn(replPrefs);
|
|
1201
1472
|
const drainRepl = async () => {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
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;
|
|
1206
1475
|
};
|
|
1207
|
-
return drainRepl() as any;
|
|
1476
|
+
return withPromiseDrain(drainRepl()) as any;
|
|
1208
1477
|
}
|
|
1209
|
-
}
|
|
1210
1478
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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;
|
|
1223
1494
|
}
|
|
1224
|
-
}
|
|
1225
1495
|
|
|
1226
|
-
|
|
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
|
+
}
|
|
1227
1511
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
if (
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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?.());
|
|
1236
1544
|
}
|
|
1237
|
-
// For sync results, schedule notification for next tick (non-blocking)
|
|
1238
|
-
updateCheckPromise.then((show) => show?.());
|
|
1239
|
-
}
|
|
1240
1545
|
|
|
1241
|
-
|
|
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
|
+
}
|
|
1242
1551
|
};
|
|
1243
1552
|
|
|
1244
1553
|
const run: AnyPadroneProgram['run'] = (command, args) => {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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 });
|
|
1248
1559
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1560
|
+
const state: Record<string, unknown> = {};
|
|
1561
|
+
const executeCtx: PluginExecuteContext = { command: commandObj, args, state };
|
|
1251
1562
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1563
|
+
const coreExecute = (): PluginExecuteResult => {
|
|
1564
|
+
const result = commandObj.action!(executeCtx.args as any, createActionContext(commandObj));
|
|
1565
|
+
return { result };
|
|
1566
|
+
};
|
|
1256
1567
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1568
|
+
const commandObjPlugins = collectPlugins(commandObj);
|
|
1569
|
+
const executedOrPromise = runPluginChain('execute', commandObjPlugins, executeCtx, coreExecute);
|
|
1259
1570
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1571
|
+
const toResult = (e: PluginExecuteResult) =>
|
|
1572
|
+
withDrain({
|
|
1573
|
+
command: commandObj as any,
|
|
1574
|
+
args: args as any,
|
|
1575
|
+
result: e.result,
|
|
1576
|
+
});
|
|
1265
1577
|
|
|
1266
|
-
|
|
1267
|
-
|
|
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;
|
|
1268
1584
|
}
|
|
1269
|
-
return toResult(executedOrPromise);
|
|
1270
1585
|
};
|
|
1271
1586
|
|
|
1272
1587
|
const tool: AnyPadroneProgram['tool'] = () => {
|
|
1588
|
+
resolveAllCommands(existingCommand);
|
|
1273
1589
|
const helpText = generateHelp(existingCommand, undefined, { format: 'text' });
|
|
1274
1590
|
|
|
1275
1591
|
const description = `Run a command. Pass the full command string including arguments. Use "help <command>" for detailed usage.\n\n${helpText}`;
|
|
@@ -1298,7 +1614,8 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1298
1614
|
needsApproval: async (input) => {
|
|
1299
1615
|
const parsed = await parse(input.command);
|
|
1300
1616
|
if (typeof parsed.command.needsApproval === 'function') return parsed.command.needsApproval(parsed.args);
|
|
1301
|
-
return !!parsed.command.needsApproval;
|
|
1617
|
+
if (parsed.command.needsApproval != null) return !!parsed.command.needsApproval;
|
|
1618
|
+
return !!parsed.command.mutation;
|
|
1302
1619
|
},
|
|
1303
1620
|
execute: async (input) => {
|
|
1304
1621
|
const output: string[] = [];
|
|
@@ -1344,6 +1661,10 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1344
1661
|
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedEnv);
|
|
1345
1662
|
return createPadroneBuilder({ ...existingCommand, envSchema: resolvedEnv as any, isAsync }) as any;
|
|
1346
1663
|
},
|
|
1664
|
+
progress(config = true) {
|
|
1665
|
+
const progress = typeof config === 'boolean' || typeof config === 'string' ? config : { ...config };
|
|
1666
|
+
return createPadroneBuilder({ ...existingCommand, progress }) as any;
|
|
1667
|
+
},
|
|
1347
1668
|
action(handler = noop) {
|
|
1348
1669
|
const baseHandler = existingCommand.action ?? noop;
|
|
1349
1670
|
return createPadroneBuilder({
|
|
@@ -1363,6 +1684,9 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1363
1684
|
// Check if a command with this name already exists (override case)
|
|
1364
1685
|
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name) as AnyPadroneCommand | undefined;
|
|
1365
1686
|
|
|
1687
|
+
// For override case, resolve the existing lazy command first so the builder starts with full state
|
|
1688
|
+
if (existingSubcommand) resolveCommand(existingSubcommand);
|
|
1689
|
+
|
|
1366
1690
|
const initialCommand: AnyPadroneCommand = existingSubcommand
|
|
1367
1691
|
? { ...existingSubcommand, aliases: aliases ?? existingSubcommand.aliases, parent: existingCommand }
|
|
1368
1692
|
: ({
|
|
@@ -1373,21 +1697,33 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1373
1697
|
'~types': {} as any,
|
|
1374
1698
|
} satisfies PadroneCommand);
|
|
1375
1699
|
|
|
1376
|
-
|
|
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
|
+
};
|
|
1377
1709
|
|
|
1378
|
-
|
|
1379
|
-
((
|
|
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];
|
|
1380
1716
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1717
|
+
return createPadroneBuilder({ ...existingCommand, commands: updatedCommands }) as any;
|
|
1718
|
+
}
|
|
1383
1719
|
|
|
1384
|
-
//
|
|
1720
|
+
// No builderFn: use the initial command as-is (no lazy resolution needed)
|
|
1385
1721
|
const commands = existingCommand.commands || [];
|
|
1386
1722
|
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1387
1723
|
const updatedCommands =
|
|
1388
1724
|
existingIndex >= 0
|
|
1389
|
-
? [...commands.slice(0, existingIndex),
|
|
1390
|
-
: [...commands,
|
|
1725
|
+
? [...commands.slice(0, existingIndex), initialCommand, ...commands.slice(existingIndex + 1)]
|
|
1726
|
+
: [...commands, initialCommand];
|
|
1391
1727
|
|
|
1392
1728
|
return createPadroneBuilder({ ...existingCommand, commands: updatedCommands }) as any;
|
|
1393
1729
|
},
|
|
@@ -1418,7 +1754,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1418
1754
|
return createPadroneBuilder({ ...existingCommand, commands: updatedCommands }) as any;
|
|
1419
1755
|
},
|
|
1420
1756
|
|
|
1421
|
-
use(plugin: PadronePlugin) {
|
|
1757
|
+
use(plugin: PadronePlugin<any, any>) {
|
|
1422
1758
|
return createPadroneBuilder({
|
|
1423
1759
|
...existingCommand,
|
|
1424
1760
|
plugins: [...(existingCommand.plugins ?? []), plugin],
|
|
@@ -1437,11 +1773,10 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1437
1773
|
cli,
|
|
1438
1774
|
tool,
|
|
1439
1775
|
|
|
1440
|
-
repl:
|
|
1441
|
-
return createReplIterator({ existingCommand, evalCommand, replActiveRef }, options);
|
|
1442
|
-
}),
|
|
1776
|
+
repl: replFn,
|
|
1443
1777
|
|
|
1444
1778
|
api() {
|
|
1779
|
+
resolveAllCommands(existingCommand);
|
|
1445
1780
|
function buildApi(command: AnyPadroneCommand) {
|
|
1446
1781
|
const runCommand = ((args) => run(command, args).result) as PadroneAPI<AnyPadroneCommand>;
|
|
1447
1782
|
if (!command.commands) return runCommand;
|
|
@@ -1453,6 +1788,7 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1453
1788
|
},
|
|
1454
1789
|
|
|
1455
1790
|
help(command, prefs) {
|
|
1791
|
+
resolveAllCommands(existingCommand);
|
|
1456
1792
|
const commandObj = !command
|
|
1457
1793
|
? existingCommand
|
|
1458
1794
|
: typeof command === 'string'
|
|
@@ -1460,14 +1796,31 @@ export function createPadroneBuilder<TBuilder extends PadroneProgram = PadronePr
|
|
|
1460
1796
|
: (command as AnyPadroneCommand);
|
|
1461
1797
|
if (!commandObj) throw new RoutingError(`Command "${command ?? ''}" not found`);
|
|
1462
1798
|
const runtime = getCommandRuntime(existingCommand);
|
|
1463
|
-
return generateHelp(existingCommand, commandObj, {
|
|
1799
|
+
return generateHelp(existingCommand, commandObj, {
|
|
1800
|
+
...prefs,
|
|
1801
|
+
format: prefs?.format ?? runtime.format,
|
|
1802
|
+
theme: prefs?.theme ?? runtime.theme,
|
|
1803
|
+
});
|
|
1464
1804
|
},
|
|
1465
1805
|
|
|
1466
1806
|
async completion(shell) {
|
|
1807
|
+
resolveAllCommands(existingCommand);
|
|
1467
1808
|
const { generateCompletionOutput } = await import('./completion.ts');
|
|
1468
1809
|
return generateCompletionOutput(existingCommand, shell as ShellType | undefined);
|
|
1469
1810
|
},
|
|
1470
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
|
+
|
|
1471
1824
|
'~types': {} as any,
|
|
1472
1825
|
|
|
1473
1826
|
[commandSymbol]: existingCommand,
|