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/stream.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { PadroneSchema } from './types.ts';
|
|
3
|
+
|
|
4
|
+
export interface AsyncStreamMeta {
|
|
5
|
+
[x: string]: unknown;
|
|
6
|
+
readonly asyncStream: number;
|
|
7
|
+
readonly itemSchema?: StandardSchemaV1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let asyncStreamIdCounter = 1;
|
|
11
|
+
export const asyncStreamRegistry = new Map<number, AsyncStreamMeta>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns metadata to mark a schema field as an async stream via `.meta()`.
|
|
15
|
+
*
|
|
16
|
+
* When used with `stdin`, padrone pipes stdin data as an `AsyncIterable` instead of
|
|
17
|
+
* buffering it. Each line is validated against the item schema (if provided) as it arrives.
|
|
18
|
+
*
|
|
19
|
+
* @param itemSchema - Optional item schema for per-item validation.
|
|
20
|
+
* Non-string schemas cause each stdin line to be `JSON.parse`'d before validation.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { asyncStream } from 'padrone';
|
|
25
|
+
*
|
|
26
|
+
* // String lines
|
|
27
|
+
* z.object({ lines: z.custom<AsyncIterable<string>>().meta(asyncStream()) })
|
|
28
|
+
*
|
|
29
|
+
* // Typed items — each line JSON.parse'd and validated
|
|
30
|
+
* z.object({ records: z.custom<AsyncIterable<{ name: string }>>().meta(asyncStream(recordSchema)) })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function asyncStream<T = string>(itemSchema?: PadroneSchema<T>): AsyncStreamMeta {
|
|
34
|
+
const id = asyncStreamIdCounter++;
|
|
35
|
+
const meta: AsyncStreamMeta = itemSchema ? { asyncStream: id, itemSchema } : { asyncStream: id };
|
|
36
|
+
asyncStreamRegistry.set(id, meta);
|
|
37
|
+
return meta;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Stdin interface matching PadroneRuntime.stdin */
|
|
41
|
+
interface StdinSource {
|
|
42
|
+
isTTY?: boolean;
|
|
43
|
+
text(): Promise<string>;
|
|
44
|
+
lines(): AsyncIterable<string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates an `AsyncIterable` from a stdin source, optionally validating each item.
|
|
49
|
+
* When no stdin is available (TTY / undefined), yields nothing.
|
|
50
|
+
*
|
|
51
|
+
* - No item schema: yields raw string lines
|
|
52
|
+
* - With item schema: `JSON.parse`s each line, validates, then yields
|
|
53
|
+
*/
|
|
54
|
+
export function createStdinStream(stdin: StdinSource | undefined, itemSchema?: StandardSchemaV1): AsyncIterable<unknown> {
|
|
55
|
+
if (!stdin) return emptyAsyncIterable;
|
|
56
|
+
|
|
57
|
+
if (!itemSchema) return stdin.lines();
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
async *[Symbol.asyncIterator]() {
|
|
61
|
+
for await (const line of stdin.lines()) {
|
|
62
|
+
const result = itemSchema['~standard'].validate(line);
|
|
63
|
+
const resolved = result instanceof Promise ? await result : result;
|
|
64
|
+
if ('issues' in resolved && resolved.issues) {
|
|
65
|
+
throw new Error(`Stream item validation failed: ${resolved.issues.map((i) => i.message).join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
yield (resolved as { value: unknown }).value;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const emptyAsyncIterable: AsyncIterable<never> = {
|
|
74
|
+
async *[Symbol.asyncIterator]() {},
|
|
75
|
+
};
|
package/src/test.ts
CHANGED
|
@@ -153,21 +153,11 @@ export function testCli(program: TestableProgram): TestCliBuilder {
|
|
|
153
153
|
const runtime = buildRuntime(stdout, stderr, { envVars, promptAnswers, configFiles, stdinData });
|
|
154
154
|
const testProgram = program.runtime(runtime);
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} catch (err) {
|
|
160
|
-
stderr.push(err instanceof Error ? err.message : String(err));
|
|
161
|
-
return {
|
|
162
|
-
command: undefined as unknown as AnyPadroneCommand,
|
|
163
|
-
args: undefined,
|
|
164
|
-
result: undefined,
|
|
165
|
-
issues: undefined,
|
|
166
|
-
stdout,
|
|
167
|
-
stderr,
|
|
168
|
-
error: err,
|
|
169
|
-
};
|
|
156
|
+
const evalResult = await testProgram.eval(runInput ?? input ?? '', { autoOutput: false });
|
|
157
|
+
if (evalResult.error) {
|
|
158
|
+
stderr.push(evalResult.error instanceof Error ? evalResult.error.message : String(evalResult.error));
|
|
170
159
|
}
|
|
160
|
+
return toTestResult(evalResult, stdout, stderr);
|
|
171
161
|
},
|
|
172
162
|
|
|
173
163
|
async repl(inputs: string[]) {
|
|
@@ -186,7 +176,7 @@ export function testCli(program: TestableProgram): TestCliBuilder {
|
|
|
186
176
|
|
|
187
177
|
for await (const r of testProgram.repl({ greeting: false, hint: false })) {
|
|
188
178
|
results.push({
|
|
189
|
-
command: r.command
|
|
179
|
+
command: r.command!,
|
|
190
180
|
args: r.args,
|
|
191
181
|
result: r.result,
|
|
192
182
|
issues: r.argsResult?.issues as TestCliResult['issues'],
|
|
@@ -202,9 +192,10 @@ export function testCli(program: TestableProgram): TestCliBuilder {
|
|
|
202
192
|
|
|
203
193
|
function toTestResult(evalResult: PadroneCommandResult, stdout: unknown[], stderr: string[]): TestCliResult {
|
|
204
194
|
return {
|
|
205
|
-
command: evalResult.command
|
|
195
|
+
command: evalResult.command!,
|
|
206
196
|
args: evalResult.args,
|
|
207
197
|
result: evalResult.result,
|
|
198
|
+
error: evalResult.error,
|
|
208
199
|
issues: evalResult.argsResult?.issues as TestCliResult['issues'],
|
|
209
200
|
stdout,
|
|
210
201
|
stderr,
|
package/src/type-utils.ts
CHANGED
|
@@ -36,9 +36,9 @@ export type OrAsync<TExisting extends boolean, TSchema> = TExisting extends true
|
|
|
36
36
|
* Detects whether argument meta contains interactive or optionalInteractive configuration.
|
|
37
37
|
* When either is `true` or a `string[]`, the command requires async execution for prompting.
|
|
38
38
|
*/
|
|
39
|
-
export type HasInteractive<TMeta> = TMeta extends { interactive: true | string[] }
|
|
39
|
+
export type HasInteractive<TMeta> = TMeta extends { interactive: true | readonly string[] }
|
|
40
40
|
? true
|
|
41
|
-
: TMeta extends { optionalInteractive: true | string[] }
|
|
41
|
+
: TMeta extends { optionalInteractive: true | readonly string[] }
|
|
42
42
|
? true
|
|
43
43
|
: false;
|
|
44
44
|
|
|
@@ -52,14 +52,38 @@ export type OrAsyncMeta<TExisting extends boolean, TMeta> = TExisting extends tr
|
|
|
52
52
|
? true
|
|
53
53
|
: false;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Unwraps a result type by resolving Promises and collecting iterables into arrays.
|
|
57
|
+
* - `AsyncIterable<U>` → `U[]`
|
|
58
|
+
* - `Iterable<U>` (excluding strings) → `U[]`
|
|
59
|
+
* - `Promise<U>` → `Drained<U>` (recursively unwraps)
|
|
60
|
+
* - `T` → `T`
|
|
61
|
+
*/
|
|
62
|
+
export type Drained<T> =
|
|
63
|
+
T extends Promise<infer U>
|
|
64
|
+
? Drained<U>
|
|
65
|
+
: T extends AsyncIterable<infer U>
|
|
66
|
+
? U[]
|
|
67
|
+
: T extends string
|
|
68
|
+
? T
|
|
69
|
+
: T extends Iterable<infer U>
|
|
70
|
+
? U[]
|
|
71
|
+
: T;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A sync value augmented with Promise-like methods (.then, .catch, .finally).
|
|
75
|
+
* Unlike a real Promise, properties of T are accessible synchronously.
|
|
76
|
+
*/
|
|
77
|
+
export type Thenable<T> = T & PromiseLike<T> & { catch: Promise<T>['catch']; finally: Promise<T>['finally'] };
|
|
78
|
+
|
|
55
79
|
/**
|
|
56
80
|
* Conditionally wraps a type in Promise based on the TAsync flag.
|
|
57
81
|
* - `true` → `Promise<T>`
|
|
58
|
-
* - `false` → `T &
|
|
82
|
+
* - `false` → `T & Thenable<T>` (thenable: supports `.then()`, `.catch()`, `.finally()`, and `await`)
|
|
59
83
|
* - `boolean` (union of true|false) → `Promise<T>` (safe default when async-ness is uncertain)
|
|
60
84
|
* - `any` → `T` (for generic/any typed commands like AnyPadroneCommand)
|
|
61
85
|
*/
|
|
62
|
-
export type MaybePromise<T, TAsync> = IsAny<TAsync> extends true ? T : true extends TAsync ? Promise<T> :
|
|
86
|
+
export type MaybePromise<T, TAsync> = IsAny<TAsync> extends true ? T : true extends TAsync ? Promise<T> : Thenable<T>;
|
|
63
87
|
|
|
64
88
|
type SplitString<TName extends string, TSplitBy extends string = ' '> = TName extends `${infer FirstPart}${TSplitBy}${infer RestParts}`
|
|
65
89
|
? [FirstPart, ...SplitString<RestParts, TSplitBy>]
|
package/src/types.ts
CHANGED
|
@@ -2,8 +2,11 @@ import type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/sp
|
|
|
2
2
|
import type { Tool } from 'ai';
|
|
3
3
|
import type { PadroneArgsSchemaMeta } from './args.ts';
|
|
4
4
|
import type { HelpPreferences } from './help.ts';
|
|
5
|
-
import type {
|
|
5
|
+
import type { PadroneMcpPreferences } from './mcp.ts';
|
|
6
|
+
import type { PadroneProgressIndicator, PadroneRuntime, PadroneSpinnerConfig, ResolvedPadroneRuntime } from './runtime.ts';
|
|
7
|
+
import type { PadroneServePreferences } from './serve.ts';
|
|
6
8
|
import type {
|
|
9
|
+
Drained,
|
|
7
10
|
FindDirectChild,
|
|
8
11
|
FlattenCommands,
|
|
9
12
|
FullCommandName,
|
|
@@ -36,6 +39,11 @@ export type PadroneActionContext = {
|
|
|
36
39
|
command: AnyPadroneCommand;
|
|
37
40
|
/** The root program instance. */
|
|
38
41
|
program: AnyPadroneProgram;
|
|
42
|
+
/**
|
|
43
|
+
* The active auto-managed progress indicator, or a no-op if none is configured.
|
|
44
|
+
* Use `.update()` to change the in-progress message mid-execution.
|
|
45
|
+
*/
|
|
46
|
+
progress: PadroneProgressIndicator;
|
|
39
47
|
};
|
|
40
48
|
|
|
41
49
|
/**
|
|
@@ -178,8 +186,24 @@ export type PadroneCommand<
|
|
|
178
186
|
aliases?: TAliases;
|
|
179
187
|
deprecated?: boolean | string;
|
|
180
188
|
hidden?: boolean;
|
|
189
|
+
/** Group name for organizing this command under a labeled section in help output. */
|
|
190
|
+
group?: string;
|
|
191
|
+
/** Whether this command performs a mutation (create, update, delete). Affects HTTP method in serve (POST-only) and MCP tool annotations (destructiveHint). */
|
|
192
|
+
mutation?: boolean;
|
|
181
193
|
needsApproval?: boolean | ((args: TArgs) => Promise<boolean> | boolean);
|
|
182
194
|
autoOutput?: boolean;
|
|
195
|
+
/** Usage examples shown in help output. Each entry is a command-line invocation string. */
|
|
196
|
+
examples?: string[];
|
|
197
|
+
/**
|
|
198
|
+
* Auto-start a progress indicator when the command's execute phase begins.
|
|
199
|
+
* - `true` — generic message based on command name.
|
|
200
|
+
* - `string` — custom message for all states.
|
|
201
|
+
* - `PadroneProgressConfig` — separate messages for progress, success, and error states.
|
|
202
|
+
*
|
|
203
|
+
* The indicator is automatically stopped on success (`.succeed()`) or failure (`.fail()`).
|
|
204
|
+
* Requires a `progress` factory on the runtime — silently skipped if not available.
|
|
205
|
+
*/
|
|
206
|
+
progress?: boolean | string | PadroneProgressPrefs;
|
|
183
207
|
argsSchema?: TArgs;
|
|
184
208
|
configSchema?: TConfig;
|
|
185
209
|
envSchema?: TEnv;
|
|
@@ -193,7 +217,7 @@ export type PadroneCommand<
|
|
|
193
217
|
runtime?: PadroneRuntime;
|
|
194
218
|
|
|
195
219
|
/** Plugins registered on this command. Collected from the parent chain at execution time. */
|
|
196
|
-
plugins?: PadronePlugin[];
|
|
220
|
+
plugins?: PadronePlugin<any, any>[];
|
|
197
221
|
|
|
198
222
|
/** Update check configuration. Only used on the root program. */
|
|
199
223
|
updateCheck?: UpdateCheckConfig;
|
|
@@ -233,6 +257,33 @@ type CommandTypesBase = {
|
|
|
233
257
|
/**
|
|
234
258
|
* Configuration for a command.
|
|
235
259
|
*/
|
|
260
|
+
/**
|
|
261
|
+
* A progress message value: a plain string, `null` to suppress, or an object with a message and custom indicator icon.
|
|
262
|
+
*/
|
|
263
|
+
export type PadroneProgressMessage = string | null | { message?: string | null; indicator?: string };
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Progress indicator configuration with per-state messages and optional dynamic callbacks.
|
|
267
|
+
*
|
|
268
|
+
* The `success` and `error` fields accept either a static value or a callback function:
|
|
269
|
+
* - `string` — static message.
|
|
270
|
+
* - `null` — suppress the message entirely.
|
|
271
|
+
* - `{ message, indicator }` — custom message with a per-call indicator icon override.
|
|
272
|
+
* - `(result) => PadroneProgressMessage` / `(error) => PadroneProgressMessage` — dynamic from the actual result/error.
|
|
273
|
+
*/
|
|
274
|
+
export type PadroneProgressPrefs<TRes = unknown> = {
|
|
275
|
+
/** Message shown during async validation. Defaults to `''` (spinner only). */
|
|
276
|
+
validation?: string;
|
|
277
|
+
/** Message shown while the command's action is running. */
|
|
278
|
+
progress?: string;
|
|
279
|
+
/** Message shown when the command succeeds. `null` to suppress. Defaults to the `progress` message. */
|
|
280
|
+
success?: PadroneProgressMessage | ((result: TRes) => PadroneProgressMessage);
|
|
281
|
+
/** Message shown when the command fails. `null` to suppress. Defaults to the error message. */
|
|
282
|
+
error?: PadroneProgressMessage | ((error: unknown) => PadroneProgressMessage);
|
|
283
|
+
/** Spinner configuration: a preset name, custom frames/interval, or `false` to disable. */
|
|
284
|
+
spinner?: PadroneSpinnerConfig;
|
|
285
|
+
};
|
|
286
|
+
|
|
236
287
|
export type PadroneCommandConfig = {
|
|
237
288
|
/** A short title for the command, displayed in help. */
|
|
238
289
|
title?: string;
|
|
@@ -244,12 +295,23 @@ export type PadroneCommandConfig = {
|
|
|
244
295
|
deprecated?: boolean | string;
|
|
245
296
|
/** Whether the command should be hidden from help output. */
|
|
246
297
|
hidden?: boolean;
|
|
298
|
+
/** Group name for organizing this command under a labeled section in help output. */
|
|
299
|
+
group?: string;
|
|
247
300
|
/**
|
|
248
301
|
* Automatically write this command's return value to output in CLI/eval/REPL mode.
|
|
249
302
|
* Overrides the `autoOutput` setting in eval/cli preferences for this command.
|
|
250
303
|
* See `PadroneEvalPreferences.autoOutput` for serialization details.
|
|
251
304
|
*/
|
|
252
305
|
autoOutput?: boolean;
|
|
306
|
+
/** Usage examples shown in help output. Each entry is a command-line invocation string. */
|
|
307
|
+
examples?: string[];
|
|
308
|
+
/**
|
|
309
|
+
* Whether this command performs a mutation (create, update, delete).
|
|
310
|
+
* - In `serve()`: mutation commands accept POST only; non-mutation commands accept GET and POST.
|
|
311
|
+
* - In `mcp()`: sets `annotations.destructiveHint` on the tool definition.
|
|
312
|
+
* - In `tool()`: defaults `needsApproval` to `true` when not explicitly set.
|
|
313
|
+
*/
|
|
314
|
+
mutation?: boolean;
|
|
253
315
|
};
|
|
254
316
|
|
|
255
317
|
/**
|
|
@@ -325,7 +387,7 @@ export type PadroneBuilderMethods<
|
|
|
325
387
|
* On subcommands, only validate and execute plugins apply (parse is handled by the root program).
|
|
326
388
|
*/
|
|
327
389
|
use: (
|
|
328
|
-
plugin: PadronePlugin
|
|
390
|
+
plugin: PadronePlugin<StandardSchemaV1.InferOutput<TArgs>, TRes>,
|
|
329
391
|
) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
|
|
330
392
|
|
|
331
393
|
configure: (
|
|
@@ -463,6 +525,31 @@ export type PadroneBuilderMethods<
|
|
|
463
525
|
OrAsync<TAsync, TNewEnv>
|
|
464
526
|
>;
|
|
465
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Configures an auto-managed progress indicator for this command.
|
|
530
|
+
* The indicator starts before validation and is automatically stopped on success or failure.
|
|
531
|
+
*
|
|
532
|
+
* - `true` — generic message based on command name.
|
|
533
|
+
* - `string` — custom message for all states.
|
|
534
|
+
* - `PadroneProgressConfig` — full control: per-state messages, dynamic callbacks, spinner config.
|
|
535
|
+
*
|
|
536
|
+
* Requires a `progress` factory on the runtime — silently skipped if not available.
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```ts
|
|
540
|
+
* .progress('Deploying...')
|
|
541
|
+
* .progress({
|
|
542
|
+
* progress: 'Deploying...',
|
|
543
|
+
* success: (result) => `Deployed ${result.version}`,
|
|
544
|
+
* error: (err) => `Deploy failed: ${err.message}`,
|
|
545
|
+
* spinner: 'line',
|
|
546
|
+
* })
|
|
547
|
+
* ```
|
|
548
|
+
*/
|
|
549
|
+
progress: (
|
|
550
|
+
config?: boolean | string | PadroneProgressPrefs<Awaited<TRes>>,
|
|
551
|
+
) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
|
|
552
|
+
|
|
466
553
|
/**
|
|
467
554
|
* Defines the handler function to be executed when the command is run.
|
|
468
555
|
* When overriding an existing command, the previous handler is passed as the third `base` parameter.
|
|
@@ -479,6 +566,8 @@ export type PadroneBuilderMethods<
|
|
|
479
566
|
* Wraps an external CLI tool with optional schema transformation.
|
|
480
567
|
* The config can include a schema that transforms command arguments to external CLI arguments.
|
|
481
568
|
*
|
|
569
|
+
* @experimental This API is experimental and may change in future releases.
|
|
570
|
+
*
|
|
482
571
|
* @example
|
|
483
572
|
* ```ts
|
|
484
573
|
* // No transformation - pass arguments as-is
|
|
@@ -769,8 +858,8 @@ export type PadroneProgram<
|
|
|
769
858
|
eval: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], true, true>>(
|
|
770
859
|
input: TCommand | SafeString,
|
|
771
860
|
prefs?: PadroneEvalPreferences,
|
|
772
|
-
) =>
|
|
773
|
-
|
|
861
|
+
) => MaybePromiseCommandResult<
|
|
862
|
+
PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>,
|
|
774
863
|
PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>['~types']['async']
|
|
775
864
|
>;
|
|
776
865
|
|
|
@@ -781,7 +870,7 @@ export type PadroneProgram<
|
|
|
781
870
|
*/
|
|
782
871
|
cli: (
|
|
783
872
|
prefs?: PadroneCliPreferences<PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>,
|
|
784
|
-
) =>
|
|
873
|
+
) => MaybePromiseCommandResult<FlattenCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>, TAsync>;
|
|
785
874
|
|
|
786
875
|
/**
|
|
787
876
|
* Parses CLI input (or the provided input string) into command and arguments without executing anything.
|
|
@@ -831,9 +920,11 @@ export type PadroneProgram<
|
|
|
831
920
|
* - History persistence: save/load history across sessions (currently in-memory only)
|
|
832
921
|
* - Middleware/hooks: onBeforeCommand, onAfterCommand, error interceptors (design alongside general middleware system)
|
|
833
922
|
*/
|
|
834
|
-
repl: (
|
|
835
|
-
|
|
836
|
-
|
|
923
|
+
repl: (options?: PadroneReplPreferences<PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>) => AsyncIterable<
|
|
924
|
+
PadroneCommandResult<FlattenCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>
|
|
925
|
+
> & {
|
|
926
|
+
drain: () => Promise<PadroneDrainResult<PadroneCommandResult<FlattenCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>[]>>;
|
|
927
|
+
};
|
|
837
928
|
|
|
838
929
|
/**
|
|
839
930
|
* Returns a tool definition that can be passed to AI SDK.
|
|
@@ -863,6 +954,34 @@ export type PadroneProgram<
|
|
|
863
954
|
* ```
|
|
864
955
|
*/
|
|
865
956
|
completion: (shell?: 'bash' | 'zsh' | 'fish' | 'powershell') => Promise<string>;
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Starts a Model Context Protocol (MCP) server that exposes commands as tools for AI assistants.
|
|
960
|
+
* Communicates over stdio using JSON-RPC with Content-Length framing.
|
|
961
|
+
* Returns a Promise that resolves when the connection closes.
|
|
962
|
+
*
|
|
963
|
+
* @experimental This API is experimental and may change in future releases.
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* ```ts
|
|
967
|
+
* await program.mcp();
|
|
968
|
+
* ```
|
|
969
|
+
*/
|
|
970
|
+
mcp: (prefs?: PadroneMcpPreferences) => Promise<void>;
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Starts a REST HTTP server that exposes commands as endpoints.
|
|
974
|
+
* Each command becomes a route: `users list` → `GET/POST /users/list`.
|
|
975
|
+
* Commands with `mutation: true` only accept POST.
|
|
976
|
+
*
|
|
977
|
+
* @experimental This API is experimental and may change in future releases.
|
|
978
|
+
*
|
|
979
|
+
* @example
|
|
980
|
+
* ```ts
|
|
981
|
+
* await program.serve({ port: 3000 });
|
|
982
|
+
* ```
|
|
983
|
+
*/
|
|
984
|
+
serve: (prefs?: PadroneServePreferences) => Promise<void>;
|
|
866
985
|
};
|
|
867
986
|
|
|
868
987
|
export type AnyPadroneProgram = PadroneProgram<string, string, string, any, any, [...AnyPadroneCommand[]]>;
|
|
@@ -959,10 +1078,48 @@ export type PadroneEvalPreferences = {
|
|
|
959
1078
|
export type PadroneCliPreferences<TScope extends string = string> = PadroneEvalPreferences & {
|
|
960
1079
|
/** REPL preferences used when `--repl` flag is passed. Set to `false` to disable the `--repl` flag. */
|
|
961
1080
|
repl?: PadroneReplPreferences<TScope> | false;
|
|
1081
|
+
/** MCP server preferences used when `mcp` command is used. Set to `false` to disable. */
|
|
1082
|
+
mcp?: PadroneMcpPreferences | false;
|
|
1083
|
+
/** REST server preferences used when `serve` command is used. Set to `false` to disable. */
|
|
1084
|
+
serve?: PadroneServePreferences | false;
|
|
962
1085
|
};
|
|
963
1086
|
|
|
964
|
-
|
|
965
|
-
|
|
1087
|
+
/**
|
|
1088
|
+
* Result of `drain()` — a discriminated union that never throws.
|
|
1089
|
+
* On success, `value` holds the fully resolved/collected result; on failure, `error` holds the error.
|
|
1090
|
+
*/
|
|
1091
|
+
export type PadroneDrainResult<TResult> = { value: Drained<TResult>; error?: never } | { error: unknown; value?: never };
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Result returned by `eval()`, `cli()`, and `run()`. Never thrown — errors are captured in the `error` field.
|
|
1095
|
+
* Discriminated union: check `error` to distinguish success from failure.
|
|
1096
|
+
*
|
|
1097
|
+
* On success: `command`, `args`, `argsResult`, `result` are populated; `error` is absent.
|
|
1098
|
+
* On failure: `error` is populated; `command` may be present if routing succeeded.
|
|
1099
|
+
*/
|
|
1100
|
+
export type PadroneCommandResult<TCommand extends AnyPadroneCommand = AnyPadroneCommand> =
|
|
1101
|
+
| (PadroneParseResult<TCommand> & {
|
|
1102
|
+
result: GetResults<TCommand>;
|
|
1103
|
+
error?: never;
|
|
1104
|
+
/** Flattens the result: awaits Promises, collects iterables, catches errors. Never throws. */
|
|
1105
|
+
drain: () => Promise<PadroneDrainResult<GetResults<TCommand>>>;
|
|
1106
|
+
})
|
|
1107
|
+
| {
|
|
1108
|
+
command?: TCommand;
|
|
1109
|
+
args?: GetArguments<'out', TCommand>;
|
|
1110
|
+
argsResult?: StandardSchemaV1.Result<GetArguments<'out', TCommand>>;
|
|
1111
|
+
error: unknown;
|
|
1112
|
+
result?: never;
|
|
1113
|
+
/** Returns `{ error }` since there is no result to drain. */
|
|
1114
|
+
drain: () => Promise<PadroneDrainResult<GetResults<TCommand>>>;
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Like `MaybePromise<PadroneCommandResult<TCommand>, TAsync>` but ensures `drain()` is available
|
|
1119
|
+
* at the outer level in all cases — both sync (Thenable) and async (Promise).
|
|
1120
|
+
*/
|
|
1121
|
+
type MaybePromiseCommandResult<TCommand extends AnyPadroneCommand, TAsync> = MaybePromise<PadroneCommandResult<TCommand>, TAsync> & {
|
|
1122
|
+
drain: () => Promise<PadroneDrainResult<GetResults<TCommand>>>;
|
|
966
1123
|
};
|
|
967
1124
|
|
|
968
1125
|
export type PadroneParseResult<TCommand extends AnyPadroneCommand = AnyPadroneCommand> = {
|
|
@@ -1020,20 +1177,20 @@ export type PluginValidateContext = PluginBaseContext & {
|
|
|
1020
1177
|
};
|
|
1021
1178
|
|
|
1022
1179
|
/** Result returned by the validate phase's `next()`. */
|
|
1023
|
-
export type PluginValidateResult = {
|
|
1024
|
-
args:
|
|
1025
|
-
argsResult: StandardSchemaV1.Result<
|
|
1180
|
+
export type PluginValidateResult<TArgs = unknown> = {
|
|
1181
|
+
args: TArgs;
|
|
1182
|
+
argsResult: StandardSchemaV1.Result<TArgs>;
|
|
1026
1183
|
};
|
|
1027
1184
|
|
|
1028
1185
|
/** Context for the execute phase. */
|
|
1029
|
-
export type PluginExecuteContext = PluginBaseContext & {
|
|
1186
|
+
export type PluginExecuteContext<TArgs = unknown> = PluginBaseContext & {
|
|
1030
1187
|
/** Validated arguments that will be passed to the action. Mutable — modify before `next()` to override. */
|
|
1031
|
-
args:
|
|
1188
|
+
args: TArgs;
|
|
1032
1189
|
};
|
|
1033
1190
|
|
|
1034
1191
|
/** Result returned by the execute phase's `next()`. */
|
|
1035
|
-
export type PluginExecuteResult = {
|
|
1036
|
-
result:
|
|
1192
|
+
export type PluginExecuteResult<TResult = unknown> = {
|
|
1193
|
+
result: TResult;
|
|
1037
1194
|
};
|
|
1038
1195
|
|
|
1039
1196
|
/** Context for the start phase. Runs before parsing, wraps the entire pipeline. */
|
|
@@ -1049,26 +1206,45 @@ export type PluginErrorContext = PluginBaseContext & {
|
|
|
1049
1206
|
};
|
|
1050
1207
|
|
|
1051
1208
|
/** Result returned by the error phase's `next()`. */
|
|
1052
|
-
export type PluginErrorResult = {
|
|
1209
|
+
export type PluginErrorResult<TResult = unknown> = {
|
|
1053
1210
|
/** The error (possibly transformed). Set to `undefined` to suppress the error. */
|
|
1054
1211
|
error?: unknown;
|
|
1055
1212
|
/** A replacement result when suppressing the error. */
|
|
1056
|
-
result?:
|
|
1213
|
+
result?: TResult;
|
|
1057
1214
|
};
|
|
1058
1215
|
|
|
1059
1216
|
/** Context for the shutdown phase. Always runs after the pipeline (success or failure). */
|
|
1060
|
-
export type PluginShutdownContext = PluginBaseContext & {
|
|
1217
|
+
export type PluginShutdownContext<TResult = unknown> = PluginBaseContext & {
|
|
1061
1218
|
/** The error, if the pipeline failed (after error phase processing). */
|
|
1062
1219
|
error?: unknown;
|
|
1063
1220
|
/** The pipeline result, if it succeeded. */
|
|
1064
|
-
result?:
|
|
1221
|
+
result?: TResult;
|
|
1065
1222
|
};
|
|
1066
1223
|
|
|
1067
|
-
|
|
1224
|
+
/**
|
|
1225
|
+
* A phase handler function for the plugin middleware chain.
|
|
1226
|
+
*
|
|
1227
|
+
* - `TCtx` — the context object available to the handler.
|
|
1228
|
+
* - `TNextResult` — the typed result returned by `next()`, giving the handler type-safe access to downstream output.
|
|
1229
|
+
* - `TReturn` — the type the handler itself returns. Defaults to `TNextResult` but can be wider,
|
|
1230
|
+
* allowing plugins to transform or replace the result (e.g., error-recovery plugins returning a different type).
|
|
1231
|
+
*/
|
|
1232
|
+
type PluginPhaseHandler<TCtx, TNextResult, TReturn = TNextResult> = (
|
|
1233
|
+
ctx: TCtx,
|
|
1234
|
+
next: () => TNextResult | Promise<TNextResult>,
|
|
1235
|
+
) => TReturn | Promise<TReturn>;
|
|
1068
1236
|
|
|
1069
1237
|
/**
|
|
1070
1238
|
* A Padrone plugin that can intercept the parse, validate, and execute phases of command execution.
|
|
1071
|
-
* Plugins are registered at the program level with `.use()
|
|
1239
|
+
* Plugins are registered at the program or subcommand level with `.use()`.
|
|
1240
|
+
*
|
|
1241
|
+
* Type parameters:
|
|
1242
|
+
* - `TArgs` — the validated arguments type (output of the args schema). Provides typed `ctx.args` in the execute phase
|
|
1243
|
+
* and typed `args` in the validate result from `next()`.
|
|
1244
|
+
* - `TResult` — the command's return type. Provides typed `result` in execute/error/shutdown phases.
|
|
1245
|
+
*
|
|
1246
|
+
* When registered inline on a builder, these are inferred from the command's types automatically.
|
|
1247
|
+
* For reusable plugins that work with any command, use `PadronePlugin<any, any>`.
|
|
1072
1248
|
*
|
|
1073
1249
|
* Each phase handler receives a context and a `next()` function (onion/middleware pattern):
|
|
1074
1250
|
* - Call `next()` to proceed to the next plugin or the core operation.
|
|
@@ -1077,9 +1253,15 @@ type PluginPhaseHandler<TCtx, TResult> = (ctx: TCtx, next: () => TResult | Promi
|
|
|
1077
1253
|
* - Modify context fields before `next()` to alter inputs.
|
|
1078
1254
|
* - Transform the return value of `next()` to alter outputs.
|
|
1079
1255
|
*/
|
|
1080
|
-
export type PadronePlugin = {
|
|
1081
|
-
/**
|
|
1256
|
+
export type PadronePlugin<TArgs = unknown, TResult = unknown> = {
|
|
1257
|
+
/** Display name for this plugin. Used for identification in logs and debugging. */
|
|
1082
1258
|
name: string;
|
|
1259
|
+
/**
|
|
1260
|
+
* Optional unique identifier for deduplication. When multiple plugins share the same `id`,
|
|
1261
|
+
* only the last one registered is kept. Useful for allowing downstream code to override
|
|
1262
|
+
* a plugin without accumulating duplicates.
|
|
1263
|
+
*/
|
|
1264
|
+
id?: string;
|
|
1083
1265
|
/**
|
|
1084
1266
|
* Ordering hint. Lower values run as outer layers (earlier before `next()`, later after).
|
|
1085
1267
|
* Plugins with the same order preserve registration order. Defaults to `0`.
|
|
@@ -1093,17 +1275,17 @@ export type PadronePlugin = {
|
|
|
1093
1275
|
/** Intercepts command routing and raw argument extraction. */
|
|
1094
1276
|
parse?: PluginPhaseHandler<PluginParseContext, PluginParseResult>;
|
|
1095
1277
|
/** Intercepts argument preprocessing, interactive prompting, and schema validation. */
|
|
1096
|
-
validate?: PluginPhaseHandler<PluginValidateContext, PluginValidateResult>;
|
|
1278
|
+
validate?: PluginPhaseHandler<PluginValidateContext, PluginValidateResult<TArgs>, PluginValidateResult>;
|
|
1097
1279
|
/** Intercepts handler execution. */
|
|
1098
|
-
execute?: PluginPhaseHandler<PluginExecuteContext
|
|
1280
|
+
execute?: PluginPhaseHandler<PluginExecuteContext<TArgs>, PluginExecuteResult<TResult>, PluginExecuteResult>;
|
|
1099
1281
|
/**
|
|
1100
1282
|
* Called when the pipeline throws an error. `next()` passes to the next error handler
|
|
1101
1283
|
* (innermost returns `{ error }` unchanged). Return `{ result }` without `error` to suppress.
|
|
1102
1284
|
*/
|
|
1103
|
-
error?: PluginPhaseHandler<PluginErrorContext, PluginErrorResult>;
|
|
1285
|
+
error?: PluginPhaseHandler<PluginErrorContext, PluginErrorResult<TResult>, PluginErrorResult>;
|
|
1104
1286
|
/**
|
|
1105
1287
|
* Always runs after the pipeline completes (success or failure). `next()` calls the next shutdown handler.
|
|
1106
1288
|
* Use for cleanup: closing connections, flushing logs, etc.
|
|
1107
1289
|
*/
|
|
1108
|
-
shutdown?: PluginPhaseHandler<PluginShutdownContext
|
|
1290
|
+
shutdown?: PluginPhaseHandler<PluginShutdownContext<TResult>, void>;
|
|
1109
1291
|
};
|
package/src/wrap.ts
CHANGED
|
@@ -58,7 +58,7 @@ export type WrapResult = {
|
|
|
58
58
|
/**
|
|
59
59
|
* Converts parsed arguments to CLI arguments for an external command.
|
|
60
60
|
*/
|
|
61
|
-
function argsToCliArgs(input: Record<string, unknown> | undefined, positional: string[] = []): string[] {
|
|
61
|
+
function argsToCliArgs(input: Record<string, unknown> | undefined, positional: readonly string[] = []): string[] {
|
|
62
62
|
const args: string[] = [];
|
|
63
63
|
|
|
64
64
|
// Handle undefined or null input
|
|
@@ -122,7 +122,7 @@ function argsToCliArgs(input: Record<string, unknown> | undefined, positional: s
|
|
|
122
122
|
export function createWrapHandler<TCommandArgs extends PadroneSchema, TWrapArgs extends PadroneSchema>(
|
|
123
123
|
config: WrapConfig<TCommandArgs, TWrapArgs>,
|
|
124
124
|
commandArguments: TCommandArgs,
|
|
125
|
-
commandPositional?: string[],
|
|
125
|
+
commandPositional?: readonly string[],
|
|
126
126
|
): (args: StandardSchemaV1.InferOutput<TCommandArgs>) => Promise<WrapResult> {
|
|
127
127
|
return async (args: StandardSchemaV1.InferOutput<TCommandArgs>): Promise<WrapResult> => {
|
|
128
128
|
const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
|
|
@@ -153,33 +153,31 @@ export function createWrapHandler<TCommandArgs extends PadroneSchema, TWrapArgs
|
|
|
153
153
|
const allArgs = [...fixedArgs, ...regularArgs];
|
|
154
154
|
|
|
155
155
|
// Execute the external command
|
|
156
|
-
const
|
|
157
|
-
stdout: inheritStdio ? 'inherit' : 'pipe',
|
|
158
|
-
stderr: inheritStdio ? 'inherit' : 'pipe',
|
|
159
|
-
stdin: inheritStdio ? 'inherit' : 'ignore',
|
|
160
|
-
});
|
|
156
|
+
const { spawn } = await import('node:child_process');
|
|
161
157
|
|
|
162
|
-
|
|
158
|
+
return new Promise<WrapResult>((resolve, reject) => {
|
|
159
|
+
const proc = spawn(command, allArgs, {
|
|
160
|
+
stdio: inheritStdio ? 'inherit' : ['ignore', 'pipe', 'pipe'],
|
|
161
|
+
});
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
const stdoutChunks: Buffer[] = [];
|
|
164
|
+
const stderrChunks: Buffer[] = [];
|
|
166
165
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
stdout = new TextDecoder().decode(stdoutBuffer);
|
|
171
|
-
}
|
|
172
|
-
if (proc.stderr) {
|
|
173
|
-
const stderrBuffer = await new Response(proc.stderr).arrayBuffer();
|
|
174
|
-
stderr = new TextDecoder().decode(stderrBuffer);
|
|
166
|
+
if (!inheritStdio) {
|
|
167
|
+
proc.stdout!.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));
|
|
168
|
+
proc.stderr!.on('data', (chunk: Buffer) => stderrChunks.push(chunk));
|
|
175
169
|
}
|
|
176
|
-
}
|
|
177
170
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
proc.on('error', reject);
|
|
172
|
+
proc.on('close', (code) => {
|
|
173
|
+
const exitCode = code ?? 1;
|
|
174
|
+
resolve({
|
|
175
|
+
exitCode,
|
|
176
|
+
stdout: inheritStdio ? undefined : Buffer.concat(stdoutChunks).toString(),
|
|
177
|
+
stderr: inheritStdio ? undefined : Buffer.concat(stderrChunks).toString(),
|
|
178
|
+
success: exitCode === 0,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
184
182
|
};
|
|
185
183
|
}
|