padrone 1.4.0 → 1.6.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 (141) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +108 -283
  3. package/dist/args-Cnq0nwSM.mjs +272 -0
  4. package/dist/args-Cnq0nwSM.mjs.map +1 -0
  5. package/dist/codegen/index.d.mts +28 -3
  6. package/dist/codegen/index.d.mts.map +1 -1
  7. package/dist/codegen/index.mjs +169 -19
  8. package/dist/codegen/index.mjs.map +1 -1
  9. package/dist/commands-B_gufyR9.mjs +514 -0
  10. package/dist/commands-B_gufyR9.mjs.map +1 -0
  11. package/dist/{completion.mjs → completion-BEuflbDO.mjs} +86 -108
  12. package/dist/completion-BEuflbDO.mjs.map +1 -0
  13. package/dist/docs/index.d.mts +22 -2
  14. package/dist/docs/index.d.mts.map +1 -1
  15. package/dist/docs/index.mjs +92 -7
  16. package/dist/docs/index.mjs.map +1 -1
  17. package/dist/errors-CL63UOzt.mjs +137 -0
  18. package/dist/errors-CL63UOzt.mjs.map +1 -0
  19. package/dist/{formatter-ClUK5hcQ.d.mts → formatter-DrvhDMrq.d.mts} +35 -6
  20. package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
  21. package/dist/help-B5Kk83of.mjs +849 -0
  22. package/dist/help-B5Kk83of.mjs.map +1 -0
  23. package/dist/index-BaU3X6dY.d.mts +1178 -0
  24. package/dist/index-BaU3X6dY.d.mts.map +1 -0
  25. package/dist/index.d.mts +763 -36
  26. package/dist/index.d.mts.map +1 -1
  27. package/dist/index.mjs +3608 -1534
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/mcp-BM-d0nZi.mjs +377 -0
  30. package/dist/mcp-BM-d0nZi.mjs.map +1 -0
  31. package/dist/serve-Bk0JUlCj.mjs +402 -0
  32. package/dist/serve-Bk0JUlCj.mjs.map +1 -0
  33. package/dist/stream-DC4H8YTx.mjs +77 -0
  34. package/dist/stream-DC4H8YTx.mjs.map +1 -0
  35. package/dist/test.d.mts +5 -8
  36. package/dist/test.d.mts.map +1 -1
  37. package/dist/test.mjs +5 -27
  38. package/dist/test.mjs.map +1 -1
  39. package/dist/{update-check-EbNDkzyV.mjs → update-check-CZ2VqjnV.mjs} +16 -17
  40. package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
  41. package/dist/zod.d.mts +32 -0
  42. package/dist/zod.d.mts.map +1 -0
  43. package/dist/zod.mjs +50 -0
  44. package/dist/zod.mjs.map +1 -0
  45. package/package.json +20 -9
  46. package/src/cli/completions.ts +14 -11
  47. package/src/cli/docs.ts +13 -16
  48. package/src/cli/doctor.ts +213 -24
  49. package/src/cli/index.ts +28 -82
  50. package/src/cli/init.ts +12 -10
  51. package/src/cli/link.ts +22 -18
  52. package/src/cli/wrap.ts +14 -11
  53. package/src/codegen/discovery.ts +80 -28
  54. package/src/codegen/index.ts +2 -1
  55. package/src/codegen/parsers/bash.ts +179 -0
  56. package/src/codegen/schema-to-code.ts +2 -1
  57. package/src/core/args.ts +296 -0
  58. package/src/core/commands.ts +373 -0
  59. package/src/core/create.ts +268 -0
  60. package/src/{runtime.ts → core/default-runtime.ts} +70 -135
  61. package/src/{errors.ts → core/errors.ts} +22 -0
  62. package/src/core/exec.ts +259 -0
  63. package/src/core/interceptors.ts +302 -0
  64. package/src/{parse.ts → core/parse.ts} +36 -89
  65. package/src/core/program-methods.ts +301 -0
  66. package/src/core/results.ts +229 -0
  67. package/src/core/runtime.ts +246 -0
  68. package/src/core/validate.ts +247 -0
  69. package/src/docs/index.ts +124 -11
  70. package/src/extension/auto-output.ts +95 -0
  71. package/src/extension/color.ts +38 -0
  72. package/src/extension/completion.ts +49 -0
  73. package/src/extension/config.ts +262 -0
  74. package/src/extension/env.ts +101 -0
  75. package/src/extension/help.ts +192 -0
  76. package/src/extension/index.ts +43 -0
  77. package/src/extension/ink.ts +93 -0
  78. package/src/extension/interactive.ts +106 -0
  79. package/src/extension/logger.ts +214 -0
  80. package/src/extension/man.ts +51 -0
  81. package/src/extension/mcp.ts +52 -0
  82. package/src/extension/progress-renderer.ts +338 -0
  83. package/src/extension/progress.ts +299 -0
  84. package/src/extension/repl.ts +94 -0
  85. package/src/extension/serve.ts +48 -0
  86. package/src/extension/signal.ts +87 -0
  87. package/src/extension/stdin.ts +62 -0
  88. package/src/extension/suggestions.ts +114 -0
  89. package/src/extension/timing.ts +81 -0
  90. package/src/extension/tracing.ts +175 -0
  91. package/src/extension/update-check.ts +77 -0
  92. package/src/extension/utils.ts +51 -0
  93. package/src/extension/version.ts +63 -0
  94. package/src/{completion.ts → feature/completion.ts} +130 -57
  95. package/src/{interactive.ts → feature/interactive.ts} +47 -6
  96. package/src/feature/mcp.ts +387 -0
  97. package/src/{repl-loop.ts → feature/repl-loop.ts} +26 -16
  98. package/src/feature/serve.ts +438 -0
  99. package/src/feature/test.ts +262 -0
  100. package/src/{update-check.ts → feature/update-check.ts} +16 -16
  101. package/src/{wrap.ts → feature/wrap.ts} +27 -27
  102. package/src/index.ts +120 -11
  103. package/src/output/colorizer.ts +154 -0
  104. package/src/{formatter.ts → output/formatter.ts} +281 -135
  105. package/src/{help.ts → output/help.ts} +62 -15
  106. package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
  107. package/src/schema/zod.ts +50 -0
  108. package/src/test.ts +2 -285
  109. package/src/types/args-meta.ts +151 -0
  110. package/src/types/builder.ts +697 -0
  111. package/src/types/command.ts +157 -0
  112. package/src/types/index.ts +59 -0
  113. package/src/types/interceptor.ts +296 -0
  114. package/src/types/preferences.ts +83 -0
  115. package/src/types/result.ts +71 -0
  116. package/src/types/schema.ts +19 -0
  117. package/src/util/dotenv.ts +244 -0
  118. package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
  119. package/src/util/stream.ts +101 -0
  120. package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
  121. package/src/{type-utils.ts → util/type-utils.ts} +99 -37
  122. package/src/util/utils.ts +51 -0
  123. package/src/zod.ts +1 -0
  124. package/dist/args-CVDbyyzG.mjs +0 -199
  125. package/dist/args-CVDbyyzG.mjs.map +0 -1
  126. package/dist/chunk-y_GBKt04.mjs +0 -5
  127. package/dist/completion.d.mts +0 -64
  128. package/dist/completion.d.mts.map +0 -1
  129. package/dist/completion.mjs.map +0 -1
  130. package/dist/formatter-ClUK5hcQ.d.mts.map +0 -1
  131. package/dist/help-CcBe91bV.mjs +0 -1254
  132. package/dist/help-CcBe91bV.mjs.map +0 -1
  133. package/dist/types-DjIdJN5G.d.mts +0 -1059
  134. package/dist/types-DjIdJN5G.d.mts.map +0 -1
  135. package/dist/update-check-EbNDkzyV.mjs.map +0 -1
  136. package/src/args.ts +0 -461
  137. package/src/colorizer.ts +0 -41
  138. package/src/command-utils.ts +0 -532
  139. package/src/create.ts +0 -1477
  140. package/src/types.ts +0 -1109
  141. package/src/utils.ts +0 -140
@@ -1,8 +1,8 @@
1
- import { extractSchemaMetadata } from './args.ts';
2
- import { detectShell, getRcFile, type ShellType, writeToRcFile } from './shell-utils.ts';
3
- import type { AnyPadroneCommand } from './types.ts';
1
+ import { extractSchemaMetadata, getJsonSchema } from '../core/args.ts';
2
+ import type { AnyPadroneCommand } from '../types/index.ts';
3
+ import { detectShell, getRcFile, type ShellType, writeToRcFile } from '../util/shell-utils.ts';
4
4
 
5
- export { detectShell, escapeRegExp, getRcFile, type ShellType, writeToRcFile } from './shell-utils.ts';
5
+ export { detectShell, escapeRegExp, getRcFile, type ShellType, writeToRcFile } from '../util/shell-utils.ts';
6
6
 
7
7
  /**
8
8
  * Collects all commands from a program recursively.
@@ -22,11 +22,19 @@ function collectAllCommands(cmd: AnyPadroneCommand): AnyPadroneCommand[] {
22
22
  return result;
23
23
  }
24
24
 
25
+ interface ExtractedArg {
26
+ name: string;
27
+ alias?: string;
28
+ isBoolean: boolean;
29
+ enum?: string[];
30
+ description?: string;
31
+ }
32
+
25
33
  /**
26
34
  * Extracts all argument names from a command's schema.
27
35
  */
28
- function extractArguments(cmd: AnyPadroneCommand): { name: string; alias?: string; isBoolean: boolean }[] {
29
- const argList: { name: string; alias?: string; isBoolean: boolean }[] = [];
36
+ function extractArguments(cmd: AnyPadroneCommand): ExtractedArg[] {
37
+ const argList: ExtractedArg[] = [];
30
38
 
31
39
  if (!cmd.argsSchema) return argList;
32
40
 
@@ -34,21 +42,24 @@ function extractArguments(cmd: AnyPadroneCommand): { name: string; alias?: strin
34
42
  const argsMeta = cmd.meta?.fields;
35
43
  const { aliases } = extractSchemaMetadata(cmd.argsSchema, argsMeta, cmd.meta?.autoAlias);
36
44
 
37
- // Reverse aliases map (alias -> arg name)
38
- const aliasToArgument: Record<string, string> = {};
39
- for (const [arg, alias] of Object.entries(aliases)) {
40
- aliasToArgument[alias] = arg;
45
+ // Build reverse map: argName aliasName
46
+ const argToAlias: Record<string, string> = {};
47
+ for (const [aliasName, argName] of Object.entries(aliases)) {
48
+ if (!argToAlias[argName]) argToAlias[argName] = aliasName;
41
49
  }
42
50
 
43
- const jsonSchema = cmd.argsSchema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
51
+ const jsonSchema = getJsonSchema(cmd.argsSchema) as Record<string, any>;
44
52
 
45
53
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
46
54
  for (const [key, prop] of Object.entries(jsonSchema.properties as Record<string, any>)) {
47
- const alias = Object.entries(aliases).find(([arg]) => arg === key)?.[1];
55
+ const enumValues = (prop.enum ?? prop.items?.enum) as string[] | undefined;
56
+ const optMeta = argsMeta?.[key];
48
57
  argList.push({
49
58
  name: key,
50
- alias: alias,
59
+ alias: argToAlias[key],
51
60
  isBoolean: prop?.type === 'boolean',
61
+ enum: enumValues,
62
+ description: optMeta?.description ?? prop.description,
52
63
  });
53
64
  }
54
65
  }
@@ -59,6 +70,23 @@ function extractArguments(cmd: AnyPadroneCommand): { name: string; alias?: strin
59
70
  return argList;
60
71
  }
61
72
 
73
+ /**
74
+ * Collects unique args across all commands, preserving first-seen enum values.
75
+ */
76
+ function collectUniqueArgs(program: AnyPadroneCommand, commands: AnyPadroneCommand[]): Map<string, ExtractedArg> {
77
+ const seen = new Map<string, ExtractedArg>();
78
+
79
+ for (const cmd of [program, ...commands]) {
80
+ for (const arg of extractArguments(cmd)) {
81
+ if (!seen.has(arg.name)) {
82
+ seen.set(arg.name, arg);
83
+ }
84
+ }
85
+ }
86
+
87
+ return seen;
88
+ }
89
+
62
90
  /**
63
91
  * Generates a Bash completion script for the program.
64
92
  */
@@ -66,21 +94,41 @@ export function generateBashCompletion(program: AnyPadroneCommand): string {
66
94
  const programName = program.name;
67
95
  const commands = collectAllCommands(program);
68
96
  const commandNames = commands.map((c) => c.name).join(' ');
97
+ const uniqueArgs = collectUniqueArgs(program, commands);
69
98
 
70
- // Collect all args from all commands
99
+ // Collect all option names
71
100
  const allArguments = new Set<string>();
72
101
  allArguments.add('--help');
73
102
  allArguments.add('--version');
74
103
 
75
- for (const cmd of [program, ...commands]) {
76
- for (const arg of extractArguments(cmd)) {
77
- allArguments.add(`--${arg.name}`);
78
- if (arg.alias) allArguments.add(`-${arg.alias}`);
79
- }
104
+ for (const arg of uniqueArgs.values()) {
105
+ allArguments.add(`--${arg.name}`);
106
+ if (arg.alias) allArguments.add(`--${arg.alias}`);
80
107
  }
81
108
 
82
109
  const argsList = Array.from(allArguments).join(' ');
83
110
 
111
+ // Build case branches for options with enum values
112
+ const enumCases: string[] = [];
113
+ for (const arg of uniqueArgs.values()) {
114
+ if (!arg.enum || arg.enum.length === 0) continue;
115
+ const values = arg.enum.join(' ');
116
+ const patterns = [`--${arg.name}`];
117
+ if (arg.alias) patterns.push(`--${arg.alias}`);
118
+ enumCases.push(` ${patterns.join('|')}) COMPREPLY=($(compgen -W "${values}" -- "$cur")); return 0 ;;`);
119
+ }
120
+
121
+ const enumBlock =
122
+ enumCases.length > 0
123
+ ? `
124
+ # Complete option values
125
+ case "$prev" in
126
+ ${enumCases.join('\n')}
127
+ esac
128
+
129
+ `
130
+ : '\n';
131
+
84
132
  return `###-begin-${programName}-completion-###
85
133
  #
86
134
  # ${programName} command completion script
@@ -104,8 +152,7 @@ if type complete &>/dev/null; then
104
152
 
105
153
  local commands="${commandNames}"
106
154
  local args="${argsList}"
107
-
108
- # Complete args when current word starts with -
155
+ ${enumBlock} # Complete args when current word starts with -
109
156
  if [[ "$cur" == -* ]]; then
110
157
  COMPREPLY=($(compgen -W "$args" -- "$cur"))
111
158
  return 0
@@ -161,26 +208,24 @@ export function generateZshCompletion(program: AnyPadroneCommand): string {
161
208
  })
162
209
  .join('\n');
163
210
 
164
- // Collect all args with descriptions
211
+ // Collect all args with descriptions and enum values
165
212
  const argumentCompletions: string[] = [];
166
213
  argumentCompletions.push(" '--help[Show help information]'");
167
214
  argumentCompletions.push(" '--version[Show version number]'");
168
215
 
169
- const seenArgs = new Set<string>(['help', 'version']);
216
+ const uniqueArgs = collectUniqueArgs(program, commands);
170
217
 
171
- for (const cmd of [program, ...commands]) {
172
- for (const arg of extractArguments(cmd)) {
173
- if (seenArgs.has(arg.name)) continue;
174
- seenArgs.add(arg.name);
218
+ for (const arg of uniqueArgs.values()) {
219
+ const desc = arg.description || '';
220
+ const escapedDesc = desc.replace(/'/g, "'\\''").replace(/\[/g, '\\[').replace(/\]/g, '\\]');
175
221
 
176
- const desc = cmd.meta?.fields?.[arg.name]?.description || '';
177
- const escapedDesc = desc.replace(/'/g, "'\\''").replace(/\[/g, '\\[').replace(/\]/g, '\\]');
222
+ // Zsh action spec for enum values: :label:(val1 val2 val3)
223
+ const valueAction = arg.enum?.length ? `: :(${arg.enum.join(' ')})` : '';
178
224
 
179
- if (arg.alias) {
180
- argumentCompletions.push(` {-${arg.alias},--${arg.name}}'[${escapedDesc}]'`);
181
- } else {
182
- argumentCompletions.push(` '--${arg.name}[${escapedDesc}]'`);
183
- }
225
+ if (arg.alias) {
226
+ argumentCompletions.push(` {--${arg.alias},--${arg.name}}'[${escapedDesc}]${valueAction}'`);
227
+ } else {
228
+ argumentCompletions.push(` '--${arg.name}[${escapedDesc}]${valueAction}'`);
184
229
  }
185
230
  }
186
231
 
@@ -253,21 +298,18 @@ export function generateFishCompletion(program: AnyPadroneCommand): string {
253
298
  lines.push(`complete -c ${programName} -l help -d 'Show help information'`);
254
299
  lines.push(`complete -c ${programName} -l version -d 'Show version number'`);
255
300
 
256
- const seenArgs = new Set<string>(['help', 'version']);
301
+ const uniqueArgs = collectUniqueArgs(program, commands);
257
302
 
258
- for (const cmd of [program, ...commands]) {
259
- for (const arg of extractArguments(cmd)) {
260
- if (seenArgs.has(arg.name)) continue;
261
- seenArgs.add(arg.name);
262
-
263
- const desc = cmd.meta?.fields?.[arg.name]?.description || '';
264
- const escapedDesc = desc.replace(/'/g, "\\'");
303
+ for (const arg of uniqueArgs.values()) {
304
+ const desc = arg.description || '';
305
+ const escapedDesc = desc.replace(/'/g, "\\'");
306
+ // Fish: -xa 'val1 val2' provides exclusive value completions
307
+ const valueFlag = arg.enum?.length ? ` -xa '${arg.enum.join(' ')}'` : '';
265
308
 
266
- if (arg.alias) {
267
- lines.push(`complete -c ${programName} -s ${arg.alias} -l ${arg.name} -d '${escapedDesc}'`);
268
- } else {
269
- lines.push(`complete -c ${programName} -l ${arg.name} -d '${escapedDesc}'`);
270
- }
309
+ if (arg.alias) {
310
+ lines.push(`complete -c ${programName} -l ${arg.name} -s ${arg.alias} -d '${escapedDesc}'${valueFlag}`);
311
+ } else {
312
+ lines.push(`complete -c ${programName} -l ${arg.name} -d '${escapedDesc}'${valueFlag}`);
271
313
  }
272
314
  }
273
315
 
@@ -282,9 +324,41 @@ export function generateFishCompletion(program: AnyPadroneCommand): string {
282
324
  export function generatePowerShellCompletion(program: AnyPadroneCommand): string {
283
325
  const programName = program.name;
284
326
  const commands = collectAllCommands(program);
327
+ const uniqueArgs = collectUniqueArgs(program, commands);
285
328
 
286
329
  const commandNames = commands.map((c) => `'${c.name}'`).join(', ');
287
330
 
331
+ // Collect all option names
332
+ const argNames: string[] = ["'--help'", "'--version'"];
333
+ for (const arg of uniqueArgs.values()) {
334
+ argNames.push(`'--${arg.name}'`);
335
+ if (arg.alias) argNames.push(`'--${arg.alias}'`);
336
+ }
337
+
338
+ // Build switch cases for option value completion
339
+ const enumCases: string[] = [];
340
+ for (const arg of uniqueArgs.values()) {
341
+ if (!arg.enum || arg.enum.length === 0) continue;
342
+ const values = arg.enum.map((v) => `'${v}'`).join(', ');
343
+ const patterns = [`'--${arg.name}'`];
344
+ if (arg.alias) patterns.push(`'--${arg.alias}'`);
345
+ enumCases.push(` ${patterns.join(', ')} { @(${values}) | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
346
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
347
+ }; return }`);
348
+ }
349
+
350
+ const enumBlock =
351
+ enumCases.length > 0
352
+ ? `
353
+ # Complete option values
354
+ $prevWord = $commandAst.CommandElements | Select-Object -Last 2 | Select-Object -First 1
355
+ switch ($prevWord) {
356
+ ${enumCases.join('\n')}
357
+ }
358
+
359
+ `
360
+ : '\n';
361
+
288
362
  return `###-begin-${programName}-completion-###
289
363
  #
290
364
  # ${programName} command completion script for PowerShell
@@ -296,9 +370,8 @@ Register-ArgumentCompleter -Native -CommandName ${programName} -ScriptBlock {
296
370
  param($wordToComplete, $commandAst, $cursorPosition)
297
371
 
298
372
  $commands = @(${commandNames})
299
- $args = @('--help', '--version')
300
-
301
- if ($wordToComplete -like '-*') {
373
+ $args = @(${argNames.join(', ')})
374
+ ${enumBlock} if ($wordToComplete -like '-*') {
302
375
  $args | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
303
376
  [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
304
377
  }
@@ -366,7 +439,7 @@ ${programName} completion powershell >> $PROFILE`;
366
439
  * Generates the completion output with automatic shell detection.
367
440
  * If shell is not specified, detects the current shell and provides instructions.
368
441
  */
369
- export function generateCompletionOutput(program: AnyPadroneCommand, shell?: ShellType): string {
442
+ export async function generateCompletionOutput(program: AnyPadroneCommand, shell?: ShellType): Promise<string> {
370
443
  const programName = program.name;
371
444
 
372
445
  if (shell) {
@@ -374,7 +447,7 @@ export function generateCompletionOutput(program: AnyPadroneCommand, shell?: She
374
447
  }
375
448
 
376
449
  // Auto-detect shell and provide instructions
377
- const detectedShell = detectShell();
450
+ const detectedShell = await detectShell();
378
451
 
379
452
  if (detectedShell) {
380
453
  const instructions = getCompletionInstallInstructions(programName, detectedShell);
@@ -419,10 +492,10 @@ export interface SetupCompletionsResult {
419
492
  * Sets up shell completions by writing an eval snippet to the appropriate shell config file.
420
493
  * Uses marker comments for idempotency — re-running replaces the existing block.
421
494
  */
422
- export function setupCompletions(programName: string, shell: ShellType): SetupCompletionsResult {
423
- const { existsSync, mkdirSync, writeFileSync } = require('node:fs') as typeof import('node:fs');
424
- const { join } = require('node:path') as typeof import('node:path');
425
- const { homedir } = require('node:os') as typeof import('node:os');
495
+ export async function setupCompletions(programName: string, shell: ShellType): Promise<SetupCompletionsResult> {
496
+ const { existsSync, mkdirSync, writeFileSync } = await import('node:fs');
497
+ const { join } = await import('node:path');
498
+ const { homedir } = await import('node:os');
426
499
 
427
500
  const beginMarker = `###-begin-${programName}-completion-###`;
428
501
  const endMarker = `###-end-${programName}-completion-###`;
@@ -437,7 +510,7 @@ export function setupCompletions(programName: string, shell: ShellType): SetupCo
437
510
  return { file: filePath, updated: existed };
438
511
  }
439
512
 
440
- const rcFile = getRcFile(shell);
513
+ const rcFile = await getRcFile(shell);
441
514
  if (!rcFile) {
442
515
  throw new Error(`Could not determine config file for ${shell}.`);
443
516
  }
@@ -1,5 +1,6 @@
1
- import type { InteractivePromptConfig, ResolvedPadroneRuntime } from './runtime.ts';
2
- import type { AnyPadroneCommand } from './types.ts';
1
+ import { getJsonSchema } from '../core/args.ts';
2
+ import type { InteractivePromptConfig, ResolvedPadroneRuntime } from '../core/runtime.ts';
3
+ import type { AnyPadroneCommand } from '../types/index.ts';
3
4
 
4
5
  /**
5
6
  * Auto-detect the prompt type for a field based on its JSON schema property definition.
@@ -44,6 +45,46 @@ export function detectPromptConfig(
44
45
  return { name, message, type: 'input', default: propSchema.default };
45
46
  }
46
47
 
48
+ /**
49
+ * Prompt a single field and validate it against the command's schema.
50
+ * Re-prompts with a warning until the user provides a valid value.
51
+ */
52
+ async function promptWithValidation(
53
+ field: string,
54
+ config: InteractivePromptConfig,
55
+ currentData: Record<string, unknown>,
56
+ command: AnyPadroneCommand,
57
+ runtime: ResolvedPadroneRuntime,
58
+ ): Promise<unknown> {
59
+ let promptConfig = config;
60
+
61
+ // eslint-disable-next-line no-constant-condition
62
+ while (true) {
63
+ const value = await runtime.prompt!(promptConfig);
64
+
65
+ if (!command.argsSchema) return value;
66
+
67
+ // Validate the full object with the new value to catch field-level issues
68
+ const testData = { ...currentData, [field]: value };
69
+ const validated = await command.argsSchema['~standard'].validate(testData);
70
+
71
+ if (!validated.issues) return value;
72
+
73
+ // Only keep issues whose path starts with this field
74
+ const fieldIssues = validated.issues.filter((issue: { path?: ReadonlyArray<PropertyKey> }) => {
75
+ const rootKey = issue.path?.[0];
76
+ return rootKey !== undefined && String(rootKey) === field;
77
+ });
78
+
79
+ if (fieldIssues.length === 0) return value;
80
+
81
+ // Warn the user and re-prompt with the invalid value as default
82
+ const messages = fieldIssues.map((i: { message: string }) => i.message).join('; ');
83
+ runtime.error(`Invalid value for "${field}": ${messages}`);
84
+ promptConfig = { ...config, default: value };
85
+ }
86
+ }
87
+
47
88
  /**
48
89
  * Prompt for missing interactive fields.
49
90
  * Runs after env/config preprocessing and before schema validation.
@@ -69,7 +110,7 @@ export async function promptInteractiveFields(
69
110
  let requiredFields: Set<string> = new Set();
70
111
  if (command.argsSchema) {
71
112
  try {
72
- const jsonSchema = command.argsSchema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
113
+ const jsonSchema = getJsonSchema(command.argsSchema) as Record<string, any>;
73
114
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
74
115
  jsonProperties = jsonSchema.properties;
75
116
  }
@@ -108,14 +149,14 @@ export async function promptInteractiveFields(
108
149
  }
109
150
  }
110
151
 
111
- // Prompt each required interactive field
152
+ // Prompt each required interactive field with per-field validation
112
153
  for (const field of fieldsToPrompt) {
113
154
  const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
114
155
  // When forced, use the current value as the default
115
156
  if (force && result[field] !== undefined) {
116
157
  config.default = result[field];
117
158
  }
118
- result[field] = await runtime.prompt(config);
159
+ result[field] = await promptWithValidation(field, config, result, command, runtime);
119
160
  }
120
161
 
121
162
  // Determine optional interactive fields
@@ -160,7 +201,7 @@ export async function promptInteractiveFields(
160
201
  if (force && result[field] !== undefined) {
161
202
  config.default = result[field];
162
203
  }
163
- result[field] = await runtime.prompt(config);
204
+ result[field] = await promptWithValidation(field, config, result, command, runtime);
164
205
  }
165
206
  }
166
207
  }