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/dist/index.mjs CHANGED
@@ -1,983 +1,228 @@
1
- import { createRequire } from "node:module";
2
-
3
- //#region rolldown:runtime
4
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
-
6
- //#endregion
7
- //#region src/options.ts
1
+ import { a as parseStdinConfig, i as parsePositionalConfig, n as detectUnknownArgs, o as preprocessArgs, r as extractSchemaMetadata, t as coerceArgs } from "./args-CKNh7Dm9.mjs";
2
+ import { S as getVersion, _ as warnIfUnexpectedAsync, a as commandSymbol, b as createTerminalReplSession, c as hasInteractiveConfig, d as noop, f as outputValue, g as thenMaybe, h as suggestSimilar, i as buildReplCompleter, l as isAsyncBranded, m as runPluginChain, o as findCommandByName, p as repathCommandTree, r as asyncSchema, s as getCommandRuntime, t as generateHelp, u as mergeCommands, v as wrapWithLifecycle, x as resolveStdin, y as REPL_SIGINT } from "./help-mUIX0T0V.mjs";
3
+ //#region src/errors.ts
8
4
  /**
9
- * Parse positional configuration to extract names and variadic info.
5
+ * Base error class for all Padrone errors.
6
+ * Carries structured metadata for user-friendly formatting and programmatic handling.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * throw new PadroneError('Something went wrong', {
11
+ * exitCode: 1,
12
+ * suggestions: ['Try --help for usage information'],
13
+ * });
14
+ * ```
10
15
  */
11
- function parsePositionalConfig(positional) {
12
- return positional.map((p) => {
13
- const isVariadic = p.startsWith("...");
16
+ var PadroneError = class extends Error {
17
+ exitCode;
18
+ suggestions;
19
+ command;
20
+ phase;
21
+ constructor(message, options) {
22
+ super(message, options?.cause ? { cause: options.cause } : void 0);
23
+ this.name = "PadroneError";
24
+ this.exitCode = options?.exitCode ?? 1;
25
+ this.suggestions = options?.suggestions ?? [];
26
+ this.command = options?.command;
27
+ this.phase = options?.phase;
28
+ }
29
+ /**
30
+ * Returns a serializable representation of the error,
31
+ * suitable for non-terminal runtimes (web UIs, APIs, etc.).
32
+ */
33
+ toJSON() {
14
34
  return {
15
- name: isVariadic ? p.slice(3) : p,
16
- variadic: isVariadic
35
+ name: this.name,
36
+ message: this.message,
37
+ exitCode: this.exitCode,
38
+ suggestions: this.suggestions,
39
+ command: this.command,
40
+ phase: this.phase
17
41
  };
18
- });
19
- }
20
- /**
21
- * Extract all option metadata from schema and meta in a single pass.
22
- * This consolidates aliases, env bindings, and config keys extraction.
23
- */
24
- function extractSchemaMetadata(schema, meta) {
25
- const aliases = {};
26
- if (meta) for (const [key, value] of Object.entries(meta)) {
27
- if (!value) continue;
28
- if (value.alias) {
29
- const list = typeof value.alias === "string" ? [value.alias] : value.alias;
30
- for (const aliasKey of list) if (typeof aliasKey === "string" && aliasKey && aliasKey !== key) aliases[aliasKey] = key;
31
- }
32
- }
33
- try {
34
- const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
35
- if (jsonSchema.type === "object" && jsonSchema.properties) for (const [propertyName, propertySchema] of Object.entries(jsonSchema.properties)) {
36
- if (!propertySchema) continue;
37
- const propAlias = propertySchema.alias;
38
- if (propAlias) {
39
- const list = typeof propAlias === "string" ? [propAlias] : propAlias;
40
- if (Array.isArray(list)) {
41
- for (const aliasKey of list) if (typeof aliasKey === "string" && aliasKey && aliasKey !== propertyName && !(aliasKey in aliases)) aliases[aliasKey] = propertyName;
42
- }
43
- }
44
- }
45
- } catch {}
46
- return { aliases };
47
- }
48
- function preprocessAliases(data, aliases) {
49
- const result = { ...data };
50
- for (const [aliasKey, fullOptionName] of Object.entries(aliases)) if (aliasKey in data && aliasKey !== fullOptionName) {
51
- const aliasValue = data[aliasKey];
52
- if (!(fullOptionName in result)) result[fullOptionName] = aliasValue;
53
- delete result[aliasKey];
54
- }
55
- return result;
56
- }
57
- /**
58
- * Apply values directly to options.
59
- * CLI values take precedence over the provided values.
60
- */
61
- function applyValues(data, values) {
62
- const result = { ...data };
63
- for (const [key, value] of Object.entries(values)) {
64
- if (key in result && result[key] !== void 0) continue;
65
- if (value !== void 0) result[key] = value;
66
- }
67
- return result;
68
- }
69
- /**
70
- * Combined preprocessing of options with all features.
71
- * Precedence order (highest to lowest): CLI args > env vars > config file
72
- */
73
- function preprocessOptions(data, ctx) {
74
- let result = { ...data };
75
- if (ctx.aliases && Object.keys(ctx.aliases).length > 0) result = preprocessAliases(result, ctx.aliases);
76
- if (ctx.envData) result = applyValues(result, ctx.envData);
77
- if (ctx.configData) result = applyValues(result, ctx.configData);
78
- return result;
79
- }
80
-
81
- //#endregion
82
- //#region src/completion.ts
83
- /**
84
- * Detects the current shell from environment variables and process info.
85
- * @returns The detected shell type, or undefined if unknown
86
- */
87
- function detectShell() {
88
- if (typeof process === "undefined") return void 0;
89
- const shellEnv = process.env.SHELL || "";
90
- if (shellEnv.includes("zsh")) return "zsh";
91
- if (shellEnv.includes("bash")) return "bash";
92
- if (shellEnv.includes("fish")) return "fish";
93
- if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) return "powershell";
94
- try {
95
- const { execSync } = __require("node:child_process");
96
- const ppid = process.ppid;
97
- if (ppid) {
98
- const processName = execSync(`ps -p ${ppid} -o comm=`, {
99
- encoding: "utf-8",
100
- stdio: [
101
- "pipe",
102
- "pipe",
103
- "ignore"
104
- ]
105
- }).trim();
106
- if (processName.includes("zsh")) return "zsh";
107
- if (processName.includes("bash")) return "bash";
108
- if (processName.includes("fish")) return "fish";
109
- }
110
- } catch {}
111
- }
112
- /**
113
- * Collects all commands from a program recursively.
114
- */
115
- function collectAllCommands(cmd) {
116
- const result = [];
117
- if (cmd.commands) {
118
- for (const subcmd of cmd.commands) if (!subcmd.hidden) {
119
- result.push(subcmd);
120
- result.push(...collectAllCommands(subcmd));
121
- }
122
- }
123
- return result;
124
- }
125
- /**
126
- * Extracts all option names from a command's schema.
127
- */
128
- function extractOptions(cmd) {
129
- const options = [];
130
- if (!cmd.options) return options;
131
- try {
132
- const optionsMeta = cmd.meta?.options;
133
- const { aliases } = extractSchemaMetadata(cmd.options, optionsMeta);
134
- const aliasToOption = {};
135
- for (const [opt, alias] of Object.entries(aliases)) aliasToOption[alias] = opt;
136
- const jsonSchema = cmd.options["~standard"].jsonSchema.input({ target: "draft-2020-12" });
137
- if (jsonSchema.type === "object" && jsonSchema.properties) for (const [key, prop] of Object.entries(jsonSchema.properties)) {
138
- const alias = Object.entries(aliases).find(([opt]) => opt === key)?.[1];
139
- options.push({
140
- name: key,
141
- alias,
142
- isBoolean: prop?.type === "boolean"
143
- });
144
- }
145
- } catch {}
146
- return options;
147
- }
148
- /**
149
- * Generates a Bash completion script for the program.
150
- */
151
- function generateBashCompletion(program) {
152
- const programName = program.name;
153
- const commands = collectAllCommands(program);
154
- const commandNames = commands.map((c) => c.name).join(" ");
155
- const allOptions = /* @__PURE__ */ new Set();
156
- allOptions.add("--help");
157
- allOptions.add("--version");
158
- for (const cmd of [program, ...commands]) for (const opt of extractOptions(cmd)) {
159
- allOptions.add(`--${opt.name}`);
160
- if (opt.alias) allOptions.add(`-${opt.alias}`);
161
42
  }
162
- const optionsList = Array.from(allOptions).join(" ");
163
- return `###-begin-${programName}-completion-###
164
- #
165
- # ${programName} command completion script
166
- #
167
- # Installation: ${programName} completion >> ~/.bashrc (or ~/.zshrc)
168
- # Or, maybe: ${programName} completion > /usr/local/etc/bash_completion.d/${programName}
169
- #
170
-
171
- if type complete &>/dev/null; then
172
- _${programName}_completion() {
173
- local cur prev words cword
174
- if type _get_comp_words_by_ref &>/dev/null; then
175
- _get_comp_words_by_ref -n = -n @ -n : -w words -i cword
176
- else
177
- cword="$COMP_CWORD"
178
- words=("\${COMP_WORDS[@]}")
179
- fi
180
-
181
- cur="\${words[cword]}"
182
- prev="\${words[cword-1]}"
183
-
184
- local commands="${commandNames}"
185
- local options="${optionsList}"
186
-
187
- # Complete options when current word starts with -
188
- if [[ "$cur" == -* ]]; then
189
- COMPREPLY=($(compgen -W "$options" -- "$cur"))
190
- return 0
191
- fi
192
-
193
- # Complete commands
194
- COMPREPLY=($(compgen -W "$commands" -- "$cur"))
195
- }
196
- complete -o bashdefault -o default -o nospace -F _${programName}_completion ${programName}
197
- elif type compdef &>/dev/null; then
198
- _${programName}_completion() {
199
- local si=$IFS
200
- local commands="${commandNames}"
201
- local options="${optionsList}"
202
-
203
- if [[ "\${words[CURRENT]}" == -* ]]; then
204
- compadd -- \${=options}
205
- else
206
- compadd -- \${=commands}
207
- fi
208
- IFS=$si
209
- }
210
- compdef _${programName}_completion ${programName}
211
- elif type compctl &>/dev/null; then
212
- _${programName}_completion() {
213
- local commands="${commandNames}"
214
- local options="${optionsList}"
215
-
216
- if [[ "\${words[CURRENT]}" == -* ]]; then
217
- reply=(\${=options})
218
- else
219
- reply=(\${=commands})
220
- fi
221
- }
222
- compctl -K _${programName}_completion ${programName}
223
- fi
224
- ###-end-${programName}-completion-###`;
225
- }
43
+ };
226
44
  /**
227
- * Generates a Zsh completion script for the program.
45
+ * Thrown when command routing fails unknown command, unexpected arguments, etc.
228
46
  */
229
- function generateZshCompletion(program) {
230
- const programName = program.name;
231
- const commands = collectAllCommands(program);
232
- const commandCompletions = commands.map((cmd) => {
233
- const escapedDesc = (cmd.description || cmd.title || "").replace(/'/g, "'\\''").replace(/:/g, "\\:");
234
- return ` '${cmd.name}:${escapedDesc}'`;
235
- }).join("\n");
236
- const optionCompletions = [];
237
- optionCompletions.push(" '--help[Show help information]'");
238
- optionCompletions.push(" '--version[Show version number]'");
239
- const seenOptions = new Set(["help", "version"]);
240
- for (const cmd of [program, ...commands]) for (const opt of extractOptions(cmd)) {
241
- if (seenOptions.has(opt.name)) continue;
242
- seenOptions.add(opt.name);
243
- const escapedDesc = (cmd.meta?.options?.[opt.name]?.description || "").replace(/'/g, "'\\''").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
244
- if (opt.alias) optionCompletions.push(` {-${opt.alias},--${opt.name}}'[${escapedDesc}]'`);
245
- else optionCompletions.push(` '--${opt.name}[${escapedDesc}]'`);
47
+ var RoutingError = class extends PadroneError {
48
+ constructor(message, options) {
49
+ super(message, {
50
+ phase: "parse",
51
+ ...options
52
+ });
53
+ this.name = "RoutingError";
246
54
  }
247
- return `#compdef ${programName}
248
- ###-begin-${programName}-completion-###
249
- #
250
- # ${programName} command completion script for Zsh
251
- #
252
- # Installation: ${programName} completion >> ~/.zshrc
253
- # Or: ${programName} completion > ~/.zsh/completions/_${programName}
254
- #
255
-
256
- _${programName}() {
257
- local -a commands
258
- local -a options
259
-
260
- commands=(
261
- ${commandCompletions}
262
- )
263
-
264
- options=(
265
- ${optionCompletions.join("\n")}
266
- )
267
-
268
- _arguments -s \\
269
- $options \\
270
- '1: :->command' \\
271
- '*::arg:->args'
272
-
273
- case "$state" in
274
- command)
275
- _describe 'command' commands
276
- ;;
277
- esac
278
- }
279
-
280
- _${programName}
281
- ###-end-${programName}-completion-###`;
282
- }
55
+ };
283
56
  /**
284
- * Generates a Fish completion script for the program.
57
+ * Thrown when argument or schema validation fails.
58
+ * Carries the structured issues from the schema validator.
285
59
  */
286
- function generateFishCompletion(program) {
287
- const programName = program.name;
288
- const commands = collectAllCommands(program);
289
- const lines = [
290
- `###-begin-${programName}-completion-###`,
291
- "#",
292
- `# ${programName} command completion script for Fish`,
293
- "#",
294
- `# Installation: ${programName} completion > ~/.config/fish/completions/${programName}.fish`,
295
- "#",
296
- "",
297
- `# Clear existing completions`,
298
- `complete -c ${programName} -e`,
299
- "",
300
- "# Commands"
301
- ];
302
- for (const cmd of commands) {
303
- const escapedDesc = (cmd.description || cmd.title || "").replace(/'/g, "\\'");
304
- lines.push(`complete -c ${programName} -n "__fish_use_subcommand" -a "${cmd.name}" -d '${escapedDesc}'`);
60
+ var ValidationError = class extends PadroneError {
61
+ issues;
62
+ constructor(message, issues, options) {
63
+ super(message, {
64
+ phase: "validate",
65
+ ...options
66
+ });
67
+ this.name = "ValidationError";
68
+ this.issues = issues;
305
69
  }
306
- lines.push("");
307
- lines.push("# Global options");
308
- lines.push(`complete -c ${programName} -l help -d 'Show help information'`);
309
- lines.push(`complete -c ${programName} -l version -d 'Show version number'`);
310
- const seenOptions = new Set(["help", "version"]);
311
- for (const cmd of [program, ...commands]) for (const opt of extractOptions(cmd)) {
312
- if (seenOptions.has(opt.name)) continue;
313
- seenOptions.add(opt.name);
314
- const escapedDesc = (cmd.meta?.options?.[opt.name]?.description || "").replace(/'/g, "\\'");
315
- if (opt.alias) lines.push(`complete -c ${programName} -s ${opt.alias} -l ${opt.name} -d '${escapedDesc}'`);
316
- else lines.push(`complete -c ${programName} -l ${opt.name} -d '${escapedDesc}'`);
70
+ toJSON() {
71
+ return {
72
+ ...super.toJSON(),
73
+ issues: this.issues.map((i) => ({
74
+ path: i.path?.map(String),
75
+ message: i.message
76
+ }))
77
+ };
317
78
  }
318
- lines.push(`###-end-${programName}-completion-###`);
319
- return lines.join("\n");
320
- }
321
- /**
322
- * Generates a PowerShell completion script for the program.
323
- */
324
- function generatePowerShellCompletion(program) {
325
- const programName = program.name;
326
- return `###-begin-${programName}-completion-###
327
- #
328
- # ${programName} command completion script for PowerShell
329
- #
330
- # Installation: ${programName} completion >> $PROFILE
331
- #
332
-
333
- Register-ArgumentCompleter -Native -CommandName ${programName} -ScriptBlock {
334
- param($wordToComplete, $commandAst, $cursorPosition)
335
-
336
- $commands = @(${collectAllCommands(program).map((c) => `'${c.name}'`).join(", ")})
337
- $options = @('--help', '--version')
338
-
339
- if ($wordToComplete -like '-*') {
340
- $options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
341
- [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
342
- }
343
- } else {
344
- $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
345
- [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
346
- }
347
- }
348
- }
349
- ###-end-${programName}-completion-###`;
350
- }
79
+ };
351
80
  /**
352
- * Generates a completion script for the specified shell.
81
+ * Thrown when config file loading or validation fails.
353
82
  */
354
- function generateCompletion(program, shell) {
355
- switch (shell) {
356
- case "bash": return generateBashCompletion(program);
357
- case "zsh": return generateZshCompletion(program);
358
- case "fish": return generateFishCompletion(program);
359
- case "powershell": return generatePowerShellCompletion(program);
360
- default: throw new Error(`Unsupported shell: ${shell}`);
83
+ var ConfigError = class extends PadroneError {
84
+ constructor(message, options) {
85
+ super(message, {
86
+ phase: "config",
87
+ ...options
88
+ });
89
+ this.name = "ConfigError";
361
90
  }
362
- }
91
+ };
363
92
  /**
364
- * Gets the installation instructions for a shell completion script.
93
+ * Thrown from user action handlers to surface structured errors with exit codes and suggestions.
94
+ * This is the primary error class users should throw from their command actions.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * throw new ActionError('Missing environment', {
99
+ * exitCode: 1,
100
+ * suggestions: ['Use --env production or --env staging'],
101
+ * });
102
+ * ```
365
103
  */
366
- function getCompletionInstallInstructions(programName, shell) {
367
- switch (shell) {
368
- case "bash": return `# Add to ~/.bashrc:
369
- ${programName} completion bash >> ~/.bashrc
370
-
371
- # Or install system-wide:
372
- ${programName} completion bash > /usr/local/etc/bash_completion.d/${programName}`;
373
- case "zsh": return `# Add to ~/.zshrc:
374
- ${programName} completion zsh >> ~/.zshrc
375
-
376
- # Or add to completions directory:
377
- ${programName} completion zsh > ~/.zsh/completions/_${programName}`;
378
- case "fish": return `# Install to Fish completions:
379
- ${programName} completion fish > ~/.config/fish/completions/${programName}.fish`;
380
- case "powershell": return `# Add to PowerShell profile:
381
- ${programName} completion powershell >> $PROFILE`;
382
- default: return `# Run: ${programName} completion <shell>
383
- # Supported shells: bash, zsh, fish, powershell`;
104
+ var ActionError = class extends PadroneError {
105
+ constructor(message, options) {
106
+ super(message, {
107
+ phase: "execute",
108
+ ...options
109
+ });
110
+ this.name = "ActionError";
384
111
  }
385
- }
386
- /**
387
- * Generates the completion output with automatic shell detection.
388
- * If shell is not specified, detects the current shell and provides instructions.
389
- */
390
- function generateCompletionOutput(program, shell) {
391
- const programName = program.name;
392
- if (shell) return generateCompletion(program, shell);
393
- const detectedShell = detectShell();
394
- if (detectedShell) return `# Detected shell: ${detectedShell}
395
- #
396
- ${getCompletionInstallInstructions(programName, detectedShell)}
397
- #
398
- # Or evaluate directly (temporary, for current session only):
399
- # eval "$(${programName} completion ${detectedShell})"
400
-
401
- ${generateCompletion(program, detectedShell)}`;
402
- return `# Shell auto-detection failed.
403
- #
404
- # Usage: ${programName} completion <shell>
405
- #
406
- # Supported shells:
407
- # bash - Bash completion script
408
- # zsh - Zsh completion script
409
- # fish - Fish completion script
410
- # powershell - PowerShell completion script
411
- #
412
- # Example:
413
- # ${programName} completion bash >> ~/.bashrc
414
- # ${programName} completion zsh >> ~/.zshrc
415
- # ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
416
- # ${programName} completion powershell >> $PROFILE`;
417
- }
418
-
419
- //#endregion
420
- //#region src/colorizer.ts
421
- const colors = {
422
- reset: "\x1B[0m",
423
- bold: "\x1B[1m",
424
- dim: "\x1B[2m",
425
- italic: "\x1B[3m",
426
- underline: "\x1B[4m",
427
- strikethrough: "\x1B[9m",
428
- cyan: "\x1B[36m",
429
- green: "\x1B[32m",
430
- yellow: "\x1B[33m",
431
- blue: "\x1B[34m",
432
- magenta: "\x1B[35m",
433
- gray: "\x1B[90m"
434
112
  };
435
- function createColorizer() {
436
- return {
437
- command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
438
- option: (text) => `${colors.green}${text}${colors.reset}`,
439
- type: (text) => `${colors.yellow}${text}${colors.reset}`,
440
- description: (text) => `${colors.dim}${text}${colors.reset}`,
441
- label: (text) => `${colors.bold}${text}${colors.reset}`,
442
- meta: (text) => `${colors.gray}${text}${colors.reset}`,
443
- example: (text) => `${colors.underline}${text}${colors.reset}`,
444
- exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
445
- deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`
446
- };
447
- }
448
-
449
113
  //#endregion
450
- //#region src/formatter.ts
451
- function createTextStyler() {
452
- return {
453
- command: (text) => text,
454
- option: (text) => text,
455
- type: (text) => text,
456
- description: (text) => text,
457
- label: (text) => text,
458
- meta: (text) => text,
459
- example: (text) => text,
460
- exampleValue: (text) => text,
461
- deprecated: (text) => text
462
- };
463
- }
464
- function createAnsiStyler() {
465
- const colorizer = createColorizer();
466
- return {
467
- command: colorizer.command,
468
- option: colorizer.option,
469
- type: colorizer.type,
470
- description: colorizer.description,
471
- label: colorizer.label,
472
- meta: colorizer.meta,
473
- example: colorizer.example,
474
- exampleValue: colorizer.exampleValue,
475
- deprecated: colorizer.deprecated
476
- };
477
- }
478
- function createConsoleStyler() {
479
- const colors = {
480
- reset: "\x1B[0m",
481
- bold: "\x1B[1m",
482
- dim: "\x1B[2m",
483
- italic: "\x1B[3m",
484
- underline: "\x1B[4m",
485
- strikethrough: "\x1B[9m",
486
- cyan: "\x1B[36m",
487
- green: "\x1B[32m",
488
- yellow: "\x1B[33m",
489
- gray: "\x1B[90m"
490
- };
491
- return {
492
- command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
493
- option: (text) => `${colors.green}${text}${colors.reset}`,
494
- type: (text) => `${colors.yellow}${text}${colors.reset}`,
495
- description: (text) => `${colors.dim}${text}${colors.reset}`,
496
- label: (text) => `${colors.bold}${text}${colors.reset}`,
497
- meta: (text) => `${colors.gray}${text}${colors.reset}`,
498
- example: (text) => `${colors.underline}${text}${colors.reset}`,
499
- exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
500
- deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`
114
+ //#region src/interactive.ts
115
+ /**
116
+ * Auto-detect the prompt type for a field based on its JSON schema property definition.
117
+ */
118
+ function detectPromptConfig(name, propSchema, description) {
119
+ const message = description || propSchema?.description || name;
120
+ if (!propSchema) return {
121
+ name,
122
+ message,
123
+ type: "input"
501
124
  };
502
- }
503
- function createMarkdownStyler() {
504
- return {
505
- command: (text) => `**${text}**`,
506
- option: (text) => `\`${text}\``,
507
- type: (text) => `\`${text}\``,
508
- description: (text) => text,
509
- label: (text) => `### ${text}`,
510
- meta: (text) => `*${text}*`,
511
- example: (text) => `**${text}**`,
512
- exampleValue: (text) => `\`${text}\``,
513
- deprecated: (text) => `~~${text}~~`
125
+ if (propSchema.type === "boolean") return {
126
+ name,
127
+ message,
128
+ type: "confirm",
129
+ default: propSchema.default
514
130
  };
515
- }
516
- function escapeHtml(text) {
517
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
518
- }
519
- function createHtmlStyler() {
520
- return {
521
- command: (text) => `<strong style="color: #00bcd4;">${escapeHtml(text)}</strong>`,
522
- option: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
523
- type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
524
- description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
525
- label: (text) => `<h3>${escapeHtml(text)}</h3>`,
526
- meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
527
- example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
528
- exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
529
- deprecated: (text) => `<del style="color: #999;">${escapeHtml(text)}</del>`
131
+ if (propSchema.enum) return {
132
+ name,
133
+ message,
134
+ type: "select",
135
+ choices: propSchema.enum.map((v) => ({
136
+ label: String(v),
137
+ value: v
138
+ })),
139
+ default: propSchema.default
530
140
  };
531
- }
532
- function createTextLayout() {
533
- return {
534
- newline: "\n",
535
- indent: (level) => " ".repeat(level),
536
- join: (parts) => parts.filter(Boolean).join(" "),
537
- usageLabel: "Usage:"
141
+ if (propSchema.type === "array" && propSchema.items?.enum) return {
142
+ name,
143
+ message,
144
+ type: "multiselect",
145
+ choices: propSchema.items.enum.map((v) => ({
146
+ label: String(v),
147
+ value: v
148
+ })),
149
+ default: propSchema.default
538
150
  };
539
- }
540
- function createMarkdownLayout() {
541
- return {
542
- newline: "\n\n",
543
- indent: (level) => {
544
- if (level === 0) return "";
545
- if (level === 1) return " ";
546
- return " ";
547
- },
548
- join: (parts) => parts.filter(Boolean).join(" "),
549
- usageLabel: "Usage:"
151
+ if (propSchema.format === "password") return {
152
+ name,
153
+ message,
154
+ type: "password",
155
+ default: propSchema.default
550
156
  };
551
- }
552
- function createHtmlLayout() {
553
157
  return {
554
- newline: "<br>",
555
- indent: (level) => "&nbsp;&nbsp;".repeat(level),
556
- join: (parts) => parts.filter(Boolean).join(" "),
557
- wrapDocument: (content) => `<div style="font-family: monospace; line-height: 1.6;">${content}</div>`,
558
- usageLabel: "<strong>Usage:</strong>"
158
+ name,
159
+ message,
160
+ type: "input",
161
+ default: propSchema.default
559
162
  };
560
163
  }
561
164
  /**
562
- * Creates a formatter that uses the given styler and layout configuration.
165
+ * Prompt for missing interactive fields.
166
+ * Runs after env/config preprocessing and before schema validation.
167
+ *
168
+ * When `force` is true, all configured interactive fields are prompted even if they already
169
+ * have values. The current values are used as defaults in the prompts.
563
170
  */
564
- function createGenericFormatter(styler, layout) {
565
- const { newline, indent, join, wrapDocument, usageLabel } = layout;
566
- function formatUsageSection(info) {
567
- return [`${usageLabel} ${join([
568
- styler.command(info.usage.command),
569
- info.usage.hasSubcommands ? styler.meta("[command]") : "",
570
- info.usage.hasArguments ? styler.meta("[args...]") : "",
571
- info.usage.hasOptions ? styler.meta("[options]") : ""
572
- ])}`];
573
- }
574
- function formatSubcommandsSection(info) {
575
- const lines = [];
576
- const subcommands = info.subcommands;
577
- lines.push(styler.label("Commands:"));
578
- const maxNameLength = Math.max(...subcommands.map((c) => {
579
- const aliases = c.aliases ? ` (${c.aliases.join(", ")})` : "";
580
- return (c.name + aliases).length;
581
- }));
582
- for (const subCmd of subcommands) {
583
- const aliases = subCmd.aliases ? ` (${subCmd.aliases.join(", ")})` : "";
584
- const commandDisplay = subCmd.name + aliases;
585
- const padding = " ".repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
586
- const isDeprecated = !!subCmd.deprecated;
587
- const lineParts = [isDeprecated ? styler.deprecated(commandDisplay) : styler.command(subCmd.name) + aliases, padding];
588
- const displayText = subCmd.title ?? subCmd.description;
589
- if (displayText) lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
590
- if (isDeprecated) {
591
- const deprecatedMeta = typeof subCmd.deprecated === "string" ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(" (deprecated)");
592
- lineParts.push(deprecatedMeta);
593
- }
594
- lines.push(indent(1) + lineParts.join(""));
595
- }
596
- lines.push("");
597
- lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
598
- return lines;
599
- }
600
- function formatArgumentsSection(info) {
601
- const lines = [];
602
- const args = info.arguments;
603
- lines.push(styler.label("Arguments:"));
604
- for (const arg of args) {
605
- const parts = [styler.option(arg.name)];
606
- if (arg.optional) parts.push(styler.meta("(optional)"));
607
- if (arg.default !== void 0) parts.push(styler.meta(`(default: ${String(arg.default)})`));
608
- lines.push(indent(1) + join(parts));
609
- if (arg.description) lines.push(indent(2) + styler.description(arg.description));
610
- }
611
- return lines;
612
- }
613
- function formatOptionsSection(info) {
614
- const lines = [];
615
- const options = info.options;
616
- lines.push(styler.label("Options:"));
617
- const maxNameLength = Math.max(...options.map((opt) => opt.name.length));
618
- for (const opt of options) {
619
- const optionName = opt.negatable ? `--[no-]${opt.name}` : `--${opt.name}`;
620
- const aliasNames = opt.aliases && opt.aliases.length > 0 ? opt.aliases.map((a) => `-${a}`).join(", ") : "";
621
- const fullOptionName = aliasNames ? `${optionName}, ${aliasNames}` : optionName;
622
- const padding = " ".repeat(Math.max(0, maxNameLength - opt.name.length + 2));
623
- const isDeprecated = !!opt.deprecated;
624
- const parts = [isDeprecated ? styler.deprecated(fullOptionName) : styler.option(fullOptionName)];
625
- if (opt.type) parts.push(styler.type(`<${opt.type}>`));
626
- if (opt.optional && !opt.deprecated) parts.push(styler.meta("(optional)"));
627
- if (opt.default !== void 0) parts.push(styler.meta(`(default: ${String(opt.default)})`));
628
- if (opt.enum) parts.push(styler.meta(`(choices: ${opt.enum.join(", ")})`));
629
- if (opt.variadic) parts.push(styler.meta("(repeatable)"));
630
- if (isDeprecated) {
631
- const deprecatedMeta = typeof opt.deprecated === "string" ? styler.meta(`(deprecated: ${opt.deprecated})`) : styler.meta("(deprecated)");
632
- parts.push(deprecatedMeta);
633
- }
634
- const description = opt.description ? styler.description(opt.description) : "";
635
- lines.push(indent(1) + join(parts) + padding + description);
636
- if (opt.env) {
637
- const envVars = typeof opt.env === "string" ? [opt.env] : opt.env;
638
- const envParts = [styler.example("Env:"), styler.exampleValue(envVars.join(", "))];
639
- lines.push(indent(3) + join(envParts));
640
- }
641
- if (opt.configKey) {
642
- const configParts = [styler.example("Config:"), styler.exampleValue(opt.configKey)];
643
- lines.push(indent(3) + join(configParts));
644
- }
645
- if (opt.examples && opt.examples.length > 0) {
646
- const exampleValues = opt.examples.map((example) => typeof example === "string" ? example : JSON.stringify(example)).join(", ");
647
- const exampleParts = [styler.example("Example:"), styler.exampleValue(exampleValues)];
648
- lines.push(indent(3) + join(exampleParts));
649
- }
650
- }
651
- return lines;
652
- }
653
- return { format(info) {
654
- const lines = [];
655
- if (info.deprecated) {
656
- const deprecationMessage = typeof info.deprecated === "string" ? `⚠️ This command is deprecated: ${info.deprecated}` : "⚠️ This command is deprecated";
657
- lines.push(styler.deprecated(deprecationMessage));
658
- lines.push("");
659
- }
660
- lines.push(...formatUsageSection(info));
661
- lines.push("");
662
- if (info.title) {
663
- lines.push(styler.label(info.title));
664
- lines.push("");
665
- }
666
- if (info.aliases && info.aliases.length > 0) {
667
- lines.push(styler.meta(`Aliases: ${info.aliases.join(", ")}`));
668
- lines.push("");
669
- }
670
- if (info.description) {
671
- lines.push(styler.description(info.description));
672
- lines.push("");
673
- }
674
- if (info.subcommands && info.subcommands.length > 0) {
675
- lines.push(...formatSubcommandsSection(info));
676
- lines.push("");
677
- }
678
- if (info.arguments && info.arguments.length > 0) {
679
- lines.push(...formatArgumentsSection(info));
680
- lines.push("");
681
- }
682
- if (info.options && info.options.length > 0) {
683
- lines.push(...formatOptionsSection(info));
684
- lines.push("");
685
- }
686
- if (info.nestedCommands?.length) {
687
- lines.push(styler.label("Subcommand Details:"));
688
- lines.push("");
689
- for (const nestedCmd of info.nestedCommands) {
690
- lines.push(styler.meta("─".repeat(60)));
691
- lines.push(this.format(nestedCmd));
692
- }
693
- }
694
- const result = lines.join(newline);
695
- return wrapDocument ? wrapDocument(result) : result;
696
- } };
697
- }
698
- function createJsonFormatter() {
699
- return { format(info) {
700
- return JSON.stringify(info, null, 2);
701
- } };
702
- }
703
- function shouldUseAnsi() {
704
- if (typeof process === "undefined") return false;
705
- if (process.env.NO_COLOR) return false;
706
- if (process.env.CI) return false;
707
- if (process.stdout && typeof process.stdout.isTTY === "boolean") return process.stdout.isTTY;
708
- return false;
709
- }
710
- /**
711
- * Creates a minimal formatter that outputs just a single-line usage string.
712
- */
713
- function createMinimalFormatter() {
714
- return { format(info) {
715
- const parts = [info.usage.command];
716
- if (info.usage.hasSubcommands) parts.push("[command]");
717
- if (info.usage.hasArguments) parts.push("[args...]");
718
- if (info.usage.hasOptions) parts.push("[options]");
719
- return parts.join(" ");
720
- } };
721
- }
722
- function createFormatter(format, detail = "standard") {
723
- if (detail === "minimal") return createMinimalFormatter();
724
- if (format === "json") return createJsonFormatter();
725
- if (format === "ansi" || format === "auto" && shouldUseAnsi()) return createGenericFormatter(createAnsiStyler(), createTextLayout());
726
- if (format === "console") return createGenericFormatter(createConsoleStyler(), createTextLayout());
727
- if (format === "markdown") return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout());
728
- if (format === "html") return createGenericFormatter(createHtmlStyler(), createHtmlLayout());
729
- return createGenericFormatter(createTextStyler(), createTextLayout());
730
- }
731
-
732
- //#endregion
733
- //#region src/utils.ts
734
- function getRootCommand(cmd) {
735
- let current = cmd;
736
- while (current.parent) current = current.parent;
737
- return current;
738
- }
739
- /**
740
- * Attempts to get the version from various sources:
741
- * 1. Explicit version set on the command
742
- * 2. npm_package_version environment variable (set by npm/yarn/pnpm when running scripts)
743
- * 3. package.json in current or parent directories
744
- * @param explicitVersion - Version explicitly set via .version()
745
- * @returns The version string or '0.0.0' if not found
746
- */
747
- function getVersion(explicitVersion) {
748
- if (explicitVersion) return explicitVersion;
749
- if (typeof process !== "undefined" && process.env?.npm_package_version) return process.env.npm_package_version;
750
- if (typeof process !== "undefined") try {
751
- const fs = __require("node:fs");
752
- const path = __require("node:path");
753
- let dir = process.cwd();
754
- for (let i = 0; i < 10; i++) {
755
- const pkgPath = path.join(dir, "package.json");
756
- if (fs.existsSync(pkgPath)) {
757
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
758
- if (pkg.version) return pkg.version;
759
- }
760
- const parentDir = path.dirname(dir);
761
- if (parentDir === dir) break;
762
- dir = parentDir;
763
- }
171
+ async function promptInteractiveFields(data, command, runtime, force) {
172
+ if (!runtime.prompt) return data;
173
+ const meta = command.meta;
174
+ const interactiveConfig = meta?.interactive;
175
+ const optionalInteractiveConfig = meta?.optionalInteractive;
176
+ if (!interactiveConfig && !optionalInteractiveConfig) return data;
177
+ let jsonProperties = {};
178
+ let requiredFields = /* @__PURE__ */ new Set();
179
+ if (command.argsSchema) try {
180
+ const jsonSchema = command.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
181
+ if (jsonSchema.type === "object" && jsonSchema.properties) jsonProperties = jsonSchema.properties;
182
+ if (Array.isArray(jsonSchema.required)) requiredFields = new Set(jsonSchema.required);
764
183
  } catch {}
765
- return "0.0.0";
766
- }
767
- /**
768
- * Loads and parses a config file from the given path.
769
- * Supports JSON, JSONC (JSON with comments), and attempts to parse other formats.
770
- * @param configPath - Path to the config file
771
- * @returns Parsed config data or undefined if loading fails
772
- */
773
- function loadConfigFile(configPath) {
774
- if (typeof process === "undefined") return void 0;
775
- try {
776
- const fs = __require("node:fs");
777
- const path = __require("node:path");
778
- const absolutePath = path.isAbsolute(configPath) ? configPath : path.resolve(process.cwd(), configPath);
779
- if (!fs.existsSync(absolutePath)) {
780
- console.error(`Config file not found: ${absolutePath}`);
781
- return;
782
- }
783
- const getContent = () => fs.readFileSync(absolutePath, "utf-8");
784
- const ext = path.extname(absolutePath).toLowerCase();
785
- if (ext === ".yaml" || ext === ".yml") return Bun.YAML.parse(getContent());
786
- if (ext === ".toml") return Bun.TOML.parse(getContent());
787
- if (ext === ".json") {
788
- if (Bun.JSONC) return Bun.JSONC.parse(getContent());
789
- try {
790
- return JSON.parse(getContent());
791
- } catch {
792
- return Bun.JSONC.parse(getContent());
793
- }
794
- }
795
- if (ext === ".jsonc") return Bun.JSONC.parse(getContent());
796
- if (ext === ".js" || ext === ".cjs" || ext === ".mjs" || ext === ".ts" || ext === ".cts" || ext === ".mts") return __require(absolutePath);
797
- try {
798
- return JSON.parse(getContent());
799
- } catch {
800
- console.error(`Unable to parse config file: ${absolutePath}`);
801
- return;
802
- }
803
- } catch (error) {
804
- console.error(`Error loading config file: ${error}`);
805
- return;
184
+ const fieldDescriptions = {};
185
+ if (meta?.fields) {
186
+ for (const [key, value] of Object.entries(meta.fields)) if (value?.description) fieldDescriptions[key] = value.description;
806
187
  }
807
- }
808
- /**
809
- * Searches for a config file from a list of possible file names.
810
- * Searches in the current working directory.
811
- * @param configFiles - Array of possible config file names to search for
812
- * @returns The path to the first found config file, or undefined if none found
813
- */
814
- function findConfigFile(configFiles) {
815
- if (typeof process === "undefined" || !configFiles?.length) return void 0;
816
- try {
817
- const fs = __require("node:fs");
818
- const path = __require("node:path");
819
- const cwd = process.cwd();
820
- for (const configFile of configFiles) {
821
- const configPath = path.isAbsolute(configFile) ? configFile : path.resolve(cwd, configFile);
822
- if (fs.existsSync(configPath)) return configPath;
823
- }
824
- } catch {}
825
- }
826
-
827
- //#endregion
828
- //#region src/help.ts
829
- /**
830
- * Extract positional arguments info from schema based on meta.positional config.
831
- */
832
- function extractPositionalArgsInfo(schema, meta) {
833
- const args = [];
834
- const positionalNames = /* @__PURE__ */ new Set();
835
- if (!schema || !meta?.positional || meta.positional.length === 0) return {
836
- args,
837
- positionalNames
838
- };
839
- const positionalConfig = parsePositionalConfig(meta.positional);
840
- try {
841
- const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
842
- if (jsonSchema.type === "object" && jsonSchema.properties) {
843
- const properties = jsonSchema.properties;
844
- const required = jsonSchema.required || [];
845
- for (const { name, variadic } of positionalConfig) {
846
- const prop = properties[name];
847
- if (!prop) continue;
848
- positionalNames.add(name);
849
- const optMeta = meta.options?.[name];
850
- args.push({
851
- name: variadic ? `...${name}` : name,
852
- description: optMeta?.description ?? prop.description,
853
- optional: !required.includes(name),
854
- default: prop.default,
855
- type: variadic ? `array<${prop.items?.type || "string"}>` : prop.type
856
- });
857
- }
858
- }
859
- } catch {}
860
- return {
861
- args,
862
- positionalNames
863
- };
864
- }
865
- function extractOptionsInfo(schema, meta, positionalNames) {
866
- const result = [];
867
- if (!schema) return result;
868
- if (!schema["~standard"].vendor.includes("zod")) return result;
869
- const optionsMeta = meta?.options;
870
- try {
871
- const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
872
- if (jsonSchema.type === "object" && jsonSchema.properties) {
873
- const properties = jsonSchema.properties;
874
- const required = jsonSchema.required || [];
875
- const propertyNames = new Set(Object.keys(properties));
876
- const hasExplicitNegation = (key) => {
877
- const camelNegated = `no${key.charAt(0).toUpperCase()}${key.slice(1)}`;
878
- if (propertyNames.has(camelNegated)) return true;
879
- const kebabNegated = `no-${key}`;
880
- if (propertyNames.has(kebabNegated)) return true;
881
- return false;
882
- };
883
- const isNegationOf = (key) => {
884
- if (key.startsWith("no") && key.length > 2 && key[2] === key[2]?.toUpperCase()) {
885
- const positiveKey = key.charAt(2).toLowerCase() + key.slice(3);
886
- if (propertyNames.has(positiveKey)) return true;
887
- }
888
- if (key.startsWith("no-")) {
889
- const positiveKey = key.slice(3);
890
- if (propertyNames.has(positiveKey)) return true;
891
- }
892
- return false;
893
- };
894
- for (const [key, prop] of Object.entries(properties)) {
895
- if (positionalNames?.has(key)) continue;
896
- const isOptional = !required.includes(key);
897
- const enumValues = prop.enum;
898
- const optMeta = optionsMeta?.[key];
899
- const propType = prop.type;
900
- const isNegatable = propType === "boolean" && !hasExplicitNegation(key) && !isNegationOf(key);
901
- result.push({
902
- name: key,
903
- description: optMeta?.description ?? prop.description,
904
- optional: isOptional,
905
- default: prop.default,
906
- type: propType,
907
- enum: enumValues,
908
- deprecated: optMeta?.deprecated ?? prop?.deprecated,
909
- hidden: optMeta?.hidden ?? prop?.hidden,
910
- examples: optMeta?.examples ?? prop?.examples,
911
- variadic: propType === "array",
912
- negatable: isNegatable
913
- });
914
- }
915
- }
916
- } catch {}
917
- return result;
918
- }
919
- /**
920
- * Builds a comprehensive HelpInfo structure from a command.
921
- * This is the single source of truth that all formatters use.
922
- * @param cmd - The command to build help info for
923
- * @param detail - The level of detail ('minimal', 'standard', or 'full')
924
- */
925
- function getHelpInfo(cmd, detail = "standard") {
926
- const rootCmd = getRootCommand(cmd);
927
- const commandName = cmd.path || cmd.name || "program";
928
- const { args: positionalArgs, positionalNames } = cmd.options ? extractPositionalArgsInfo(cmd.options, cmd.meta) : {
929
- args: [],
930
- positionalNames: /* @__PURE__ */ new Set()
931
- };
932
- const hasArguments = positionalArgs.length > 0;
933
- const helpInfo = {
934
- name: commandName,
935
- title: cmd.title,
936
- description: cmd.description,
937
- aliases: cmd.aliases,
938
- deprecated: cmd.deprecated,
939
- hidden: cmd.hidden,
940
- usage: {
941
- command: rootCmd === cmd ? commandName : `${rootCmd.name} ${commandName}`,
942
- hasSubcommands: !!(cmd.commands && cmd.commands.length > 0),
943
- hasArguments,
944
- hasOptions: !!cmd.options
945
- }
946
- };
947
- if (cmd.commands && cmd.commands.length > 0) {
948
- const visibleCommands = detail === "full" ? cmd.commands : cmd.commands.filter((c) => !c.hidden);
949
- helpInfo.subcommands = visibleCommands.map((c) => {
950
- return {
951
- name: c.name,
952
- title: c.title,
953
- description: c.description,
954
- aliases: c.aliases,
955
- deprecated: c.deprecated,
956
- hidden: c.hidden
957
- };
958
- });
959
- if (detail === "full") helpInfo.nestedCommands = visibleCommands.map((c) => getHelpInfo(c, "full"));
188
+ const result = { ...data };
189
+ let fieldsToPrompt = [];
190
+ if (interactiveConfig === true) if (force) fieldsToPrompt = [...requiredFields];
191
+ else fieldsToPrompt = [...requiredFields].filter((name) => result[name] === void 0);
192
+ else if (Array.isArray(interactiveConfig)) if (force) fieldsToPrompt = [...interactiveConfig];
193
+ else fieldsToPrompt = interactiveConfig.filter((name) => result[name] === void 0);
194
+ for (const field of fieldsToPrompt) {
195
+ const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
196
+ if (force && result[field] !== void 0) config.default = result[field];
197
+ result[field] = await runtime.prompt(config);
960
198
  }
961
- if (hasArguments) helpInfo.arguments = positionalArgs;
962
- if (cmd.options) {
963
- const optionsInfo = extractOptionsInfo(cmd.options, cmd.meta, positionalNames);
964
- const optMap = Object.fromEntries(optionsInfo.map((opt) => [opt.name, opt]));
965
- const { aliases } = extractSchemaMetadata(cmd.options, cmd.meta?.options);
966
- for (const [alias, name] of Object.entries(aliases)) {
967
- const opt = optMap[name];
968
- if (!opt) continue;
969
- opt.aliases = [...opt.aliases || [], alias];
199
+ let optionalFields = [];
200
+ if (optionalInteractiveConfig === true) if (force) optionalFields = Object.keys(jsonProperties).filter((name) => !requiredFields.has(name));
201
+ else optionalFields = Object.keys(jsonProperties).filter((name) => !requiredFields.has(name) && result[name] === void 0);
202
+ else if (Array.isArray(optionalInteractiveConfig)) if (force) optionalFields = [...optionalInteractiveConfig];
203
+ else optionalFields = optionalInteractiveConfig.filter((name) => result[name] === void 0);
204
+ if (optionalFields.length > 0) {
205
+ const selected = await runtime.prompt({
206
+ name: "_optionalFields",
207
+ message: "Would you also like to configure:",
208
+ type: "multiselect",
209
+ choices: optionalFields.map((f) => {
210
+ const label = fieldDescriptions[f] || jsonProperties[f]?.description || f;
211
+ const currentValue = result[f];
212
+ return {
213
+ label: force && currentValue !== void 0 ? `${label} (current: ${currentValue})` : label,
214
+ value: f
215
+ };
216
+ })
217
+ });
218
+ if (Array.isArray(selected)) for (const field of selected) {
219
+ const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
220
+ if (force && result[field] !== void 0) config.default = result[field];
221
+ result[field] = await runtime.prompt(config);
970
222
  }
971
- const visibleOptions = optionsInfo.filter((opt) => !opt.hidden);
972
- if (visibleOptions.length > 0) helpInfo.options = visibleOptions;
973
223
  }
974
- return helpInfo;
975
- }
976
- function generateHelp(rootCommand, commandObj = rootCommand, options) {
977
- const helpInfo = getHelpInfo(commandObj, options?.detail);
978
- return createFormatter(options?.format ?? "auto", options?.detail).format(helpInfo);
224
+ return result;
979
225
  }
980
-
981
226
  //#endregion
982
227
  //#region src/parse.ts
983
228
  /**
@@ -1027,29 +272,43 @@ function parseCliInputToParts(input) {
1027
272
  const result = [];
1028
273
  let pendingValue;
1029
274
  let allowTerm = true;
275
+ let afterDoubleDash = false;
1030
276
  for (const part of parts) {
1031
277
  if (!part) continue;
278
+ if (part === "--" && !afterDoubleDash) {
279
+ if (pendingValue) pendingValue = void 0;
280
+ afterDoubleDash = true;
281
+ allowTerm = false;
282
+ continue;
283
+ }
284
+ if (afterDoubleDash) {
285
+ result.push({
286
+ type: "arg",
287
+ value: part
288
+ });
289
+ continue;
290
+ }
1032
291
  const wasPending = pendingValue;
1033
292
  pendingValue = void 0;
1034
293
  if (part.startsWith("--no-") && part.length > 5) {
1035
294
  const p = {
1036
- type: "option",
295
+ type: "named",
1037
296
  key: part.slice(5).split("."),
1038
297
  value: void 0,
1039
298
  negated: true
1040
299
  };
1041
300
  result.push(p);
1042
301
  } else if (part.startsWith("--")) {
1043
- const [keyStr = "", value] = splitOptionValue(part.slice(2));
302
+ const [keyStr = "", value] = splitNamedArgValue(part.slice(2));
1044
303
  const p = {
1045
- type: "option",
304
+ type: "named",
1046
305
  key: keyStr.split("."),
1047
306
  value
1048
307
  };
1049
308
  if (typeof value === "undefined") pendingValue = p;
1050
309
  result.push(p);
1051
310
  } else if (part.startsWith("-") && part.length > 1 && !/^-\d/.test(part)) {
1052
- const [keyStr = "", value] = splitOptionValue(part.slice(1));
311
+ const [keyStr = "", value] = splitNamedArgValue(part.slice(1));
1053
312
  const p = {
1054
313
  type: "alias",
1055
314
  key: [keyStr],
@@ -1073,9 +332,9 @@ function parseCliInputToParts(input) {
1073
332
  return result;
1074
333
  }
1075
334
  /**
1076
- * Split option key and value, handling quoted values after =.
335
+ * Split named arg key and value, handling quoted values after =.
1077
336
  */
1078
- function splitOptionValue(str) {
337
+ function splitNamedArgValue(str) {
1079
338
  const eqIndex = str.indexOf("=");
1080
339
  if (eqIndex === -1) return [str, void 0];
1081
340
  const key = str.slice(0, eqIndex);
@@ -1146,11 +405,275 @@ function parseArrayItems(input) {
1146
405
  if (current || items.length > 0) items.push(current.trim());
1147
406
  return items;
1148
407
  }
1149
-
408
+ //#endregion
409
+ //#region src/repl-loop.ts
410
+ /**
411
+ * Creates a REPL async iterable for running commands interactively.
412
+ */
413
+ function createReplIterator(deps, options) {
414
+ const { existingCommand, evalCommand, replActiveRef } = deps;
415
+ if (replActiveRef.value) {
416
+ getCommandRuntime(existingCommand).error("REPL is already running. Nested REPL sessions are not supported.");
417
+ return (async function* () {})();
418
+ }
419
+ const runtime = getCommandRuntime(existingCommand);
420
+ const programName = existingCommand.name || "padrone";
421
+ const useAnsi = runtime.format === "ansi" || runtime.format === "auto" && typeof process !== "undefined" && !process.env.NO_COLOR && !process.env.CI && process.stdout?.isTTY;
422
+ const commandHistory = [];
423
+ const resolveScope = (scope) => {
424
+ const parts = scope.split(/\s+/);
425
+ const stack = [];
426
+ let current = existingCommand;
427
+ for (const part of parts) {
428
+ const found = findCommandByName(part, current.commands);
429
+ if (!found) break;
430
+ stack.push(found);
431
+ current = found;
432
+ }
433
+ return stack;
434
+ };
435
+ async function* replIterator() {
436
+ replActiveRef.value = true;
437
+ const showGreeting = options?.greeting !== false;
438
+ const showHint = options?.hint !== false;
439
+ if (showGreeting || showHint) runtime.output("");
440
+ if (showGreeting) if (options?.greeting) runtime.output(options.greeting);
441
+ else {
442
+ const displayName = existingCommand.title || programName;
443
+ const version = existingCommand.version ? getVersion(existingCommand.version) : void 0;
444
+ const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;
445
+ runtime.output(greeting);
446
+ }
447
+ if (showHint) {
448
+ const hintText = (typeof options?.hint === "string" ? options.hint : void 0) ?? "Type \".help\" for more information, \".exit\" to quit.";
449
+ runtime.output(useAnsi ? `\x1b[2m${hintText}\x1b[0m` : hintText);
450
+ }
451
+ if (showGreeting || showHint) runtime.output("");
452
+ const scopeStack = options?.scope ? resolveScope(options.scope) : [];
453
+ const getScopeCommand = () => scopeStack.length ? scopeStack[scopeStack.length - 1] : existingCommand;
454
+ const getScopePath = () => scopeStack.map((c) => c.name).join(" ");
455
+ const buildPrompt = () => {
456
+ if (options?.prompt) return typeof options.prompt === "function" ? options.prompt() : options.prompt;
457
+ const scopePath = getScopePath();
458
+ const label = scopePath ? `${programName}/${scopePath.replace(/ /g, "/")}` : programName;
459
+ return useAnsi ? `\x1b[1m${label}\x1b[0m ❯ ` : `${label} ❯ `;
460
+ };
461
+ const buildScopedCompleter = () => {
462
+ return buildReplCompleter(getScopeCommand(), { inScope: scopeStack.length > 0 });
463
+ };
464
+ const sessionConfig = { history: options?.history };
465
+ if (options?.completion !== false) sessionConfig.completer = buildScopedCompleter();
466
+ const session = runtime.readLine ? void 0 : createTerminalReplSession(sessionConfig);
467
+ const questionFn = session ? (prompt) => session.question(prompt) : runtime.readLine;
468
+ const updateCompleter = () => {
469
+ if (options?.completion === false) return;
470
+ const completer = buildScopedCompleter();
471
+ if (session) session.completer = completer;
472
+ sessionConfig.completer = completer;
473
+ };
474
+ let lastSigintTime = 0;
475
+ try {
476
+ while (true) {
477
+ const input = await questionFn(buildPrompt());
478
+ if (input === null) break;
479
+ if (input === REPL_SIGINT) {
480
+ const now = Date.now();
481
+ if (now - lastSigintTime < 2e3) break;
482
+ lastSigintTime = now;
483
+ runtime.output("(press Ctrl+C again to exit, or Ctrl+D)");
484
+ continue;
485
+ }
486
+ const trimmed = input.trim();
487
+ if (!trimmed) continue;
488
+ lastSigintTime = 0;
489
+ commandHistory.push(trimmed);
490
+ if (trimmed === ".exit" || trimmed === ".quit") break;
491
+ if (trimmed === ".clear") {
492
+ runtime.output("\x1B[2J\x1B[H");
493
+ continue;
494
+ }
495
+ if (trimmed === ".help") {
496
+ const lines = [
497
+ "REPL Commands:",
498
+ " . Execute the current scoped command",
499
+ " .help Print this help message",
500
+ " .exit Exit the REPL",
501
+ " .clear Clear the screen",
502
+ " .history Show command history",
503
+ " .scope <cmd> Scope into a subcommand",
504
+ " .scope .. Go up one scope level"
505
+ ];
506
+ lines.push("", "Keybindings:", " Ctrl+C Cancel current line (press twice to exit)", " Ctrl+D Exit the REPL", " Up/Down Navigate history", " Tab Auto-complete", "", "Type \"help\" to see available commands.");
507
+ runtime.output(lines.join("\n"));
508
+ continue;
509
+ }
510
+ if (trimmed === ".history") {
511
+ const entries = commandHistory.slice(0, -1);
512
+ if (entries.length === 0) runtime.output("No history.");
513
+ else runtime.output(entries.map((entry, i) => `${i + 1} ${entry}`).join("\n"));
514
+ continue;
515
+ }
516
+ if (trimmed.startsWith(".scope ") || trimmed === ".scope") {
517
+ const target = trimmed.slice(6).trim();
518
+ if (target === ".." || target === "") {
519
+ if (scopeStack.length > 0) {
520
+ scopeStack.pop();
521
+ updateCompleter();
522
+ }
523
+ } else {
524
+ const found = findCommandByName(target, getScopeCommand().commands);
525
+ if (found) if (found.commands?.length) {
526
+ scopeStack.push(found);
527
+ updateCompleter();
528
+ } else runtime.error(`"${target}" has no subcommands to scope into.`);
529
+ else runtime.error(`Unknown command: ${target}`);
530
+ }
531
+ continue;
532
+ }
533
+ if (trimmed === "..") {
534
+ if (scopeStack.length > 0) {
535
+ scopeStack.pop();
536
+ updateCompleter();
537
+ }
538
+ continue;
539
+ }
540
+ let evalInput = trimmed;
541
+ if (trimmed === ".") evalInput = "";
542
+ const prefix = options?.outputPrefix;
543
+ const prefixLines = prefix ? (text) => text.split("\n").map((l) => prefix + l).join("\n") : void 0;
544
+ const savedRuntimes = [];
545
+ if (prefixLines) {
546
+ const prefixedRuntime = {
547
+ ...existingCommand.runtime,
548
+ output: (...args) => {
549
+ const first = args[0];
550
+ runtime.output(typeof first === "string" ? prefixLines(first) : first, ...args.slice(1));
551
+ },
552
+ error: (text) => runtime.error(prefixLines(text))
553
+ };
554
+ const patchAll = (cmd) => {
555
+ savedRuntimes.push({
556
+ cmd,
557
+ runtime: cmd.runtime
558
+ });
559
+ cmd.runtime = prefixedRuntime;
560
+ cmd.commands?.forEach(patchAll);
561
+ };
562
+ patchAll(existingCommand);
563
+ }
564
+ const sp = options?.spacing;
565
+ const isSpacingObject = typeof sp === "object" && sp !== null && !Array.isArray(sp);
566
+ const spacingBefore = isSpacingObject ? sp.before : sp;
567
+ const spacingAfter = isSpacingObject ? sp.after : sp;
568
+ const emitSpacingLine = (value) => {
569
+ if (typeof value === "string") {
570
+ const sep = value.length === 1 ? value.repeat(typeof process !== "undefined" && process.stdout?.columns ? process.stdout.columns : 80) : value;
571
+ runtime.output(sep);
572
+ } else if (value) runtime.output("");
573
+ };
574
+ const emitSpacing = (value) => {
575
+ if (!value) return;
576
+ if (Array.isArray(value)) for (const line of value) emitSpacingLine(line);
577
+ else emitSpacingLine(value);
578
+ };
579
+ emitSpacing(spacingBefore);
580
+ const scopePath = getScopePath();
581
+ const scopedInput = scopePath ? evalInput ? `${scopePath} ${evalInput}` : scopePath : evalInput;
582
+ try {
583
+ const result = await evalCommand(scopedInput, options?.autoOutput === false ? { autoOutput: false } : void 0);
584
+ if (result.argsResult?.issues) {
585
+ const msg = `Validation error:\n${result.argsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`;
586
+ runtime.error(prefixLines ? prefixLines(msg) : msg);
587
+ }
588
+ yield result;
589
+ } catch (err) {
590
+ const msg = err instanceof Error ? err.message : String(err);
591
+ runtime.error(prefixLines ? prefixLines(msg) : msg);
592
+ } finally {
593
+ for (const { cmd, runtime: saved } of savedRuntimes) cmd.runtime = saved;
594
+ emitSpacing(spacingAfter);
595
+ }
596
+ }
597
+ } finally {
598
+ replActiveRef.value = false;
599
+ session?.close();
600
+ }
601
+ }
602
+ return replIterator();
603
+ }
604
+ //#endregion
605
+ //#region src/wrap.ts
606
+ /**
607
+ * Converts parsed arguments to CLI arguments for an external command.
608
+ */
609
+ function argsToCliArgs(input, positional = []) {
610
+ const args = [];
611
+ if (!input) return args;
612
+ const positionalValues = {};
613
+ const regularArguments = {};
614
+ for (const [key, value] of Object.entries(input)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
615
+ else regularArguments[key] = value;
616
+ for (const [key, value] of Object.entries(regularArguments)) {
617
+ if (value === void 0 || value === null) continue;
618
+ const flag = `--${key}`;
619
+ if (typeof value === "boolean") {
620
+ if (value) args.push(flag);
621
+ } else if (Array.isArray(value)) for (const item of value) args.push(flag, String(item));
622
+ else args.push(flag, String(value));
623
+ }
624
+ for (const posKey of positional) {
625
+ const isVariadic = posKey.startsWith("...");
626
+ const value = positionalValues[isVariadic ? posKey.slice(3) : posKey];
627
+ if (value === void 0 || value === null) continue;
628
+ if (isVariadic && Array.isArray(value)) args.push(...value.map(String));
629
+ else args.push(String(value));
630
+ }
631
+ return args;
632
+ }
633
+ /**
634
+ * Creates an action handler that wraps an external CLI tool.
635
+ * @param config - Configuration for wrapping the external command (includes optional schema)
636
+ * @param commandArguments - The command's arguments schema
637
+ * @param commandPositional - Default positional config from the wrapping command
638
+ */
639
+ function createWrapHandler(config, commandArguments, commandPositional) {
640
+ return async (args) => {
641
+ const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
642
+ const validationResult = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandArguments) : wrapSchema : commandArguments)["~standard"].validate(args);
643
+ const processResult = (result) => {
644
+ if (result.issues) throw new ValidationError(`Wrap schema validation failed:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, result.issues);
645
+ return result.value;
646
+ };
647
+ const regularArgs = argsToCliArgs(validationResult instanceof Promise ? await validationResult.then(processResult) : processResult(validationResult), positional);
648
+ const allArgs = [...fixedArgs, ...regularArgs];
649
+ const proc = Bun.spawn([command, ...allArgs], {
650
+ stdout: inheritStdio ? "inherit" : "pipe",
651
+ stderr: inheritStdio ? "inherit" : "pipe",
652
+ stdin: inheritStdio ? "inherit" : "ignore"
653
+ });
654
+ const exitCode = await proc.exited;
655
+ let stdout;
656
+ let stderr;
657
+ if (!inheritStdio) {
658
+ if (proc.stdout) {
659
+ const stdoutBuffer = await new Response(proc.stdout).arrayBuffer();
660
+ stdout = new TextDecoder().decode(stdoutBuffer);
661
+ }
662
+ if (proc.stderr) {
663
+ const stderrBuffer = await new Response(proc.stderr).arrayBuffer();
664
+ stderr = new TextDecoder().decode(stderrBuffer);
665
+ }
666
+ }
667
+ return {
668
+ exitCode,
669
+ stdout,
670
+ stderr,
671
+ success: exitCode === 0
672
+ };
673
+ };
674
+ }
1150
675
  //#endregion
1151
676
  //#region src/create.ts
1152
- const commandSymbol = Symbol("padrone_command");
1153
- const noop = () => void 0;
1154
677
  function createPadrone(name) {
1155
678
  return createPadroneBuilder({
1156
679
  name,
@@ -1158,162 +681,261 @@ function createPadrone(name) {
1158
681
  commands: []
1159
682
  });
1160
683
  }
1161
- function createPadroneBuilder(existingCommand) {
1162
- function findCommandByName(name, commands) {
1163
- if (!commands) return void 0;
1164
- const foundByName = commands.find((cmd) => cmd.name === name);
1165
- if (foundByName) return foundByName;
1166
- const foundByAlias = commands.find((cmd) => cmd.aliases?.includes(name));
1167
- if (foundByAlias) return foundByAlias;
1168
- for (const cmd of commands) {
1169
- if (cmd.commands && name.startsWith(`${cmd.name} `)) {
1170
- const subCommand = findCommandByName(name.slice(cmd.name.length + 1), cmd.commands);
1171
- if (subCommand) return subCommand;
1172
- }
1173
- if (cmd.commands && cmd.aliases) {
1174
- for (const alias of cmd.aliases) if (name.startsWith(`${alias} `)) {
1175
- const subCommand = findCommandByName(name.slice(alias.length + 1), cmd.commands);
1176
- if (subCommand) return subCommand;
1177
- }
1178
- }
1179
- }
1180
- }
684
+ function createPadroneBuilder(inputCommand) {
685
+ const existingCommand = inputCommand.commands?.length && inputCommand.commands.some((c) => c.parent && c.parent !== inputCommand) ? {
686
+ ...inputCommand,
687
+ commands: inputCommand.commands.map((c) => c.parent && c.parent !== inputCommand ? {
688
+ ...c,
689
+ parent: inputCommand
690
+ } : c)
691
+ } : inputCommand;
692
+ /** Creates the action context passed to command handlers. References `builder` which is defined later but only called at runtime. */
693
+ const createActionContext = (cmd) => ({
694
+ runtime: getCommandRuntime(cmd),
695
+ command: cmd,
696
+ program: builder
697
+ });
1181
698
  const find = (command) => {
1182
699
  if (typeof command !== "string") return findCommandByName(command.path, existingCommand.commands);
1183
700
  return findCommandByName(command, existingCommand.commands);
1184
701
  };
1185
702
  /**
1186
- * Parses CLI input to find the command and extract raw options without validation.
703
+ * Parses CLI input to find the command and extract raw arguments without validation.
1187
704
  */
1188
705
  const parseCommand = (input) => {
1189
- input ??= typeof process !== "undefined" ? process.argv.slice(2).join(" ") : void 0;
1190
- if (!input) return {
1191
- command: existingCommand,
1192
- rawOptions: {},
1193
- args: []
1194
- };
706
+ input ??= getCommandRuntime(existingCommand).argv().join(" ") || void 0;
707
+ if (!input) {
708
+ const defaultCommand = findCommandByName("", existingCommand.commands);
709
+ if (defaultCommand) return {
710
+ command: defaultCommand,
711
+ rawArgs: {},
712
+ args: [],
713
+ unmatchedTerms: []
714
+ };
715
+ return {
716
+ command: existingCommand,
717
+ rawArgs: {},
718
+ args: [],
719
+ unmatchedTerms: []
720
+ };
721
+ }
1195
722
  const parts = parseCliInputToParts(input);
1196
723
  const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
1197
724
  const args = parts.filter((p) => p.type === "arg").map((p) => p.value);
1198
725
  let curCommand = existingCommand;
726
+ let unmatchedTerms = [];
1199
727
  if (terms[0] === existingCommand.name) terms.shift();
1200
728
  for (let i = 0; i < terms.length; i++) {
1201
729
  const found = findCommandByName(terms[i] || "", curCommand.commands);
1202
730
  if (found) curCommand = found;
1203
731
  else {
1204
- args.unshift(...terms.slice(i));
732
+ unmatchedTerms = terms.slice(i);
733
+ args.unshift(...unmatchedTerms);
1205
734
  break;
1206
735
  }
1207
736
  }
737
+ if (unmatchedTerms.length === 0 && curCommand.commands?.length) {
738
+ const defaultCommand = findCommandByName("", curCommand.commands);
739
+ if (defaultCommand) curCommand = defaultCommand;
740
+ }
1208
741
  if (!curCommand) return {
1209
742
  command: existingCommand,
1210
- rawOptions: {},
1211
- args
743
+ rawArgs: {},
744
+ args,
745
+ unmatchedTerms
1212
746
  };
1213
- const optionsMeta = curCommand.meta?.options;
1214
- const { aliases } = curCommand.options ? extractSchemaMetadata(curCommand.options, optionsMeta) : { aliases: {} };
1215
- const arrayOptions = /* @__PURE__ */ new Set();
1216
- if (curCommand.options) try {
1217
- const jsonSchema = curCommand.options["~standard"].jsonSchema.input({ target: "draft-2020-12" });
747
+ const argsMeta = curCommand.meta?.fields;
748
+ const { aliases } = curCommand.argsSchema ? extractSchemaMetadata(curCommand.argsSchema, argsMeta) : { aliases: {} };
749
+ const arrayArguments = /* @__PURE__ */ new Set();
750
+ if (curCommand.argsSchema) try {
751
+ const jsonSchema = curCommand.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
1218
752
  if (jsonSchema.type === "object" && jsonSchema.properties) {
1219
- for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayOptions.add(key);
753
+ for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayArguments.add(key);
1220
754
  }
1221
755
  } catch {}
1222
- const opts = parts.filter((p) => p.type === "option" || p.type === "alias");
1223
- const rawOptions = {};
1224
- for (const opt of opts) {
1225
- const key = opt.type === "alias" && opt.key.length === 1 && aliases[opt.key[0]] ? [aliases[opt.key[0]]] : opt.key;
756
+ const argParts = parts.filter((p) => p.type === "named" || p.type === "alias");
757
+ const rawArgs = {};
758
+ for (const arg of argParts) {
759
+ const key = arg.type === "alias" && arg.key.length === 1 && aliases[arg.key[0]] ? [aliases[arg.key[0]]] : arg.key;
1226
760
  const rootKey = key[0];
1227
- if (opt.type === "option" && opt.negated) {
1228
- setNestedValue(rawOptions, key, false);
761
+ if (arg.type === "named" && arg.negated) {
762
+ setNestedValue(rawArgs, key, false);
1229
763
  continue;
1230
764
  }
1231
- const value = opt.value ?? true;
1232
- if (arrayOptions.has(rootKey)) {
1233
- const existing = getNestedValue(rawOptions, key);
765
+ const value = arg.value ?? true;
766
+ if (arrayArguments.has(rootKey)) {
767
+ const existing = getNestedValue(rawArgs, key);
1234
768
  if (existing !== void 0) if (Array.isArray(existing)) if (Array.isArray(value)) existing.push(...value);
1235
769
  else existing.push(value);
1236
- else if (Array.isArray(value)) setNestedValue(rawOptions, key, [existing, ...value]);
1237
- else setNestedValue(rawOptions, key, [existing, value]);
1238
- else setNestedValue(rawOptions, key, Array.isArray(value) ? value : [value]);
1239
- } else setNestedValue(rawOptions, key, value);
770
+ else if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
771
+ else setNestedValue(rawArgs, key, [existing, value]);
772
+ else setNestedValue(rawArgs, key, Array.isArray(value) ? value : [value]);
773
+ } else setNestedValue(rawArgs, key, value);
1240
774
  }
1241
775
  return {
1242
776
  command: curCommand,
1243
- rawOptions,
1244
- args
777
+ rawArgs,
778
+ args,
779
+ unmatchedTerms
1245
780
  };
1246
781
  };
1247
782
  /**
1248
- * Validates raw options against the command's schema and applies preprocessing.
783
+ * Preprocesses raw arguments: applies env/config values and maps positional arguments.
784
+ * Also performs auto-coercion (string→number/boolean) and unknown arg detection.
1249
785
  */
1250
- const validateOptions = (command, rawOptions, args, parseOptions) => {
1251
- const preprocessedOptions = preprocessOptions(rawOptions, {
786
+ const buildCommandArgs = (command, rawArgs, args, context) => {
787
+ let preprocessedArgs = preprocessArgs(rawArgs, {
1252
788
  aliases: {},
1253
- envData: parseOptions?.envData,
1254
- configData: parseOptions?.configData
789
+ stdinData: context?.stdinData,
790
+ envData: context?.envData,
791
+ configData: context?.configData
1255
792
  });
1256
793
  const positionalConfig = command.meta?.positional ? parsePositionalConfig(command.meta.positional) : [];
1257
794
  if (positionalConfig.length > 0) {
1258
795
  let argIndex = 0;
1259
- for (const { name, variadic } of positionalConfig) {
796
+ for (let i = 0; i < positionalConfig.length; i++) {
797
+ const { name, variadic } = positionalConfig[i];
1260
798
  if (argIndex >= args.length) break;
1261
799
  if (variadic) {
1262
- const nonVariadicAfter = positionalConfig.slice(positionalConfig.indexOf({
1263
- name,
1264
- variadic
1265
- }) + 1).filter((p) => !p.variadic).length;
800
+ const nonVariadicAfter = positionalConfig.slice(i + 1).filter((p) => !p.variadic).length;
1266
801
  const variadicEnd = args.length - nonVariadicAfter;
1267
- preprocessedOptions[name] = args.slice(argIndex, variadicEnd);
802
+ preprocessedArgs[name] = args.slice(argIndex, variadicEnd);
1268
803
  argIndex = variadicEnd;
804
+ } else if (i === positionalConfig.length - 1 && args.length > argIndex + 1) {
805
+ preprocessedArgs[name] = args.slice(argIndex).join(" ");
806
+ argIndex = args.length;
1269
807
  } else {
1270
- preprocessedOptions[name] = args[argIndex];
808
+ preprocessedArgs[name] = args[argIndex];
1271
809
  argIndex++;
1272
810
  }
1273
811
  }
1274
812
  }
1275
- const optionsParsed = command.options ? command.options["~standard"].validate(preprocessedOptions) : { value: preprocessedOptions };
1276
- if (optionsParsed instanceof Promise) throw new Error("Async validation is not supported. Schema validate() must return a synchronous result.");
1277
- const hasOptions = command.options || Object.keys(preprocessedOptions).length > 0;
1278
- return {
1279
- options: optionsParsed.issues ? void 0 : hasOptions ? optionsParsed.value : void 0,
1280
- optionsResult: optionsParsed
1281
- };
813
+ if (command.argsSchema) preprocessedArgs = coerceArgs(preprocessedArgs, command.argsSchema);
814
+ return preprocessedArgs;
815
+ };
816
+ /**
817
+ * Detects unknown options in args that aren't defined in the schema.
818
+ * Returns unknown key info with suggestions, or empty array if schema is loose.
819
+ */
820
+ const checkUnknownArgs = (command, preprocessedArgs) => {
821
+ if (!command.argsSchema) return [];
822
+ const argsMeta = command.meta?.fields;
823
+ const { aliases } = extractSchemaMetadata(command.argsSchema, argsMeta);
824
+ return detectUnknownArgs(preprocessedArgs, command.argsSchema, aliases, suggestSimilar);
1282
825
  };
1283
- const parse = (input, parseOptions) => {
1284
- const { command, rawOptions, args } = parseCommand(input);
1285
- const resolveEnvSchema = (cmd) => {
1286
- if (cmd.envSchema !== void 0) return cmd.envSchema;
1287
- if (cmd.parent) return resolveEnvSchema(cmd.parent);
826
+ /**
827
+ * Validates preprocessed arguments against the command's schema.
828
+ * First checks for unknown args (strict by default), then runs schema validation.
829
+ * Returns sync or async result depending on the schema's validate method.
830
+ */
831
+ const validateCommandArgs = (command, preprocessedArgs) => {
832
+ const unknownArgs = checkUnknownArgs(command, preprocessedArgs);
833
+ if (unknownArgs.length > 0) return {
834
+ args: void 0,
835
+ argsResult: { issues: unknownArgs.map(({ key, suggestion }) => ({
836
+ path: [key],
837
+ message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
838
+ })) }
1288
839
  };
1289
- const envSchema = resolveEnvSchema(command);
1290
- let envData = parseOptions?.envData;
1291
- if (envSchema && !envData) {
1292
- const rawEnv = parseOptions?.env ?? (typeof process !== "undefined" ? process.env : {});
1293
- const envValidated = envSchema["~standard"].validate(rawEnv);
1294
- if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
1295
- if (!envValidated.issues) envData = envValidated.value;
1296
- }
1297
- const { options, optionsResult } = validateOptions(command, rawOptions, args, {
1298
- envData,
1299
- configData: parseOptions?.configData
840
+ const argsParsed = command.argsSchema ? command.argsSchema["~standard"].validate(preprocessedArgs) : { value: preprocessedArgs };
841
+ const hasArgs = command.argsSchema || Object.keys(preprocessedArgs).length > 0;
842
+ const buildResult = (parsed) => ({
843
+ args: parsed.issues ? void 0 : hasArgs ? parsed.value : void 0,
844
+ argsResult: parsed
1300
845
  });
1301
- return {
1302
- command,
1303
- options,
1304
- optionsResult
846
+ return thenMaybe(argsParsed, buildResult);
847
+ };
848
+ /**
849
+ * Preprocesses and validates raw arguments against the command's schema.
850
+ * Returns sync or async result depending on the schema's validate method.
851
+ */
852
+ const validateArgs = (command, rawArgs, args, context) => {
853
+ return validateCommandArgs(command, buildCommandArgs(command, rawArgs, args, context));
854
+ };
855
+ const parse = (input) => {
856
+ const state = {};
857
+ const parseCtx = {
858
+ input,
859
+ command: existingCommand,
860
+ state
861
+ };
862
+ const coreParse = () => {
863
+ const { command, rawArgs, args } = parseCommand(parseCtx.input);
864
+ return {
865
+ command,
866
+ rawArgs,
867
+ positionalArgs: args
868
+ };
869
+ };
870
+ const parsedOrPromise = runPluginChain("parse", existingCommand.plugins ?? [], parseCtx, coreParse);
871
+ const continueAfterParse = (parsed) => {
872
+ const { command } = parsed;
873
+ const commandPlugins = collectPlugins(command);
874
+ const validateCtx = {
875
+ command,
876
+ rawArgs: parsed.rawArgs,
877
+ positionalArgs: parsed.positionalArgs,
878
+ state
879
+ };
880
+ const coreValidate = () => {
881
+ const resolveEnvSchema = (cmd) => {
882
+ if (cmd.envSchema !== void 0) return cmd.envSchema;
883
+ if (cmd.parent) return resolveEnvSchema(cmd.parent);
884
+ };
885
+ const envSchema = resolveEnvSchema(command);
886
+ const readStdinForParse = () => {
887
+ const stdinConfig = command.meta?.stdin;
888
+ if (!stdinConfig) return {};
889
+ const { field, as } = parseStdinConfig(stdinConfig);
890
+ if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
891
+ const stdin = resolveStdin(getCommandRuntime(existingCommand));
892
+ if (!stdin) return {};
893
+ if (as === "lines") return (async () => {
894
+ const lines = [];
895
+ for await (const line of stdin.lines()) lines.push(line);
896
+ return { [field]: lines };
897
+ })();
898
+ return stdin.text().then((text) => text ? { [field]: text } : {});
899
+ };
900
+ const finalize = (envData, stdinData) => {
901
+ return thenMaybe(validateArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
902
+ stdinData,
903
+ envData
904
+ }), (v) => v);
905
+ };
906
+ let envData;
907
+ const afterEnv = (envResult) => {
908
+ return thenMaybe(readStdinForParse(), (stdinData) => {
909
+ return finalize(envResult, Object.keys(stdinData).length > 0 ? stdinData : void 0);
910
+ });
911
+ };
912
+ if (envSchema) {
913
+ const rawEnv = getCommandRuntime(existingCommand).env();
914
+ return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
915
+ if (!result.issues) envData = result.value;
916
+ return afterEnv(envData);
917
+ });
918
+ }
919
+ return afterEnv(envData);
920
+ };
921
+ return warnIfUnexpectedAsync(thenMaybe(runPluginChain("validate", commandPlugins, validateCtx, coreValidate), (v) => ({
922
+ command,
923
+ args: v.args,
924
+ argsResult: v.argsResult
925
+ })), command);
1305
926
  };
927
+ return thenMaybe(parsedOrPromise, continueAfterParse);
1306
928
  };
1307
- const stringify = (command = "", options) => {
929
+ const stringify = (command = "", args) => {
1308
930
  const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
1309
- if (!commandObj) throw new Error(`Command "${command ?? ""}" not found`);
931
+ if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
1310
932
  const parts = [];
1311
933
  if (commandObj.path) parts.push(commandObj.path);
1312
934
  const positionalConfig = commandObj.meta?.positional ? parsePositionalConfig(commandObj.meta.positional) : [];
1313
935
  const positionalNames = new Set(positionalConfig.map((p) => p.name));
1314
- if (options && typeof options === "object") {
936
+ if (args && typeof args === "object") {
1315
937
  for (const { name, variadic } of positionalConfig) {
1316
- const value = options[name];
938
+ const value = args[name];
1317
939
  if (value === void 0) continue;
1318
940
  if (variadic && Array.isArray(value)) for (const v of value) {
1319
941
  const vStr = String(v);
@@ -1340,7 +962,7 @@ function createPadroneBuilder(existingCommand) {
1340
962
  else parts.push(`--${key}=${value}`);
1341
963
  else parts.push(`--${key}=${value}`);
1342
964
  };
1343
- for (const [key, value] of Object.entries(options)) {
965
+ for (const [key, value] of Object.entries(args)) {
1344
966
  if (value === void 0 || positionalNames.has(key)) continue;
1345
967
  stringifyValue(key, value);
1346
968
  }
@@ -1355,16 +977,16 @@ function createPadroneBuilder(existingCommand) {
1355
977
  if (!input) return null;
1356
978
  const parts = parseCliInputToParts(input);
1357
979
  const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
1358
- const opts = parts.filter((p) => p.type === "option" || p.type === "alias");
980
+ const args = parts.filter((p) => p.type === "named" || p.type === "alias");
1359
981
  const keyIs = (key, name) => key.length === 1 && key[0] === name;
1360
- const hasHelpFlag = opts.some((p) => p.type === "option" && keyIs(p.key, "help") || p.type === "alias" && keyIs(p.key, "h"));
982
+ const hasHelpFlag = args.some((p) => p.type === "named" && keyIs(p.key, "help") || p.type === "alias" && keyIs(p.key, "h"));
1361
983
  const getDetailLevel = () => {
1362
- for (const opt of opts) {
1363
- if (opt.type === "option" && keyIs(opt.key, "detail") && typeof opt.value === "string") {
1364
- if (opt.value === "minimal" || opt.value === "standard" || opt.value === "full") return opt.value;
984
+ for (const arg of args) {
985
+ if (arg.type === "named" && keyIs(arg.key, "detail") && typeof arg.value === "string") {
986
+ if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
1365
987
  }
1366
- if (opt.type === "alias" && keyIs(opt.key, "d") && typeof opt.value === "string") {
1367
- if (opt.value === "minimal" || opt.value === "standard" || opt.value === "full") return opt.value;
988
+ if (arg.type === "alias" && keyIs(arg.key, "d") && typeof arg.value === "string") {
989
+ if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
1368
990
  }
1369
991
  }
1370
992
  };
@@ -1379,17 +1001,17 @@ function createPadroneBuilder(existingCommand) {
1379
1001
  "json",
1380
1002
  "auto"
1381
1003
  ];
1382
- for (const opt of opts) {
1383
- if (opt.type === "option" && keyIs(opt.key, "format") && typeof opt.value === "string") {
1384
- if (validFormats.includes(opt.value)) return opt.value;
1004
+ for (const arg of args) {
1005
+ if (arg.type === "named" && keyIs(arg.key, "format") && typeof arg.value === "string") {
1006
+ if (validFormats.includes(arg.value)) return arg.value;
1385
1007
  }
1386
- if (opt.type === "alias" && keyIs(opt.key, "f") && typeof opt.value === "string") {
1387
- if (validFormats.includes(opt.value)) return opt.value;
1008
+ if (arg.type === "alias" && keyIs(arg.key, "f") && typeof arg.value === "string") {
1009
+ if (validFormats.includes(arg.value)) return arg.value;
1388
1010
  }
1389
1011
  }
1390
1012
  };
1391
1013
  const format = getFormat();
1392
- const hasVersionFlag = opts.some((p) => p.type === "option" && keyIs(p.key, "version") || p.type === "alias" && (keyIs(p.key, "v") || keyIs(p.key, "V")));
1014
+ const hasVersionFlag = args.some((p) => p.type === "named" && keyIs(p.key, "version") || p.type === "alias" && (keyIs(p.key, "v") || keyIs(p.key, "V")));
1393
1015
  const normalizedTerms = [...terms];
1394
1016
  if (normalizedTerms[0] === existingCommand.name) normalizedTerms.shift();
1395
1017
  const userHelpCommand = findCommandByName("help", existingCommand.commands);
@@ -1404,6 +1026,24 @@ function createPadroneBuilder(existingCommand) {
1404
1026
  format
1405
1027
  };
1406
1028
  }
1029
+ if (!userHelpCommand && normalizedTerms.length > 0 && normalizedTerms[normalizedTerms.length - 1] === "help") {
1030
+ const commandTerms = normalizedTerms.slice(0, -1);
1031
+ let targetCommand;
1032
+ let current = existingCommand;
1033
+ for (const term of commandTerms) {
1034
+ const found = findCommandByName(term, current.commands);
1035
+ if (found) {
1036
+ targetCommand = found;
1037
+ current = found;
1038
+ } else break;
1039
+ }
1040
+ return {
1041
+ type: "help",
1042
+ command: targetCommand,
1043
+ detail,
1044
+ format
1045
+ };
1046
+ }
1407
1047
  if (!userVersionCommand && normalizedTerms[0] === "version") return { type: "version" };
1408
1048
  if (!userCompletionCommand && normalizedTerms[0] === "completion") {
1409
1049
  const shellArg = normalizedTerms[1];
@@ -1414,7 +1054,8 @@ function createPadroneBuilder(existingCommand) {
1414
1054
  "zsh",
1415
1055
  "fish",
1416
1056
  "powershell"
1417
- ].includes(shellArg) ? shellArg : void 0
1057
+ ].includes(shellArg) ? shellArg : void 0,
1058
+ setup: args.some((p) => p.type === "named" && keyIs(p.key, "setup"))
1418
1059
  };
1419
1060
  }
1420
1061
  if (hasHelpFlag) {
@@ -1427,6 +1068,10 @@ function createPadroneBuilder(existingCommand) {
1427
1068
  };
1428
1069
  }
1429
1070
  if (hasVersionFlag && normalizedTerms.length === 0) return { type: "version" };
1071
+ if (args.some((p) => p.type === "named" && keyIs(p.key, "repl"))) return {
1072
+ type: "repl",
1073
+ scope: normalizedTerms.length > 0 ? normalizedTerms.join(" ") : void 0
1074
+ };
1430
1075
  return null;
1431
1076
  };
1432
1077
  /**
@@ -1434,124 +1079,455 @@ function createPadroneBuilder(existingCommand) {
1434
1079
  */
1435
1080
  const extractConfigPath = (input) => {
1436
1081
  if (!input) return void 0;
1437
- const opts = parseCliInputToParts(input).filter((p) => p.type === "option" || p.type === "alias");
1438
- for (const opt of opts) {
1439
- if (opt.type === "option" && opt.key.length === 1 && opt.key[0] === "config" && typeof opt.value === "string") return opt.value;
1440
- if (opt.type === "alias" && opt.key.length === 1 && opt.key[0] === "c" && typeof opt.value === "string") return opt.value;
1082
+ const args = parseCliInputToParts(input).filter((p) => p.type === "named" || p.type === "alias");
1083
+ for (const arg of args) {
1084
+ if (arg.type === "named" && arg.key.length === 1 && arg.key[0] === "config" && typeof arg.value === "string") return arg.value;
1085
+ if (arg.type === "alias" && arg.key.length === 1 && arg.key[0] === "c" && typeof arg.value === "string") return arg.value;
1441
1086
  }
1442
1087
  };
1443
- const cli = (input, cliOptions) => {
1444
- const resolvedInput = input ?? (typeof process !== "undefined" ? process.argv.slice(2).join(" ") : void 0);
1088
+ /**
1089
+ * Core execution logic shared by eval() and cli().
1090
+ * errorMode controls validation error behavior:
1091
+ * - 'soft': return result with issues (eval behavior)
1092
+ * - 'hard': print error + help and throw (cli-without-input behavior)
1093
+ */
1094
+ const execCommand = (resolvedInput, evalOptions, errorMode = "soft") => {
1095
+ const baseRuntime = getCommandRuntime(existingCommand);
1096
+ const runtime = evalOptions?.runtime ? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== void 0))) : baseRuntime;
1445
1097
  const builtin = checkBuiltinCommands(resolvedInput);
1446
1098
  if (builtin) {
1447
1099
  if (builtin.type === "help") {
1448
1100
  const helpText = generateHelp(existingCommand, builtin.command ?? existingCommand, {
1449
1101
  detail: builtin.detail,
1450
- format: builtin.format
1102
+ format: builtin.format ?? runtime.format
1451
1103
  });
1452
- console.log(helpText);
1104
+ runtime.output(helpText);
1453
1105
  return {
1454
1106
  command: existingCommand,
1455
1107
  args: void 0,
1456
- options: void 0,
1457
1108
  result: helpText
1458
1109
  };
1459
1110
  }
1460
1111
  if (builtin.type === "version") {
1461
1112
  const version = getVersion(existingCommand.version);
1462
- console.log(version);
1113
+ runtime.output(version);
1463
1114
  return {
1464
1115
  command: existingCommand,
1465
- options: void 0,
1116
+ args: void 0,
1466
1117
  result: version
1467
1118
  };
1468
1119
  }
1469
- if (builtin.type === "completion") {
1120
+ if (builtin.type === "completion") return import("./completion.mjs").then(({ detectShell, generateCompletionOutput, setupCompletions }) => {
1121
+ if (builtin.setup) {
1122
+ const shell = builtin.shell ?? detectShell();
1123
+ if (!shell) throw new Error("Could not detect shell. Specify one: completion bash --setup");
1124
+ const result = setupCompletions(existingCommand.name, shell);
1125
+ const message = `${result.updated ? "Updated" : "Added"} ${existingCommand.name} completions in ${result.file}`;
1126
+ runtime.output(message);
1127
+ return {
1128
+ command: existingCommand,
1129
+ args: void 0,
1130
+ result: message
1131
+ };
1132
+ }
1470
1133
  const completionScript = generateCompletionOutput(existingCommand, builtin.shell);
1471
- console.log(completionScript);
1134
+ runtime.output(completionScript);
1472
1135
  return {
1473
1136
  command: existingCommand,
1474
- options: void 0,
1137
+ args: void 0,
1475
1138
  result: completionScript
1476
1139
  };
1477
- }
1140
+ });
1478
1141
  }
1479
- const { command, rawOptions, args } = parseCommand(resolvedInput);
1480
- const configPath = extractConfigPath(resolvedInput);
1481
- const resolveConfigFiles = (cmd) => {
1482
- if (cmd.configFiles !== void 0) return cmd.configFiles;
1483
- if (cmd.parent) return resolveConfigFiles(cmd.parent);
1484
- };
1485
- const effectiveConfigFiles = resolveConfigFiles(command);
1486
- const resolveConfigSchema = (cmd) => {
1487
- if (cmd.config !== void 0) return cmd.config;
1488
- if (cmd.parent) return resolveConfigSchema(cmd.parent);
1489
- };
1490
- const configSchema = resolveConfigSchema(command);
1491
- const resolveEnvSchema = (cmd) => {
1492
- if (cmd.envSchema !== void 0) return cmd.envSchema;
1493
- if (cmd.parent) return resolveEnvSchema(cmd.parent);
1142
+ const state = {};
1143
+ const rootPlugins = existingCommand.plugins ?? [];
1144
+ const runPipeline = () => {
1145
+ const parseCtx = {
1146
+ input: resolvedInput,
1147
+ command: existingCommand,
1148
+ state
1149
+ };
1150
+ const coreParse = () => {
1151
+ const { command, rawArgs, args, unmatchedTerms } = parseCommand(parseCtx.input);
1152
+ const hasSubcommands = command.commands && command.commands.length > 0;
1153
+ const hasSchema = command.argsSchema != null;
1154
+ if (!command.action && (hasSubcommands || !hasSchema) && unmatchedTerms.length === 0) {
1155
+ const helpText = generateHelp(existingCommand, command, { format: runtime.format });
1156
+ runtime.output(helpText);
1157
+ return {
1158
+ command,
1159
+ rawArgs: { "~help": helpText },
1160
+ positionalArgs: []
1161
+ };
1162
+ }
1163
+ if (unmatchedTerms.length > 0) {
1164
+ if (!(command.meta?.positional && command.meta.positional.length > 0)) {
1165
+ const isRootCommand = command === existingCommand;
1166
+ const commandDisplayName = command.name || command.aliases?.[0] || command.path || "(default)";
1167
+ const candidateNames = [];
1168
+ if (isRootCommand && existingCommand.commands) {
1169
+ for (const cmd of existingCommand.commands) if (!cmd.hidden) {
1170
+ candidateNames.push(cmd.name);
1171
+ if (cmd.aliases) candidateNames.push(...cmd.aliases);
1172
+ }
1173
+ } else if (command.commands) {
1174
+ for (const cmd of command.commands) if (!cmd.hidden) {
1175
+ candidateNames.push(cmd.name);
1176
+ if (cmd.aliases) candidateNames.push(...cmd.aliases);
1177
+ }
1178
+ }
1179
+ const suggestion = suggestSimilar(unmatchedTerms[0], candidateNames);
1180
+ const suggestions = suggestion ? [suggestion] : [];
1181
+ const baseMsg = isRootCommand ? `Unknown command: ${unmatchedTerms[0]}` : `Unexpected arguments for '${commandDisplayName}': ${unmatchedTerms.join(" ")}`;
1182
+ const errorMsg = suggestions.length ? `${baseMsg}\n\n ${suggestions[0]}` : baseMsg;
1183
+ if (errorMode === "hard") {
1184
+ runtime.error(errorMsg);
1185
+ if (suggestions.length > 0) {
1186
+ const visibleCommands = ((isRootCommand ? existingCommand : command).commands ?? []).filter((c) => !c.hidden && c.name);
1187
+ if (visibleCommands.length > 0) {
1188
+ const cmdList = visibleCommands.map((c) => c.name).join(", ");
1189
+ runtime.output(`\nAvailable commands: ${cmdList}`);
1190
+ }
1191
+ } else {
1192
+ const helpText = generateHelp(existingCommand, isRootCommand ? existingCommand : command, { format: runtime.format });
1193
+ runtime.error(helpText);
1194
+ }
1195
+ throw new RoutingError(errorMsg, {
1196
+ suggestions,
1197
+ command: command.path || command.name
1198
+ });
1199
+ }
1200
+ throw new RoutingError(errorMsg, {
1201
+ suggestions,
1202
+ command: command.path || command.name
1203
+ });
1204
+ }
1205
+ }
1206
+ return {
1207
+ command,
1208
+ rawArgs,
1209
+ positionalArgs: args
1210
+ };
1211
+ };
1212
+ const parsedOrPromise = runPluginChain("parse", rootPlugins, parseCtx, coreParse);
1213
+ const continueAfterParse = (parsed) => {
1214
+ const { command } = parsed;
1215
+ const commandPlugins = collectPlugins(command);
1216
+ if (parsed.rawArgs["~help"]) return {
1217
+ command,
1218
+ args: void 0,
1219
+ result: parsed.rawArgs["~help"]
1220
+ };
1221
+ const validateCtx = {
1222
+ command,
1223
+ rawArgs: parsed.rawArgs,
1224
+ positionalArgs: parsed.positionalArgs,
1225
+ state
1226
+ };
1227
+ const coreValidate = () => {
1228
+ let flagInteractive;
1229
+ if (hasInteractiveConfig(command.meta)) {
1230
+ if (validateCtx.rawArgs.interactive !== void 0) {
1231
+ flagInteractive = validateCtx.rawArgs.interactive !== false && validateCtx.rawArgs.interactive !== "false";
1232
+ delete validateCtx.rawArgs.interactive;
1233
+ }
1234
+ if (validateCtx.rawArgs.i !== void 0) {
1235
+ flagInteractive = validateCtx.rawArgs.i !== false && validateCtx.rawArgs.i !== "false";
1236
+ delete validateCtx.rawArgs.i;
1237
+ }
1238
+ }
1239
+ const runtimeDefault = runtime.interactive === "forced" ? true : runtime.interactive === "disabled" ? false : void 0;
1240
+ const effectiveInteractive = flagInteractive ?? evalOptions?.interactive ?? runtimeDefault;
1241
+ const stdinIsPiped = !!command.meta?.stdin && (runtime.stdin ? !runtime.stdin.isTTY : typeof process !== "undefined" && process.stdin?.isTTY !== true);
1242
+ const interactivitySuppressed = runtime.interactive === "unsupported" || effectiveInteractive === false || stdinIsPiped && effectiveInteractive !== true;
1243
+ const forceInteractive = !interactivitySuppressed && effectiveInteractive === true;
1244
+ const configPath = extractConfigPath(parseCtx.input);
1245
+ const resolveConfigFiles = (cmd) => {
1246
+ if (cmd.configFiles !== void 0) return cmd.configFiles;
1247
+ if (cmd.parent) return resolveConfigFiles(cmd.parent);
1248
+ };
1249
+ const effectiveConfigFiles = resolveConfigFiles(command);
1250
+ const resolveConfigSchema = (cmd) => {
1251
+ if (cmd.configSchema !== void 0) return cmd.configSchema;
1252
+ if (cmd.parent) return resolveConfigSchema(cmd.parent);
1253
+ };
1254
+ const configSchema = resolveConfigSchema(command);
1255
+ const resolveEnvSchema = (cmd) => {
1256
+ if (cmd.envSchema !== void 0) return cmd.envSchema;
1257
+ if (cmd.parent) return resolveEnvSchema(cmd.parent);
1258
+ };
1259
+ const envSchema = resolveEnvSchema(command);
1260
+ let configData;
1261
+ if (configPath) configData = runtime.loadConfigFile(configPath);
1262
+ else if (effectiveConfigFiles?.length) {
1263
+ const foundConfigPath = runtime.findFile(effectiveConfigFiles);
1264
+ if (foundConfigPath) configData = runtime.loadConfigFile(foundConfigPath) ?? configData;
1265
+ }
1266
+ const validateConfig = () => {
1267
+ if (configData && configSchema) return thenMaybe(configSchema["~standard"].validate(configData), (result) => {
1268
+ if (result.issues) throw new ConfigError(`Invalid config file:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, { command: command.path || command.name });
1269
+ return result.value;
1270
+ });
1271
+ return configData;
1272
+ };
1273
+ const validateEnv = () => {
1274
+ let envData;
1275
+ if (envSchema) {
1276
+ const rawEnv = runtime.env();
1277
+ return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
1278
+ if (!result.issues) envData = result.value;
1279
+ return envData;
1280
+ });
1281
+ }
1282
+ return envData;
1283
+ };
1284
+ const readStdin = () => {
1285
+ const stdinConfig = command.meta?.stdin;
1286
+ if (!stdinConfig) return {};
1287
+ const { field, as } = parseStdinConfig(stdinConfig);
1288
+ if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
1289
+ const stdin = resolveStdin(runtime);
1290
+ if (!stdin) return {};
1291
+ if (as === "lines") return (async () => {
1292
+ const lines = [];
1293
+ for await (const line of stdin.lines()) lines.push(line);
1294
+ return { [field]: lines };
1295
+ })();
1296
+ return stdin.text().then((text) => {
1297
+ if (!text) return {};
1298
+ return { [field]: text };
1299
+ });
1300
+ };
1301
+ const finalizeValidation = (validatedConfigData, envData, stdinData) => {
1302
+ const preprocessedArgs = buildCommandArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
1303
+ stdinData,
1304
+ envData,
1305
+ configData: validatedConfigData
1306
+ });
1307
+ if (!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta)) {
1308
+ const unknowns = checkUnknownArgs(command, preprocessedArgs);
1309
+ if (unknowns.length > 0) return {
1310
+ args: void 0,
1311
+ argsResult: { issues: unknowns.map(({ key, suggestion }) => ({
1312
+ path: [key],
1313
+ message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
1314
+ })) }
1315
+ };
1316
+ if (command.argsSchema) {
1317
+ const providedKeys = new Set(Object.keys(preprocessedArgs).filter((k) => preprocessedArgs[k] !== void 0));
1318
+ const earlyCheck = command.argsSchema["~standard"].validate(preprocessedArgs);
1319
+ const checkForProvidedFieldErrors = (result) => {
1320
+ if (!result.issues) return void 0;
1321
+ const providedFieldIssues = result.issues.filter((issue) => {
1322
+ const rootKey = issue.path?.[0];
1323
+ return rootKey !== void 0 && providedKeys.has(String(rootKey));
1324
+ });
1325
+ if (providedFieldIssues.length > 0) return {
1326
+ args: void 0,
1327
+ argsResult: { issues: providedFieldIssues }
1328
+ };
1329
+ };
1330
+ const earlyResult = thenMaybe(earlyCheck, (result) => {
1331
+ const errors = checkForProvidedFieldErrors(result);
1332
+ if (errors) return errors;
1333
+ });
1334
+ if (earlyResult instanceof Promise) return earlyResult.then((err) => {
1335
+ if (err) return err;
1336
+ return continueWithPrompt(preprocessedArgs);
1337
+ });
1338
+ if (earlyResult) return earlyResult;
1339
+ }
1340
+ }
1341
+ return continueWithPrompt(preprocessedArgs);
1342
+ };
1343
+ const continueWithPrompt = (preprocessedArgs) => {
1344
+ return thenMaybe(!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta) ? promptInteractiveFields(preprocessedArgs, command, runtime, forceInteractive || void 0) : preprocessedArgs, (filledArgs) => {
1345
+ return thenMaybe(validateCommandArgs(command, filledArgs), (v) => v);
1346
+ });
1347
+ };
1348
+ return thenMaybe(validateConfig(), (cfgData) => {
1349
+ return thenMaybe(validateEnv(), (envData) => {
1350
+ return thenMaybe(readStdin(), (stdinData) => {
1351
+ return finalizeValidation(cfgData, envData, Object.keys(stdinData).length > 0 ? stdinData : void 0);
1352
+ });
1353
+ });
1354
+ });
1355
+ };
1356
+ const validatedOrPromise = runPluginChain("validate", commandPlugins, validateCtx, coreValidate);
1357
+ const continueAfterValidate = (v) => {
1358
+ if (v.argsResult?.issues) {
1359
+ let knownOptions;
1360
+ const getKnownOptions = () => {
1361
+ if (knownOptions) return knownOptions;
1362
+ knownOptions = [];
1363
+ if (command.argsSchema) try {
1364
+ const js = command.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
1365
+ if (js.type === "object" && js.properties) knownOptions = Object.keys(js.properties);
1366
+ } catch {}
1367
+ return knownOptions;
1368
+ };
1369
+ const issueMessages = v.argsResult.issues.map((i) => {
1370
+ const base = ` - ${i.path?.join(".") || "root"}: ${i.message}`;
1371
+ const unrecognizedKeys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
1372
+ if (unrecognizedKeys?.length) {
1373
+ const hints = unrecognizedKeys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
1374
+ if (hints.length) return `${base}\n ${hints.join("\n ")}`;
1375
+ }
1376
+ return base;
1377
+ }).join("\n");
1378
+ if (errorMode === "hard") {
1379
+ const helpText = generateHelp(existingCommand, command, { format: runtime.format });
1380
+ runtime.error(`Validation error:\n${issueMessages}`);
1381
+ runtime.error(helpText);
1382
+ throw new ValidationError(`Validation error:\n${issueMessages}`, v.argsResult.issues, {
1383
+ suggestions: v.argsResult.issues.flatMap((i) => {
1384
+ const keys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
1385
+ if (!keys?.length) return [];
1386
+ return keys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
1387
+ }),
1388
+ command: command.path || command.name
1389
+ });
1390
+ }
1391
+ return {
1392
+ command,
1393
+ args: void 0,
1394
+ argsResult: v.argsResult,
1395
+ result: void 0
1396
+ };
1397
+ }
1398
+ const executeCtx = {
1399
+ command,
1400
+ args: v.args,
1401
+ state
1402
+ };
1403
+ const coreExecute = () => {
1404
+ const handler = command.action ?? noop;
1405
+ const ctx = evalOptions?.runtime ? {
1406
+ ...createActionContext(command),
1407
+ runtime
1408
+ } : createActionContext(command);
1409
+ return { result: handler(executeCtx.args, ctx) };
1410
+ };
1411
+ return thenMaybe(runPluginChain("execute", commandPlugins, executeCtx, coreExecute), (e) => {
1412
+ const commandResult = {
1413
+ command,
1414
+ args: v.args,
1415
+ argsResult: v.argsResult,
1416
+ result: e.result
1417
+ };
1418
+ if (command.autoOutput ?? evalOptions?.autoOutput ?? true) {
1419
+ const outputOrPromise = outputValue(e.result, runtime.output);
1420
+ if (outputOrPromise instanceof Promise) return outputOrPromise.then(() => commandResult);
1421
+ }
1422
+ return commandResult;
1423
+ });
1424
+ };
1425
+ return warnIfUnexpectedAsync(thenMaybe(validatedOrPromise, continueAfterValidate), command);
1426
+ };
1427
+ return thenMaybe(parsedOrPromise, continueAfterParse);
1494
1428
  };
1495
- const envSchema = resolveEnvSchema(command);
1496
- let configData = cliOptions?.configData;
1497
- if (configPath) configData = loadConfigFile(configPath);
1498
- else if (effectiveConfigFiles?.length) {
1499
- const foundConfigPath = findConfigFile(effectiveConfigFiles);
1500
- if (foundConfigPath) configData = loadConfigFile(foundConfigPath) ?? configData;
1429
+ return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) => ({
1430
+ command: existingCommand,
1431
+ args: void 0,
1432
+ argsResult: void 0,
1433
+ result
1434
+ }));
1435
+ };
1436
+ const evalCommand = (input, evalOptions) => {
1437
+ return execCommand(input, evalOptions, "soft");
1438
+ };
1439
+ /**
1440
+ * Collects plugins from the command's parent chain (root → ... → target).
1441
+ * Root/program plugins come first (outermost), target command's plugins last (innermost).
1442
+ *
1443
+ * The `programRoot` parameter provides the current program command, because
1444
+ * subcommands' `.parent` references may be stale (builders are immutable — each
1445
+ * method returns a new builder, so a subcommand's parent was captured before
1446
+ * `.use()` was called on the program). We substitute `programRoot` for the
1447
+ * top of the chain to ensure program-level plugins are always included.
1448
+ */
1449
+ const collectPlugins = (cmd) => {
1450
+ const chain = [];
1451
+ let current = cmd;
1452
+ while (current) {
1453
+ if (!current.parent) {
1454
+ if (existingCommand.plugins?.length) chain.unshift(existingCommand.plugins);
1455
+ } else if (current.plugins?.length) chain.unshift(current.plugins);
1456
+ current = current.parent;
1501
1457
  }
1502
- if (configData && configSchema) {
1503
- const configValidated = configSchema["~standard"].validate(configData);
1504
- if (configValidated instanceof Promise) throw new Error("Async validation is not supported. Config schema validate() must return a synchronous result.");
1505
- if (configValidated.issues) {
1506
- const issueMessages = configValidated.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n");
1507
- throw new Error(`Invalid config file:\n${issueMessages}`);
1458
+ return chain.flat();
1459
+ };
1460
+ let replFn;
1461
+ const replActiveRef = { value: false };
1462
+ const cli = (cliOptions) => {
1463
+ const runtime = getCommandRuntime(existingCommand);
1464
+ const resolvedInput = runtime.argv().join(" ") || void 0;
1465
+ if (cliOptions?.repl !== false) {
1466
+ const builtin = checkBuiltinCommands(resolvedInput);
1467
+ if (builtin?.type === "repl") {
1468
+ const replPrefs = {
1469
+ ...typeof cliOptions?.repl === "object" ? cliOptions.repl : {},
1470
+ scope: builtin.scope,
1471
+ autoOutput: (typeof cliOptions?.repl === "object" ? cliOptions.repl.autoOutput : void 0) ?? cliOptions?.autoOutput
1472
+ };
1473
+ const drainRepl = async () => {
1474
+ for await (const _ of replFn(replPrefs));
1475
+ return {
1476
+ command: existingCommand,
1477
+ args: void 0,
1478
+ result: void 0
1479
+ };
1480
+ };
1481
+ return drainRepl();
1508
1482
  }
1509
- configData = configValidated.value;
1510
1483
  }
1511
- let envData = cliOptions?.envData;
1512
- if (envSchema) {
1513
- const rawEnv = cliOptions?.env ?? (typeof process !== "undefined" ? process.env : {});
1514
- const envValidated = envSchema["~standard"].validate(rawEnv);
1515
- if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
1516
- if (!envValidated.issues) envData = envValidated.value;
1484
+ let updateCheckPromise;
1485
+ if (existingCommand.updateCheck) {
1486
+ if (!(resolvedInput && parseCliInputToParts(resolvedInput).some((p) => p.type === "named" && p.key.length === 1 && p.key[0] === "no-update-check"))) {
1487
+ const currentVersion = getVersion(existingCommand.version);
1488
+ updateCheckPromise = import("./update-check-EbNDkzyV.mjs").then(({ createUpdateChecker }) => createUpdateChecker(existingCommand.name, currentVersion, existingCommand.updateCheck, runtime));
1489
+ }
1517
1490
  }
1518
- const { options, optionsResult } = validateOptions(command, rawOptions, args, {
1519
- envData,
1520
- configData
1521
- });
1522
- return {
1523
- ...run(command, options),
1524
- optionsResult
1525
- };
1491
+ const result = execCommand(resolvedInput, cliOptions, "hard");
1492
+ if (updateCheckPromise) {
1493
+ if (result instanceof Promise) return result.then(async (r) => {
1494
+ (await updateCheckPromise)?.();
1495
+ return r;
1496
+ });
1497
+ updateCheckPromise.then((show) => show?.());
1498
+ }
1499
+ return result;
1526
1500
  };
1527
- const run = (command, options) => {
1501
+ const run = (command, args) => {
1528
1502
  const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
1529
- if (!commandObj) throw new Error(`Command "${command ?? ""}" not found`);
1530
- if (!commandObj.handler) throw new Error(`Command "${commandObj.path}" has no handler`);
1531
- return {
1503
+ if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
1504
+ if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
1505
+ const executeCtx = {
1532
1506
  command: commandObj,
1533
- options,
1534
- result: commandObj.handler(options)
1507
+ args,
1508
+ state: {}
1509
+ };
1510
+ const coreExecute = () => {
1511
+ return { result: commandObj.action(executeCtx.args, createActionContext(commandObj)) };
1535
1512
  };
1513
+ const executedOrPromise = runPluginChain("execute", collectPlugins(commandObj), executeCtx, coreExecute);
1514
+ const toResult = (e) => ({
1515
+ command: commandObj,
1516
+ args,
1517
+ result: e.result
1518
+ });
1519
+ if (executedOrPromise instanceof Promise) return executedOrPromise.then(toResult);
1520
+ return toResult(executedOrPromise);
1536
1521
  };
1537
1522
  const tool = () => {
1538
- const description = `\n
1539
- This is a CLI tool created with Padrone. You can run any of the defined commands described in the help text below. If you need assistance, refer to the documentation or use the help command.
1540
-
1541
- <help_output>
1542
- ${generateHelp(existingCommand, void 0, {
1543
- format: "text",
1544
- detail: "full"
1545
- })}
1546
- </help_output>
1547
- `;
1523
+ const description = `Run a command. Pass the full command string including arguments. Use "help <command>" for detailed usage.\n\n${generateHelp(existingCommand, void 0, { format: "text" })}`;
1548
1524
  return {
1549
1525
  type: "function",
1550
1526
  name: existingCommand.name,
1551
1527
  strict: true,
1552
1528
  title: existingCommand.description,
1553
1529
  description,
1554
- inputExamples: [{ input: { command: "<command> [args...] [options...]" } }],
1530
+ inputExamples: [{ input: { command: "<command> [positionals...] [arguments...]" } }],
1555
1531
  inputSchema: {
1556
1532
  [Symbol.for("vercel.ai.schema")]: true,
1557
1533
  jsonSchema: {
@@ -1572,57 +1548,105 @@ ${generateHelp(existingCommand, void 0, {
1572
1548
  };
1573
1549
  }
1574
1550
  },
1575
- needsApproval: (input) => {
1576
- const { command, options } = parse(input.command);
1577
- if (typeof command.needsApproval === "function") return command.needsApproval(options);
1578
- return !!command.needsApproval;
1551
+ needsApproval: async (input) => {
1552
+ const parsed = await parse(input.command);
1553
+ if (typeof parsed.command.needsApproval === "function") return parsed.command.needsApproval(parsed.args);
1554
+ return !!parsed.command.needsApproval;
1579
1555
  },
1580
- execute: (input) => {
1581
- return cli(input.command).result;
1556
+ execute: async (input) => {
1557
+ const output = [];
1558
+ const errors = [];
1559
+ return {
1560
+ result: (await evalCommand(input.command, {
1561
+ autoOutput: false,
1562
+ runtime: {
1563
+ output: (...args) => output.push(args.map(String).join(" ")),
1564
+ error: (text) => errors.push(text),
1565
+ interactive: "unsupported",
1566
+ format: "text"
1567
+ }
1568
+ })).result,
1569
+ logs: output.join("\n"),
1570
+ error: errors.join("\n")
1571
+ };
1582
1572
  }
1583
1573
  };
1584
1574
  };
1585
- return {
1575
+ const builder = {
1586
1576
  configure(config) {
1587
1577
  return createPadroneBuilder({
1588
1578
  ...existingCommand,
1589
1579
  ...config
1590
1580
  });
1591
1581
  },
1592
- options(options, meta) {
1593
- const resolvedOptions = typeof options === "function" ? options(existingCommand.options) : options;
1582
+ runtime(runtimeConfig) {
1583
+ return createPadroneBuilder({
1584
+ ...existingCommand,
1585
+ runtime: {
1586
+ ...existingCommand.runtime,
1587
+ ...runtimeConfig
1588
+ }
1589
+ });
1590
+ },
1591
+ async() {
1594
1592
  return createPadroneBuilder({
1595
1593
  ...existingCommand,
1596
- options: resolvedOptions,
1597
- meta
1594
+ isAsync: true
1595
+ });
1596
+ },
1597
+ arguments(schema, meta) {
1598
+ const resolvedArgs = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
1599
+ const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedArgs) || hasInteractiveConfig(meta);
1600
+ return createPadroneBuilder({
1601
+ ...existingCommand,
1602
+ argsSchema: resolvedArgs,
1603
+ meta,
1604
+ isAsync
1598
1605
  });
1599
1606
  },
1600
1607
  configFile(file, schema) {
1601
1608
  const configFiles = file === void 0 ? void 0 : Array.isArray(file) ? file : [file];
1602
- const resolvedConfig = typeof schema === "function" ? schema(existingCommand.options) : schema ?? existingCommand.options;
1609
+ const resolvedConfig = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema ?? existingCommand.argsSchema;
1610
+ const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedConfig);
1603
1611
  return createPadroneBuilder({
1604
1612
  ...existingCommand,
1605
1613
  configFiles,
1606
- config: resolvedConfig
1614
+ configSchema: resolvedConfig,
1615
+ isAsync
1607
1616
  });
1608
1617
  },
1609
1618
  env(schema) {
1610
- const resolvedEnv = typeof schema === "function" ? schema(existingCommand.options) : schema;
1619
+ const resolvedEnv = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
1620
+ const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedEnv);
1611
1621
  return createPadroneBuilder({
1612
1622
  ...existingCommand,
1613
- envSchema: resolvedEnv
1623
+ envSchema: resolvedEnv,
1624
+ isAsync
1614
1625
  });
1615
1626
  },
1616
1627
  action(handler = noop) {
1628
+ const baseHandler = existingCommand.action ?? noop;
1617
1629
  return createPadroneBuilder({
1618
1630
  ...existingCommand,
1619
- handler
1631
+ action: (args, ctx) => handler(args, ctx, baseHandler)
1632
+ });
1633
+ },
1634
+ wrap(config) {
1635
+ const handler = createWrapHandler(config, existingCommand.argsSchema, existingCommand.meta?.positional);
1636
+ return createPadroneBuilder({
1637
+ ...existingCommand,
1638
+ action: handler
1620
1639
  });
1621
1640
  },
1622
1641
  command(nameOrNames, builderFn) {
1623
1642
  const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
1624
1643
  const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
1625
- const initialCommand = {
1644
+ const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
1645
+ const initialCommand = existingSubcommand ? {
1646
+ ...existingSubcommand,
1647
+ aliases: aliases ?? existingSubcommand.aliases,
1648
+ parent: existingCommand
1649
+ } : {
1626
1650
  name,
1627
1651
  path: existingCommand.path ? `${existingCommand.path} ${name}` : name,
1628
1652
  aliases,
@@ -1631,39 +1655,94 @@ ${generateHelp(existingCommand, void 0, {
1631
1655
  };
1632
1656
  const builder = createPadroneBuilder(initialCommand);
1633
1657
  const commandObj = (builderFn?.(builder))?.[commandSymbol] ?? initialCommand;
1658
+ const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, commandObj) : commandObj;
1659
+ const commands = existingCommand.commands || [];
1660
+ const existingIndex = commands.findIndex((c) => c.name === name);
1661
+ const updatedCommands = existingIndex >= 0 ? [
1662
+ ...commands.slice(0, existingIndex),
1663
+ mergedCommandObj,
1664
+ ...commands.slice(existingIndex + 1)
1665
+ ] : [...commands, mergedCommandObj];
1666
+ return createPadroneBuilder({
1667
+ ...existingCommand,
1668
+ commands: updatedCommands
1669
+ });
1670
+ },
1671
+ mount(nameOrNames, program) {
1672
+ const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
1673
+ const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
1674
+ const programCommand = program[commandSymbol];
1675
+ if (!programCommand) throw new RoutingError("Cannot mount: not a valid Padrone program");
1676
+ const remounted = repathCommandTree(programCommand, name, existingCommand.path || "", existingCommand);
1677
+ remounted.aliases = aliases;
1678
+ const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
1679
+ const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, remounted) : remounted;
1680
+ const commands = existingCommand.commands || [];
1681
+ const existingIndex = commands.findIndex((c) => c.name === name);
1682
+ const updatedCommands = existingIndex >= 0 ? [
1683
+ ...commands.slice(0, existingIndex),
1684
+ mergedCommandObj,
1685
+ ...commands.slice(existingIndex + 1)
1686
+ ] : [...commands, mergedCommandObj];
1634
1687
  return createPadroneBuilder({
1635
1688
  ...existingCommand,
1636
- commands: [...existingCommand.commands || [], commandObj]
1689
+ commands: updatedCommands
1690
+ });
1691
+ },
1692
+ use(plugin) {
1693
+ return createPadroneBuilder({
1694
+ ...existingCommand,
1695
+ plugins: [...existingCommand.plugins ?? [], plugin]
1696
+ });
1697
+ },
1698
+ updateCheck(config = {}) {
1699
+ return createPadroneBuilder({
1700
+ ...existingCommand,
1701
+ updateCheck: config
1637
1702
  });
1638
1703
  },
1639
1704
  run,
1640
1705
  find,
1641
1706
  parse,
1642
1707
  stringify,
1708
+ eval: evalCommand,
1643
1709
  cli,
1644
1710
  tool,
1711
+ repl: replFn = (options) => {
1712
+ return createReplIterator({
1713
+ existingCommand,
1714
+ evalCommand,
1715
+ replActiveRef
1716
+ }, options);
1717
+ },
1645
1718
  api() {
1646
1719
  function buildApi(command) {
1647
- const runCommand = ((options) => run(command, options).result);
1720
+ const runCommand = ((args) => run(command, args).result);
1648
1721
  if (!command.commands) return runCommand;
1649
1722
  for (const cmd of command.commands) runCommand[cmd.name] = buildApi(cmd);
1650
1723
  return runCommand;
1651
1724
  }
1652
1725
  return buildApi(existingCommand);
1653
1726
  },
1654
- help(command, options) {
1727
+ help(command, prefs) {
1655
1728
  const commandObj = !command ? existingCommand : typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
1656
- if (!commandObj) throw new Error(`Command "${command ?? ""}" not found`);
1657
- return generateHelp(existingCommand, commandObj, options);
1729
+ if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
1730
+ const runtime = getCommandRuntime(existingCommand);
1731
+ return generateHelp(existingCommand, commandObj, {
1732
+ ...prefs,
1733
+ format: prefs?.format ?? runtime.format
1734
+ });
1658
1735
  },
1659
- completion(shell) {
1736
+ async completion(shell) {
1737
+ const { generateCompletionOutput } = await import("./completion.mjs");
1660
1738
  return generateCompletionOutput(existingCommand, shell);
1661
1739
  },
1662
1740
  "~types": {},
1663
1741
  [commandSymbol]: existingCommand
1664
1742
  };
1743
+ return builder;
1665
1744
  }
1666
-
1667
1745
  //#endregion
1668
- export { createPadrone };
1746
+ export { ActionError, ConfigError, PadroneError, REPL_SIGINT, RoutingError, ValidationError, asyncSchema, buildReplCompleter, createPadrone };
1747
+
1669
1748
  //# sourceMappingURL=index.mjs.map