padrone 1.1.0 → 1.3.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 +97 -1
  2. package/LICENSE +1 -1
  3. package/README.md +60 -30
  4. package/dist/args-DFEI7_G_.mjs +197 -0
  5. package/dist/args-DFEI7_G_.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 +1358 -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 +405 -0
  18. package/dist/docs/index.mjs.map +1 -0
  19. package/dist/formatter-XroimS3Q.d.mts +83 -0
  20. package/dist/formatter-XroimS3Q.d.mts.map +1 -0
  21. package/dist/help-CgGP7hQU.mjs +1229 -0
  22. package/dist/help-CgGP7hQU.mjs.map +1 -0
  23. package/dist/index.d.mts +120 -546
  24. package/dist/index.d.mts.map +1 -1
  25. package/dist/index.mjs +1220 -1204
  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-BS7RP5Ls.d.mts +1059 -0
  32. package/dist/types-BS7RP5Ls.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 -21
  36. package/src/args.ts +457 -0
  37. package/src/cli/completions.ts +29 -0
  38. package/src/cli/docs.ts +86 -0
  39. package/src/cli/doctor.ts +330 -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 +197 -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 +504 -0
  61. package/src/completion.ts +110 -97
  62. package/src/create.ts +1048 -308
  63. package/src/docs/index.ts +607 -0
  64. package/src/errors.ts +131 -0
  65. package/src/formatter.ts +195 -73
  66. package/src/help.ts +159 -58
  67. package/src/index.ts +12 -15
  68. package/src/interactive.ts +169 -0
  69. package/src/parse.ts +52 -21
  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 +10 -10
  75. package/src/type-utils.ts +124 -14
  76. package/src/types.ts +752 -154
  77. package/src/update-check.ts +244 -0
  78. package/src/wrap.ts +44 -40
  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 parsePositionalConfig, i as extractSchemaMetadata, n as coerceArgs, o as parseStdinConfig, r as detectUnknownArgs, s as preprocessArgs } from "./args-DFEI7_G_.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-CgGP7hQU.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,36 +272,76 @@ 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));
1053
- const p = {
1054
- type: "alias",
1055
- key: [keyStr],
1056
- value
1057
- };
1058
- if (typeof value === "undefined") pendingValue = p;
1059
- result.push(p);
311
+ const [keyStr = "", value] = splitNamedArgValue(part.slice(1));
312
+ if (keyStr.length > 1 && typeof value === "undefined") {
313
+ for (let ci = 0; ci < keyStr.length - 1; ci++) result.push({
314
+ type: "alias",
315
+ key: [keyStr[ci]],
316
+ value: void 0
317
+ });
318
+ const lastFlag = {
319
+ type: "alias",
320
+ key: [keyStr[keyStr.length - 1]],
321
+ value: void 0
322
+ };
323
+ pendingValue = lastFlag;
324
+ result.push(lastFlag);
325
+ } else if (keyStr.length > 1 && typeof value !== "undefined") {
326
+ for (let ci = 0; ci < keyStr.length - 1; ci++) result.push({
327
+ type: "alias",
328
+ key: [keyStr[ci]],
329
+ value: void 0
330
+ });
331
+ result.push({
332
+ type: "alias",
333
+ key: [keyStr[keyStr.length - 1]],
334
+ value
335
+ });
336
+ } else {
337
+ const p = {
338
+ type: "alias",
339
+ key: [keyStr],
340
+ value
341
+ };
342
+ if (typeof value === "undefined") pendingValue = p;
343
+ result.push(p);
344
+ }
1060
345
  } else if (wasPending) wasPending.value = part;
1061
346
  else if (/^[a-zA-Z0-9_-]+$/.test(part) && allowTerm) result.push({
1062
347
  type: "term",
@@ -1073,9 +358,9 @@ function parseCliInputToParts(input) {
1073
358
  return result;
1074
359
  }
1075
360
  /**
1076
- * Split option key and value, handling quoted values after =.
361
+ * Split named arg key and value, handling quoted values after =.
1077
362
  */
1078
- function splitOptionValue(str) {
363
+ function splitNamedArgValue(str) {
1079
364
  const eqIndex = str.indexOf("=");
1080
365
  if (eqIndex === -1) return [str, void 0];
1081
366
  const key = str.slice(0, eqIndex);
@@ -1146,20 +431,215 @@ function parseArrayItems(input) {
1146
431
  if (current || items.length > 0) items.push(current.trim());
1147
432
  return items;
1148
433
  }
1149
-
434
+ //#endregion
435
+ //#region src/repl-loop.ts
436
+ /**
437
+ * Creates a REPL async iterable for running commands interactively.
438
+ */
439
+ function createReplIterator(deps, options) {
440
+ const { existingCommand, evalCommand, replActiveRef } = deps;
441
+ if (replActiveRef.value) {
442
+ getCommandRuntime(existingCommand).error("REPL is already running. Nested REPL sessions are not supported.");
443
+ return (async function* () {})();
444
+ }
445
+ const runtime = getCommandRuntime(existingCommand);
446
+ const programName = existingCommand.name || "padrone";
447
+ const useAnsi = runtime.format === "ansi" || runtime.format === "auto" && typeof process !== "undefined" && !process.env.NO_COLOR && !process.env.CI && process.stdout?.isTTY;
448
+ const commandHistory = [];
449
+ const resolveScope = (scope) => {
450
+ const parts = scope.split(/\s+/);
451
+ const stack = [];
452
+ let current = existingCommand;
453
+ for (const part of parts) {
454
+ const found = findCommandByName(part, current.commands);
455
+ if (!found) break;
456
+ stack.push(found);
457
+ current = found;
458
+ }
459
+ return stack;
460
+ };
461
+ async function* replIterator() {
462
+ replActiveRef.value = true;
463
+ const showGreeting = options?.greeting !== false;
464
+ const showHint = options?.hint !== false;
465
+ if (showGreeting || showHint) runtime.output("");
466
+ if (showGreeting) if (options?.greeting) runtime.output(options.greeting);
467
+ else {
468
+ const displayName = existingCommand.title || programName;
469
+ const version = existingCommand.version ? getVersion(existingCommand.version) : void 0;
470
+ const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;
471
+ runtime.output(greeting);
472
+ }
473
+ if (showHint) {
474
+ const hintText = (typeof options?.hint === "string" ? options.hint : void 0) ?? "Type \".help\" for more information, \".exit\" to quit.";
475
+ runtime.output(useAnsi ? `\x1b[2m${hintText}\x1b[0m` : hintText);
476
+ }
477
+ if (showGreeting || showHint) runtime.output("");
478
+ const scopeStack = options?.scope ? resolveScope(options.scope) : [];
479
+ const getScopeCommand = () => scopeStack.length ? scopeStack[scopeStack.length - 1] : existingCommand;
480
+ const getScopePath = () => scopeStack.map((c) => c.name).join(" ");
481
+ const buildPrompt = () => {
482
+ if (options?.prompt) return typeof options.prompt === "function" ? options.prompt() : options.prompt;
483
+ const scopePath = getScopePath();
484
+ const label = scopePath ? `${programName}/${scopePath.replace(/ /g, "/")}` : programName;
485
+ return useAnsi ? `\x1b[1m${label}\x1b[0m ❯ ` : `${label} ❯ `;
486
+ };
487
+ const buildScopedCompleter = () => {
488
+ return buildReplCompleter(getScopeCommand(), { inScope: scopeStack.length > 0 });
489
+ };
490
+ const sessionConfig = { history: options?.history };
491
+ if (options?.completion !== false) sessionConfig.completer = buildScopedCompleter();
492
+ const session = runtime.readLine ? void 0 : createTerminalReplSession(sessionConfig);
493
+ const questionFn = session ? (prompt) => session.question(prompt) : runtime.readLine;
494
+ const updateCompleter = () => {
495
+ if (options?.completion === false) return;
496
+ const completer = buildScopedCompleter();
497
+ if (session) session.completer = completer;
498
+ sessionConfig.completer = completer;
499
+ };
500
+ let lastSigintTime = 0;
501
+ try {
502
+ while (true) {
503
+ const input = await questionFn(buildPrompt());
504
+ if (input === null) break;
505
+ if (input === REPL_SIGINT) {
506
+ const now = Date.now();
507
+ if (now - lastSigintTime < 2e3) break;
508
+ lastSigintTime = now;
509
+ runtime.output("(press Ctrl+C again to exit, or Ctrl+D)");
510
+ continue;
511
+ }
512
+ const trimmed = input.trim();
513
+ if (!trimmed) continue;
514
+ lastSigintTime = 0;
515
+ commandHistory.push(trimmed);
516
+ if (trimmed === ".exit" || trimmed === ".quit") break;
517
+ if (trimmed === ".clear") {
518
+ runtime.output("\x1B[2J\x1B[H");
519
+ continue;
520
+ }
521
+ if (trimmed === ".help") {
522
+ const lines = [
523
+ "REPL Commands:",
524
+ " . Execute the current scoped command",
525
+ " .help Print this help message",
526
+ " .exit Exit the REPL",
527
+ " .clear Clear the screen",
528
+ " .history Show command history",
529
+ " .scope <cmd> Scope into a subcommand",
530
+ " .scope .. Go up one scope level"
531
+ ];
532
+ 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.");
533
+ runtime.output(lines.join("\n"));
534
+ continue;
535
+ }
536
+ if (trimmed === ".history") {
537
+ const entries = commandHistory.slice(0, -1);
538
+ if (entries.length === 0) runtime.output("No history.");
539
+ else runtime.output(entries.map((entry, i) => `${i + 1} ${entry}`).join("\n"));
540
+ continue;
541
+ }
542
+ if (trimmed.startsWith(".scope ") || trimmed === ".scope") {
543
+ const target = trimmed.slice(6).trim();
544
+ if (target === ".." || target === "") {
545
+ if (scopeStack.length > 0) {
546
+ scopeStack.pop();
547
+ updateCompleter();
548
+ }
549
+ } else {
550
+ const found = findCommandByName(target, getScopeCommand().commands);
551
+ if (found) if (found.commands?.length) {
552
+ scopeStack.push(found);
553
+ updateCompleter();
554
+ } else runtime.error(`"${target}" has no subcommands to scope into.`);
555
+ else runtime.error(`Unknown command: ${target}`);
556
+ }
557
+ continue;
558
+ }
559
+ if (trimmed === "..") {
560
+ if (scopeStack.length > 0) {
561
+ scopeStack.pop();
562
+ updateCompleter();
563
+ }
564
+ continue;
565
+ }
566
+ let evalInput = trimmed;
567
+ if (trimmed === ".") evalInput = "";
568
+ const prefix = options?.outputPrefix;
569
+ const prefixLines = prefix ? (text) => text.split("\n").map((l) => prefix + l).join("\n") : void 0;
570
+ const savedRuntimes = [];
571
+ if (prefixLines) {
572
+ const prefixedRuntime = {
573
+ ...existingCommand.runtime,
574
+ output: (...args) => {
575
+ const first = args[0];
576
+ runtime.output(typeof first === "string" ? prefixLines(first) : first, ...args.slice(1));
577
+ },
578
+ error: (text) => runtime.error(prefixLines(text))
579
+ };
580
+ const patchAll = (cmd) => {
581
+ savedRuntimes.push({
582
+ cmd,
583
+ runtime: cmd.runtime
584
+ });
585
+ cmd.runtime = prefixedRuntime;
586
+ cmd.commands?.forEach(patchAll);
587
+ };
588
+ patchAll(existingCommand);
589
+ }
590
+ const sp = options?.spacing;
591
+ const isSpacingObject = typeof sp === "object" && sp !== null && !Array.isArray(sp);
592
+ const spacingBefore = isSpacingObject ? sp.before : sp;
593
+ const spacingAfter = isSpacingObject ? sp.after : sp;
594
+ const emitSpacingLine = (value) => {
595
+ if (typeof value === "string") {
596
+ const sep = value.length === 1 ? value.repeat(typeof process !== "undefined" && process.stdout?.columns ? process.stdout.columns : 80) : value;
597
+ runtime.output(sep);
598
+ } else if (value) runtime.output("");
599
+ };
600
+ const emitSpacing = (value) => {
601
+ if (!value) return;
602
+ if (Array.isArray(value)) for (const line of value) emitSpacingLine(line);
603
+ else emitSpacingLine(value);
604
+ };
605
+ emitSpacing(spacingBefore);
606
+ const scopePath = getScopePath();
607
+ const scopedInput = scopePath ? evalInput ? `${scopePath} ${evalInput}` : scopePath : evalInput;
608
+ try {
609
+ const result = await evalCommand(scopedInput, options?.autoOutput === false ? { autoOutput: false } : void 0);
610
+ if (result.argsResult?.issues) {
611
+ const msg = `Validation error:\n${result.argsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`;
612
+ runtime.error(prefixLines ? prefixLines(msg) : msg);
613
+ }
614
+ yield result;
615
+ } catch (err) {
616
+ const msg = err instanceof Error ? err.message : String(err);
617
+ runtime.error(prefixLines ? prefixLines(msg) : msg);
618
+ } finally {
619
+ for (const { cmd, runtime: saved } of savedRuntimes) cmd.runtime = saved;
620
+ emitSpacing(spacingAfter);
621
+ }
622
+ }
623
+ } finally {
624
+ replActiveRef.value = false;
625
+ session?.close();
626
+ }
627
+ }
628
+ return replIterator();
629
+ }
1150
630
  //#endregion
1151
631
  //#region src/wrap.ts
1152
632
  /**
1153
- * Converts parsed options to CLI arguments for an external command.
633
+ * Converts parsed arguments to CLI arguments for an external command.
1154
634
  */
1155
- function optionsToArgs(options, positional = []) {
635
+ function argsToCliArgs(input, positional = []) {
1156
636
  const args = [];
1157
- if (!options) return args;
637
+ if (!input) return args;
1158
638
  const positionalValues = {};
1159
- const regularOptions = {};
1160
- for (const [key, value] of Object.entries(options)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
1161
- else regularOptions[key] = value;
1162
- for (const [key, value] of Object.entries(regularOptions)) {
639
+ const regularArguments = {};
640
+ for (const [key, value] of Object.entries(input)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
641
+ else regularArguments[key] = value;
642
+ for (const [key, value] of Object.entries(regularArguments)) {
1163
643
  if (value === void 0 || value === null) continue;
1164
644
  const flag = `--${key}`;
1165
645
  if (typeof value === "boolean") {
@@ -1179,21 +659,19 @@ function optionsToArgs(options, positional = []) {
1179
659
  /**
1180
660
  * Creates an action handler that wraps an external CLI tool.
1181
661
  * @param config - Configuration for wrapping the external command (includes optional schema)
1182
- * @param commandOptions - The command's options schema
662
+ * @param commandArguments - The command's arguments schema
1183
663
  * @param commandPositional - Default positional config from the wrapping command
1184
664
  */
1185
- function createWrapHandler(config, commandOptions, commandPositional) {
1186
- return async (options) => {
665
+ function createWrapHandler(config, commandArguments, commandPositional) {
666
+ return async (args) => {
1187
667
  const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
1188
- const result = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandOptions) : wrapSchema : commandOptions)["~standard"].validate(options);
1189
- if (result instanceof Promise) throw new Error("Async validation is not supported. Wrap schema validate() must return a synchronous result.");
1190
- if (result.issues) {
1191
- const issueMessages = result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n");
1192
- throw new Error(`Wrap schema validation failed:\n${issueMessages}`);
1193
- }
1194
- const externalOptions = result.value;
1195
- const optionArgs = optionsToArgs(externalOptions, positional);
1196
- const allArgs = [...fixedArgs, ...optionArgs];
668
+ const validationResult = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandArguments) : wrapSchema : commandArguments)["~standard"].validate(args);
669
+ const processResult = (result) => {
670
+ 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);
671
+ return result.value;
672
+ };
673
+ const regularArgs = argsToCliArgs(validationResult instanceof Promise ? await validationResult.then(processResult) : processResult(validationResult), positional);
674
+ const allArgs = [...fixedArgs, ...regularArgs];
1197
675
  const proc = Bun.spawn([command, ...allArgs], {
1198
676
  stdout: inheritStdio ? "inherit" : "pipe",
1199
677
  stderr: inheritStdio ? "inherit" : "pipe",
@@ -1220,11 +698,8 @@ function createWrapHandler(config, commandOptions, commandPositional) {
1220
698
  };
1221
699
  };
1222
700
  }
1223
-
1224
701
  //#endregion
1225
702
  //#region src/create.ts
1226
- const commandSymbol = Symbol("padrone_command");
1227
- const noop = () => void 0;
1228
703
  function createPadrone(name) {
1229
704
  return createPadroneBuilder({
1230
705
  name,
@@ -1232,162 +707,268 @@ function createPadrone(name) {
1232
707
  commands: []
1233
708
  });
1234
709
  }
1235
- function createPadroneBuilder(existingCommand) {
1236
- function findCommandByName(name, commands) {
1237
- if (!commands) return void 0;
1238
- const foundByName = commands.find((cmd) => cmd.name === name);
1239
- if (foundByName) return foundByName;
1240
- const foundByAlias = commands.find((cmd) => cmd.aliases?.includes(name));
1241
- if (foundByAlias) return foundByAlias;
1242
- for (const cmd of commands) {
1243
- if (cmd.commands && name.startsWith(`${cmd.name} `)) {
1244
- const subCommand = findCommandByName(name.slice(cmd.name.length + 1), cmd.commands);
1245
- if (subCommand) return subCommand;
1246
- }
1247
- if (cmd.commands && cmd.aliases) {
1248
- for (const alias of cmd.aliases) if (name.startsWith(`${alias} `)) {
1249
- const subCommand = findCommandByName(name.slice(alias.length + 1), cmd.commands);
1250
- if (subCommand) return subCommand;
1251
- }
1252
- }
1253
- }
1254
- }
710
+ function createPadroneBuilder(inputCommand) {
711
+ const existingCommand = inputCommand.commands?.length && inputCommand.commands.some((c) => c.parent && c.parent !== inputCommand) ? {
712
+ ...inputCommand,
713
+ commands: inputCommand.commands.map((c) => c.parent && c.parent !== inputCommand ? {
714
+ ...c,
715
+ parent: inputCommand
716
+ } : c)
717
+ } : inputCommand;
718
+ /** Creates the action context passed to command handlers. References `builder` which is defined later but only called at runtime. */
719
+ const createActionContext = (cmd) => ({
720
+ runtime: getCommandRuntime(cmd),
721
+ command: cmd,
722
+ program: builder
723
+ });
1255
724
  const find = (command) => {
1256
725
  if (typeof command !== "string") return findCommandByName(command.path, existingCommand.commands);
1257
726
  return findCommandByName(command, existingCommand.commands);
1258
727
  };
1259
728
  /**
1260
- * Parses CLI input to find the command and extract raw options without validation.
729
+ * Parses CLI input to find the command and extract raw arguments without validation.
1261
730
  */
1262
731
  const parseCommand = (input) => {
1263
- input ??= typeof process !== "undefined" ? process.argv.slice(2).join(" ") : void 0;
1264
- if (!input) return {
1265
- command: existingCommand,
1266
- rawOptions: {},
1267
- args: []
1268
- };
732
+ input ??= getCommandRuntime(existingCommand).argv().join(" ") || void 0;
733
+ if (!input) {
734
+ const defaultCommand = findCommandByName("", existingCommand.commands);
735
+ if (defaultCommand) return {
736
+ command: defaultCommand,
737
+ rawArgs: {},
738
+ args: [],
739
+ unmatchedTerms: []
740
+ };
741
+ return {
742
+ command: existingCommand,
743
+ rawArgs: {},
744
+ args: [],
745
+ unmatchedTerms: []
746
+ };
747
+ }
1269
748
  const parts = parseCliInputToParts(input);
1270
749
  const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
1271
750
  const args = parts.filter((p) => p.type === "arg").map((p) => p.value);
1272
751
  let curCommand = existingCommand;
752
+ let unmatchedTerms = [];
1273
753
  if (terms[0] === existingCommand.name) terms.shift();
1274
754
  for (let i = 0; i < terms.length; i++) {
1275
755
  const found = findCommandByName(terms[i] || "", curCommand.commands);
1276
756
  if (found) curCommand = found;
1277
757
  else {
1278
- args.unshift(...terms.slice(i));
758
+ unmatchedTerms = terms.slice(i);
759
+ args.unshift(...unmatchedTerms);
1279
760
  break;
1280
761
  }
1281
762
  }
763
+ if (unmatchedTerms.length === 0 && curCommand.commands?.length) {
764
+ const defaultCommand = findCommandByName("", curCommand.commands);
765
+ if (defaultCommand) curCommand = defaultCommand;
766
+ }
1282
767
  if (!curCommand) return {
1283
768
  command: existingCommand,
1284
- rawOptions: {},
1285
- args
769
+ rawArgs: {},
770
+ args,
771
+ unmatchedTerms
1286
772
  };
1287
- const optionsMeta = curCommand.meta?.options;
1288
- const { aliases } = curCommand.options ? extractSchemaMetadata(curCommand.options, optionsMeta) : { aliases: {} };
1289
- const arrayOptions = /* @__PURE__ */ new Set();
1290
- if (curCommand.options) try {
1291
- const jsonSchema = curCommand.options["~standard"].jsonSchema.input({ target: "draft-2020-12" });
773
+ const argsMeta = curCommand.meta?.fields;
774
+ const { flags, aliases } = curCommand.argsSchema ? extractSchemaMetadata(curCommand.argsSchema, argsMeta, curCommand.meta?.autoAlias) : {
775
+ flags: {},
776
+ aliases: {}
777
+ };
778
+ const arrayArguments = /* @__PURE__ */ new Set();
779
+ if (curCommand.argsSchema) try {
780
+ const jsonSchema = curCommand.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
1292
781
  if (jsonSchema.type === "object" && jsonSchema.properties) {
1293
- for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayOptions.add(key);
782
+ for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayArguments.add(key);
1294
783
  }
1295
784
  } catch {}
1296
- const opts = parts.filter((p) => p.type === "option" || p.type === "alias");
1297
- const rawOptions = {};
1298
- for (const opt of opts) {
1299
- const key = opt.type === "alias" && opt.key.length === 1 && aliases[opt.key[0]] ? [aliases[opt.key[0]]] : opt.key;
785
+ const argParts = parts.filter((p) => p.type === "named" || p.type === "alias");
786
+ const rawArgs = {};
787
+ for (const arg of argParts) {
788
+ let key;
789
+ if (arg.type === "alias" && arg.key.length === 1 && flags[arg.key[0]]) key = [flags[arg.key[0]]];
790
+ else if (arg.type === "named" && arg.key.length === 1 && aliases[arg.key[0]]) key = [aliases[arg.key[0]]];
791
+ else key = arg.key;
1300
792
  const rootKey = key[0];
1301
- if (opt.type === "option" && opt.negated) {
1302
- setNestedValue(rawOptions, key, false);
793
+ if (arg.type === "named" && arg.negated) {
794
+ setNestedValue(rawArgs, key, false);
1303
795
  continue;
1304
796
  }
1305
- const value = opt.value ?? true;
1306
- if (arrayOptions.has(rootKey)) {
1307
- const existing = getNestedValue(rawOptions, key);
797
+ const value = arg.value ?? true;
798
+ if (arrayArguments.has(rootKey)) {
799
+ const existing = getNestedValue(rawArgs, key);
1308
800
  if (existing !== void 0) if (Array.isArray(existing)) if (Array.isArray(value)) existing.push(...value);
1309
801
  else existing.push(value);
1310
- else if (Array.isArray(value)) setNestedValue(rawOptions, key, [existing, ...value]);
1311
- else setNestedValue(rawOptions, key, [existing, value]);
1312
- else setNestedValue(rawOptions, key, Array.isArray(value) ? value : [value]);
1313
- } else setNestedValue(rawOptions, key, value);
802
+ else if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
803
+ else setNestedValue(rawArgs, key, [existing, value]);
804
+ else setNestedValue(rawArgs, key, Array.isArray(value) ? value : [value]);
805
+ } else setNestedValue(rawArgs, key, value);
1314
806
  }
1315
807
  return {
1316
808
  command: curCommand,
1317
- rawOptions,
1318
- args
809
+ rawArgs,
810
+ args,
811
+ unmatchedTerms
1319
812
  };
1320
813
  };
1321
814
  /**
1322
- * Validates raw options against the command's schema and applies preprocessing.
815
+ * Preprocesses raw arguments: applies env/config values and maps positional arguments.
816
+ * Also performs auto-coercion (string→number/boolean) and unknown arg detection.
1323
817
  */
1324
- const validateOptions = (command, rawOptions, args, parseOptions) => {
1325
- const preprocessedOptions = preprocessOptions(rawOptions, {
818
+ const buildCommandArgs = (command, rawArgs, args, context) => {
819
+ let preprocessedArgs = preprocessArgs(rawArgs, {
820
+ flags: {},
1326
821
  aliases: {},
1327
- envData: parseOptions?.envData,
1328
- configData: parseOptions?.configData
822
+ stdinData: context?.stdinData,
823
+ envData: context?.envData,
824
+ configData: context?.configData
1329
825
  });
1330
826
  const positionalConfig = command.meta?.positional ? parsePositionalConfig(command.meta.positional) : [];
1331
827
  if (positionalConfig.length > 0) {
1332
828
  let argIndex = 0;
1333
- for (const { name, variadic } of positionalConfig) {
829
+ for (let i = 0; i < positionalConfig.length; i++) {
830
+ const { name, variadic } = positionalConfig[i];
1334
831
  if (argIndex >= args.length) break;
1335
832
  if (variadic) {
1336
- const nonVariadicAfter = positionalConfig.slice(positionalConfig.indexOf({
1337
- name,
1338
- variadic
1339
- }) + 1).filter((p) => !p.variadic).length;
833
+ const nonVariadicAfter = positionalConfig.slice(i + 1).filter((p) => !p.variadic).length;
1340
834
  const variadicEnd = args.length - nonVariadicAfter;
1341
- preprocessedOptions[name] = args.slice(argIndex, variadicEnd);
835
+ preprocessedArgs[name] = args.slice(argIndex, variadicEnd);
1342
836
  argIndex = variadicEnd;
837
+ } else if (i === positionalConfig.length - 1 && args.length > argIndex + 1) {
838
+ preprocessedArgs[name] = args.slice(argIndex).join(" ");
839
+ argIndex = args.length;
1343
840
  } else {
1344
- preprocessedOptions[name] = args[argIndex];
841
+ preprocessedArgs[name] = args[argIndex];
1345
842
  argIndex++;
1346
843
  }
1347
844
  }
1348
845
  }
1349
- const optionsParsed = command.options ? command.options["~standard"].validate(preprocessedOptions) : { value: preprocessedOptions };
1350
- if (optionsParsed instanceof Promise) throw new Error("Async validation is not supported. Schema validate() must return a synchronous result.");
1351
- const hasOptions = command.options || Object.keys(preprocessedOptions).length > 0;
1352
- return {
1353
- options: optionsParsed.issues ? void 0 : hasOptions ? optionsParsed.value : void 0,
1354
- optionsResult: optionsParsed
1355
- };
846
+ if (command.argsSchema) preprocessedArgs = coerceArgs(preprocessedArgs, command.argsSchema);
847
+ return preprocessedArgs;
1356
848
  };
1357
- const parse = (input, parseOptions) => {
1358
- const { command, rawOptions, args } = parseCommand(input);
1359
- const resolveEnvSchema = (cmd) => {
1360
- if (cmd.envSchema !== void 0) return cmd.envSchema;
1361
- if (cmd.parent) return resolveEnvSchema(cmd.parent);
849
+ /**
850
+ * Detects unknown options in args that aren't defined in the schema.
851
+ * Returns unknown key info with suggestions, or empty array if schema is loose.
852
+ */
853
+ const checkUnknownArgs = (command, preprocessedArgs) => {
854
+ if (!command.argsSchema) return [];
855
+ const argsMeta = command.meta?.fields;
856
+ const { flags, aliases } = extractSchemaMetadata(command.argsSchema, argsMeta, command.meta?.autoAlias);
857
+ return detectUnknownArgs(preprocessedArgs, command.argsSchema, flags, aliases, suggestSimilar);
858
+ };
859
+ /**
860
+ * Validates preprocessed arguments against the command's schema.
861
+ * First checks for unknown args (strict by default), then runs schema validation.
862
+ * Returns sync or async result depending on the schema's validate method.
863
+ */
864
+ const validateCommandArgs = (command, preprocessedArgs) => {
865
+ const unknownArgs = checkUnknownArgs(command, preprocessedArgs);
866
+ if (unknownArgs.length > 0) return {
867
+ args: void 0,
868
+ argsResult: { issues: unknownArgs.map(({ key, suggestion }) => ({
869
+ path: [key],
870
+ message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
871
+ })) }
1362
872
  };
1363
- const envSchema = resolveEnvSchema(command);
1364
- let envData = parseOptions?.envData;
1365
- if (envSchema && !envData) {
1366
- const rawEnv = parseOptions?.env ?? (typeof process !== "undefined" ? process.env : {});
1367
- const envValidated = envSchema["~standard"].validate(rawEnv);
1368
- if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
1369
- if (!envValidated.issues) envData = envValidated.value;
1370
- }
1371
- const { options, optionsResult } = validateOptions(command, rawOptions, args, {
1372
- envData,
1373
- configData: parseOptions?.configData
873
+ const argsParsed = command.argsSchema ? command.argsSchema["~standard"].validate(preprocessedArgs) : { value: preprocessedArgs };
874
+ const hasArgs = command.argsSchema || Object.keys(preprocessedArgs).length > 0;
875
+ const buildResult = (parsed) => ({
876
+ args: parsed.issues ? void 0 : hasArgs ? parsed.value : void 0,
877
+ argsResult: parsed
1374
878
  });
1375
- return {
1376
- command,
1377
- options,
1378
- optionsResult
879
+ return thenMaybe(argsParsed, buildResult);
880
+ };
881
+ /**
882
+ * Preprocesses and validates raw arguments against the command's schema.
883
+ * Returns sync or async result depending on the schema's validate method.
884
+ */
885
+ const validateArgs = (command, rawArgs, args, context) => {
886
+ return validateCommandArgs(command, buildCommandArgs(command, rawArgs, args, context));
887
+ };
888
+ const parse = (input) => {
889
+ const state = {};
890
+ const parseCtx = {
891
+ input,
892
+ command: existingCommand,
893
+ state
1379
894
  };
895
+ const coreParse = () => {
896
+ const { command, rawArgs, args } = parseCommand(parseCtx.input);
897
+ return {
898
+ command,
899
+ rawArgs,
900
+ positionalArgs: args
901
+ };
902
+ };
903
+ const parsedOrPromise = runPluginChain("parse", existingCommand.plugins ?? [], parseCtx, coreParse);
904
+ const continueAfterParse = (parsed) => {
905
+ const { command } = parsed;
906
+ const commandPlugins = collectPlugins(command);
907
+ const validateCtx = {
908
+ command,
909
+ rawArgs: parsed.rawArgs,
910
+ positionalArgs: parsed.positionalArgs,
911
+ state
912
+ };
913
+ const coreValidate = () => {
914
+ const resolveEnvSchema = (cmd) => {
915
+ if (cmd.envSchema !== void 0) return cmd.envSchema;
916
+ if (cmd.parent) return resolveEnvSchema(cmd.parent);
917
+ };
918
+ const envSchema = resolveEnvSchema(command);
919
+ const readStdinForParse = () => {
920
+ const stdinConfig = command.meta?.stdin;
921
+ if (!stdinConfig) return {};
922
+ const { field, as } = parseStdinConfig(stdinConfig);
923
+ if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
924
+ const stdin = resolveStdin(getCommandRuntime(existingCommand));
925
+ if (!stdin) return {};
926
+ if (as === "lines") return (async () => {
927
+ const lines = [];
928
+ for await (const line of stdin.lines()) lines.push(line);
929
+ return { [field]: lines };
930
+ })();
931
+ return stdin.text().then((text) => text ? { [field]: text } : {});
932
+ };
933
+ const finalize = (envData, stdinData) => {
934
+ return thenMaybe(validateArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
935
+ stdinData,
936
+ envData
937
+ }), (v) => v);
938
+ };
939
+ let envData;
940
+ const afterEnv = (envResult) => {
941
+ return thenMaybe(readStdinForParse(), (stdinData) => {
942
+ return finalize(envResult, Object.keys(stdinData).length > 0 ? stdinData : void 0);
943
+ });
944
+ };
945
+ if (envSchema) {
946
+ const rawEnv = getCommandRuntime(existingCommand).env();
947
+ return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
948
+ if (!result.issues) envData = result.value;
949
+ return afterEnv(envData);
950
+ });
951
+ }
952
+ return afterEnv(envData);
953
+ };
954
+ return warnIfUnexpectedAsync(thenMaybe(runPluginChain("validate", commandPlugins, validateCtx, coreValidate), (v) => ({
955
+ command,
956
+ args: v.args,
957
+ argsResult: v.argsResult
958
+ })), command);
959
+ };
960
+ return thenMaybe(parsedOrPromise, continueAfterParse);
1380
961
  };
1381
- const stringify = (command = "", options) => {
962
+ const stringify = (command = "", args) => {
1382
963
  const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
1383
- if (!commandObj) throw new Error(`Command "${command ?? ""}" not found`);
964
+ if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
1384
965
  const parts = [];
1385
966
  if (commandObj.path) parts.push(commandObj.path);
1386
967
  const positionalConfig = commandObj.meta?.positional ? parsePositionalConfig(commandObj.meta.positional) : [];
1387
968
  const positionalNames = new Set(positionalConfig.map((p) => p.name));
1388
- if (options && typeof options === "object") {
969
+ if (args && typeof args === "object") {
1389
970
  for (const { name, variadic } of positionalConfig) {
1390
- const value = options[name];
971
+ const value = args[name];
1391
972
  if (value === void 0) continue;
1392
973
  if (variadic && Array.isArray(value)) for (const v of value) {
1393
974
  const vStr = String(v);
@@ -1414,7 +995,7 @@ function createPadroneBuilder(existingCommand) {
1414
995
  else parts.push(`--${key}=${value}`);
1415
996
  else parts.push(`--${key}=${value}`);
1416
997
  };
1417
- for (const [key, value] of Object.entries(options)) {
998
+ for (const [key, value] of Object.entries(args)) {
1418
999
  if (value === void 0 || positionalNames.has(key)) continue;
1419
1000
  stringifyValue(key, value);
1420
1001
  }
@@ -1429,16 +1010,16 @@ function createPadroneBuilder(existingCommand) {
1429
1010
  if (!input) return null;
1430
1011
  const parts = parseCliInputToParts(input);
1431
1012
  const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
1432
- const opts = parts.filter((p) => p.type === "option" || p.type === "alias");
1013
+ const args = parts.filter((p) => p.type === "named" || p.type === "alias");
1433
1014
  const keyIs = (key, name) => key.length === 1 && key[0] === name;
1434
- const hasHelpFlag = opts.some((p) => p.type === "option" && keyIs(p.key, "help") || p.type === "alias" && keyIs(p.key, "h"));
1015
+ const hasHelpFlag = args.some((p) => p.type === "named" && keyIs(p.key, "help") || p.type === "alias" && keyIs(p.key, "h"));
1435
1016
  const getDetailLevel = () => {
1436
- for (const opt of opts) {
1437
- if (opt.type === "option" && keyIs(opt.key, "detail") && typeof opt.value === "string") {
1438
- if (opt.value === "minimal" || opt.value === "standard" || opt.value === "full") return opt.value;
1017
+ for (const arg of args) {
1018
+ if (arg.type === "named" && keyIs(arg.key, "detail") && typeof arg.value === "string") {
1019
+ if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
1439
1020
  }
1440
- if (opt.type === "alias" && keyIs(opt.key, "d") && typeof opt.value === "string") {
1441
- if (opt.value === "minimal" || opt.value === "standard" || opt.value === "full") return opt.value;
1021
+ if (arg.type === "alias" && keyIs(arg.key, "d") && typeof arg.value === "string") {
1022
+ if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
1442
1023
  }
1443
1024
  }
1444
1025
  };
@@ -1453,17 +1034,17 @@ function createPadroneBuilder(existingCommand) {
1453
1034
  "json",
1454
1035
  "auto"
1455
1036
  ];
1456
- for (const opt of opts) {
1457
- if (opt.type === "option" && keyIs(opt.key, "format") && typeof opt.value === "string") {
1458
- if (validFormats.includes(opt.value)) return opt.value;
1037
+ for (const arg of args) {
1038
+ if (arg.type === "named" && keyIs(arg.key, "format") && typeof arg.value === "string") {
1039
+ if (validFormats.includes(arg.value)) return arg.value;
1459
1040
  }
1460
- if (opt.type === "alias" && keyIs(opt.key, "f") && typeof opt.value === "string") {
1461
- if (validFormats.includes(opt.value)) return opt.value;
1041
+ if (arg.type === "alias" && keyIs(arg.key, "f") && typeof arg.value === "string") {
1042
+ if (validFormats.includes(arg.value)) return arg.value;
1462
1043
  }
1463
1044
  }
1464
1045
  };
1465
1046
  const format = getFormat();
1466
- const hasVersionFlag = opts.some((p) => p.type === "option" && keyIs(p.key, "version") || p.type === "alias" && (keyIs(p.key, "v") || keyIs(p.key, "V")));
1047
+ const hasVersionFlag = args.some((p) => p.type === "named" && keyIs(p.key, "version") || p.type === "alias" && (keyIs(p.key, "v") || keyIs(p.key, "V")));
1467
1048
  const normalizedTerms = [...terms];
1468
1049
  if (normalizedTerms[0] === existingCommand.name) normalizedTerms.shift();
1469
1050
  const userHelpCommand = findCommandByName("help", existingCommand.commands);
@@ -1478,6 +1059,24 @@ function createPadroneBuilder(existingCommand) {
1478
1059
  format
1479
1060
  };
1480
1061
  }
1062
+ if (!userHelpCommand && normalizedTerms.length > 0 && normalizedTerms[normalizedTerms.length - 1] === "help") {
1063
+ const commandTerms = normalizedTerms.slice(0, -1);
1064
+ let targetCommand;
1065
+ let current = existingCommand;
1066
+ for (const term of commandTerms) {
1067
+ const found = findCommandByName(term, current.commands);
1068
+ if (found) {
1069
+ targetCommand = found;
1070
+ current = found;
1071
+ } else break;
1072
+ }
1073
+ return {
1074
+ type: "help",
1075
+ command: targetCommand,
1076
+ detail,
1077
+ format
1078
+ };
1079
+ }
1481
1080
  if (!userVersionCommand && normalizedTerms[0] === "version") return { type: "version" };
1482
1081
  if (!userCompletionCommand && normalizedTerms[0] === "completion") {
1483
1082
  const shellArg = normalizedTerms[1];
@@ -1488,7 +1087,8 @@ function createPadroneBuilder(existingCommand) {
1488
1087
  "zsh",
1489
1088
  "fish",
1490
1089
  "powershell"
1491
- ].includes(shellArg) ? shellArg : void 0
1090
+ ].includes(shellArg) ? shellArg : void 0,
1091
+ setup: args.some((p) => p.type === "named" && keyIs(p.key, "setup"))
1492
1092
  };
1493
1093
  }
1494
1094
  if (hasHelpFlag) {
@@ -1501,6 +1101,10 @@ function createPadroneBuilder(existingCommand) {
1501
1101
  };
1502
1102
  }
1503
1103
  if (hasVersionFlag && normalizedTerms.length === 0) return { type: "version" };
1104
+ if (args.some((p) => p.type === "named" && keyIs(p.key, "repl"))) return {
1105
+ type: "repl",
1106
+ scope: normalizedTerms.length > 0 ? normalizedTerms.join(" ") : void 0
1107
+ };
1504
1108
  return null;
1505
1109
  };
1506
1110
  /**
@@ -1508,139 +1112,455 @@ function createPadroneBuilder(existingCommand) {
1508
1112
  */
1509
1113
  const extractConfigPath = (input) => {
1510
1114
  if (!input) return void 0;
1511
- const opts = parseCliInputToParts(input).filter((p) => p.type === "option" || p.type === "alias");
1512
- for (const opt of opts) {
1513
- if (opt.type === "option" && opt.key.length === 1 && opt.key[0] === "config" && typeof opt.value === "string") return opt.value;
1514
- if (opt.type === "alias" && opt.key.length === 1 && opt.key[0] === "c" && typeof opt.value === "string") return opt.value;
1115
+ const args = parseCliInputToParts(input).filter((p) => p.type === "named" || p.type === "alias");
1116
+ for (const arg of args) {
1117
+ if (arg.type === "named" && arg.key.length === 1 && arg.key[0] === "config" && typeof arg.value === "string") return arg.value;
1118
+ if (arg.type === "alias" && arg.key.length === 1 && arg.key[0] === "c" && typeof arg.value === "string") return arg.value;
1515
1119
  }
1516
1120
  };
1517
- const cli = (input, cliOptions) => {
1518
- const resolvedInput = input ?? (typeof process !== "undefined" ? process.argv.slice(2).join(" ") : void 0);
1121
+ /**
1122
+ * Core execution logic shared by eval() and cli().
1123
+ * errorMode controls validation error behavior:
1124
+ * - 'soft': return result with issues (eval behavior)
1125
+ * - 'hard': print error + help and throw (cli-without-input behavior)
1126
+ */
1127
+ const execCommand = (resolvedInput, evalOptions, errorMode = "soft") => {
1128
+ const baseRuntime = getCommandRuntime(existingCommand);
1129
+ const runtime = evalOptions?.runtime ? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== void 0))) : baseRuntime;
1519
1130
  const builtin = checkBuiltinCommands(resolvedInput);
1520
1131
  if (builtin) {
1521
1132
  if (builtin.type === "help") {
1522
1133
  const helpText = generateHelp(existingCommand, builtin.command ?? existingCommand, {
1523
1134
  detail: builtin.detail,
1524
- format: builtin.format
1135
+ format: builtin.format ?? runtime.format
1525
1136
  });
1526
- console.log(helpText);
1137
+ runtime.output(helpText);
1527
1138
  return {
1528
1139
  command: existingCommand,
1529
1140
  args: void 0,
1530
- options: void 0,
1531
1141
  result: helpText
1532
1142
  };
1533
1143
  }
1534
1144
  if (builtin.type === "version") {
1535
1145
  const version = getVersion(existingCommand.version);
1536
- console.log(version);
1146
+ runtime.output(version);
1537
1147
  return {
1538
1148
  command: existingCommand,
1539
- options: void 0,
1149
+ args: void 0,
1540
1150
  result: version
1541
1151
  };
1542
1152
  }
1543
- if (builtin.type === "completion") {
1153
+ if (builtin.type === "completion") return import("./completion.mjs").then(({ detectShell, generateCompletionOutput, setupCompletions }) => {
1154
+ if (builtin.setup) {
1155
+ const shell = builtin.shell ?? detectShell();
1156
+ if (!shell) throw new Error("Could not detect shell. Specify one: completion bash --setup");
1157
+ const result = setupCompletions(existingCommand.name, shell);
1158
+ const message = `${result.updated ? "Updated" : "Added"} ${existingCommand.name} completions in ${result.file}`;
1159
+ runtime.output(message);
1160
+ return {
1161
+ command: existingCommand,
1162
+ args: void 0,
1163
+ result: message
1164
+ };
1165
+ }
1544
1166
  const completionScript = generateCompletionOutput(existingCommand, builtin.shell);
1545
- console.log(completionScript);
1167
+ runtime.output(completionScript);
1546
1168
  return {
1547
1169
  command: existingCommand,
1548
- options: void 0,
1170
+ args: void 0,
1549
1171
  result: completionScript
1550
1172
  };
1551
- }
1173
+ });
1552
1174
  }
1553
- const { command, rawOptions, args } = parseCommand(resolvedInput);
1554
- const configPath = extractConfigPath(resolvedInput);
1555
- const resolveConfigFiles = (cmd) => {
1556
- if (cmd.configFiles !== void 0) return cmd.configFiles;
1557
- if (cmd.parent) return resolveConfigFiles(cmd.parent);
1558
- };
1559
- const effectiveConfigFiles = resolveConfigFiles(command);
1560
- const resolveConfigSchema = (cmd) => {
1561
- if (cmd.config !== void 0) return cmd.config;
1562
- if (cmd.parent) return resolveConfigSchema(cmd.parent);
1563
- };
1564
- const configSchema = resolveConfigSchema(command);
1565
- const resolveEnvSchema = (cmd) => {
1566
- if (cmd.envSchema !== void 0) return cmd.envSchema;
1567
- if (cmd.parent) return resolveEnvSchema(cmd.parent);
1175
+ const state = {};
1176
+ const rootPlugins = existingCommand.plugins ?? [];
1177
+ const runPipeline = () => {
1178
+ const parseCtx = {
1179
+ input: resolvedInput,
1180
+ command: existingCommand,
1181
+ state
1182
+ };
1183
+ const coreParse = () => {
1184
+ const { command, rawArgs, args, unmatchedTerms } = parseCommand(parseCtx.input);
1185
+ const hasSubcommands = command.commands && command.commands.length > 0;
1186
+ const hasSchema = command.argsSchema != null;
1187
+ if (!command.action && (hasSubcommands || !hasSchema) && unmatchedTerms.length === 0) {
1188
+ const helpText = generateHelp(existingCommand, command, { format: runtime.format });
1189
+ runtime.output(helpText);
1190
+ return {
1191
+ command,
1192
+ rawArgs: { "~help": helpText },
1193
+ positionalArgs: []
1194
+ };
1195
+ }
1196
+ if (unmatchedTerms.length > 0) {
1197
+ if (!(command.meta?.positional && command.meta.positional.length > 0)) {
1198
+ const isRootCommand = command === existingCommand;
1199
+ const commandDisplayName = command.name || command.aliases?.[0] || command.path || "(default)";
1200
+ const candidateNames = [];
1201
+ if (isRootCommand && existingCommand.commands) {
1202
+ for (const cmd of existingCommand.commands) if (!cmd.hidden) {
1203
+ candidateNames.push(cmd.name);
1204
+ if (cmd.aliases) candidateNames.push(...cmd.aliases);
1205
+ }
1206
+ } else if (command.commands) {
1207
+ for (const cmd of command.commands) if (!cmd.hidden) {
1208
+ candidateNames.push(cmd.name);
1209
+ if (cmd.aliases) candidateNames.push(...cmd.aliases);
1210
+ }
1211
+ }
1212
+ const suggestion = suggestSimilar(unmatchedTerms[0], candidateNames);
1213
+ const suggestions = suggestion ? [suggestion] : [];
1214
+ const baseMsg = isRootCommand ? `Unknown command: ${unmatchedTerms[0]}` : `Unexpected arguments for '${commandDisplayName}': ${unmatchedTerms.join(" ")}`;
1215
+ const errorMsg = suggestions.length ? `${baseMsg}\n\n ${suggestions[0]}` : baseMsg;
1216
+ if (errorMode === "hard") {
1217
+ runtime.error(errorMsg);
1218
+ if (suggestions.length > 0) {
1219
+ const visibleCommands = ((isRootCommand ? existingCommand : command).commands ?? []).filter((c) => !c.hidden && c.name);
1220
+ if (visibleCommands.length > 0) {
1221
+ const cmdList = visibleCommands.map((c) => c.name).join(", ");
1222
+ runtime.output(`\nAvailable commands: ${cmdList}`);
1223
+ }
1224
+ } else {
1225
+ const helpText = generateHelp(existingCommand, isRootCommand ? existingCommand : command, { format: runtime.format });
1226
+ runtime.error(helpText);
1227
+ }
1228
+ throw new RoutingError(errorMsg, {
1229
+ suggestions,
1230
+ command: command.path || command.name
1231
+ });
1232
+ }
1233
+ throw new RoutingError(errorMsg, {
1234
+ suggestions,
1235
+ command: command.path || command.name
1236
+ });
1237
+ }
1238
+ }
1239
+ return {
1240
+ command,
1241
+ rawArgs,
1242
+ positionalArgs: args
1243
+ };
1244
+ };
1245
+ const parsedOrPromise = runPluginChain("parse", rootPlugins, parseCtx, coreParse);
1246
+ const continueAfterParse = (parsed) => {
1247
+ const { command } = parsed;
1248
+ const commandPlugins = collectPlugins(command);
1249
+ if (parsed.rawArgs["~help"]) return {
1250
+ command,
1251
+ args: void 0,
1252
+ result: parsed.rawArgs["~help"]
1253
+ };
1254
+ const validateCtx = {
1255
+ command,
1256
+ rawArgs: parsed.rawArgs,
1257
+ positionalArgs: parsed.positionalArgs,
1258
+ state
1259
+ };
1260
+ const coreValidate = () => {
1261
+ let flagInteractive;
1262
+ if (hasInteractiveConfig(command.meta)) {
1263
+ if (validateCtx.rawArgs.interactive !== void 0) {
1264
+ flagInteractive = validateCtx.rawArgs.interactive !== false && validateCtx.rawArgs.interactive !== "false";
1265
+ delete validateCtx.rawArgs.interactive;
1266
+ }
1267
+ if (validateCtx.rawArgs.i !== void 0) {
1268
+ flagInteractive = validateCtx.rawArgs.i !== false && validateCtx.rawArgs.i !== "false";
1269
+ delete validateCtx.rawArgs.i;
1270
+ }
1271
+ }
1272
+ const runtimeDefault = runtime.interactive === "forced" ? true : runtime.interactive === "disabled" ? false : void 0;
1273
+ const effectiveInteractive = flagInteractive ?? evalOptions?.interactive ?? runtimeDefault;
1274
+ const stdinIsPiped = !!command.meta?.stdin && (runtime.stdin ? !runtime.stdin.isTTY : typeof process !== "undefined" && process.stdin?.isTTY !== true);
1275
+ const interactivitySuppressed = runtime.interactive === "unsupported" || effectiveInteractive === false || stdinIsPiped && effectiveInteractive !== true;
1276
+ const forceInteractive = !interactivitySuppressed && effectiveInteractive === true;
1277
+ const configPath = extractConfigPath(parseCtx.input);
1278
+ const resolveConfigFiles = (cmd) => {
1279
+ if (cmd.configFiles !== void 0) return cmd.configFiles;
1280
+ if (cmd.parent) return resolveConfigFiles(cmd.parent);
1281
+ };
1282
+ const effectiveConfigFiles = resolveConfigFiles(command);
1283
+ const resolveConfigSchema = (cmd) => {
1284
+ if (cmd.configSchema !== void 0) return cmd.configSchema;
1285
+ if (cmd.parent) return resolveConfigSchema(cmd.parent);
1286
+ };
1287
+ const configSchema = resolveConfigSchema(command);
1288
+ const resolveEnvSchema = (cmd) => {
1289
+ if (cmd.envSchema !== void 0) return cmd.envSchema;
1290
+ if (cmd.parent) return resolveEnvSchema(cmd.parent);
1291
+ };
1292
+ const envSchema = resolveEnvSchema(command);
1293
+ let configData;
1294
+ if (configPath) configData = runtime.loadConfigFile(configPath);
1295
+ else if (effectiveConfigFiles?.length) {
1296
+ const foundConfigPath = runtime.findFile(effectiveConfigFiles);
1297
+ if (foundConfigPath) configData = runtime.loadConfigFile(foundConfigPath) ?? configData;
1298
+ }
1299
+ const validateConfig = () => {
1300
+ if (configData && configSchema) return thenMaybe(configSchema["~standard"].validate(configData), (result) => {
1301
+ 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 });
1302
+ return result.value;
1303
+ });
1304
+ return configData;
1305
+ };
1306
+ const validateEnv = () => {
1307
+ let envData;
1308
+ if (envSchema) {
1309
+ const rawEnv = runtime.env();
1310
+ return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
1311
+ if (!result.issues) envData = result.value;
1312
+ return envData;
1313
+ });
1314
+ }
1315
+ return envData;
1316
+ };
1317
+ const readStdin = () => {
1318
+ const stdinConfig = command.meta?.stdin;
1319
+ if (!stdinConfig) return {};
1320
+ const { field, as } = parseStdinConfig(stdinConfig);
1321
+ if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
1322
+ const stdin = resolveStdin(runtime);
1323
+ if (!stdin) return {};
1324
+ if (as === "lines") return (async () => {
1325
+ const lines = [];
1326
+ for await (const line of stdin.lines()) lines.push(line);
1327
+ return { [field]: lines };
1328
+ })();
1329
+ return stdin.text().then((text) => {
1330
+ if (!text) return {};
1331
+ return { [field]: text };
1332
+ });
1333
+ };
1334
+ const finalizeValidation = (validatedConfigData, envData, stdinData) => {
1335
+ const preprocessedArgs = buildCommandArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
1336
+ stdinData,
1337
+ envData,
1338
+ configData: validatedConfigData
1339
+ });
1340
+ if (!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta)) {
1341
+ const unknowns = checkUnknownArgs(command, preprocessedArgs);
1342
+ if (unknowns.length > 0) return {
1343
+ args: void 0,
1344
+ argsResult: { issues: unknowns.map(({ key, suggestion }) => ({
1345
+ path: [key],
1346
+ message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
1347
+ })) }
1348
+ };
1349
+ if (command.argsSchema) {
1350
+ const providedKeys = new Set(Object.keys(preprocessedArgs).filter((k) => preprocessedArgs[k] !== void 0));
1351
+ const earlyCheck = command.argsSchema["~standard"].validate(preprocessedArgs);
1352
+ const checkForProvidedFieldErrors = (result) => {
1353
+ if (!result.issues) return void 0;
1354
+ const providedFieldIssues = result.issues.filter((issue) => {
1355
+ const rootKey = issue.path?.[0];
1356
+ return rootKey !== void 0 && providedKeys.has(String(rootKey));
1357
+ });
1358
+ if (providedFieldIssues.length > 0) return {
1359
+ args: void 0,
1360
+ argsResult: { issues: providedFieldIssues }
1361
+ };
1362
+ };
1363
+ const earlyResult = thenMaybe(earlyCheck, (result) => {
1364
+ const errors = checkForProvidedFieldErrors(result);
1365
+ if (errors) return errors;
1366
+ });
1367
+ if (earlyResult instanceof Promise) return earlyResult.then((err) => {
1368
+ if (err) return err;
1369
+ return continueWithPrompt(preprocessedArgs);
1370
+ });
1371
+ if (earlyResult) return earlyResult;
1372
+ }
1373
+ }
1374
+ return continueWithPrompt(preprocessedArgs);
1375
+ };
1376
+ const continueWithPrompt = (preprocessedArgs) => {
1377
+ return thenMaybe(!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta) ? promptInteractiveFields(preprocessedArgs, command, runtime, forceInteractive || void 0) : preprocessedArgs, (filledArgs) => {
1378
+ return thenMaybe(validateCommandArgs(command, filledArgs), (v) => v);
1379
+ });
1380
+ };
1381
+ return thenMaybe(validateConfig(), (cfgData) => {
1382
+ return thenMaybe(validateEnv(), (envData) => {
1383
+ return thenMaybe(readStdin(), (stdinData) => {
1384
+ return finalizeValidation(cfgData, envData, Object.keys(stdinData).length > 0 ? stdinData : void 0);
1385
+ });
1386
+ });
1387
+ });
1388
+ };
1389
+ const validatedOrPromise = runPluginChain("validate", commandPlugins, validateCtx, coreValidate);
1390
+ const continueAfterValidate = (v) => {
1391
+ if (v.argsResult?.issues) {
1392
+ let knownOptions;
1393
+ const getKnownOptions = () => {
1394
+ if (knownOptions) return knownOptions;
1395
+ knownOptions = [];
1396
+ if (command.argsSchema) try {
1397
+ const js = command.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
1398
+ if (js.type === "object" && js.properties) knownOptions = Object.keys(js.properties);
1399
+ } catch {}
1400
+ return knownOptions;
1401
+ };
1402
+ const issueMessages = v.argsResult.issues.map((i) => {
1403
+ const base = ` - ${i.path?.join(".") || "root"}: ${i.message}`;
1404
+ const unrecognizedKeys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
1405
+ if (unrecognizedKeys?.length) {
1406
+ const hints = unrecognizedKeys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
1407
+ if (hints.length) return `${base}\n ${hints.join("\n ")}`;
1408
+ }
1409
+ return base;
1410
+ }).join("\n");
1411
+ if (errorMode === "hard") {
1412
+ const helpText = generateHelp(existingCommand, command, { format: runtime.format });
1413
+ runtime.error(`Validation error:\n${issueMessages}`);
1414
+ runtime.error(helpText);
1415
+ throw new ValidationError(`Validation error:\n${issueMessages}`, v.argsResult.issues, {
1416
+ suggestions: v.argsResult.issues.flatMap((i) => {
1417
+ const keys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
1418
+ if (!keys?.length) return [];
1419
+ return keys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
1420
+ }),
1421
+ command: command.path || command.name
1422
+ });
1423
+ }
1424
+ return {
1425
+ command,
1426
+ args: void 0,
1427
+ argsResult: v.argsResult,
1428
+ result: void 0
1429
+ };
1430
+ }
1431
+ const executeCtx = {
1432
+ command,
1433
+ args: v.args,
1434
+ state
1435
+ };
1436
+ const coreExecute = () => {
1437
+ const handler = command.action ?? noop;
1438
+ const ctx = evalOptions?.runtime ? {
1439
+ ...createActionContext(command),
1440
+ runtime
1441
+ } : createActionContext(command);
1442
+ return { result: handler(executeCtx.args, ctx) };
1443
+ };
1444
+ return thenMaybe(runPluginChain("execute", commandPlugins, executeCtx, coreExecute), (e) => {
1445
+ const commandResult = {
1446
+ command,
1447
+ args: v.args,
1448
+ argsResult: v.argsResult,
1449
+ result: e.result
1450
+ };
1451
+ if (command.autoOutput ?? evalOptions?.autoOutput ?? true) {
1452
+ const outputOrPromise = outputValue(e.result, runtime.output);
1453
+ if (outputOrPromise instanceof Promise) return outputOrPromise.then(() => commandResult);
1454
+ }
1455
+ return commandResult;
1456
+ });
1457
+ };
1458
+ return warnIfUnexpectedAsync(thenMaybe(validatedOrPromise, continueAfterValidate), command);
1459
+ };
1460
+ return thenMaybe(parsedOrPromise, continueAfterParse);
1568
1461
  };
1569
- const envSchema = resolveEnvSchema(command);
1570
- let configData = cliOptions?.configData;
1571
- if (configPath) configData = loadConfigFile(configPath);
1572
- else if (effectiveConfigFiles?.length) {
1573
- const foundConfigPath = findConfigFile(effectiveConfigFiles);
1574
- if (foundConfigPath) configData = loadConfigFile(foundConfigPath) ?? configData;
1462
+ return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) => ({
1463
+ command: existingCommand,
1464
+ args: void 0,
1465
+ argsResult: void 0,
1466
+ result
1467
+ }));
1468
+ };
1469
+ const evalCommand = (input, evalOptions) => {
1470
+ return execCommand(input, evalOptions, "soft");
1471
+ };
1472
+ /**
1473
+ * Collects plugins from the command's parent chain (root → ... → target).
1474
+ * Root/program plugins come first (outermost), target command's plugins last (innermost).
1475
+ *
1476
+ * The `programRoot` parameter provides the current program command, because
1477
+ * subcommands' `.parent` references may be stale (builders are immutable — each
1478
+ * method returns a new builder, so a subcommand's parent was captured before
1479
+ * `.use()` was called on the program). We substitute `programRoot` for the
1480
+ * top of the chain to ensure program-level plugins are always included.
1481
+ */
1482
+ const collectPlugins = (cmd) => {
1483
+ const chain = [];
1484
+ let current = cmd;
1485
+ while (current) {
1486
+ if (!current.parent) {
1487
+ if (existingCommand.plugins?.length) chain.unshift(existingCommand.plugins);
1488
+ } else if (current.plugins?.length) chain.unshift(current.plugins);
1489
+ current = current.parent;
1575
1490
  }
1576
- if (configData && configSchema) {
1577
- const configValidated = configSchema["~standard"].validate(configData);
1578
- if (configValidated instanceof Promise) throw new Error("Async validation is not supported. Config schema validate() must return a synchronous result.");
1579
- if (configValidated.issues) {
1580
- const issueMessages = configValidated.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n");
1581
- throw new Error(`Invalid config file:\n${issueMessages}`);
1491
+ return chain.flat();
1492
+ };
1493
+ let replFn;
1494
+ const replActiveRef = { value: false };
1495
+ const cli = (cliOptions) => {
1496
+ const runtime = getCommandRuntime(existingCommand);
1497
+ const resolvedInput = runtime.argv().join(" ") || void 0;
1498
+ if (cliOptions?.repl !== false) {
1499
+ const builtin = checkBuiltinCommands(resolvedInput);
1500
+ if (builtin?.type === "repl") {
1501
+ const replPrefs = {
1502
+ ...typeof cliOptions?.repl === "object" ? cliOptions.repl : {},
1503
+ scope: builtin.scope,
1504
+ autoOutput: (typeof cliOptions?.repl === "object" ? cliOptions.repl.autoOutput : void 0) ?? cliOptions?.autoOutput
1505
+ };
1506
+ const drainRepl = async () => {
1507
+ for await (const _ of replFn(replPrefs));
1508
+ return {
1509
+ command: existingCommand,
1510
+ args: void 0,
1511
+ result: void 0
1512
+ };
1513
+ };
1514
+ return drainRepl();
1582
1515
  }
1583
- configData = configValidated.value;
1584
- }
1585
- let envData = cliOptions?.envData;
1586
- if (envSchema) {
1587
- const rawEnv = cliOptions?.env ?? (typeof process !== "undefined" ? process.env : {});
1588
- const envValidated = envSchema["~standard"].validate(rawEnv);
1589
- if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
1590
- if (!envValidated.issues) envData = envValidated.value;
1591
1516
  }
1592
- const { options, optionsResult } = validateOptions(command, rawOptions, args, {
1593
- envData,
1594
- configData
1595
- });
1596
- if (optionsResult?.issues) {
1597
- const issueMessages = optionsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n");
1598
- if (input === void 0) {
1599
- const helpText = generateHelp(existingCommand, command, { format: "text" });
1600
- console.error(`Validation error:\n${issueMessages}`);
1601
- console.error(helpText);
1602
- throw new Error(`Validation error:\n${issueMessages}`);
1517
+ let updateCheckPromise;
1518
+ if (existingCommand.updateCheck) {
1519
+ if (!(resolvedInput && parseCliInputToParts(resolvedInput).some((p) => p.type === "named" && p.key.length === 1 && p.key[0] === "no-update-check"))) {
1520
+ const currentVersion = getVersion(existingCommand.version);
1521
+ updateCheckPromise = import("./update-check-EbNDkzyV.mjs").then(({ createUpdateChecker }) => createUpdateChecker(existingCommand.name, currentVersion, existingCommand.updateCheck, runtime));
1603
1522
  }
1604
- return {
1605
- command,
1606
- options: void 0,
1607
- optionsResult,
1608
- result: void 0
1609
- };
1610
1523
  }
1611
- return {
1612
- ...run(command, options),
1613
- optionsResult
1614
- };
1524
+ const result = execCommand(resolvedInput, cliOptions, "hard");
1525
+ if (updateCheckPromise) {
1526
+ if (result instanceof Promise) return result.then(async (r) => {
1527
+ (await updateCheckPromise)?.();
1528
+ return r;
1529
+ });
1530
+ updateCheckPromise.then((show) => show?.());
1531
+ }
1532
+ return result;
1615
1533
  };
1616
- const run = (command, options) => {
1534
+ const run = (command, args) => {
1617
1535
  const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
1618
- if (!commandObj) throw new Error(`Command "${command ?? ""}" not found`);
1619
- if (!commandObj.handler) throw new Error(`Command "${commandObj.path}" has no handler`);
1620
- return {
1536
+ if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
1537
+ if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
1538
+ const executeCtx = {
1621
1539
  command: commandObj,
1622
- options,
1623
- result: commandObj.handler(options)
1540
+ args,
1541
+ state: {}
1542
+ };
1543
+ const coreExecute = () => {
1544
+ return { result: commandObj.action(executeCtx.args, createActionContext(commandObj)) };
1624
1545
  };
1546
+ const executedOrPromise = runPluginChain("execute", collectPlugins(commandObj), executeCtx, coreExecute);
1547
+ const toResult = (e) => ({
1548
+ command: commandObj,
1549
+ args,
1550
+ result: e.result
1551
+ });
1552
+ if (executedOrPromise instanceof Promise) return executedOrPromise.then(toResult);
1553
+ return toResult(executedOrPromise);
1625
1554
  };
1626
1555
  const tool = () => {
1627
- const description = `\n
1628
- 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.
1629
-
1630
- <help_output>
1631
- ${generateHelp(existingCommand, void 0, {
1632
- format: "text",
1633
- detail: "full"
1634
- })}
1635
- </help_output>
1636
- `;
1556
+ 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" })}`;
1637
1557
  return {
1638
1558
  type: "function",
1639
1559
  name: existingCommand.name,
1640
1560
  strict: true,
1641
1561
  title: existingCommand.description,
1642
1562
  description,
1643
- inputExamples: [{ input: { command: "<command> [args...] [options...]" } }],
1563
+ inputExamples: [{ input: { command: "<command> [positionals...] [arguments...]" } }],
1644
1564
  inputSchema: {
1645
1565
  [Symbol.for("vercel.ai.schema")]: true,
1646
1566
  jsonSchema: {
@@ -1661,64 +1581,105 @@ ${generateHelp(existingCommand, void 0, {
1661
1581
  };
1662
1582
  }
1663
1583
  },
1664
- needsApproval: (input) => {
1665
- const { command, options } = parse(input.command);
1666
- if (typeof command.needsApproval === "function") return command.needsApproval(options);
1667
- return !!command.needsApproval;
1584
+ needsApproval: async (input) => {
1585
+ const parsed = await parse(input.command);
1586
+ if (typeof parsed.command.needsApproval === "function") return parsed.command.needsApproval(parsed.args);
1587
+ return !!parsed.command.needsApproval;
1668
1588
  },
1669
- execute: (input) => {
1670
- return cli(input.command).result;
1589
+ execute: async (input) => {
1590
+ const output = [];
1591
+ const errors = [];
1592
+ return {
1593
+ result: (await evalCommand(input.command, {
1594
+ autoOutput: false,
1595
+ runtime: {
1596
+ output: (...args) => output.push(args.map(String).join(" ")),
1597
+ error: (text) => errors.push(text),
1598
+ interactive: "unsupported",
1599
+ format: "text"
1600
+ }
1601
+ })).result,
1602
+ logs: output.join("\n"),
1603
+ error: errors.join("\n")
1604
+ };
1671
1605
  }
1672
1606
  };
1673
1607
  };
1674
- return {
1608
+ const builder = {
1675
1609
  configure(config) {
1676
1610
  return createPadroneBuilder({
1677
1611
  ...existingCommand,
1678
1612
  ...config
1679
1613
  });
1680
1614
  },
1681
- arguments(options, meta) {
1682
- const resolvedOptions = typeof options === "function" ? options(existingCommand.options) : options;
1615
+ runtime(runtimeConfig) {
1616
+ return createPadroneBuilder({
1617
+ ...existingCommand,
1618
+ runtime: {
1619
+ ...existingCommand.runtime,
1620
+ ...runtimeConfig
1621
+ }
1622
+ });
1623
+ },
1624
+ async() {
1683
1625
  return createPadroneBuilder({
1684
1626
  ...existingCommand,
1685
- options: resolvedOptions,
1686
- meta
1627
+ isAsync: true
1628
+ });
1629
+ },
1630
+ arguments(schema, meta) {
1631
+ const resolvedArgs = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
1632
+ const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedArgs) || hasInteractiveConfig(meta);
1633
+ return createPadroneBuilder({
1634
+ ...existingCommand,
1635
+ argsSchema: resolvedArgs,
1636
+ meta,
1637
+ isAsync
1687
1638
  });
1688
1639
  },
1689
1640
  configFile(file, schema) {
1690
1641
  const configFiles = file === void 0 ? void 0 : Array.isArray(file) ? file : [file];
1691
- const resolvedConfig = typeof schema === "function" ? schema(existingCommand.options) : schema ?? existingCommand.options;
1642
+ const resolvedConfig = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema ?? existingCommand.argsSchema;
1643
+ const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedConfig);
1692
1644
  return createPadroneBuilder({
1693
1645
  ...existingCommand,
1694
1646
  configFiles,
1695
- config: resolvedConfig
1647
+ configSchema: resolvedConfig,
1648
+ isAsync
1696
1649
  });
1697
1650
  },
1698
1651
  env(schema) {
1699
- const resolvedEnv = typeof schema === "function" ? schema(existingCommand.options) : schema;
1652
+ const resolvedEnv = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
1653
+ const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedEnv);
1700
1654
  return createPadroneBuilder({
1701
1655
  ...existingCommand,
1702
- envSchema: resolvedEnv
1656
+ envSchema: resolvedEnv,
1657
+ isAsync
1703
1658
  });
1704
1659
  },
1705
1660
  action(handler = noop) {
1661
+ const baseHandler = existingCommand.action ?? noop;
1706
1662
  return createPadroneBuilder({
1707
1663
  ...existingCommand,
1708
- handler
1664
+ action: (args, ctx) => handler(args, ctx, baseHandler)
1709
1665
  });
1710
1666
  },
1711
1667
  wrap(config) {
1712
- const handler = createWrapHandler(config, existingCommand.options, existingCommand.meta?.positional);
1668
+ const handler = createWrapHandler(config, existingCommand.argsSchema, existingCommand.meta?.positional);
1713
1669
  return createPadroneBuilder({
1714
1670
  ...existingCommand,
1715
- handler
1671
+ action: handler
1716
1672
  });
1717
1673
  },
1718
1674
  command(nameOrNames, builderFn) {
1719
1675
  const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
1720
1676
  const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
1721
- const initialCommand = {
1677
+ const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
1678
+ const initialCommand = existingSubcommand ? {
1679
+ ...existingSubcommand,
1680
+ aliases: aliases ?? existingSubcommand.aliases,
1681
+ parent: existingCommand
1682
+ } : {
1722
1683
  name,
1723
1684
  path: existingCommand.path ? `${existingCommand.path} ${name}` : name,
1724
1685
  aliases,
@@ -1727,39 +1688,94 @@ ${generateHelp(existingCommand, void 0, {
1727
1688
  };
1728
1689
  const builder = createPadroneBuilder(initialCommand);
1729
1690
  const commandObj = (builderFn?.(builder))?.[commandSymbol] ?? initialCommand;
1691
+ const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, commandObj) : commandObj;
1692
+ const commands = existingCommand.commands || [];
1693
+ const existingIndex = commands.findIndex((c) => c.name === name);
1694
+ const updatedCommands = existingIndex >= 0 ? [
1695
+ ...commands.slice(0, existingIndex),
1696
+ mergedCommandObj,
1697
+ ...commands.slice(existingIndex + 1)
1698
+ ] : [...commands, mergedCommandObj];
1699
+ return createPadroneBuilder({
1700
+ ...existingCommand,
1701
+ commands: updatedCommands
1702
+ });
1703
+ },
1704
+ mount(nameOrNames, program) {
1705
+ const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
1706
+ const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
1707
+ const programCommand = program[commandSymbol];
1708
+ if (!programCommand) throw new RoutingError("Cannot mount: not a valid Padrone program");
1709
+ const remounted = repathCommandTree(programCommand, name, existingCommand.path || "", existingCommand);
1710
+ remounted.aliases = aliases;
1711
+ const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
1712
+ const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, remounted) : remounted;
1713
+ const commands = existingCommand.commands || [];
1714
+ const existingIndex = commands.findIndex((c) => c.name === name);
1715
+ const updatedCommands = existingIndex >= 0 ? [
1716
+ ...commands.slice(0, existingIndex),
1717
+ mergedCommandObj,
1718
+ ...commands.slice(existingIndex + 1)
1719
+ ] : [...commands, mergedCommandObj];
1730
1720
  return createPadroneBuilder({
1731
1721
  ...existingCommand,
1732
- commands: [...existingCommand.commands || [], commandObj]
1722
+ commands: updatedCommands
1723
+ });
1724
+ },
1725
+ use(plugin) {
1726
+ return createPadroneBuilder({
1727
+ ...existingCommand,
1728
+ plugins: [...existingCommand.plugins ?? [], plugin]
1729
+ });
1730
+ },
1731
+ updateCheck(config = {}) {
1732
+ return createPadroneBuilder({
1733
+ ...existingCommand,
1734
+ updateCheck: config
1733
1735
  });
1734
1736
  },
1735
1737
  run,
1736
1738
  find,
1737
1739
  parse,
1738
1740
  stringify,
1741
+ eval: evalCommand,
1739
1742
  cli,
1740
1743
  tool,
1744
+ repl: replFn = (options) => {
1745
+ return createReplIterator({
1746
+ existingCommand,
1747
+ evalCommand,
1748
+ replActiveRef
1749
+ }, options);
1750
+ },
1741
1751
  api() {
1742
1752
  function buildApi(command) {
1743
- const runCommand = ((options) => run(command, options).result);
1753
+ const runCommand = ((args) => run(command, args).result);
1744
1754
  if (!command.commands) return runCommand;
1745
1755
  for (const cmd of command.commands) runCommand[cmd.name] = buildApi(cmd);
1746
1756
  return runCommand;
1747
1757
  }
1748
1758
  return buildApi(existingCommand);
1749
1759
  },
1750
- help(command, options) {
1760
+ help(command, prefs) {
1751
1761
  const commandObj = !command ? existingCommand : typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
1752
- if (!commandObj) throw new Error(`Command "${command ?? ""}" not found`);
1753
- return generateHelp(existingCommand, commandObj, options);
1762
+ if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
1763
+ const runtime = getCommandRuntime(existingCommand);
1764
+ return generateHelp(existingCommand, commandObj, {
1765
+ ...prefs,
1766
+ format: prefs?.format ?? runtime.format
1767
+ });
1754
1768
  },
1755
- completion(shell) {
1769
+ async completion(shell) {
1770
+ const { generateCompletionOutput } = await import("./completion.mjs");
1756
1771
  return generateCompletionOutput(existingCommand, shell);
1757
1772
  },
1758
1773
  "~types": {},
1759
1774
  [commandSymbol]: existingCommand
1760
1775
  };
1776
+ return builder;
1761
1777
  }
1762
-
1763
1778
  //#endregion
1764
- export { createPadrone };
1779
+ export { ActionError, ConfigError, PadroneError, REPL_SIGINT, RoutingError, ValidationError, asyncSchema, buildReplCompleter, createPadrone };
1780
+
1765
1781
  //# sourceMappingURL=index.mjs.map