politty 0.4.0 → 0.4.2

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 (67) hide show
  1. package/dist/{arg-registry-C3GP-5C9.d.ts → arg-registry-BNoIwnNr.d.cts} +15 -8
  2. package/dist/arg-registry-BNoIwnNr.d.cts.map +1 -0
  3. package/dist/{arg-registry-B4a4Fj7f.d.cts → arg-registry-BUUhZ7JR.d.ts} +15 -8
  4. package/dist/arg-registry-BUUhZ7JR.d.ts.map +1 -0
  5. package/dist/augment.d.cts +1 -1
  6. package/dist/augment.d.ts +1 -1
  7. package/dist/completion/index.cjs +20 -19
  8. package/dist/completion/index.cjs.map +1 -1
  9. package/dist/completion/index.d.cts +4 -4
  10. package/dist/completion/index.d.cts.map +1 -1
  11. package/dist/completion/index.d.ts +4 -4
  12. package/dist/completion/index.d.ts.map +1 -1
  13. package/dist/completion/index.js +3 -3
  14. package/dist/completion/index.js.map +1 -1
  15. package/dist/docs/index.cjs +6 -6
  16. package/dist/docs/index.cjs.map +1 -1
  17. package/dist/docs/index.d.cts +3 -3
  18. package/dist/docs/index.d.cts.map +1 -1
  19. package/dist/docs/index.d.ts +3 -3
  20. package/dist/docs/index.d.ts.map +1 -1
  21. package/dist/docs/index.js +3 -3
  22. package/dist/index.cjs +10 -8
  23. package/dist/index.d.cts +4 -4
  24. package/dist/index.d.cts.map +1 -1
  25. package/dist/index.d.ts +4 -4
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -4
  28. package/dist/{schema-extractor-Cv7ipqLS.cjs → lazy-BEDnSR0m.cjs} +81 -2
  29. package/dist/lazy-BEDnSR0m.cjs.map +1 -0
  30. package/dist/{schema-extractor-CP3ar0Wi.js → lazy-BrEg8SgI.js} +63 -2
  31. package/dist/lazy-BrEg8SgI.js.map +1 -0
  32. package/dist/{runner-LJRI4haB.js → runner-B6W9A8P0.js} +5 -5
  33. package/dist/runner-B6W9A8P0.js.map +1 -0
  34. package/dist/{runner-9dLE13Dv.cjs → runner-CpyB8JiY.cjs} +10 -10
  35. package/dist/runner-CpyB8JiY.cjs.map +1 -0
  36. package/dist/{schema-extractor-DyfK21m_.d.cts → schema-extractor-DFaAZzaY.d.cts} +49 -4
  37. package/dist/schema-extractor-DFaAZzaY.d.cts.map +1 -0
  38. package/dist/{schema-extractor-CHiBRT39.d.ts → schema-extractor-n9288WJ6.d.ts} +49 -4
  39. package/dist/schema-extractor-n9288WJ6.d.ts.map +1 -0
  40. package/dist/{subcommand-router-DtCeT_O9.js → subcommand-router-CAzBsLSI.js} +4 -1
  41. package/dist/{subcommand-router-DtCeT_O9.js.map → subcommand-router-CAzBsLSI.js.map} +1 -1
  42. package/dist/{subcommand-router-4d1Xbp8B.cjs → subcommand-router-ZjNjFaUL.cjs} +3 -1
  43. package/dist/subcommand-router-ZjNjFaUL.cjs.map +1 -0
  44. package/dist/{extractor-DsJ6hYqQ.d.cts → value-completion-resolver-BQgHsX7b.d.cts} +73 -20
  45. package/dist/value-completion-resolver-BQgHsX7b.d.cts.map +1 -0
  46. package/dist/{extractor-CCi4rjSI.d.ts → value-completion-resolver-C9LTGr0O.d.ts} +73 -20
  47. package/dist/value-completion-resolver-C9LTGr0O.d.ts.map +1 -0
  48. package/dist/zsh-CASZWn0o.cjs +1586 -0
  49. package/dist/zsh-CASZWn0o.cjs.map +1 -0
  50. package/dist/zsh-hjvdI8uZ.js +1508 -0
  51. package/dist/zsh-hjvdI8uZ.js.map +1 -0
  52. package/package.json +7 -6
  53. package/dist/arg-registry-B4a4Fj7f.d.cts.map +0 -1
  54. package/dist/arg-registry-C3GP-5C9.d.ts.map +0 -1
  55. package/dist/extractor-CCi4rjSI.d.ts.map +0 -1
  56. package/dist/extractor-CqfDnGKd.cjs +0 -970
  57. package/dist/extractor-CqfDnGKd.cjs.map +0 -1
  58. package/dist/extractor-DsJ6hYqQ.d.cts.map +0 -1
  59. package/dist/extractor-JfoYSoMk.js +0 -898
  60. package/dist/extractor-JfoYSoMk.js.map +0 -1
  61. package/dist/runner-9dLE13Dv.cjs.map +0 -1
  62. package/dist/runner-LJRI4haB.js.map +0 -1
  63. package/dist/schema-extractor-CHiBRT39.d.ts.map +0 -1
  64. package/dist/schema-extractor-CP3ar0Wi.js.map +0 -1
  65. package/dist/schema-extractor-Cv7ipqLS.cjs.map +0 -1
  66. package/dist/schema-extractor-DyfK21m_.d.cts.map +0 -1
  67. package/dist/subcommand-router-4d1Xbp8B.cjs.map +0 -1
@@ -0,0 +1,1508 @@
1
+ import { c as arg, i as extractFields, r as resolveSubCommandMeta } from "./lazy-BrEg8SgI.js";
2
+ import { z } from "zod";
3
+ import { execSync } from "node:child_process";
4
+
5
+ //#region src/core/command.ts
6
+ function defineCommand(config) {
7
+ return {
8
+ name: config.name,
9
+ description: config.description,
10
+ args: config.args,
11
+ subCommands: config.subCommands,
12
+ setup: config.setup,
13
+ run: config.run,
14
+ cleanup: config.cleanup,
15
+ notes: config.notes,
16
+ examples: config.examples
17
+ };
18
+ }
19
+
20
+ //#endregion
21
+ //#region src/completion/value-completion-resolver.ts
22
+ /**
23
+ * Resolve value completion from field metadata
24
+ *
25
+ * Priority:
26
+ * 1. Explicit custom completion (choices or shellCommand)
27
+ * 2. Explicit completion type (file, directory, none)
28
+ * 3. Auto-detected enum values from schema
29
+ */
30
+ function resolveValueCompletion(field) {
31
+ const meta = field.completion;
32
+ if (meta?.custom) {
33
+ if (meta.custom.choices && meta.custom.choices.length > 0) return {
34
+ type: "choices",
35
+ choices: meta.custom.choices
36
+ };
37
+ if (meta.custom.shellCommand) return {
38
+ type: "command",
39
+ shellCommand: meta.custom.shellCommand
40
+ };
41
+ }
42
+ if (meta?.type) {
43
+ if (meta.type === "file") {
44
+ if (meta.matcher) return {
45
+ type: "file",
46
+ matcher: meta.matcher
47
+ };
48
+ if (meta.extensions) return {
49
+ type: "file",
50
+ extensions: meta.extensions
51
+ };
52
+ return { type: "file" };
53
+ }
54
+ if (meta.type === "directory") return { type: "directory" };
55
+ if (meta.type === "none") return { type: "none" };
56
+ }
57
+ if (field.enumValues && field.enumValues.length > 0) return {
58
+ type: "choices",
59
+ choices: field.enumValues
60
+ };
61
+ }
62
+
63
+ //#endregion
64
+ //#region src/completion/extractor.ts
65
+ /**
66
+ * Extract completion data from commands
67
+ */
68
+ /**
69
+ * Sanitize a name for use as a shell function/variable identifier.
70
+ * Replaces any character that is not alphanumeric or underscore with underscore.
71
+ */
72
+ function sanitize(name) {
73
+ return name.replace(/[^a-zA-Z0-9_]/g, "_");
74
+ }
75
+ /**
76
+ * Filter subcommands to only visible (non-internal) ones.
77
+ * Internal subcommands start with "__" and are hidden from completion/help.
78
+ */
79
+ function getVisibleSubs(subs) {
80
+ return subs.filter((s) => !s.name.startsWith("__"));
81
+ }
82
+ /**
83
+ * Convert a resolved field to a completable option
84
+ */
85
+ function fieldToOption(field) {
86
+ return {
87
+ name: field.name,
88
+ cliName: field.cliName,
89
+ alias: field.alias,
90
+ description: field.description,
91
+ takesValue: field.type !== "boolean",
92
+ valueType: field.type,
93
+ required: field.required,
94
+ valueCompletion: resolveValueCompletion(field)
95
+ };
96
+ }
97
+ /**
98
+ * Extract options from a command's args schema
99
+ */
100
+ function extractOptions$1(command) {
101
+ if (!command.args) return [];
102
+ return extractFields(command.args).fields.filter((field) => !field.positional).map(fieldToOption);
103
+ }
104
+ /**
105
+ * Extract positional arguments from a command
106
+ */
107
+ function extractPositionals(command) {
108
+ if (!command.args) return [];
109
+ return extractFields(command.args).fields.filter((field) => field.positional);
110
+ }
111
+ /**
112
+ * Extract completable positional arguments from a command
113
+ */
114
+ function extractCompletablePositionals(command) {
115
+ if (!command.args) return [];
116
+ return extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
117
+ name: field.name,
118
+ cliName: field.cliName,
119
+ position: index,
120
+ description: field.description,
121
+ required: field.required,
122
+ variadic: field.type === "array",
123
+ valueCompletion: resolveValueCompletion(field)
124
+ }));
125
+ }
126
+ /**
127
+ * Extract a completable subcommand from a command
128
+ */
129
+ function extractSubcommand(name, command) {
130
+ const subcommands = [];
131
+ if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) {
132
+ const resolved = resolveSubCommandMeta(subCommand);
133
+ if (resolved) subcommands.push(extractSubcommand(subName, resolved));
134
+ else subcommands.push({
135
+ name: subName,
136
+ description: "(lazy loaded)",
137
+ subcommands: [],
138
+ options: [],
139
+ positionals: []
140
+ });
141
+ }
142
+ return {
143
+ name,
144
+ description: command.description,
145
+ subcommands,
146
+ options: extractOptions$1(command),
147
+ positionals: extractCompletablePositionals(command)
148
+ };
149
+ }
150
+ /**
151
+ * Collect opt-takes-value case entries for a subcommand tree.
152
+ * Used by bash and zsh generators (identical case syntax: `subcmd:--opt) return 0 ;;`).
153
+ */
154
+ function optTakesValueEntries(sub, subcmdName) {
155
+ const lines = [];
156
+ for (const opt of sub.options) if (opt.takesValue) {
157
+ const patterns = [`${subcmdName}:--${opt.cliName}`];
158
+ if (opt.alias) patterns.push(`${subcmdName}:-${opt.alias}`);
159
+ lines.push(` ${patterns.join("|")}) return 0 ;;`);
160
+ }
161
+ for (const child of getVisibleSubs(sub.subcommands)) lines.push(...optTakesValueEntries(child, child.name));
162
+ return lines;
163
+ }
164
+ /**
165
+ * Extract completion data from a command tree
166
+ */
167
+ function extractCompletionData(command, programName) {
168
+ const rootSubcommand = extractSubcommand(programName, command);
169
+ return {
170
+ command: rootSubcommand,
171
+ programName,
172
+ globalOptions: rootSubcommand.options
173
+ };
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/completion/bash.ts
178
+ /** Escape a string for use inside bash double-quotes */
179
+ function escapeBashDQ(s) {
180
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
181
+ }
182
+ /**
183
+ * Generate bash value completion code for a ValueCompletion spec.
184
+ * Returns an array of bash lines.
185
+ */
186
+ function bashValueLines(vc, inline) {
187
+ if (!vc) return [];
188
+ switch (vc.type) {
189
+ case "choices": {
190
+ const items = vc.choices.map((c) => `"${escapeBashDQ(c)}"`).join(" ");
191
+ if (inline) return [
192
+ `local -a _choices=(${items})`,
193
+ `COMPREPLY=()`,
194
+ `local _c; for _c in "\${_choices[@]}"; do [[ "$_c" == "$_cur"* ]] && COMPREPLY+=("\${_inline_prefix}\${_c}"); done`,
195
+ `compopt -o nospace`,
196
+ `compopt +o default 2>/dev/null`
197
+ ];
198
+ return [
199
+ `local -a _choices=(${items})`,
200
+ `COMPREPLY=()`,
201
+ `local _c; for _c in "\${_choices[@]}"; do [[ "$_c" == "$_cur"* ]] && COMPREPLY+=("$_c"); done`,
202
+ `compopt +o default 2>/dev/null`
203
+ ];
204
+ }
205
+ case "file":
206
+ if (vc.matcher?.length) return bashFileFilter(vc.matcher.map((p) => `[[ "\${_f##*/}" == ${p} ]]`).join(" || "), inline);
207
+ if (vc.extensions?.length) return bashFileFilter(vc.extensions.map((ext) => `[[ "$_f" == *".${ext}" ]]`).join(" || "), inline);
208
+ if (inline) return [
209
+ `local -a _entries=($(compgen -f -- "$_cur"))`,
210
+ `COMPREPLY=("\${_entries[@]/#/$_inline_prefix}")`,
211
+ `compopt -o filenames`
212
+ ];
213
+ return [`COMPREPLY=($(compgen -f -- "$_cur"))`, `compopt -o filenames`];
214
+ case "directory":
215
+ if (inline) return [
216
+ `local -a _dirs=($(compgen -d -- "$_cur"))`,
217
+ `COMPREPLY=("\${_dirs[@]/#/$_inline_prefix}")`,
218
+ `compopt -o filenames`
219
+ ];
220
+ return [`COMPREPLY=($(compgen -d -- "$_cur"))`, `compopt -o filenames`];
221
+ case "command": {
222
+ const cmd = vc.shellCommand;
223
+ if (inline) return [`COMPREPLY=($(compgen -P "$_inline_prefix" -W "$(${cmd})" -- "$_cur"))`];
224
+ return [`COMPREPLY=($(compgen -W "$(${cmd})" -- "$_cur"))`];
225
+ }
226
+ case "none": return [`compopt +o default 2>/dev/null`];
227
+ }
228
+ }
229
+ function bashFileFilter(checks, inline) {
230
+ const prefix = inline ? `"\${_inline_prefix}$_f"` : `"$_f"`;
231
+ return [
232
+ `local -a _all_entries=($(compgen -f -- "$_cur"))`,
233
+ `for _f in "\${_all_entries[@]}"; do`,
234
+ ` if [[ -d "$_f" ]]; then`,
235
+ ` COMPREPLY+=(${prefix})`,
236
+ ` elif ${checks}; then`,
237
+ ` COMPREPLY+=(${prefix})`,
238
+ ` fi`,
239
+ `done`,
240
+ `compopt -o filenames`,
241
+ `compopt +o default 2>/dev/null`
242
+ ];
243
+ }
244
+ /** Collect value-taking option patterns for case matching */
245
+ function optionValueCases$2(options, inline) {
246
+ const lines = [];
247
+ for (const opt of options) {
248
+ if (!opt.takesValue || !opt.valueCompletion) continue;
249
+ const valLines = bashValueLines(opt.valueCompletion, inline);
250
+ if (valLines.length === 0) continue;
251
+ const patterns = [`--${opt.cliName}`];
252
+ if (opt.alias) patterns.push(`-${opt.alias}`);
253
+ const patternStr = patterns.join("|");
254
+ lines.push(` ${patternStr})`);
255
+ for (const vl of valLines) lines.push(` ${vl}`);
256
+ lines.push(` return ;;`);
257
+ }
258
+ return lines;
259
+ }
260
+ /** Generate positional completion block */
261
+ function positionalBlock$2(positionals) {
262
+ if (positionals.length === 0) return [];
263
+ const lines = [];
264
+ lines.push(` case "$_pos_count" in`);
265
+ for (const pos of positionals) {
266
+ if (pos.variadic) lines.push(` ${pos.position}|*)`);
267
+ else lines.push(` ${pos.position})`);
268
+ for (const vl of bashValueLines(pos.valueCompletion, false)) lines.push(` ${vl}`);
269
+ lines.push(` ;;`);
270
+ }
271
+ lines.push(` esac`);
272
+ return lines;
273
+ }
274
+ /** Generate prev/inline value completion blocks for options */
275
+ function valueCompletionBlocks(options) {
276
+ if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
277
+ const lines = [];
278
+ const prevCases = optionValueCases$2(options, false);
279
+ if (prevCases.length > 0) {
280
+ lines.push(` if [[ -z "$_inline_prefix" ]]; then`);
281
+ lines.push(` case "$_prev" in`);
282
+ lines.push(...prevCases);
283
+ lines.push(` esac`);
284
+ lines.push(` fi`);
285
+ }
286
+ const inlineCases = optionValueCases$2(options, true);
287
+ if (inlineCases.length > 0) {
288
+ lines.push(` if [[ -n "$_inline_prefix" ]]; then`);
289
+ lines.push(` case "\${_inline_prefix%=}" in`);
290
+ lines.push(...inlineCases);
291
+ lines.push(` esac`);
292
+ lines.push(` fi`);
293
+ }
294
+ return lines;
295
+ }
296
+ /** Generate available-options list lines */
297
+ function availableOptionLines$2(options, fn) {
298
+ const lines = [];
299
+ for (const opt of options) if (opt.valueType === "array") lines.push(` _avail+=(--${opt.cliName})`);
300
+ else {
301
+ const patterns = [`"--${opt.cliName}"`];
302
+ if (opt.alias) patterns.push(`"-${opt.alias}"`);
303
+ lines.push(` __${fn}_not_used ${patterns.join(" ")} && _avail+=(--${opt.cliName})`);
304
+ }
305
+ lines.push(` __${fn}_not_used "--help" && _avail+=(--help)`);
306
+ return lines;
307
+ }
308
+ /**
309
+ * Generate a per-subcommand completion function.
310
+ * Recursively generates functions for nested subcommands.
311
+ */
312
+ function generateSubHandler$2(sub, fn, path) {
313
+ const fullPath = [...path, sub.name];
314
+ const funcName = `__${fn}_complete_${fullPath.map(sanitize).join("_")}`;
315
+ const visibleSubs = getVisibleSubs(sub.subcommands);
316
+ const lines = [];
317
+ for (const child of visibleSubs) lines.push(...generateSubHandler$2(child, fn, fullPath));
318
+ lines.push(`${funcName}() {`);
319
+ lines.push(...valueCompletionBlocks(sub.options));
320
+ lines.push(` if [[ -z "$_inline_prefix" ]] && __${fn}_opt_takes_value "${sub.name}" "$_prev"; then return; fi`);
321
+ lines.push(` if [[ -n "$_inline_prefix" ]] && __${fn}_opt_takes_value "${sub.name}" "\${_inline_prefix%=}"; then return; fi`);
322
+ if (sub.positionals.length > 0) {
323
+ lines.push(` if (( _after_dd )); then`);
324
+ lines.push(...positionalBlock$2(sub.positionals).map((l) => ` ${l}`));
325
+ lines.push(` return`);
326
+ lines.push(` fi`);
327
+ } else lines.push(` if (( _after_dd )); then return; fi`);
328
+ lines.push(` if [[ "$_cur" == -* ]]; then`);
329
+ lines.push(` local -a _avail=()`);
330
+ lines.push(...availableOptionLines$2(sub.options, fn));
331
+ lines.push(` COMPREPLY=($(compgen -W "\${_avail[*]}" -- "$_cur"))`);
332
+ lines.push(` compopt +o default 2>/dev/null`);
333
+ lines.push(` return`);
334
+ lines.push(` fi`);
335
+ if (visibleSubs.length > 0) {
336
+ const subNames = visibleSubs.map((s) => s.name).join(" ");
337
+ lines.push(` COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`);
338
+ lines.push(` compopt +o default 2>/dev/null`);
339
+ } else if (sub.positionals.length > 0) lines.push(...positionalBlock$2(sub.positionals));
340
+ lines.push(`}`);
341
+ lines.push(``);
342
+ return lines;
343
+ }
344
+ function generateBashCompletion(command, options) {
345
+ const { programName } = options;
346
+ const data = extractCompletionData(command, programName);
347
+ const fn = sanitize(programName);
348
+ const root = data.command;
349
+ const visibleSubs = getVisibleSubs(root.subcommands);
350
+ const lines = [];
351
+ lines.push(`# Bash completion for ${programName}`);
352
+ lines.push(`# Generated by politty`);
353
+ lines.push(``);
354
+ lines.push(`__${fn}_not_used() {`);
355
+ lines.push(` for _u in "\${_used_opts[@]}"; do`);
356
+ lines.push(` for _chk in "$@"; do`);
357
+ lines.push(` [[ "$_u" == "$_chk" ]] && return 1`);
358
+ lines.push(` done`);
359
+ lines.push(` done`);
360
+ lines.push(` return 0`);
361
+ lines.push(`}`);
362
+ lines.push(``);
363
+ lines.push(`__${fn}_opt_takes_value() {`);
364
+ lines.push(` case "$1:$2" in`);
365
+ lines.push(...optTakesValueEntries(root, ""));
366
+ lines.push(` esac`);
367
+ lines.push(` return 1`);
368
+ lines.push(`}`);
369
+ lines.push(``);
370
+ for (const sub of visibleSubs) lines.push(...generateSubHandler$2(sub, fn, []));
371
+ lines.push(`__${fn}_complete_root() {`);
372
+ lines.push(...valueCompletionBlocks(root.options));
373
+ lines.push(` if [[ -z "$_inline_prefix" ]] && __${fn}_opt_takes_value "" "$_prev"; then return; fi`);
374
+ lines.push(` if [[ -n "$_inline_prefix" ]] && __${fn}_opt_takes_value "" "\${_inline_prefix%=}"; then return; fi`);
375
+ if (root.positionals.length > 0) {
376
+ lines.push(` if (( _after_dd )); then`);
377
+ lines.push(...positionalBlock$2(root.positionals).map((l) => ` ${l}`));
378
+ lines.push(` return`);
379
+ lines.push(` fi`);
380
+ } else lines.push(` if (( _after_dd )); then return; fi`);
381
+ lines.push(` if [[ "$_cur" == -* ]]; then`);
382
+ lines.push(` local -a _avail=()`);
383
+ lines.push(...availableOptionLines$2(root.options, fn));
384
+ lines.push(` COMPREPLY=($(compgen -W "\${_avail[*]}" -- "$_cur"))`);
385
+ lines.push(` compopt +o default 2>/dev/null`);
386
+ if (visibleSubs.length > 0) {
387
+ lines.push(` else`);
388
+ const subNames = visibleSubs.map((s) => s.name).join(" ");
389
+ lines.push(` COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`);
390
+ lines.push(` compopt +o default 2>/dev/null`);
391
+ } else if (root.positionals.length > 0) {
392
+ lines.push(` else`);
393
+ lines.push(...positionalBlock$2(root.positionals).map((l) => ` ${l}`));
394
+ }
395
+ lines.push(` fi`);
396
+ lines.push(`}`);
397
+ lines.push(``);
398
+ const subRouting = visibleSubs.map((s) => ` ${s.name}) __${fn}_complete_${sanitize(s.name)} ;;`).join("\n");
399
+ lines.push(`_${fn}_completions() {`);
400
+ lines.push(` COMPREPLY=()`);
401
+ lines.push(``);
402
+ lines.push(` # Rejoin words split by '=' in COMP_WORDBREAKS`);
403
+ lines.push(` local -a _words=()`);
404
+ lines.push(` local _i=1`);
405
+ lines.push(` while (( _i <= COMP_CWORD )); do`);
406
+ lines.push(` if [[ "\${COMP_WORDS[_i]}" == "=" && \${#_words[@]} -gt 0 ]]; then`);
407
+ lines.push(` _words[\${#_words[@]}-1]+="=\${COMP_WORDS[_i+1]:-}"`);
408
+ lines.push(` (( _i += 2 ))`);
409
+ lines.push(` else`);
410
+ lines.push(` _words+=("\${COMP_WORDS[_i]}")`);
411
+ lines.push(` (( _i++ ))`);
412
+ lines.push(` fi`);
413
+ lines.push(` done`);
414
+ lines.push(``);
415
+ lines.push(` local _cur=""`);
416
+ lines.push(` (( \${#_words[@]} > 0 )) && _cur="\${_words[\${#_words[@]}-1]}"`);
417
+ lines.push(``);
418
+ lines.push(` local _inline_prefix=""`);
419
+ lines.push(` if [[ "$_cur" == --*=* ]]; then`);
420
+ lines.push(` _inline_prefix="\${_cur%%=*}="`);
421
+ lines.push(` _cur="\${_cur#*=}"`);
422
+ lines.push(` fi`);
423
+ lines.push(``);
424
+ lines.push(` local _prev=""`);
425
+ lines.push(` (( \${#_words[@]} > 1 )) && _prev="\${_words[\${#_words[@]}-2]}"`);
426
+ lines.push(``);
427
+ lines.push(` local _subcmd="" _after_dd=0 _pos_count=0 _skip_next=0`);
428
+ lines.push(` local -a _used_opts=()`);
429
+ lines.push(``);
430
+ lines.push(` local _j=0`);
431
+ lines.push(` while (( _j < \${#_words[@]} - 1 )); do`);
432
+ lines.push(` local _w="\${_words[_j]}"`);
433
+ lines.push(` if (( _skip_next )); then _skip_next=0; (( _j++ )); continue; fi`);
434
+ lines.push(` if [[ "$_w" == "--" ]]; then _after_dd=1; (( _j++ )); continue; fi`);
435
+ lines.push(` if (( _after_dd )); then (( _pos_count++ )); (( _j++ )); continue; fi`);
436
+ lines.push(` if [[ "$_w" == --*=* ]]; then _used_opts+=("\${_w%%=*}"); (( _j++ )); continue; fi`);
437
+ lines.push(` if [[ "$_w" == -* ]]; then`);
438
+ lines.push(` _used_opts+=("$_w")`);
439
+ lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w" && _skip_next=1`);
440
+ lines.push(` (( _j++ )); continue`);
441
+ lines.push(` fi`);
442
+ if (visibleSubs.length > 0) lines.push(` if [[ -z "$_subcmd" ]]; then _subcmd="$_w"; _used_opts=(); else (( _pos_count++ )); fi`);
443
+ else lines.push(` (( _pos_count++ ))`);
444
+ lines.push(` (( _j++ ))`);
445
+ lines.push(` done`);
446
+ lines.push(``);
447
+ lines.push(` case "$_subcmd" in`);
448
+ lines.push(subRouting);
449
+ lines.push(` *) __${fn}_complete_root ;;`);
450
+ lines.push(` esac`);
451
+ lines.push(`}`);
452
+ lines.push(``);
453
+ lines.push(`complete -o default -F _${fn}_completions ${programName}`);
454
+ lines.push(``);
455
+ return {
456
+ script: lines.join("\n"),
457
+ shell: "bash",
458
+ installInstructions: `# To enable completions, add the following to your ~/.bashrc:
459
+
460
+ # Option 1: Source directly
461
+ eval "$(${programName} completion bash)"
462
+
463
+ # Option 2: Save to a file
464
+ ${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
465
+
466
+ # Then reload your shell or run:
467
+ source ~/.bashrc`
468
+ };
469
+ }
470
+
471
+ //#endregion
472
+ //#region src/completion/dynamic/candidate-generator.ts
473
+ /**
474
+ * Generate completion candidates based on context
475
+ */
476
+ /**
477
+ * Completion directive flags (bitwise)
478
+ */
479
+ const CompletionDirective = {
480
+ Default: 0,
481
+ NoSpace: 1,
482
+ NoFileCompletion: 2,
483
+ FilterPrefix: 4,
484
+ KeepOrder: 8,
485
+ FileCompletion: 16,
486
+ DirectoryCompletion: 32,
487
+ Error: 64
488
+ };
489
+ /**
490
+ * Generate completion candidates based on context
491
+ */
492
+ function generateCandidates(context) {
493
+ const candidates = [];
494
+ let directive = CompletionDirective.Default;
495
+ switch (context.completionType) {
496
+ case "subcommand": return generateSubcommandCandidates(context);
497
+ case "option-name": return generateOptionNameCandidates(context);
498
+ case "option-value": return generateOptionValueCandidates(context);
499
+ case "positional": return generatePositionalCandidates(context);
500
+ default: return {
501
+ candidates,
502
+ directive
503
+ };
504
+ }
505
+ }
506
+ /**
507
+ * Execute a shell command and return results as candidates
508
+ */
509
+ function executeShellCommand(command) {
510
+ try {
511
+ return execSync(command, {
512
+ encoding: "utf-8",
513
+ timeout: 5e3
514
+ }).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => ({
515
+ value: line,
516
+ type: "value"
517
+ }));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ /**
523
+ * Resolve value completion, executing shell commands and file lookups in JS
524
+ */
525
+ function resolveValueCandidates(vc, candidates, _currentWord, description) {
526
+ let directive = CompletionDirective.FilterPrefix;
527
+ let fileExtensions;
528
+ let fileMatchers;
529
+ switch (vc.type) {
530
+ case "choices":
531
+ if (vc.choices) for (const choice of vc.choices) candidates.push({
532
+ value: choice,
533
+ description,
534
+ type: "value"
535
+ });
536
+ directive |= CompletionDirective.NoFileCompletion;
537
+ break;
538
+ case "file":
539
+ if (vc.matcher && vc.matcher.length > 0) {
540
+ fileMatchers = vc.matcher.filter((m) => m.trim().length > 0);
541
+ if (fileMatchers.length === 0) {
542
+ fileMatchers = void 0;
543
+ directive |= CompletionDirective.FileCompletion;
544
+ }
545
+ } else if (vc.extensions && vc.extensions.length > 0) {
546
+ fileExtensions = Array.from(new Set(vc.extensions.map((ext) => ext.trim().replace(/^\./, "")).filter((ext) => ext.length > 0)));
547
+ if (fileExtensions.length === 0) {
548
+ fileExtensions = void 0;
549
+ directive |= CompletionDirective.FileCompletion;
550
+ }
551
+ } else directive |= CompletionDirective.FileCompletion;
552
+ break;
553
+ case "directory":
554
+ directive |= CompletionDirective.DirectoryCompletion;
555
+ break;
556
+ case "command":
557
+ if (vc.shellCommand) candidates.push(...executeShellCommand(vc.shellCommand));
558
+ directive |= CompletionDirective.NoFileCompletion;
559
+ break;
560
+ case "none":
561
+ directive |= CompletionDirective.NoFileCompletion;
562
+ break;
563
+ }
564
+ return {
565
+ directive,
566
+ fileExtensions,
567
+ fileMatchers
568
+ };
569
+ }
570
+ /**
571
+ * Generate subcommand candidates
572
+ */
573
+ function generateSubcommandCandidates(context) {
574
+ const candidates = [];
575
+ let directive = CompletionDirective.FilterPrefix;
576
+ for (const name of context.subcommands) {
577
+ const sub = context.currentCommand.subCommands?.[name];
578
+ const description = sub ? resolveSubCommandMeta(sub)?.description : void 0;
579
+ candidates.push({
580
+ value: name,
581
+ description,
582
+ type: "subcommand"
583
+ });
584
+ }
585
+ if (candidates.length === 0 || context.currentWord.startsWith("-")) {
586
+ const optionResult = generateOptionNameCandidates(context);
587
+ candidates.push(...optionResult.candidates);
588
+ }
589
+ return {
590
+ candidates,
591
+ directive
592
+ };
593
+ }
594
+ /**
595
+ * Generate option name candidates
596
+ */
597
+ function generateOptionNameCandidates(context) {
598
+ const candidates = [];
599
+ const directive = CompletionDirective.FilterPrefix;
600
+ const availableOptions = context.options.filter((opt) => {
601
+ if (opt.valueType === "array") return true;
602
+ return !context.usedOptions.has(opt.cliName) && !context.usedOptions.has(opt.alias || "");
603
+ });
604
+ for (const opt of availableOptions) candidates.push({
605
+ value: `--${opt.cliName}`,
606
+ description: opt.description,
607
+ type: "option"
608
+ });
609
+ if (!context.usedOptions.has("help")) candidates.push({
610
+ value: "--help",
611
+ description: "Show help information",
612
+ type: "option"
613
+ });
614
+ return {
615
+ candidates,
616
+ directive
617
+ };
618
+ }
619
+ /**
620
+ * Generate option value candidates
621
+ */
622
+ function generateOptionValueCandidates(context) {
623
+ const candidates = [];
624
+ if (!context.targetOption) return {
625
+ candidates,
626
+ directive: CompletionDirective.FilterPrefix
627
+ };
628
+ const vc = context.targetOption.valueCompletion;
629
+ if (!vc) return {
630
+ candidates,
631
+ directive: CompletionDirective.FilterPrefix
632
+ };
633
+ return {
634
+ candidates,
635
+ ...resolveValueCandidates(vc, candidates, context.currentWord)
636
+ };
637
+ }
638
+ /**
639
+ * Generate positional argument candidates
640
+ */
641
+ function generatePositionalCandidates(context) {
642
+ const candidates = [];
643
+ const positionalIndex = context.positionalIndex ?? 0;
644
+ const positional = context.positionals[positionalIndex] ?? (context.positionals.at(-1)?.variadic ? context.positionals.at(-1) : void 0);
645
+ if (!positional) return {
646
+ candidates,
647
+ directive: CompletionDirective.FilterPrefix
648
+ };
649
+ const vc = positional.valueCompletion;
650
+ if (!vc) return {
651
+ candidates,
652
+ directive: CompletionDirective.FilterPrefix
653
+ };
654
+ return {
655
+ candidates,
656
+ ...resolveValueCandidates(vc, candidates, context.currentWord, positional.description)
657
+ };
658
+ }
659
+
660
+ //#endregion
661
+ //#region src/completion/dynamic/context-parser.ts
662
+ /**
663
+ * Parse completion context from partial command line
664
+ */
665
+ /**
666
+ * Extract options from a command
667
+ */
668
+ function extractOptions(command) {
669
+ if (!command.args) return [];
670
+ return extractFields(command.args).fields.filter((field) => !field.positional).map((field) => ({
671
+ name: field.name,
672
+ cliName: field.cliName,
673
+ alias: field.alias,
674
+ description: field.description,
675
+ takesValue: field.type !== "boolean",
676
+ valueType: field.type,
677
+ required: field.required,
678
+ valueCompletion: resolveValueCompletion(field)
679
+ }));
680
+ }
681
+ /**
682
+ * Extract positionals from a command
683
+ */
684
+ function extractPositionalsForContext(command) {
685
+ if (!command.args) return [];
686
+ return extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
687
+ name: field.name,
688
+ cliName: field.cliName,
689
+ position: index,
690
+ description: field.description,
691
+ required: field.required,
692
+ variadic: field.type === "array",
693
+ valueCompletion: resolveValueCompletion(field)
694
+ }));
695
+ }
696
+ /**
697
+ * Get subcommand names from a command
698
+ */
699
+ function getSubcommandNames(command) {
700
+ if (!command.subCommands) return [];
701
+ return Object.keys(command.subCommands).filter((name) => !name.startsWith("__"));
702
+ }
703
+ /**
704
+ * Resolve subcommand by name
705
+ */
706
+ function resolveSubcommand(command, name) {
707
+ if (!command.subCommands) return null;
708
+ const sub = command.subCommands[name];
709
+ if (!sub) return null;
710
+ return resolveSubCommandMeta(sub);
711
+ }
712
+ /**
713
+ * Check if a word is an option (starts with - or --)
714
+ */
715
+ function isOption(word) {
716
+ return word.startsWith("-");
717
+ }
718
+ /**
719
+ * Parse option name from word (e.g., "--foo=bar" -> "foo", "-v" -> "v")
720
+ */
721
+ function parseOptionName(word) {
722
+ if (word.startsWith("--")) {
723
+ const withoutPrefix = word.slice(2);
724
+ const eqIndex = withoutPrefix.indexOf("=");
725
+ return eqIndex >= 0 ? withoutPrefix.slice(0, eqIndex) : withoutPrefix;
726
+ }
727
+ if (word.startsWith("-")) return word.slice(1, 2);
728
+ return word;
729
+ }
730
+ /**
731
+ * Check if option has inline value (e.g., "--foo=bar")
732
+ */
733
+ function hasInlineValue(word) {
734
+ return word.includes("=");
735
+ }
736
+ /**
737
+ * Find option by name or alias
738
+ */
739
+ function findOption(options, nameOrAlias) {
740
+ return options.find((opt) => opt.cliName === nameOrAlias || opt.alias === nameOrAlias);
741
+ }
742
+ /**
743
+ * Parse completion context from command line arguments
744
+ *
745
+ * @param argv - Arguments after the program name (e.g., ["build", "--fo"])
746
+ * @param rootCommand - The root command
747
+ * @returns Completion context
748
+ */
749
+ function parseCompletionContext(argv, rootCommand) {
750
+ let currentCommand = rootCommand;
751
+ const subcommandPath = [];
752
+ const usedOptions = /* @__PURE__ */ new Set();
753
+ let positionalCount = 0;
754
+ let i = 0;
755
+ let options = extractOptions(currentCommand);
756
+ let afterDoubleDash = false;
757
+ while (i < argv.length - 1) {
758
+ const word = argv[i];
759
+ if (!afterDoubleDash && word === "--") {
760
+ afterDoubleDash = true;
761
+ i++;
762
+ continue;
763
+ }
764
+ if (!afterDoubleDash && isOption(word)) {
765
+ const optName = parseOptionName(word);
766
+ const opt = findOption(options, optName);
767
+ if (opt) {
768
+ usedOptions.add(opt.cliName);
769
+ if (opt.alias) usedOptions.add(opt.alias);
770
+ if (opt.takesValue && !hasInlineValue(word)) i++;
771
+ }
772
+ i++;
773
+ continue;
774
+ }
775
+ const subcommand = afterDoubleDash ? null : resolveSubcommand(currentCommand, word);
776
+ if (subcommand) {
777
+ subcommandPath.push(word);
778
+ currentCommand = subcommand;
779
+ options = extractOptions(currentCommand);
780
+ usedOptions.clear();
781
+ positionalCount = 0;
782
+ i++;
783
+ continue;
784
+ }
785
+ positionalCount++;
786
+ i++;
787
+ }
788
+ const currentWord = argv[argv.length - 1] ?? "";
789
+ const previousWord = argv[argv.length - 2] ?? "";
790
+ const positionals = extractPositionalsForContext(currentCommand);
791
+ const subcommands = getSubcommandNames(currentCommand);
792
+ let completionType;
793
+ let targetOption;
794
+ let positionalIndex;
795
+ if (!afterDoubleDash && previousWord && isOption(previousWord) && !hasInlineValue(previousWord)) {
796
+ const optName = parseOptionName(previousWord);
797
+ const opt = findOption(options, optName);
798
+ if (opt && opt.takesValue) {
799
+ completionType = "option-value";
800
+ targetOption = opt;
801
+ } else if (currentWord.startsWith("-")) completionType = "option-name";
802
+ else {
803
+ completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount);
804
+ if (completionType === "positional") positionalIndex = positionalCount;
805
+ }
806
+ } else if (!afterDoubleDash && currentWord.startsWith("--") && hasInlineValue(currentWord)) {
807
+ const optName = parseOptionName(currentWord);
808
+ const opt = findOption(options, optName);
809
+ if (opt && opt.takesValue) {
810
+ completionType = "option-value";
811
+ targetOption = opt;
812
+ } else completionType = "option-name";
813
+ } else if (!afterDoubleDash && currentWord.startsWith("-")) completionType = "option-name";
814
+ else {
815
+ completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash);
816
+ if (completionType === "positional") positionalIndex = positionalCount;
817
+ }
818
+ return {
819
+ subcommandPath,
820
+ currentCommand,
821
+ currentWord,
822
+ previousWord,
823
+ completionType,
824
+ targetOption,
825
+ positionalIndex,
826
+ options,
827
+ subcommands,
828
+ positionals,
829
+ usedOptions,
830
+ providedPositionalCount: positionalCount
831
+ };
832
+ }
833
+ /**
834
+ * Determine default completion type when not completing an option
835
+ */
836
+ function determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash) {
837
+ if (afterDoubleDash) return "positional";
838
+ if (subcommands.length > 0) {
839
+ if (subcommands.filter((s) => s.startsWith(currentWord)).length > 0 || currentWord === "") return "subcommand";
840
+ }
841
+ if (positionalCount < positionals.length) return "positional";
842
+ if (positionals.length > 0 && positionals[positionals.length - 1].variadic) return "positional";
843
+ return "subcommand";
844
+ }
845
+
846
+ //#endregion
847
+ //#region src/completion/dynamic/shell-formatter.ts
848
+ /**
849
+ * Format completion candidates for the specified shell
850
+ *
851
+ * @returns Shell-ready output string (lines separated by newline, last line is :directive)
852
+ */
853
+ function formatForShell(result, options) {
854
+ switch (options.shell) {
855
+ case "bash": return formatForBash(result, options);
856
+ case "zsh": return formatForZsh(result, options);
857
+ case "fish": return formatForFish(result, options);
858
+ }
859
+ }
860
+ /**
861
+ * Check if the FilterPrefix directive is set
862
+ */
863
+ function shouldFilterPrefix(directive) {
864
+ return (directive & CompletionDirective.FilterPrefix) !== 0;
865
+ }
866
+ /**
867
+ * Filter candidates by prefix
868
+ */
869
+ function filterByPrefix(candidates, prefix) {
870
+ if (!prefix) return candidates;
871
+ return candidates.filter((c) => c.value.startsWith(prefix));
872
+ }
873
+ /**
874
+ * Append extension metadata and directive to output lines
875
+ */
876
+ function appendMetadata(lines, result) {
877
+ if (result.fileExtensions && result.fileExtensions.length > 0) lines.push(`@ext:${result.fileExtensions.join(",")}`);
878
+ if (result.fileMatchers && result.fileMatchers.length > 0) lines.push(`@matcher:${result.fileMatchers.join(",")}`);
879
+ lines.push(`:${result.directive}`);
880
+ }
881
+ /**
882
+ * Format for bash
883
+ *
884
+ * - Pre-filters candidates by currentWord prefix (replaces compgen -W)
885
+ * - Handles --opt=value inline values by prepending prefix
886
+ * - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
887
+ * - Last line: :directive
888
+ */
889
+ function formatForBash(result, options) {
890
+ let { candidates } = result;
891
+ if (shouldFilterPrefix(result.directive)) candidates = filterByPrefix(candidates, options.currentWord);
892
+ const lines = candidates.map((c) => {
893
+ if (options.inlinePrefix) return `${options.inlinePrefix}${c.value}`;
894
+ return c.value;
895
+ });
896
+ appendMetadata(lines, result);
897
+ return lines.join("\n");
898
+ }
899
+ /**
900
+ * Format for zsh
901
+ *
902
+ * - Outputs value:description pairs for _describe
903
+ * - Colons in values/descriptions are escaped with backslash
904
+ * - Last line: :directive
905
+ */
906
+ function formatForZsh(result, _options) {
907
+ const lines = result.candidates.map((c) => {
908
+ const escapedValue = c.value.replace(/:/g, "\\:");
909
+ if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
910
+ return escapedValue;
911
+ });
912
+ appendMetadata(lines, result);
913
+ return lines.join("\n");
914
+ }
915
+ /**
916
+ * Format for fish
917
+ *
918
+ * - Outputs value\tdescription pairs
919
+ * - Last line: :directive
920
+ */
921
+ function formatForFish(result, _options) {
922
+ const lines = result.candidates.map((c) => {
923
+ if (c.description) return `${c.value}\t${c.description}`;
924
+ return c.value;
925
+ });
926
+ appendMetadata(lines, result);
927
+ return lines.join("\n");
928
+ }
929
+
930
+ //#endregion
931
+ //#region src/completion/dynamic/complete-command.ts
932
+ /**
933
+ * Dynamic completion command implementation
934
+ *
935
+ * This creates a hidden `__complete` command that outputs completion candidates
936
+ * for shell scripts to consume. Usage:
937
+ *
938
+ * mycli __complete --shell bash -- build --fo
939
+ * mycli __complete --shell zsh -- plugin add
940
+ *
941
+ * Output format depends on the target shell:
942
+ * bash: plain values (pre-filtered by prefix), last line :directive
943
+ * zsh: value:description pairs, last line :directive
944
+ * fish: value\tdescription pairs, last line :directive
945
+ */
946
+ /**
947
+ * Detect inline option-value prefix (e.g., "--format=" from "--format=json")
948
+ */
949
+ function detectInlinePrefix(currentWord) {
950
+ if (currentWord.startsWith("--") && currentWord.includes("=")) return currentWord.slice(0, currentWord.indexOf("=") + 1);
951
+ }
952
+ /**
953
+ * Schema for the __complete command
954
+ */
955
+ const completeArgsSchema = z.object({
956
+ shell: arg(z.enum([
957
+ "bash",
958
+ "zsh",
959
+ "fish"
960
+ ]), { description: "Target shell for output formatting" }),
961
+ args: arg(z.array(z.string()).default([]), {
962
+ positional: true,
963
+ description: "Arguments to complete",
964
+ variadic: true
965
+ })
966
+ });
967
+ /**
968
+ * Create the dynamic completion command
969
+ *
970
+ * @param rootCommand - The root command to generate completions for
971
+ * @param programName - The program name (optional, defaults to rootCommand.name)
972
+ * @returns A command that outputs completion candidates
973
+ */
974
+ function createDynamicCompleteCommand(rootCommand, _programName) {
975
+ return defineCommand({
976
+ name: "__complete",
977
+ args: completeArgsSchema,
978
+ run(args) {
979
+ const context = parseCompletionContext(args.args, rootCommand);
980
+ const result = generateCandidates(context);
981
+ const inlinePrefix = detectInlinePrefix(context.currentWord);
982
+ const output = formatForShell(result, {
983
+ shell: args.shell,
984
+ currentWord: inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord,
985
+ inlinePrefix
986
+ });
987
+ console.log(output);
988
+ }
989
+ });
990
+ }
991
+ /**
992
+ * Check if a command tree contains the __complete command
993
+ */
994
+ function hasCompleteCommand(command) {
995
+ return Boolean(command.subCommands?.["__complete"]);
996
+ }
997
+
998
+ //#endregion
999
+ //#region src/completion/fish.ts
1000
+ /** Escape shell-special characters for fish double-quoted strings */
1001
+ function escapeDesc$1(s) {
1002
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
1003
+ }
1004
+ /**
1005
+ * Generate fish value completion lines for a ValueCompletion spec.
1006
+ * Each line outputs candidates via echo (tab-separated value\tdescription).
1007
+ */
1008
+ function fishValueLines(vc) {
1009
+ if (!vc) return [];
1010
+ switch (vc.type) {
1011
+ case "choices": return vc.choices.map((c) => `echo "${escapeDesc$1(c)}"`);
1012
+ case "file":
1013
+ if (vc.matcher?.length) return fishMatcherLines(vc.matcher);
1014
+ if (vc.extensions?.length) return fishExtensionLines(vc.extensions);
1015
+ return [`__fish_complete_path "$_cur"`];
1016
+ case "directory": return [`__fish_complete_directories "$_cur"`];
1017
+ case "command": return [
1018
+ `for _v in (${vc.shellCommand})`,
1019
+ ` echo "$_v"`,
1020
+ `end`
1021
+ ];
1022
+ case "none": return [];
1023
+ }
1024
+ }
1025
+ /** Generate fish matcher-filtered file completion */
1026
+ function fishMatcherLines(patterns) {
1027
+ return [
1028
+ `__fish_complete_directories "$_cur"`,
1029
+ `set -l _dir ""`,
1030
+ `if string match -q '*/*' "$_cur"`,
1031
+ ` set _dir (string replace -r '[^/]*$' '' "$_cur")`,
1032
+ `end`,
1033
+ ...patterns.flatMap((p) => [
1034
+ `for _f in "$_dir"${p}`,
1035
+ ` test -f "$_f"; and string match -q "$_cur*" "$_f"; and echo "$_f"`,
1036
+ `end`
1037
+ ])
1038
+ ];
1039
+ }
1040
+ /** Generate fish extension-filtered file completion */
1041
+ function fishExtensionLines(extensions) {
1042
+ const lines = [];
1043
+ lines.push(`__fish_complete_directories "$_cur"`);
1044
+ for (const ext of extensions) {
1045
+ lines.push(`for _f in "$_cur"*.${ext}`);
1046
+ lines.push(` test -f "$_f"; and echo "$_f"`);
1047
+ lines.push(`end`);
1048
+ }
1049
+ return lines;
1050
+ }
1051
+ /** Generate option-value switch cases for fish */
1052
+ function optionValueCases$1(options) {
1053
+ const lines = [];
1054
+ for (const opt of options) {
1055
+ if (!opt.takesValue || !opt.valueCompletion) continue;
1056
+ const valLines = fishValueLines(opt.valueCompletion);
1057
+ if (valLines.length === 0) continue;
1058
+ const conditions = [`test "$_prev" = "--${opt.cliName}"`];
1059
+ if (opt.alias) conditions.push(`test "$_prev" = "-${opt.alias}"`);
1060
+ const cond = conditions.join("; or ");
1061
+ lines.push(` if ${cond}`);
1062
+ for (const vl of valLines) lines.push(` ${vl}`);
1063
+ lines.push(` return`);
1064
+ lines.push(` end`);
1065
+ }
1066
+ return lines;
1067
+ }
1068
+ /** Generate positional completion block for fish */
1069
+ function positionalBlock$1(positionals) {
1070
+ if (positionals.length === 0) return [];
1071
+ const lines = [];
1072
+ for (const pos of positionals) {
1073
+ const valLines = fishValueLines(pos.valueCompletion);
1074
+ if (valLines.length === 0) continue;
1075
+ if (pos.variadic) lines.push(` if test $_pos_count -ge ${pos.position}`);
1076
+ else lines.push(` if test $_pos_count -eq ${pos.position}`);
1077
+ for (const vl of valLines) lines.push(` ${vl}`);
1078
+ lines.push(` return`);
1079
+ lines.push(` end`);
1080
+ }
1081
+ return lines;
1082
+ }
1083
+ /** Generate available-option echo lines for fish */
1084
+ function availableOptionLines$1(options, fn) {
1085
+ const lines = [];
1086
+ for (const opt of options) {
1087
+ const desc = escapeDesc$1(opt.description ?? "");
1088
+ if (opt.valueType === "array") lines.push(` echo "--${opt.cliName}\t${desc}"`);
1089
+ else {
1090
+ const checks = [`"--${opt.cliName}"`];
1091
+ if (opt.alias) checks.push(`"-${opt.alias}"`);
1092
+ lines.push(` __${fn}_not_used ${checks.join(" ")}; and echo "--${opt.cliName}\t${desc}"`);
1093
+ }
1094
+ }
1095
+ lines.push(` __${fn}_not_used "--help"; and echo "--help\tShow help"`);
1096
+ return lines;
1097
+ }
1098
+ /** Generate value-option completion block if any value-taking options exist */
1099
+ function valueCompletionBlock$1(options) {
1100
+ if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
1101
+ return optionValueCases$1(options);
1102
+ }
1103
+ /**
1104
+ * Generate a per-subcommand completion function for fish.
1105
+ * Recursively generates functions for nested subcommands.
1106
+ */
1107
+ function generateSubHandler$1(sub, fn, path) {
1108
+ const fullPath = [...path, sub.name];
1109
+ const funcName = `__${fn}_complete_${fullPath.map(sanitize).join("_")}`;
1110
+ const visibleSubs = getVisibleSubs(sub.subcommands);
1111
+ const lines = [];
1112
+ for (const child of visibleSubs) lines.push(...generateSubHandler$1(child, fn, fullPath));
1113
+ lines.push(`function ${funcName} --no-scope-shadowing`);
1114
+ lines.push(...valueCompletionBlock$1(sub.options));
1115
+ lines.push(` if __${fn}_opt_takes_value "${sub.name}" "$_prev"; return; end`);
1116
+ if (sub.positionals.length > 0) {
1117
+ lines.push(` if test $_after_dd -eq 1`);
1118
+ lines.push(...positionalBlock$1(sub.positionals).map((l) => ` ${l}`));
1119
+ lines.push(` return`);
1120
+ lines.push(` end`);
1121
+ } else lines.push(` if test $_after_dd -eq 1; return; end`);
1122
+ lines.push(` if string match -q -- '-*' "$_cur"`);
1123
+ lines.push(...availableOptionLines$1(sub.options, fn));
1124
+ lines.push(` return`);
1125
+ lines.push(` end`);
1126
+ if (visibleSubs.length > 0) for (const s of visibleSubs) {
1127
+ const desc = escapeDesc$1(s.description ?? "");
1128
+ lines.push(` echo "${s.name}\t${desc}"`);
1129
+ }
1130
+ else if (sub.positionals.length > 0) lines.push(...positionalBlock$1(sub.positionals));
1131
+ lines.push(`end`);
1132
+ lines.push(``);
1133
+ return lines;
1134
+ }
1135
+ /** Generate opt-takes-value entries for fish switch cases */
1136
+ function optTakesValueCases(sub, subcmdName) {
1137
+ const lines = [];
1138
+ for (const opt of sub.options) if (opt.takesValue) {
1139
+ const patterns = [`"${subcmdName}:--${opt.cliName}"`];
1140
+ if (opt.alias) patterns.push(`"${subcmdName}:-${opt.alias}"`);
1141
+ lines.push(` case ${patterns.join(" ")}`);
1142
+ lines.push(` return 0`);
1143
+ }
1144
+ for (const child of getVisibleSubs(sub.subcommands)) lines.push(...optTakesValueCases(child, child.name));
1145
+ return lines;
1146
+ }
1147
+ function generateFishCompletion(command, options) {
1148
+ const { programName } = options;
1149
+ const data = extractCompletionData(command, programName);
1150
+ const fn = sanitize(programName);
1151
+ const root = data.command;
1152
+ const visibleSubs = getVisibleSubs(root.subcommands);
1153
+ const lines = [];
1154
+ lines.push(`# Fish completion for ${programName}`);
1155
+ lines.push(`# Generated by politty`);
1156
+ lines.push(``);
1157
+ lines.push(`function __${fn}_not_used --no-scope-shadowing`);
1158
+ lines.push(` for _chk in $argv`);
1159
+ lines.push(` if contains -- "$_chk" $_used_opts`);
1160
+ lines.push(` return 1`);
1161
+ lines.push(` end`);
1162
+ lines.push(` end`);
1163
+ lines.push(` return 0`);
1164
+ lines.push(`end`);
1165
+ lines.push(``);
1166
+ lines.push(`function __${fn}_opt_takes_value`);
1167
+ lines.push(` switch "$argv[1]:$argv[2]"`);
1168
+ lines.push(...optTakesValueCases(root, ""));
1169
+ lines.push(` end`);
1170
+ lines.push(` return 1`);
1171
+ lines.push(`end`);
1172
+ lines.push(``);
1173
+ for (const sub of visibleSubs) lines.push(...generateSubHandler$1(sub, fn, []));
1174
+ lines.push(`function __${fn}_complete_root --no-scope-shadowing`);
1175
+ lines.push(...valueCompletionBlock$1(root.options));
1176
+ lines.push(` if __${fn}_opt_takes_value "" "$_prev"; return; end`);
1177
+ if (root.positionals.length > 0) {
1178
+ lines.push(` if test $_after_dd -eq 1`);
1179
+ lines.push(...positionalBlock$1(root.positionals).map((l) => ` ${l}`));
1180
+ lines.push(` return`);
1181
+ lines.push(` end`);
1182
+ } else lines.push(` if test $_after_dd -eq 1; return; end`);
1183
+ lines.push(` if string match -q -- '-*' "$_cur"`);
1184
+ lines.push(...availableOptionLines$1(root.options, fn));
1185
+ if (visibleSubs.length > 0) {
1186
+ lines.push(` else`);
1187
+ for (const s of visibleSubs) {
1188
+ const desc = escapeDesc$1(s.description ?? "");
1189
+ lines.push(` echo "${s.name}\t${desc}"`);
1190
+ }
1191
+ } else if (root.positionals.length > 0) {
1192
+ lines.push(` else`);
1193
+ lines.push(...positionalBlock$1(root.positionals));
1194
+ }
1195
+ lines.push(` end`);
1196
+ lines.push(`end`);
1197
+ lines.push(``);
1198
+ lines.push(`function __fish_${fn}_complete`);
1199
+ lines.push(` set -l _args (commandline -opc)`);
1200
+ lines.push(` set -e _args[1]`);
1201
+ lines.push(``);
1202
+ lines.push(` set -l _ct (commandline -ct)`);
1203
+ lines.push(` if test (count $_ct) -eq 0`);
1204
+ lines.push(` set -a _args ""`);
1205
+ lines.push(` else`);
1206
+ lines.push(` set -a _args $_ct`);
1207
+ lines.push(` end`);
1208
+ lines.push(``);
1209
+ lines.push(` set -l _cur ""`);
1210
+ lines.push(` if test (count $_args) -gt 0`);
1211
+ lines.push(` set _cur "$_args[-1]"`);
1212
+ lines.push(` end`);
1213
+ lines.push(``);
1214
+ lines.push(` set -l _prev ""`);
1215
+ lines.push(` if test (count $_args) -gt 1`);
1216
+ lines.push(` set _prev "$_args[-2]"`);
1217
+ lines.push(` end`);
1218
+ lines.push(``);
1219
+ lines.push(` set -l _subcmd "" ; set -l _after_dd 0 ; set -l _pos_count 0 ; set -l _skip_next 0`);
1220
+ lines.push(` set -l _used_opts`);
1221
+ lines.push(``);
1222
+ lines.push(` set -l _j 1`);
1223
+ lines.push(` set -l _limit (math (count $_args) - 1)`);
1224
+ lines.push(` while test $_j -le $_limit`);
1225
+ lines.push(` set -l _w "$_args[$_j]"`);
1226
+ lines.push(` if test $_skip_next -eq 1; set _skip_next 0; set _j (math $_j + 1); continue; end`);
1227
+ lines.push(` if test "$_w" = "--"; set _after_dd 1; set _j (math $_j + 1); continue; end`);
1228
+ lines.push(` if test $_after_dd -eq 1; set _pos_count (math $_pos_count + 1); set _j (math $_j + 1); continue; end`);
1229
+ lines.push(` if string match -q -- '--*=*' "$_w"; set -a _used_opts (string replace -r '=.*' '' -- "$_w"); set _j (math $_j + 1); continue; end`);
1230
+ lines.push(` if string match -q -- '-*' "$_w"`);
1231
+ lines.push(` set -a _used_opts "$_w"`);
1232
+ lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w"; and set _skip_next 1`);
1233
+ lines.push(` set _j (math $_j + 1); continue`);
1234
+ lines.push(` end`);
1235
+ if (visibleSubs.length > 0) lines.push(` if test -z "$_subcmd"; set _subcmd "$_w"; set _used_opts; else; set _pos_count (math $_pos_count + 1); end`);
1236
+ else lines.push(` set _pos_count (math $_pos_count + 1)`);
1237
+ lines.push(` set _j (math $_j + 1)`);
1238
+ lines.push(` end`);
1239
+ lines.push(``);
1240
+ lines.push(` switch "$_subcmd"`);
1241
+ for (const s of visibleSubs) lines.push(` case "${s.name}"; __${fn}_complete_${sanitize(s.name)}`);
1242
+ lines.push(` case '*'; __${fn}_complete_root`);
1243
+ lines.push(` end`);
1244
+ lines.push(`end`);
1245
+ lines.push(``);
1246
+ lines.push(`# Clear existing completions`);
1247
+ lines.push(`complete -e -c ${programName}`);
1248
+ lines.push(``);
1249
+ lines.push(`# Register completion`);
1250
+ lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
1251
+ lines.push(``);
1252
+ return {
1253
+ script: lines.join("\n"),
1254
+ shell: "fish",
1255
+ installInstructions: `# To enable completions, run one of the following:
1256
+
1257
+ # Option 1: Source directly
1258
+ ${programName} completion fish | source
1259
+
1260
+ # Option 2: Save to the fish completions directory
1261
+ ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
1262
+
1263
+ # The completion will be available immediately in new shell sessions.
1264
+ # To use in the current session, run:
1265
+ source ~/.config/fish/completions/${programName}.fish`
1266
+ };
1267
+ }
1268
+
1269
+ //#endregion
1270
+ //#region src/completion/zsh.ts
1271
+ function escapeDesc(s) {
1272
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/:/g, "\\:");
1273
+ }
1274
+ /**
1275
+ * Generate zsh value completion lines for a ValueCompletion spec.
1276
+ * Uses `_vals` array (must be declared in the calling function scope).
1277
+ */
1278
+ function zshValueLines(vc, fn) {
1279
+ if (!vc) return [];
1280
+ switch (vc.type) {
1281
+ case "choices": return [`_vals=(${vc.choices.map((c) => `"${escapeDesc(c)}"`).join(" ")})`, `__${fn}_cdescribe 'completions' _vals`];
1282
+ case "file":
1283
+ if (vc.matcher?.length) return vc.matcher.map((p) => `_files -g "${p}"`);
1284
+ if (vc.extensions?.length) return vc.extensions.map((ext) => `_files -g "*.${ext}"`);
1285
+ return [`_files`];
1286
+ case "directory": return [`_files -/`];
1287
+ case "command": return [`_vals=("\${(@f)$(${vc.shellCommand})}")`, `__${fn}_cdescribe 'completions' _vals`];
1288
+ case "none": return [];
1289
+ }
1290
+ }
1291
+ /** Generate option-value case branches */
1292
+ function optionValueCases(options, fn) {
1293
+ const lines = [];
1294
+ for (const opt of options) {
1295
+ if (!opt.takesValue || !opt.valueCompletion) continue;
1296
+ const valLines = zshValueLines(opt.valueCompletion, fn);
1297
+ if (valLines.length === 0) continue;
1298
+ const patterns = [`--${opt.cliName}`];
1299
+ if (opt.alias) patterns.push(`-${opt.alias}`);
1300
+ lines.push(` ${patterns.join("|")})`);
1301
+ for (const vl of valLines) lines.push(` ${vl}`);
1302
+ lines.push(` return 0 ;;`);
1303
+ }
1304
+ return lines;
1305
+ }
1306
+ /** Generate positional completion block */
1307
+ function positionalBlock(positionals, fn) {
1308
+ if (positionals.length === 0) return [];
1309
+ const lines = [];
1310
+ lines.push(` case "$_pos_count" in`);
1311
+ for (const pos of positionals) {
1312
+ if (pos.variadic) lines.push(` ${pos.position}|*)`);
1313
+ else lines.push(` ${pos.position})`);
1314
+ const valLines = zshValueLines(pos.valueCompletion, fn);
1315
+ for (const vl of valLines) lines.push(` ${vl}`);
1316
+ lines.push(` ;;`);
1317
+ }
1318
+ lines.push(` esac`);
1319
+ return lines;
1320
+ }
1321
+ /** Generate prev-word value completion case block */
1322
+ function valueCompletionBlock(options, fn) {
1323
+ if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
1324
+ const prevCases = optionValueCases(options, fn);
1325
+ if (prevCases.length === 0) return [];
1326
+ return [
1327
+ ` case "\${words[CURRENT-1]}" in`,
1328
+ ...prevCases,
1329
+ ` esac`
1330
+ ];
1331
+ }
1332
+ /** Generate available-options list lines */
1333
+ function availableOptionLines(options, fn) {
1334
+ const lines = [];
1335
+ for (const opt of options) {
1336
+ const desc = opt.description ? `:${escapeDesc(opt.description)}` : "";
1337
+ if (opt.valueType === "array") lines.push(` _opts+=("--${opt.cliName}${desc}")`);
1338
+ else {
1339
+ const patterns = [`"--${opt.cliName}"`];
1340
+ if (opt.alias) patterns.push(`"-${opt.alias}"`);
1341
+ lines.push(` __${fn}_not_used ${patterns.join(" ")} && _opts+=("--${opt.cliName}${desc}")`);
1342
+ }
1343
+ }
1344
+ lines.push(` __${fn}_not_used "--help" && _opts+=("--help:Show help")`);
1345
+ return lines;
1346
+ }
1347
+ /**
1348
+ * Generate a per-subcommand completion function.
1349
+ * Recursively generates functions for nested subcommands.
1350
+ */
1351
+ function generateSubHandler(sub, fn, path) {
1352
+ const fullPath = [...path, sub.name];
1353
+ const funcName = `__${fn}_complete_${fullPath.map(sanitize).join("_")}`;
1354
+ const visibleSubs = getVisibleSubs(sub.subcommands);
1355
+ const lines = [];
1356
+ for (const child of visibleSubs) lines.push(...generateSubHandler(child, fn, fullPath));
1357
+ lines.push(`${funcName}() {`);
1358
+ lines.push(` local -a _vals=()`);
1359
+ lines.push(...valueCompletionBlock(sub.options, fn));
1360
+ lines.push(` if __${fn}_opt_takes_value "${sub.name}" "\${words[CURRENT-1]}"; then return 0; fi`);
1361
+ if (sub.positionals.length > 0) {
1362
+ lines.push(` if (( _after_dd )); then`);
1363
+ lines.push(...positionalBlock(sub.positionals, fn).map((l) => ` ${l}`));
1364
+ lines.push(` return 0`);
1365
+ lines.push(` fi`);
1366
+ } else lines.push(` if (( _after_dd )); then return 0; fi`);
1367
+ lines.push(` if [[ "\${words[CURRENT]}" == -* ]]; then`);
1368
+ lines.push(` local -a _opts=()`);
1369
+ lines.push(...availableOptionLines(sub.options, fn));
1370
+ lines.push(` __${fn}_cdescribe 'options' _opts`);
1371
+ lines.push(` return 0`);
1372
+ lines.push(` fi`);
1373
+ if (visibleSubs.length > 0) {
1374
+ const subItems = visibleSubs.map((s) => {
1375
+ const desc = s.description ? `:${escapeDesc(s.description)}` : "";
1376
+ return `"${s.name}${desc}"`;
1377
+ }).join(" ");
1378
+ lines.push(` local -a _subs=(${subItems})`);
1379
+ lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
1380
+ } else if (sub.positionals.length > 0) lines.push(...positionalBlock(sub.positionals, fn));
1381
+ lines.push(`}`);
1382
+ lines.push(``);
1383
+ return lines;
1384
+ }
1385
+ function generateZshCompletion(command, options) {
1386
+ const { programName } = options;
1387
+ const data = extractCompletionData(command, programName);
1388
+ const fn = sanitize(programName);
1389
+ const root = data.command;
1390
+ const visibleSubs = getVisibleSubs(root.subcommands);
1391
+ const lines = [];
1392
+ lines.push(`#compdef ${programName}`);
1393
+ lines.push(``);
1394
+ lines.push(`# Zsh completion for ${programName}`);
1395
+ lines.push(`# Generated by politty`);
1396
+ lines.push(``);
1397
+ lines.push(`__${fn}_not_used() {`);
1398
+ lines.push(` local _u _chk`);
1399
+ lines.push(` for _u in "\${_used_opts[@]}"; do`);
1400
+ lines.push(` for _chk in "$@"; do`);
1401
+ lines.push(` [[ "$_u" == "$_chk" ]] && return 1`);
1402
+ lines.push(` done`);
1403
+ lines.push(` done`);
1404
+ lines.push(` return 0`);
1405
+ lines.push(`}`);
1406
+ lines.push(``);
1407
+ lines.push(`__${fn}_cdescribe() {`);
1408
+ lines.push(` _describe "$@" 2>/dev/null && return 0`);
1409
+ lines.push(` shift`);
1410
+ lines.push(` local -a _cd_vals=("\${(@)\${(P)1}%%:*}")`);
1411
+ lines.push(` compadd -a _cd_vals 2>/dev/null`);
1412
+ lines.push(` return 0`);
1413
+ lines.push(`}`);
1414
+ lines.push(``);
1415
+ lines.push(`__${fn}_opt_takes_value() {`);
1416
+ lines.push(` case "$1:$2" in`);
1417
+ lines.push(...optTakesValueEntries(root, ""));
1418
+ lines.push(` esac`);
1419
+ lines.push(` return 1`);
1420
+ lines.push(`}`);
1421
+ lines.push(``);
1422
+ for (const sub of visibleSubs) lines.push(...generateSubHandler(sub, fn, []));
1423
+ lines.push(`__${fn}_complete_root() {`);
1424
+ lines.push(` local -a _vals=()`);
1425
+ lines.push(...valueCompletionBlock(root.options, fn));
1426
+ lines.push(` if __${fn}_opt_takes_value "" "\${words[CURRENT-1]}"; then return 0; fi`);
1427
+ if (root.positionals.length > 0) {
1428
+ lines.push(` if (( _after_dd )); then`);
1429
+ lines.push(...positionalBlock(root.positionals, fn).map((l) => ` ${l}`));
1430
+ lines.push(` return 0`);
1431
+ lines.push(` fi`);
1432
+ } else lines.push(` if (( _after_dd )); then return 0; fi`);
1433
+ lines.push(` if [[ "\${words[CURRENT]}" == -* ]]; then`);
1434
+ lines.push(` local -a _opts=()`);
1435
+ lines.push(...availableOptionLines(root.options, fn));
1436
+ lines.push(` __${fn}_cdescribe 'options' _opts`);
1437
+ if (visibleSubs.length > 0) {
1438
+ lines.push(` else`);
1439
+ const subItems = visibleSubs.map((s) => {
1440
+ const desc = s.description ? `:${escapeDesc(s.description)}` : "";
1441
+ return `"${s.name}${desc}"`;
1442
+ }).join(" ");
1443
+ lines.push(` local -a _subs=(${subItems})`);
1444
+ lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
1445
+ } else if (root.positionals.length > 0) {
1446
+ lines.push(` else`);
1447
+ lines.push(...positionalBlock(root.positionals, fn).map((l) => ` ${l}`));
1448
+ }
1449
+ lines.push(` fi`);
1450
+ lines.push(`}`);
1451
+ lines.push(``);
1452
+ const subRouting = visibleSubs.map((s) => ` ${s.name}) __${fn}_complete_${sanitize(s.name)} ;;`).join("\n");
1453
+ lines.push(`_${fn}() {`);
1454
+ lines.push(` (( CURRENT )) || CURRENT=\${#words}`);
1455
+ lines.push(``);
1456
+ lines.push(` local _subcmd="" _after_dd=0 _pos_count=0 _skip_next=0`);
1457
+ lines.push(` local -a _used_opts=()`);
1458
+ lines.push(``);
1459
+ lines.push(` local _j=2`);
1460
+ lines.push(` while (( _j < CURRENT )); do`);
1461
+ lines.push(` local _w="\${words[_j]}"`);
1462
+ lines.push(` if (( _skip_next )); then _skip_next=0; (( _j++ )); continue; fi`);
1463
+ lines.push(` if [[ "$_w" == "--" ]]; then _after_dd=1; (( _j++ )); continue; fi`);
1464
+ lines.push(` if (( _after_dd )); then (( _pos_count++ )); (( _j++ )); continue; fi`);
1465
+ lines.push(` if [[ "$_w" == --*=* ]]; then _used_opts+=("\${_w%%=*}"); (( _j++ )); continue; fi`);
1466
+ lines.push(` if [[ "$_w" == -* ]]; then`);
1467
+ lines.push(` _used_opts+=("$_w")`);
1468
+ lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w" && _skip_next=1`);
1469
+ lines.push(` (( _j++ )); continue`);
1470
+ lines.push(` fi`);
1471
+ if (visibleSubs.length > 0) lines.push(` if [[ -z "$_subcmd" ]]; then _subcmd="$_w"; _used_opts=(); else (( _pos_count++ )); fi`);
1472
+ else lines.push(` (( _pos_count++ ))`);
1473
+ lines.push(` (( _j++ ))`);
1474
+ lines.push(` done`);
1475
+ lines.push(``);
1476
+ lines.push(` case "$_subcmd" in`);
1477
+ lines.push(subRouting);
1478
+ lines.push(` *) __${fn}_complete_root ;;`);
1479
+ lines.push(` esac`);
1480
+ lines.push(`}`);
1481
+ lines.push(``);
1482
+ lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
1483
+ lines.push(``);
1484
+ lines.push(`compdef _${fn} ${programName}`);
1485
+ lines.push(``);
1486
+ return {
1487
+ script: lines.join("\n"),
1488
+ shell: "zsh",
1489
+ installInstructions: `# To enable completions, add the following to your ~/.zshrc:
1490
+
1491
+ # Option 1: Source directly (add before compinit)
1492
+ eval "$(${programName} completion zsh)"
1493
+
1494
+ # Option 2: Save to a file in your fpath
1495
+ ${programName} completion zsh > ~/.zsh/completions/_${programName}
1496
+
1497
+ # Make sure your fpath includes the completions directory:
1498
+ # fpath=(~/.zsh/completions $fpath)
1499
+ # autoload -Uz compinit && compinit
1500
+
1501
+ # Then reload your shell or run:
1502
+ source ~/.zshrc`
1503
+ };
1504
+ }
1505
+
1506
+ //#endregion
1507
+ export { formatForShell as a, generateCandidates as c, extractPositionals as d, resolveValueCompletion as f, hasCompleteCommand as i, generateBashCompletion as l, generateFishCompletion as n, parseCompletionContext as o, defineCommand as p, createDynamicCompleteCommand as r, CompletionDirective as s, generateZshCompletion as t, extractCompletionData as u };
1508
+ //# sourceMappingURL=zsh-hjvdI8uZ.js.map