padrone 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/LICENSE +1 -1
  3. package/README.md +92 -49
  4. package/dist/args-CKNh7Dm9.mjs +175 -0
  5. package/dist/args-CKNh7Dm9.mjs.map +1 -0
  6. package/dist/chunk-y_GBKt04.mjs +5 -0
  7. package/dist/codegen/index.d.mts +305 -0
  8. package/dist/codegen/index.d.mts.map +1 -0
  9. package/dist/codegen/index.mjs +1348 -0
  10. package/dist/codegen/index.mjs.map +1 -0
  11. package/dist/completion.d.mts +64 -0
  12. package/dist/completion.d.mts.map +1 -0
  13. package/dist/completion.mjs +417 -0
  14. package/dist/completion.mjs.map +1 -0
  15. package/dist/docs/index.d.mts +34 -0
  16. package/dist/docs/index.d.mts.map +1 -0
  17. package/dist/docs/index.mjs +404 -0
  18. package/dist/docs/index.mjs.map +1 -0
  19. package/dist/formatter-Dvx7jFXr.d.mts +82 -0
  20. package/dist/formatter-Dvx7jFXr.d.mts.map +1 -0
  21. package/dist/help-mUIX0T0V.mjs +1195 -0
  22. package/dist/help-mUIX0T0V.mjs.map +1 -0
  23. package/dist/index.d.mts +122 -438
  24. package/dist/index.d.mts.map +1 -1
  25. package/dist/index.mjs +1240 -1161
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/test.d.mts +112 -0
  28. package/dist/test.d.mts.map +1 -0
  29. package/dist/test.mjs +138 -0
  30. package/dist/test.mjs.map +1 -0
  31. package/dist/types-qrtt0135.d.mts +1037 -0
  32. package/dist/types-qrtt0135.d.mts.map +1 -0
  33. package/dist/update-check-EbNDkzyV.mjs +146 -0
  34. package/dist/update-check-EbNDkzyV.mjs.map +1 -0
  35. package/package.json +61 -20
  36. package/src/args.ts +365 -0
  37. package/src/cli/completions.ts +29 -0
  38. package/src/cli/docs.ts +86 -0
  39. package/src/cli/doctor.ts +312 -0
  40. package/src/cli/index.ts +159 -0
  41. package/src/cli/init.ts +135 -0
  42. package/src/cli/link.ts +320 -0
  43. package/src/cli/wrap.ts +152 -0
  44. package/src/codegen/README.md +118 -0
  45. package/src/codegen/code-builder.ts +226 -0
  46. package/src/codegen/discovery.ts +232 -0
  47. package/src/codegen/file-emitter.ts +73 -0
  48. package/src/codegen/generators/barrel-file.ts +16 -0
  49. package/src/codegen/generators/command-file.ts +184 -0
  50. package/src/codegen/generators/command-tree.ts +124 -0
  51. package/src/codegen/index.ts +33 -0
  52. package/src/codegen/parsers/fish.ts +163 -0
  53. package/src/codegen/parsers/help.ts +378 -0
  54. package/src/codegen/parsers/merge.ts +158 -0
  55. package/src/codegen/parsers/zsh.ts +221 -0
  56. package/src/codegen/schema-to-code.ts +199 -0
  57. package/src/codegen/template.ts +69 -0
  58. package/src/codegen/types.ts +143 -0
  59. package/src/colorizer.ts +2 -2
  60. package/src/command-utils.ts +501 -0
  61. package/src/completion.ts +110 -97
  62. package/src/create.ts +1044 -284
  63. package/src/docs/index.ts +607 -0
  64. package/src/errors.ts +131 -0
  65. package/src/formatter.ts +149 -63
  66. package/src/help.ts +151 -55
  67. package/src/index.ts +13 -15
  68. package/src/interactive.ts +169 -0
  69. package/src/parse.ts +31 -16
  70. package/src/repl-loop.ts +317 -0
  71. package/src/runtime.ts +304 -0
  72. package/src/shell-utils.ts +83 -0
  73. package/src/test.ts +285 -0
  74. package/src/type-helpers.ts +12 -12
  75. package/src/type-utils.ts +124 -14
  76. package/src/types.ts +803 -144
  77. package/src/update-check.ts +244 -0
  78. package/src/wrap.ts +185 -0
  79. package/src/zod.d.ts +2 -2
  80. package/src/options.ts +0 -180
package/src/types.ts CHANGED
@@ -1,26 +1,61 @@
1
1
  import type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
2
2
  import type { Tool } from 'ai';
3
- import type { HelpOptions } from './help.ts';
4
- import type { PadroneMeta } from './options.ts';
3
+ import type { PadroneArgsSchemaMeta } from './args.ts';
4
+ import type { HelpPreferences } from './help.ts';
5
+ import type { PadroneRuntime, ResolvedPadroneRuntime } from './runtime.ts';
5
6
  import type {
7
+ FindDirectChild,
8
+ FlattenCommands,
6
9
  FullCommandName,
7
10
  IsGeneric,
11
+ MaybePromise,
12
+ OrAsync,
13
+ OrAsyncMeta,
8
14
  PickCommandByName,
9
15
  PickCommandByPossibleCommands,
10
16
  PossibleCommands,
17
+ RepathCommands,
18
+ ReplaceOrAppendCommand,
11
19
  SafeString,
12
20
  } from './type-utils.ts';
21
+ import type { UpdateCheckConfig } from './update-check.ts';
22
+ import type { WrapConfig, WrapResult } from './wrap.ts';
13
23
 
14
24
  type UnknownRecord = Record<string, unknown>;
15
25
  type EmptyRecord = Record<string, never>;
16
- type DefaultOpts = UnknownRecord | void;
26
+ type DefaultArgs = UnknownRecord | void;
27
+
28
+ /**
29
+ * Context object passed as the second argument to command action handlers.
30
+ * Contains the resolved runtime, the executing command, and the program instance.
31
+ */
32
+ export type PadroneActionContext = {
33
+ /** The resolved runtime for this command (I/O, env, config, etc.). */
34
+ runtime: ResolvedPadroneRuntime;
35
+ /** The command being executed. */
36
+ command: AnyPadroneCommand;
37
+ /** The root program instance. */
38
+ program: AnyPadroneProgram;
39
+ };
17
40
 
18
41
  /**
19
42
  * A schema that supports both validation (StandardSchemaV1) and JSON schema generation (StandardJSONSchemaV1).
20
- * This is the type required for command arguments and options in Padrone.
43
+ * This is the type required for command arguments in Padrone.
21
44
  */
22
45
  export type PadroneSchema<Input = unknown, Output = Input> = StandardSchemaV1<Input, Output> & StandardJSONSchemaV1<Input, Output>;
23
46
 
47
+ /**
48
+ * A schema branded as async. When passed to `.arguments()`, `.configFile()`, or `.env()`,
49
+ * the command is automatically marked as async, causing `parse()` and `cli()` to return Promises.
50
+ *
51
+ * Use the `asyncSchema()` helper to brand an existing schema:
52
+ * ```ts
53
+ * import { asyncSchema } from 'padrone';
54
+ * const schema = asyncSchema(z.object({ name: z.string() }).check(async (v) => { ... }));
55
+ * ```
56
+ */
57
+ export type AsyncPadroneSchema<Input = unknown, Output = Input> = PadroneSchema<Input, Output> & { '~async': true };
58
+
24
59
  /**
25
60
  * Helper type to set aliases on a command type.
26
61
  * Uses intersection to override just the aliases while preserving all other type information.
@@ -30,15 +65,109 @@ type WithAliases<TCommand extends AnyPadroneCommand, TAliases extends string[]>
30
65
  '~types': Omit<TCommand['~types'], 'aliases'> & { aliases: TAliases };
31
66
  };
32
67
 
68
+ /**
69
+ * Resolves aliases for a command override: if new aliases are provided (non-empty), use them;
70
+ * otherwise, preserve the existing command's aliases.
71
+ */
72
+ type ResolvedAliases<
73
+ TCommands extends [...AnyPadroneCommand[]],
74
+ TNameNested extends string,
75
+ TAliases extends string[],
76
+ > = TAliases extends []
77
+ ? FindDirectChild<TCommands, TNameNested> extends infer E extends AnyPadroneCommand
78
+ ? E['~types']['aliases']
79
+ : []
80
+ : TAliases;
81
+
82
+ /**
83
+ * Resolves the initial builder type for a `.command()` call.
84
+ * If TNameNested already exists in TCommands, the builder starts pre-populated with that command's types.
85
+ * Otherwise, a fresh builder with default types is used.
86
+ */
87
+ type InitialCommandBuilder<
88
+ TProgramName extends string,
89
+ TNameNested extends string,
90
+ TParentPath extends string,
91
+ TParentArgs extends PadroneSchema,
92
+ TCommands extends [...AnyPadroneCommand[]],
93
+ > = [FindDirectChild<TCommands, TNameNested>] extends [never]
94
+ ? PadroneBuilder<
95
+ TProgramName,
96
+ TNameNested,
97
+ TParentPath,
98
+ PadroneSchema<void>,
99
+ void,
100
+ [],
101
+ TParentArgs,
102
+ PadroneSchema<void>,
103
+ PadroneSchema<void>,
104
+ false
105
+ >
106
+ : FindDirectChild<TCommands, TNameNested> extends infer E extends AnyPadroneCommand
107
+ ? PadroneBuilder<
108
+ TProgramName,
109
+ TNameNested,
110
+ TParentPath,
111
+ E['~types']['argsSchema'],
112
+ E['~types']['result'],
113
+ E['~types']['commands'],
114
+ TParentArgs,
115
+ E['~types']['configSchema'],
116
+ E['~types']['envSchema'],
117
+ E['~types']['async']
118
+ >
119
+ : PadroneBuilder<
120
+ TProgramName,
121
+ TNameNested,
122
+ TParentPath,
123
+ PadroneSchema<void>,
124
+ void,
125
+ [],
126
+ TParentArgs,
127
+ PadroneSchema<void>,
128
+ PadroneSchema<void>,
129
+ false
130
+ >;
131
+
132
+ export type AnyPadroneBuilder = InitialCommandBuilder<string, string, string, any, [...AnyPadroneCommand[]]>;
133
+
134
+ /**
135
+ * Like InitialCommandBuilder but uses `any` for args/config/env in the fresh case.
136
+ * Used as the default for TBuilder when no builderFn is provided.
137
+ */
138
+ type DefaultCommandBuilder<
139
+ TProgramName extends string,
140
+ TNameNested extends string,
141
+ TParentPath extends string,
142
+ TParentArgs extends PadroneSchema,
143
+ TCommands extends [...AnyPadroneCommand[]],
144
+ > = [FindDirectChild<TCommands, TNameNested>] extends [never]
145
+ ? PadroneBuilder<TProgramName, TNameNested, TParentPath, any, void, [], TParentArgs, any, any, false>
146
+ : FindDirectChild<TCommands, TNameNested> extends infer E extends AnyPadroneCommand
147
+ ? PadroneBuilder<
148
+ TProgramName,
149
+ TNameNested,
150
+ TParentPath,
151
+ E['~types']['argsSchema'],
152
+ E['~types']['result'],
153
+ E['~types']['commands'],
154
+ TParentArgs,
155
+ E['~types']['configSchema'],
156
+ E['~types']['envSchema'],
157
+ E['~types']['async']
158
+ >
159
+ : PadroneBuilder<TProgramName, TNameNested, TParentPath, any, void, [], TParentArgs, any, any, false>;
160
+
33
161
  export type PadroneCommand<
34
162
  TName extends string = string,
35
163
  TParentName extends string = '',
36
- TOpts extends PadroneSchema = PadroneSchema<DefaultOpts>,
164
+ TArgs extends PadroneSchema = PadroneSchema<DefaultArgs>,
37
165
  TRes = void,
38
166
  TCommands extends [...AnyPadroneCommand[]] = [],
39
167
  TAliases extends string[] = string[],
40
- TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = PadroneSchema<void>,
41
- TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = PadroneSchema<void>,
168
+ TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = PadroneSchema<void>,
169
+ TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = PadroneSchema<void>,
170
+ TAsync extends boolean = false,
42
171
  > = {
43
172
  name: TName;
44
173
  path: FullCommandName<TName, TParentName>;
@@ -49,14 +178,25 @@ export type PadroneCommand<
49
178
  aliases?: TAliases;
50
179
  deprecated?: boolean | string;
51
180
  hidden?: boolean;
52
- needsApproval?: boolean | ((options: TOpts) => Promise<boolean> | boolean);
53
- options?: TOpts;
54
- config?: TConfig;
181
+ needsApproval?: boolean | ((args: TArgs) => Promise<boolean> | boolean);
182
+ autoOutput?: boolean;
183
+ argsSchema?: TArgs;
184
+ configSchema?: TConfig;
55
185
  envSchema?: TEnv;
56
- meta?: GetMeta<TOpts>;
57
- handler?: (options: StandardSchemaV1.InferOutput<TOpts>) => TRes;
186
+ meta?: GetArgsMeta<TArgs>;
187
+ action?: (args: StandardSchemaV1.InferOutput<TArgs>, ctx: PadroneActionContext) => TRes;
58
188
  /** List of possible config file names to search for. */
59
189
  configFiles?: string[];
190
+ /** Runtime flag indicating this command uses async validation. Set by `.async()` or `asyncSchema()`. */
191
+ isAsync?: boolean;
192
+ /** Runtime configuration for I/O abstraction. */
193
+ runtime?: PadroneRuntime;
194
+
195
+ /** Plugins registered on this command. Collected from the parent chain at execution time. */
196
+ plugins?: PadronePlugin[];
197
+
198
+ /** Update check configuration. Only used on the root program. */
199
+ updateCheck?: UpdateCheckConfig;
60
200
 
61
201
  parent?: AnyPadroneCommand;
62
202
  commands?: TCommands;
@@ -67,14 +207,18 @@ export type PadroneCommand<
67
207
  parentName: TParentName;
68
208
  path: FullCommandName<TName, TParentName>;
69
209
  aliases: TAliases;
70
- optionsInput: StandardSchemaV1.InferInput<TOpts>;
71
- optionsOutput: StandardSchemaV1.InferOutput<TOpts>;
210
+ argsSchema: TArgs;
211
+ argsInput: StandardSchemaV1.InferInput<TArgs>;
212
+ argsOutput: StandardSchemaV1.InferOutput<TArgs>;
72
213
  result: TRes;
73
214
  commands: TCommands;
215
+ configSchema: TConfig;
216
+ envSchema: TEnv;
217
+ async: TAsync;
74
218
  };
75
219
  };
76
220
 
77
- export type AnyPadroneCommand = PadroneCommand<string, any, any, any, [...AnyPadroneCommand[]], string[]>;
221
+ export type AnyPadroneCommand = PadroneCommand<string, any, any, any, [...AnyPadroneCommand[]], string[], any, any, any>;
78
222
 
79
223
  /**
80
224
  * Base type for extracting command information from builder or program.
@@ -87,7 +231,7 @@ type CommandTypesBase = {
87
231
  };
88
232
 
89
233
  /**
90
- * Configuration options for a command.
234
+ * Configuration for a command.
91
235
  */
92
236
  export type PadroneCommandConfig = {
93
237
  /** A short title for the command, displayed in help. */
@@ -100,6 +244,12 @@ export type PadroneCommandConfig = {
100
244
  deprecated?: boolean | string;
101
245
  /** Whether the command should be hidden from help output. */
102
246
  hidden?: boolean;
247
+ /**
248
+ * Automatically write this command's return value to output in CLI/eval/REPL mode.
249
+ * Overrides the `autoOutput` setting in eval/cli preferences for this command.
250
+ * See `PadroneEvalPreferences.autoOutput` for serialization details.
251
+ */
252
+ autoOutput?: boolean;
103
253
  };
104
254
 
105
255
  /**
@@ -111,58 +261,122 @@ type BuilderOrProgram<
111
261
  TProgramName extends string,
112
262
  TName extends string,
113
263
  TParentName extends string,
114
- TOpts extends PadroneSchema,
264
+ TArgs extends PadroneSchema,
115
265
  TRes,
116
266
  TCommands extends [...AnyPadroneCommand[]],
117
- TParentOpts extends PadroneSchema,
118
- TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>>,
119
- TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>>,
267
+ TParentArgs extends PadroneSchema,
268
+ TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>>,
269
+ TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>>,
270
+ TAsync extends boolean,
120
271
  > = TReturn extends 'builder'
121
- ? PadroneBuilder<TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TConfig, TEnv>
122
- : PadroneProgram<TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TConfig, TEnv>;
272
+ ? PadroneBuilder<TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>
273
+ : PadroneProgram<TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
123
274
 
124
275
  /**
125
276
  * Base builder methods shared between PadroneBuilder and PadroneProgram.
126
- * These methods are used for defining command structure (options, config, env, action, subcommands).
277
+ * These methods are used for defining command structure (arguments, config, env, action, subcommands).
127
278
  */
128
279
  export type PadroneBuilderMethods<
129
280
  TProgramName extends string,
130
281
  TName extends string,
131
282
  TParentName extends string,
132
- TOpts extends PadroneSchema,
283
+ TArgs extends PadroneSchema,
133
284
  TRes,
134
285
  TCommands extends [...AnyPadroneCommand[]],
135
- TParentOpts extends PadroneSchema,
136
- TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>>,
137
- TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>>,
286
+ TParentArgs extends PadroneSchema,
287
+ TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>>,
288
+ TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>>,
289
+ TAsync extends boolean,
138
290
  /** The return type for builder methods - either PadroneBuilder or PadroneProgram */
139
291
  TReturn extends 'builder' | 'program',
140
292
  > = {
141
293
  /**
142
- * Configures command properties like title, description, version, deprecated, and hidden.
294
+ * Enables automatic update checking against a package registry.
295
+ * When enabled, the program checks for a newer version in the background
296
+ * and displays a notification after command output if an update is available.
297
+ *
298
+ * - Non-blocking: check runs in background, never delays command execution.
299
+ * - Non-intrusive: shows a one-line notice after command output, not before.
300
+ * - Respects CI: disabled when `CI=true` or non-TTY.
301
+ * - Respects user preference: `--no-update-check` flag or env var.
302
+ * - Caches last check timestamp to avoid hitting the registry on every invocation.
303
+ *
143
304
  * @example
144
305
  * ```ts
145
- * .configure({
146
- * title: 'Build Project',
147
- * description: 'Compiles the project',
148
- * deprecated: 'Use "compile" instead',
149
- * })
306
+ * createPadrone('myapp')
307
+ * .version('1.2.3')
308
+ * .updateCheck({
309
+ * registry: 'npm', // or custom URL
310
+ * interval: '1d', // check at most once per day
311
+ * cache: '~/.myapp-update' // where to store last check
312
+ * })
150
313
  * ```
151
314
  */
315
+ updateCheck: (
316
+ config?: UpdateCheckConfig,
317
+ ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
318
+
319
+ /**
320
+ * Registers a plugin that intercepts command execution phases (parse, validate, execute).
321
+ * Plugins are applied in order: first registered = outermost wrapper (runs first before `next()`).
322
+ * Use `plugin.order` for explicit ordering (lower = outermost).
323
+ *
324
+ * On the program, parse/validate/execute plugins all apply.
325
+ * On subcommands, only validate and execute plugins apply (parse is handled by the root program).
326
+ */
327
+ use: (
328
+ plugin: PadronePlugin,
329
+ ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
330
+
152
331
  configure: (
153
332
  config: PadroneCommandConfig,
154
- ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TConfig, TEnv>;
333
+ ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
334
+
335
+ /**
336
+ * Configures the runtime adapter for I/O abstraction.
337
+ * Allows the CLI framework to work outside of a terminal (e.g., web UIs, chat interfaces, testing).
338
+ * Unspecified fields fall back to the Node.js/Bun defaults.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * .runtime({
343
+ * output: (text) => panel.append(text),
344
+ * error: (text) => panel.appendError(text),
345
+ * format: 'html',
346
+ * })
347
+ * ```
348
+ */
349
+ runtime: (
350
+ runtime: PadroneRuntime,
351
+ ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
155
352
 
156
353
  /**
157
- * Defines the options schema for the command, including positional arguments.
158
- * Can accept either a schema directly or a function that takes parent options as a base and returns a schema.
159
- * Use the `positional` array in meta to specify which options are positional args.
354
+ * Explicitly marks this command as using async validation.
355
+ * When a command is async, `parse()` and `cli()` return Promises.
356
+ *
357
+ * This is an alternative to using `asyncSchema()` on individual schemas.
358
+ * Use this when your schema has async refinements but you don't want to
359
+ * (or can't) brand the schema itself.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * .arguments(z.object({ name: z.string() }).check(async (v) => { ... }))
364
+ * .async()
365
+ * .action((args) => { ... })
366
+ * ```
367
+ */
368
+ async: () => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, true>;
369
+
370
+ /**
371
+ * Defines the arguments schema for the command, including positional arguments.
372
+ * Can accept either a schema directly or a function that takes parent args schema as a base and returns a schema.
373
+ * Use the `positional` array in meta to specify which arguments are positional args.
160
374
  * Use '...name' prefix for variadic (rest) arguments, matching JS/TS rest syntax.
161
375
  *
162
376
  * @example
163
377
  * ```ts
164
378
  * // Direct schema
165
- * .options(z.object({
379
+ * .arguments(z.object({
166
380
  * source: z.string(),
167
381
  * files: z.string().array(),
168
382
  * dest: z.string(),
@@ -174,19 +388,31 @@ export type PadroneBuilderMethods<
174
388
  *
175
389
  * @example
176
390
  * ```ts
177
- * // Function-based schema extending parent options
178
- * .options((parentOpts) => {
391
+ * // Function-based schema extending parent arguments
392
+ * .arguments((parentArgs) => {
179
393
  * return z.object({
180
- * ...parentOpts.shape,
394
+ * ...parentArgs.shape,
181
395
  * verbose: z.boolean().default(false),
182
396
  * });
183
397
  * })
184
398
  * ```
185
399
  */
186
- options: <TNewOpts extends PadroneSchema = PadroneSchema<void>>(
187
- options?: TNewOpts | ((parentOptions: TParentOpts) => TNewOpts),
188
- meta?: GetMeta<TNewOpts>,
189
- ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TNewOpts, TRes, TCommands, TParentOpts, TConfig, TEnv>;
400
+ arguments: <TNewArgs extends PadroneSchema = PadroneSchema<void>, TMeta extends GetArgsMeta<TNewArgs> = GetArgsMeta<TNewArgs>>(
401
+ schema?: TNewArgs | ((parentSchema: TParentArgs) => TNewArgs),
402
+ meta?: TMeta,
403
+ ) => BuilderOrProgram<
404
+ TReturn,
405
+ TProgramName,
406
+ TName,
407
+ TParentName,
408
+ TNewArgs,
409
+ TRes,
410
+ TCommands,
411
+ TParentArgs,
412
+ TConfig,
413
+ TEnv,
414
+ OrAsyncMeta<OrAsync<TAsync, TNewArgs>, TMeta>
415
+ >;
190
416
 
191
417
  /**
192
418
  * Configures config file path(s) and schema for parsing config files.
@@ -195,39 +421,152 @@ export type PadroneBuilderMethods<
195
421
  * .configFile('config.json', z.object({ port: z.number() }))
196
422
  * ```
197
423
  */
198
- configFile: <TNewConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = TOpts>(
424
+ configFile: <TNewConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = TArgs>(
199
425
  file: string | string[] | undefined,
200
- schema?: TNewConfig | ((optionsSchema: TOpts) => TNewConfig),
201
- ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TNewConfig, TEnv>;
426
+ schema?: TNewConfig | ((argsSchema: TArgs) => TNewConfig),
427
+ ) => BuilderOrProgram<
428
+ TReturn,
429
+ TProgramName,
430
+ TName,
431
+ TParentName,
432
+ TArgs,
433
+ TRes,
434
+ TCommands,
435
+ TParentArgs,
436
+ TNewConfig,
437
+ TEnv,
438
+ OrAsync<TAsync, TNewConfig>
439
+ >;
202
440
 
203
441
  /**
204
- * Configures environment variable schema for parsing env vars into options.
442
+ * Configures environment variable schema for parsing env vars into arguments.
205
443
  * The schema should transform environment variables (typically SCREAMING_SNAKE_CASE)
206
- * into the option names used by the command.
444
+ * into the argument names used by the command.
207
445
  * @example
208
446
  * ```ts
209
447
  * .env(z.object({ MY_APP_PORT: z.coerce.number() }).transform(e => ({ port: e.MY_APP_PORT })))
210
448
  * ```
211
449
  */
212
- env: <TNewEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = TOpts>(
213
- schema: TNewEnv | ((optionsSchema: TOpts) => TNewEnv),
214
- ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TConfig, TNewEnv>;
450
+ env: <TNewEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = TArgs>(
451
+ schema: TNewEnv | ((argsSchema: TArgs) => TNewEnv),
452
+ ) => BuilderOrProgram<
453
+ TReturn,
454
+ TProgramName,
455
+ TName,
456
+ TParentName,
457
+ TArgs,
458
+ TRes,
459
+ TCommands,
460
+ TParentArgs,
461
+ TConfig,
462
+ TNewEnv,
463
+ OrAsync<TAsync, TNewEnv>
464
+ >;
215
465
 
216
466
  /**
217
467
  * Defines the handler function to be executed when the command is run.
468
+ * When overriding an existing command, the previous handler is passed as the third `base` parameter.
218
469
  */
219
470
  action: <TNewRes>(
220
- handler?: (options: StandardSchemaV1.InferOutput<TOpts>) => TNewRes,
221
- ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TOpts, TNewRes, TCommands, TParentOpts, TConfig, TEnv>;
471
+ handler?: (
472
+ args: StandardSchemaV1.InferOutput<TArgs>,
473
+ ctx: PadroneActionContext,
474
+ base: (args: StandardSchemaV1.InferOutput<TArgs>, ctx: PadroneActionContext) => TRes,
475
+ ) => TNewRes,
476
+ ) => BuilderOrProgram<TReturn, TProgramName, TName, TParentName, TArgs, TNewRes, TCommands, TParentArgs, TConfig, TEnv, TAsync>;
477
+
478
+ /**
479
+ * Wraps an external CLI tool with optional schema transformation.
480
+ * The config can include a schema that transforms command arguments to external CLI arguments.
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * // No transformation - pass arguments as-is
485
+ * .arguments(z.object({
486
+ * message: z.string(),
487
+ * }))
488
+ * .wrap({
489
+ * command: 'echo',
490
+ * })
491
+ * ```
492
+ *
493
+ * @example
494
+ * ```ts
495
+ * // With transformation schema
496
+ * .arguments(z.object({
497
+ * message: z.string(),
498
+ * all: z.boolean().optional(),
499
+ * }), {
500
+ * positional: ['message'],
501
+ * })
502
+ * .wrap({
503
+ * command: 'git',
504
+ * args: ['commit'],
505
+ * positional: ['m'],
506
+ * schema: z.object({
507
+ * message: z.string(),
508
+ * all: z.boolean().optional(),
509
+ * }).transform(args => ({
510
+ * m: args.message,
511
+ * a: args.all,
512
+ * })),
513
+ * })
514
+ * ```
515
+ *
516
+ * @example
517
+ * ```ts
518
+ * // Using function-based schema for type inference
519
+ * .arguments(z.object({
520
+ * image: z.string(),
521
+ * detach: z.boolean().optional(),
522
+ * }))
523
+ * .wrap({
524
+ * command: 'docker',
525
+ * args: ['run'],
526
+ * positional: ['image'],
527
+ * schema: (schema) => schema.transform(args => ({
528
+ * d: args.detach,
529
+ * image: args.image,
530
+ * })),
531
+ * })
532
+ * ```
533
+ */
534
+ wrap: <TWrapArgs extends PadroneSchema = TArgs>(
535
+ config: WrapConfig<TArgs, TWrapArgs>,
536
+ ) => BuilderOrProgram<
537
+ TReturn,
538
+ TProgramName,
539
+ TName,
540
+ TParentName,
541
+ TArgs,
542
+ Promise<WrapResult>,
543
+ TCommands,
544
+ TParentArgs,
545
+ TConfig,
546
+ TEnv,
547
+ TAsync
548
+ >;
222
549
 
223
550
  /**
224
- * Creates a nested command within the current command with the given name and builder function.
225
- * The name can be a single string or a tuple of [name, ...aliases] where additional strings are aliases.
551
+ * Creates or extends a nested command within the current command.
552
+ * If a command with the same name already exists, it is extended:
553
+ * - Configuration is merged (new values override old).
554
+ * - The builder callback receives a builder pre-populated with the existing command's state.
555
+ * - `.action()` receives the previous handler as the third `base` parameter.
556
+ * - `.arguments()` callback receives the existing schema as its parameter.
557
+ * - Subcommands are recursively merged by name.
558
+ *
226
559
  * @example
227
560
  * ```ts
228
- * // Single name
561
+ * // Fresh command
229
562
  * .command('list', (c) => c.action(() => 'list'))
230
563
  *
564
+ * // Override — extend an existing command
565
+ * .command('list', (c) => c.action((args, ctx, base) => {
566
+ * const original = base(args, ctx);
567
+ * return `modified: ${original}`;
568
+ * }))
569
+ *
231
570
  * // Name with aliases
232
571
  * .command(['list', 'ls', 'l'], (c) => c.action(() => 'list'))
233
572
  * ```
@@ -235,47 +574,135 @@ export type PadroneBuilderMethods<
235
574
  command: <
236
575
  TNameNested extends string,
237
576
  TAliases extends string[] = [],
238
- TBuilder extends CommandTypesBase = PadroneBuilder<
577
+ TBuilder extends CommandTypesBase = DefaultCommandBuilder<
239
578
  TProgramName,
240
579
  TNameNested,
241
580
  FullCommandName<TName, TParentName>,
242
- any,
243
- void,
244
- [],
245
- TOpts,
246
- any,
247
- any
581
+ TArgs,
582
+ TCommands
248
583
  >,
249
584
  >(
250
585
  name: TNameNested | readonly [TNameNested, ...TAliases],
251
586
  builderFn?: (
252
- builder: PadroneBuilder<
253
- TProgramName,
254
- TNameNested,
255
- FullCommandName<TName, TParentName>,
256
- PadroneSchema<void>,
257
- void,
258
- [],
259
- TOpts,
260
- PadroneSchema<void>,
261
- PadroneSchema<void>
262
- >,
587
+ builder: InitialCommandBuilder<TProgramName, TNameNested, FullCommandName<TName, TParentName>, TArgs, TCommands>,
263
588
  ) => TBuilder,
264
589
  ) => BuilderOrProgram<
265
590
  TReturn,
266
591
  TProgramName,
267
592
  TName,
268
593
  TParentName,
269
- TOpts,
594
+ TArgs,
270
595
  TRes,
271
596
  TCommands extends []
272
597
  ? [WithAliases<TBuilder['~types']['command'], TAliases>]
273
598
  : AnyPadroneCommand[] extends TCommands
274
599
  ? [WithAliases<TBuilder['~types']['command'], TAliases>]
275
- : [...TCommands, WithAliases<TBuilder['~types']['command'], TAliases>],
276
- TParentOpts,
600
+ : ReplaceOrAppendCommand<
601
+ TCommands,
602
+ TNameNested,
603
+ WithAliases<TBuilder['~types']['command'], ResolvedAliases<TCommands, TNameNested, TAliases>>
604
+ >,
605
+ TParentArgs,
606
+ TConfig,
607
+ TEnv,
608
+ TAsync
609
+ >;
610
+
611
+ /**
612
+ * Mounts an existing Padrone program as a subcommand.
613
+ * The program's root-level properties (name, path, parent) are replaced to fit the mount point.
614
+ * All subcommands are recursively re-pathed. Root-level `version` is dropped.
615
+ *
616
+ * @example
617
+ * ```ts
618
+ * const admin = createPadrone('admin')
619
+ * .command('users', (c) => c.action(() => 'users'))
620
+ * .command('roles', (c) => c.action(() => 'roles'));
621
+ *
622
+ * const app = createPadrone('app')
623
+ * .mount('admin', admin)
624
+ * // Now: app admin users, app admin roles
625
+ *
626
+ * // With aliases
627
+ * const app2 = createPadrone('app')
628
+ * .mount(['admin', 'adm'], admin)
629
+ * ```
630
+ */
631
+ mount: <TNameNested extends string, TAliases extends string[] = [], TProgram extends CommandTypesBase = CommandTypesBase>(
632
+ name: TNameNested | readonly [TNameNested, ...TAliases],
633
+ program: TProgram,
634
+ ) => BuilderOrProgram<
635
+ TReturn,
636
+ TProgramName,
637
+ TName,
638
+ TParentName,
639
+ TArgs,
640
+ TRes,
641
+ TCommands extends []
642
+ ? [
643
+ WithAliases<
644
+ PadroneCommand<
645
+ TNameNested,
646
+ FullCommandName<TName, TParentName>,
647
+ TProgram['~types']['command']['~types']['argsSchema'],
648
+ TProgram['~types']['command']['~types']['result'],
649
+ RepathCommands<
650
+ TProgram['~types']['command']['~types']['commands'],
651
+ FullCommandName<TNameNested, FullCommandName<TName, TParentName>>
652
+ >,
653
+ [],
654
+ TProgram['~types']['command']['~types']['configSchema'],
655
+ TProgram['~types']['command']['~types']['envSchema'],
656
+ TProgram['~types']['command']['~types']['async']
657
+ >,
658
+ TAliases
659
+ >,
660
+ ]
661
+ : AnyPadroneCommand[] extends TCommands
662
+ ? [
663
+ WithAliases<
664
+ PadroneCommand<
665
+ TNameNested,
666
+ FullCommandName<TName, TParentName>,
667
+ TProgram['~types']['command']['~types']['argsSchema'],
668
+ TProgram['~types']['command']['~types']['result'],
669
+ RepathCommands<
670
+ TProgram['~types']['command']['~types']['commands'],
671
+ FullCommandName<TNameNested, FullCommandName<TName, TParentName>>
672
+ >,
673
+ [],
674
+ TProgram['~types']['command']['~types']['configSchema'],
675
+ TProgram['~types']['command']['~types']['envSchema'],
676
+ TProgram['~types']['command']['~types']['async']
677
+ >,
678
+ TAliases
679
+ >,
680
+ ]
681
+ : ReplaceOrAppendCommand<
682
+ TCommands,
683
+ TNameNested,
684
+ WithAliases<
685
+ PadroneCommand<
686
+ TNameNested,
687
+ FullCommandName<TName, TParentName>,
688
+ TProgram['~types']['command']['~types']['argsSchema'],
689
+ TProgram['~types']['command']['~types']['result'],
690
+ RepathCommands<
691
+ TProgram['~types']['command']['~types']['commands'],
692
+ FullCommandName<TNameNested, FullCommandName<TName, TParentName>>
693
+ >,
694
+ [],
695
+ TProgram['~types']['command']['~types']['configSchema'],
696
+ TProgram['~types']['command']['~types']['envSchema'],
697
+ TProgram['~types']['command']['~types']['async']
698
+ >,
699
+ ResolvedAliases<TCommands, TNameNested, TAliases>
700
+ >
701
+ >,
702
+ TParentArgs,
277
703
  TConfig,
278
- TEnv
704
+ TEnv,
705
+ TAsync
279
706
  >;
280
707
 
281
708
  /** @deprecated Internal use only */
@@ -285,10 +712,11 @@ export type PadroneBuilderMethods<
285
712
  parentName: TParentName;
286
713
  path: FullCommandName<TName, TParentName>;
287
714
  aliases: [];
288
- options: TOpts;
715
+ argsSchema: TArgs;
289
716
  result: TRes;
290
717
  commands: TCommands;
291
- command: PadroneCommand<TName, TParentName, TOpts, TRes, TCommands, []>;
718
+ async: TAsync;
719
+ command: PadroneCommand<TName, TParentName, TArgs, TRes, TCommands, [], TConfig, TEnv, TAsync>;
292
720
  };
293
721
  };
294
722
 
@@ -296,80 +724,116 @@ export type PadroneBuilder<
296
724
  TProgramName extends string = '',
297
725
  TName extends string = string,
298
726
  TParentName extends string = '',
299
- TOpts extends PadroneSchema = PadroneSchema<DefaultOpts>,
727
+ TArgs extends PadroneSchema = PadroneSchema<DefaultArgs>,
300
728
  TRes = void,
301
729
  TCommands extends [...AnyPadroneCommand[]] = [],
302
- TParentOpts extends PadroneSchema = PadroneSchema<void>,
303
- TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = PadroneSchema<void>,
304
- TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = PadroneSchema<void>,
305
- > = PadroneBuilderMethods<TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TConfig, TEnv, 'builder'>;
730
+ TParentArgs extends PadroneSchema = PadroneSchema<void>,
731
+ TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = PadroneSchema<void>,
732
+ TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = PadroneSchema<void>,
733
+ TAsync extends boolean = false,
734
+ > = PadroneBuilderMethods<TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync, 'builder'>;
306
735
 
307
736
  export type PadroneProgram<
308
737
  TProgramName extends string = '',
309
738
  TName extends string = string,
310
739
  TParentName extends string = '',
311
- TOpts extends PadroneSchema = PadroneSchema<DefaultOpts>,
740
+ TArgs extends PadroneSchema = PadroneSchema<DefaultArgs>,
312
741
  TRes = void,
313
742
  TCommands extends [...AnyPadroneCommand[]] = [],
314
- TParentOpts extends PadroneSchema = PadroneSchema<void>,
315
- TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = PadroneSchema<void>,
316
- TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TOpts>> = PadroneSchema<void>,
317
- > = PadroneBuilderMethods<TProgramName, TName, TParentName, TOpts, TRes, TCommands, TParentOpts, TConfig, TEnv, 'program'> & {
743
+ TParentArgs extends PadroneSchema = PadroneSchema<void>,
744
+ TConfig extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = PadroneSchema<void>,
745
+ TEnv extends PadroneSchema<unknown, StandardSchemaV1.InferInput<TArgs>> = PadroneSchema<void>,
746
+ TAsync extends boolean = false,
747
+ > = PadroneBuilderMethods<TProgramName, TName, TParentName, TArgs, TRes, TCommands, TParentArgs, TConfig, TEnv, TAsync, 'program'> & {
318
748
  /**
319
- * Runs a command programmatically by name with provided options (including positional args).
749
+ * Runs a command programmatically by name with provided arguments (including positional args).
320
750
  */
321
- run: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], true, true>>(
751
+ run: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], true, true>>(
322
752
  name: TCommand | SafeString,
323
- options: NoInfer<GetOptions<'in', PickCommandByName<[PadroneCommand<'', '', TOpts, TRes, TCommands>], TCommand>>>,
324
- ) => PadroneCommandResult<PickCommandByName<[PadroneCommand<'', '', TOpts, TRes, TCommands>], TCommand>>;
753
+ args: NoInfer<GetArguments<'in', PickCommandByName<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>>>,
754
+ ) => PadroneCommandResult<PickCommandByName<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>>;
325
755
 
326
756
  /**
327
- * Runs the program as a CLI application, parsing `process.argv` or provided input.
757
+ * Evaluates a command string: parses, validates, and executes.
758
+ * On validation errors, returns a result with issues instead of throwing.
759
+ * This is the method used by `repl()` internally, and the right choice for
760
+ * programmatic invocation, testing, chat interfaces, or any context where
761
+ * you have a command string and want a result — not a process exit.
762
+ *
763
+ * @example
764
+ * ```ts
765
+ * const result = await program.eval('greet --name Alice');
766
+ * if (result.argsResult?.issues) { /* handle validation errors *\/ }
767
+ * ```
328
768
  */
329
- cli: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], true, true>>(
330
- input?: TCommand | SafeString,
331
- options?: PadroneParseOptions,
332
- ) => PadroneCommandResult<PickCommandByPossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], TCommand>>;
769
+ eval: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], true, true>>(
770
+ input: TCommand | SafeString,
771
+ prefs?: PadroneEvalPreferences,
772
+ ) => MaybePromise<
773
+ PadroneCommandResult<PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>>,
774
+ PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>['~types']['async']
775
+ >;
333
776
 
334
777
  /**
335
- * Parses CLI input (or the provided input string) into command, args, and options without executing anything.
778
+ * Runs the program as a CLI entry point, parsing `process.argv`.
779
+ * On validation errors, throws and prints help.
780
+ * For programmatic invocation with a command string, use `eval()` instead.
336
781
  */
337
- parse: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], true, false>>(
782
+ cli: (
783
+ prefs?: PadroneCliPreferences<PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>,
784
+ ) => MaybePromise<PadroneCommandResult<FlattenCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>, TAsync>;
785
+
786
+ /**
787
+ * Parses CLI input (or the provided input string) into command and arguments without executing anything.
788
+ */
789
+ parse: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], true, false>>(
338
790
  input?: TCommand | SafeString,
339
- options?: PadroneParseOptions,
340
- ) => PadroneParseResult<PickCommandByPossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], TCommand>>;
791
+ ) => MaybePromise<
792
+ PadroneParseResult<PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>>,
793
+ PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>['~types']['async']
794
+ >;
341
795
 
342
796
  /**
343
- * Converts command and options back into a CLI string.
797
+ * Converts command and arguments back into a CLI string.
344
798
  */
345
- stringify: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], false, true>>(
799
+ stringify: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], false, true>>(
346
800
  command?: TCommand | SafeString,
347
- options?: GetOptions<'out', PickCommandByPossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], TCommand>>,
801
+ args?: GetArguments<'out', PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TCommand>>,
348
802
  ) => string;
349
803
 
350
804
  /**
351
805
  * Finds a command by name, returning `undefined` if not found.
352
806
  */
353
- find: <const TFind extends PossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], false, true>>(
807
+ find: <const TFind extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], false, true>>(
354
808
  command: TFind | SafeString,
355
- ) => PickCommandByPossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], TFind> | undefined;
809
+ ) => PickCommandByPossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], TFind> | undefined;
356
810
 
357
811
  /**
358
812
  * Generates a type-safe API for invoking commands programmatically.
359
813
  */
360
- api: () => PadroneAPI<PadroneCommand<'', '', TOpts, TRes, TCommands>>;
361
-
362
- // TODO: implement interactive and repl methods
363
-
364
- /**
365
- * Starts an interactive prompt to run commands.
366
- */
367
- // interactive: () => Promise<PadroneCommandResult<FlattenCommands<[TCmd]>> | undefined>;
814
+ api: () => PadroneAPI<PadroneCommand<'', '', TArgs, TRes, TCommands>>;
368
815
 
369
816
  /**
370
817
  * Starts a REPL (Read-Eval-Print Loop) for running commands interactively.
818
+ * Returns an AsyncIterable that yields a `PadroneCommandResult` for each successfully executed command.
819
+ * Errors are printed via `runtime.error()` and the loop continues.
820
+ * The loop ends when the user sends EOF (Ctrl+D), types `.exit`/`.quit`,
821
+ * or presses Ctrl+C twice within 2 seconds.
822
+ *
823
+ * @example
824
+ * ```ts
825
+ * for await (const result of program.repl()) {
826
+ * console.log(result.command.name, result.result);
827
+ * }
828
+ * ```
829
+ *
830
+ * TODO: REPL future enhancements:
831
+ * - History persistence: save/load history across sessions (currently in-memory only)
832
+ * - Middleware/hooks: onBeforeCommand, onAfterCommand, error interceptors (design alongside general middleware system)
371
833
  */
372
- // repl: () => Promise<PadroneCommandResult<FlattenCommands<[TCmd]>>[]>;
834
+ repl: (
835
+ options?: PadroneReplPreferences<PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>,
836
+ ) => AsyncIterable<PadroneCommandResult<FlattenCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>]>>>;
373
837
 
374
838
  /**
375
839
  * Returns a tool definition that can be passed to AI SDK.
@@ -379,9 +843,9 @@ export type PadroneProgram<
379
843
  /**
380
844
  * Returns the help information for the program or a specific command.
381
845
  */
382
- help: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TOpts, TRes, TCommands>], false, true>>(
846
+ help: <const TCommand extends PossibleCommands<[PadroneCommand<'', '', TArgs, TRes, TCommands>], false, true>>(
383
847
  command?: TCommand,
384
- options?: HelpOptions,
848
+ prefs?: HelpPreferences,
385
849
  ) => string;
386
850
 
387
851
  /**
@@ -398,53 +862,248 @@ export type PadroneProgram<
398
862
  * const script = program.completion();
399
863
  * ```
400
864
  */
401
- completion: (shell?: 'bash' | 'zsh' | 'fish' | 'powershell') => string;
865
+ completion: (shell?: 'bash' | 'zsh' | 'fish' | 'powershell') => Promise<string>;
402
866
  };
403
867
 
404
868
  export type AnyPadroneProgram = PadroneProgram<string, string, string, any, any, [...AnyPadroneCommand[]]>;
405
869
 
406
- export type PadroneCommandResult<TCommand extends AnyPadroneCommand = AnyPadroneCommand> = PadroneParseResult<TCommand> & {
407
- result: GetResults<TCommand>;
870
+ /**
871
+ * Options for `repl()` to customize the REPL session.
872
+ */
873
+ /** A single spacing value: blank line (`true`), separator string, or an array of these for multiple lines. */
874
+ export type PadroneReplSpacing = boolean | string | (boolean | string)[];
875
+
876
+ export type PadroneReplPreferences<TScope extends string = string> = {
877
+ /** The prompt string displayed before each input, or a function returning it. Defaults to `"<programName>> "`. */
878
+ prompt?: string | (() => string);
879
+ /**
880
+ * A greeting message displayed when the REPL starts.
881
+ * When not provided, defaults to `"Welcome to <name> v<version>"` (or just `"Welcome to <name>"` if no version).
882
+ * Set to `false` to suppress the default greeting entirely.
883
+ */
884
+ greeting?: string | false;
885
+ /**
886
+ * A hint message displayed below the greeting in dimmed text.
887
+ * When not provided, defaults to `'Type ".help" for more information, ".exit" to quit.'`.
888
+ * Set to `false` to suppress the hint.
889
+ */
890
+ hint?: string | false;
891
+ /** Initial history entries (most recent last). Arrow keys navigate history in the terminal. */
892
+ history?: string[];
893
+ /** Set to `false` to disable tab completion. Defaults to `true`. */
894
+ completion?: boolean;
895
+ /**
896
+ * Add spacing/separators around each command's output.
897
+ * A spacing value can be:
898
+ * - `true` — blank line
899
+ * - A string — separator line (single char like `'─'` repeats to terminal width, multi-char prints as-is)
900
+ * - An array of the above — multiple lines in order (e.g. `[true, '─']` for blank line then separator)
901
+ *
902
+ * Shorthand applies to both before and after. Use `{ before?, after? }` for independent control.
903
+ */
904
+ spacing?: PadroneReplSpacing | { before?: PadroneReplSpacing; after?: PadroneReplSpacing };
905
+ /** Prefix each line of command output/error with this string (e.g. `'│ '`, `' '`, `'▎ '`). */
906
+ outputPrefix?: string;
907
+ /**
908
+ * Start the REPL scoped to a command subtree. The scope path is a space-separated command path
909
+ * (e.g. `'db'` or `'db migrate'`). Commands are resolved relative to the scoped command.
910
+ * Users can change scope at runtime with `.scope <subcommand>` and `.scope ..`/`..`.
911
+ */
912
+ scope?: TScope;
913
+
914
+ /**
915
+ * Automatically write each command's return value to output.
916
+ * See `PadroneEvalPreferences.autoOutput` for details on how values are serialized.
917
+ * Defaults to `true`.
918
+ */
919
+ autoOutput?: boolean;
408
920
  };
409
921
 
410
922
  /**
411
- * Options for parsing CLI input.
923
+ * Options that can be passed to `eval()` to control execution behavior.
412
924
  */
413
- export type PadroneParseOptions = {
925
+ export type PadroneEvalPreferences = {
414
926
  /**
415
- * Raw environment variables to use for env schema validation.
416
- * If not provided, process.env will be used.
927
+ * Controls interactive prompting for this execution.
928
+ * Overrides the runtime's `interactive` setting, but is itself overridden by `--interactive` / `-i` flags.
929
+ *
930
+ * - `undefined`: inherit from runtime (default).
931
+ * - `true`: force prompting for all configured interactive fields, even if values are already provided.
932
+ * - `false`: suppress all interactive prompts.
417
933
  */
418
- env?: Record<string, string | undefined>;
934
+ interactive?: boolean;
935
+
419
936
  /**
420
- * Pre-parsed environment data to use directly (bypasses env schema validation).
421
- * Keys should match option names.
937
+ * Automatically write the command's return value to output.
938
+ *
939
+ * - Values are passed directly to the runtime's `output` function (no stringification).
940
+ * - Promises are awaited before output.
941
+ * - Iterators and async iterators are consumed, outputting each yielded value as it arrives.
942
+ * - `undefined` and `null` results produce no output.
943
+ *
944
+ * Defaults to `true`. Set to `false` to disable.
422
945
  */
423
- envData?: Record<string, unknown>;
946
+ autoOutput?: boolean;
947
+
424
948
  /**
425
- * Config file data to use for config binding.
426
- * This should be the parsed content of a config file (JSON, YAML, etc.).
949
+ * Override the runtime for this execution.
950
+ * Partial only the provided fields replace the command's resolved runtime.
951
+ * Useful for capturing output, injecting test doubles, or running in non-terminal contexts (e.g. AI tool calls).
427
952
  */
428
- configData?: Record<string, unknown>;
953
+ runtime?: PadroneRuntime;
954
+ };
955
+
956
+ /**
957
+ * Options that can be passed to `cli()` to control execution behavior.
958
+ */
959
+ export type PadroneCliPreferences<TScope extends string = string> = PadroneEvalPreferences & {
960
+ /** REPL preferences used when `--repl` flag is passed. Set to `false` to disable the `--repl` flag. */
961
+ repl?: PadroneReplPreferences<TScope> | false;
962
+ };
963
+
964
+ export type PadroneCommandResult<TCommand extends AnyPadroneCommand = AnyPadroneCommand> = PadroneParseResult<TCommand> & {
965
+ result: GetResults<TCommand>;
429
966
  };
430
967
 
431
968
  export type PadroneParseResult<TCommand extends AnyPadroneCommand = AnyPadroneCommand> = {
432
969
  command: TCommand;
433
- options?: GetOptions<'out', TCommand>;
434
- optionsResult?: StandardSchemaV1.Result<GetOptions<'out', TCommand>>;
970
+ args?: GetArguments<'out', TCommand>;
971
+ argsResult?: StandardSchemaV1.Result<GetArguments<'out', TCommand>>;
435
972
  };
436
973
 
437
974
  export type PadroneAPI<TCommand extends AnyPadroneCommand> = PadroneAPICommand<TCommand> & {
438
975
  [K in TCommand['~types']['commands'][number] as K['name']]: PadroneAPI<K>;
439
976
  };
440
977
 
441
- type PadroneAPICommand<TCommand extends AnyPadroneCommand> = (options: GetOptions<'in', TCommand>) => GetResults<TCommand>;
978
+ type PadroneAPICommand<TCommand extends AnyPadroneCommand> = (args: GetArguments<'in', TCommand>) => GetResults<TCommand>;
979
+
980
+ type NormalizeArguments<TArgs> = IsGeneric<TArgs> extends true ? void | EmptyRecord : TArgs;
981
+ type GetArguments<TDir extends 'in' | 'out', TCommand extends AnyPadroneCommand> = TDir extends 'in'
982
+ ? NormalizeArguments<TCommand['~types']['argsInput']>
983
+ : NormalizeArguments<TCommand['~types']['argsOutput']>;
442
984
 
443
- type NormalizeOptions<TOptions> = IsGeneric<TOptions> extends true ? void | EmptyRecord : TOptions;
444
- type GetOptions<TDir extends 'in' | 'out', TCommand extends AnyPadroneCommand> = TDir extends 'in'
445
- ? NormalizeOptions<TCommand['~types']['optionsInput']>
446
- : NormalizeOptions<TCommand['~types']['optionsOutput']>;
985
+ type GetResults<TCommand extends AnyPadroneCommand> = ReturnType<NonNullable<TCommand['action']>>;
447
986
 
448
- type GetResults<TCommand extends AnyPadroneCommand> = ReturnType<NonNullable<TCommand['handler']>>;
987
+ type GetArgsMeta<TArgs extends PadroneSchema> = PadroneArgsSchemaMeta<NonNullable<StandardSchemaV1.InferInput<TArgs>>>;
988
+
989
+ // ---------------------------------------------------------------------------
990
+ // Plugin system
991
+ // ---------------------------------------------------------------------------
992
+
993
+ /** Base context shared across all plugin phases within a single execution. */
994
+ export type PluginBaseContext = {
995
+ /** The resolved command for this execution. In the parse phase, this is the root program. */
996
+ command: AnyPadroneCommand;
997
+ /** Mutable state bag shared across phases for this execution. Plugins can store cross-phase data here. */
998
+ state: Record<string, unknown>;
999
+ };
1000
+
1001
+ /** Context for the parse phase. */
1002
+ export type PluginParseContext = PluginBaseContext & {
1003
+ /** The raw CLI input string (undefined when invoked without input). */
1004
+ input: string | undefined;
1005
+ };
1006
+
1007
+ /** Result returned by the parse phase's `next()`. */
1008
+ export type PluginParseResult = {
1009
+ command: AnyPadroneCommand;
1010
+ rawArgs: Record<string, unknown>;
1011
+ positionalArgs: string[];
1012
+ };
449
1013
 
450
- type GetMeta<TOpts extends PadroneSchema> = PadroneMeta<NonNullable<StandardSchemaV1.InferInput<TOpts>>>;
1014
+ /** Context for the validate phase. */
1015
+ export type PluginValidateContext = PluginBaseContext & {
1016
+ /** Raw named arguments extracted by the parser. Mutable — modify before `next()` to inject/override values. */
1017
+ rawArgs: Record<string, unknown>;
1018
+ /** Positional argument strings extracted by the parser. */
1019
+ positionalArgs: string[];
1020
+ };
1021
+
1022
+ /** Result returned by the validate phase's `next()`. */
1023
+ export type PluginValidateResult = {
1024
+ args: unknown;
1025
+ argsResult: StandardSchemaV1.Result<unknown>;
1026
+ };
1027
+
1028
+ /** Context for the execute phase. */
1029
+ export type PluginExecuteContext = PluginBaseContext & {
1030
+ /** Validated arguments that will be passed to the action. Mutable — modify before `next()` to override. */
1031
+ args: unknown;
1032
+ };
1033
+
1034
+ /** Result returned by the execute phase's `next()`. */
1035
+ export type PluginExecuteResult = {
1036
+ result: unknown;
1037
+ };
1038
+
1039
+ /** Context for the start phase. Runs before parsing, wraps the entire pipeline. */
1040
+ export type PluginStartContext = PluginBaseContext & {
1041
+ /** The raw CLI input string (undefined when invoked without input). */
1042
+ input: string | undefined;
1043
+ };
1044
+
1045
+ /** Context for the error phase. Called when the pipeline throws. */
1046
+ export type PluginErrorContext = PluginBaseContext & {
1047
+ /** The error that was thrown. */
1048
+ error: unknown;
1049
+ };
1050
+
1051
+ /** Result returned by the error phase's `next()`. */
1052
+ export type PluginErrorResult = {
1053
+ /** The error (possibly transformed). Set to `undefined` to suppress the error. */
1054
+ error?: unknown;
1055
+ /** A replacement result when suppressing the error. */
1056
+ result?: unknown;
1057
+ };
1058
+
1059
+ /** Context for the shutdown phase. Always runs after the pipeline (success or failure). */
1060
+ export type PluginShutdownContext = PluginBaseContext & {
1061
+ /** The error, if the pipeline failed (after error phase processing). */
1062
+ error?: unknown;
1063
+ /** The pipeline result, if it succeeded. */
1064
+ result?: unknown;
1065
+ };
1066
+
1067
+ type PluginPhaseHandler<TCtx, TResult> = (ctx: TCtx, next: () => TResult | Promise<TResult>) => TResult | Promise<TResult>;
1068
+
1069
+ /**
1070
+ * 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()` and apply to all commands.
1072
+ *
1073
+ * Each phase handler receives a context and a `next()` function (onion/middleware pattern):
1074
+ * - Call `next()` to proceed to the next plugin or the core operation.
1075
+ * - Return without calling `next()` to short-circuit.
1076
+ * - Wrap `next()` in try/catch for error handling.
1077
+ * - Modify context fields before `next()` to alter inputs.
1078
+ * - Transform the return value of `next()` to alter outputs.
1079
+ */
1080
+ export type PadronePlugin = {
1081
+ /** Unique name for this plugin. Used for identification and future disable/override support. */
1082
+ name: string;
1083
+ /**
1084
+ * Ordering hint. Lower values run as outer layers (earlier before `next()`, later after).
1085
+ * Plugins with the same order preserve registration order. Defaults to `0`.
1086
+ */
1087
+ order?: number;
1088
+ /**
1089
+ * Runs before the pipeline (parse → validate → execute). `next()` proceeds to the pipeline.
1090
+ * Root plugins only. Use for startup tasks like telemetry, update checks, or global config loading.
1091
+ */
1092
+ start?: PluginPhaseHandler<PluginStartContext, unknown>;
1093
+ /** Intercepts command routing and raw argument extraction. */
1094
+ parse?: PluginPhaseHandler<PluginParseContext, PluginParseResult>;
1095
+ /** Intercepts argument preprocessing, interactive prompting, and schema validation. */
1096
+ validate?: PluginPhaseHandler<PluginValidateContext, PluginValidateResult>;
1097
+ /** Intercepts handler execution. */
1098
+ execute?: PluginPhaseHandler<PluginExecuteContext, PluginExecuteResult>;
1099
+ /**
1100
+ * Called when the pipeline throws an error. `next()` passes to the next error handler
1101
+ * (innermost returns `{ error }` unchanged). Return `{ result }` without `error` to suppress.
1102
+ */
1103
+ error?: PluginPhaseHandler<PluginErrorContext, PluginErrorResult>;
1104
+ /**
1105
+ * Always runs after the pipeline completes (success or failure). `next()` calls the next shutdown handler.
1106
+ * Use for cleanup: closing connections, flushing logs, etc.
1107
+ */
1108
+ shutdown?: PluginPhaseHandler<PluginShutdownContext, void>;
1109
+ };