politty 0.4.1 → 0.4.3

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 (64) 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 -20
  8. package/dist/completion/index.cjs.map +1 -1
  9. package/dist/completion/index.d.cts +3 -3
  10. package/dist/completion/index.d.ts +3 -3
  11. package/dist/completion/index.js +2 -2
  12. package/dist/docs/index.cjs +6 -6
  13. package/dist/docs/index.cjs.map +1 -1
  14. package/dist/docs/index.d.cts +3 -3
  15. package/dist/docs/index.d.cts.map +1 -1
  16. package/dist/docs/index.d.ts +3 -3
  17. package/dist/docs/index.d.ts.map +1 -1
  18. package/dist/docs/index.js +3 -3
  19. package/dist/index.cjs +10 -8
  20. package/dist/index.d.cts +4 -4
  21. package/dist/index.d.cts.map +1 -1
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -4
  25. package/dist/{schema-extractor-Mk1MHBkQ.cjs → lazy-BEDnSR0m.cjs} +77 -2
  26. package/dist/lazy-BEDnSR0m.cjs.map +1 -0
  27. package/dist/{schema-extractor-DAkmmrOy.js → lazy-BrEg8SgI.js} +59 -2
  28. package/dist/lazy-BrEg8SgI.js.map +1 -0
  29. package/dist/{runner-0yr2HFay.cjs → runner-C4fSHJMe.cjs} +11 -10
  30. package/dist/runner-C4fSHJMe.cjs.map +1 -0
  31. package/dist/{runner-BoZpJtIR.js → runner-D6k4BgB4.js} +6 -5
  32. package/dist/runner-D6k4BgB4.js.map +1 -0
  33. package/dist/{schema-extractor-DyfK21m_.d.cts → schema-extractor-DFaAZzaY.d.cts} +49 -4
  34. package/dist/schema-extractor-DFaAZzaY.d.cts.map +1 -0
  35. package/dist/{schema-extractor-CHiBRT39.d.ts → schema-extractor-n9288WJ6.d.ts} +49 -4
  36. package/dist/schema-extractor-n9288WJ6.d.ts.map +1 -0
  37. package/dist/{subcommand-router-DtCeT_O9.js → subcommand-router-CAzBsLSI.js} +4 -1
  38. package/dist/{subcommand-router-DtCeT_O9.js.map → subcommand-router-CAzBsLSI.js.map} +1 -1
  39. package/dist/{subcommand-router-4d1Xbp8B.cjs → subcommand-router-ZjNjFaUL.cjs} +3 -1
  40. package/dist/subcommand-router-ZjNjFaUL.cjs.map +1 -0
  41. package/dist/{value-completion-resolver-0xf8_07p.d.cts → value-completion-resolver-BQgHsX7b.d.cts} +20 -12
  42. package/dist/value-completion-resolver-BQgHsX7b.d.cts.map +1 -0
  43. package/dist/{value-completion-resolver-CUKbibx-.d.ts → value-completion-resolver-C9LTGr0O.d.ts} +20 -12
  44. package/dist/value-completion-resolver-C9LTGr0O.d.ts.map +1 -0
  45. package/dist/zsh-CASZWn0o.cjs +1586 -0
  46. package/dist/zsh-CASZWn0o.cjs.map +1 -0
  47. package/dist/zsh-hjvdI8uZ.js +1508 -0
  48. package/dist/zsh-hjvdI8uZ.js.map +1 -0
  49. package/package.json +4 -4
  50. package/dist/arg-registry-B4a4Fj7f.d.cts.map +0 -1
  51. package/dist/arg-registry-C3GP-5C9.d.ts.map +0 -1
  52. package/dist/extractor-DO-FDKkW.js +0 -1000
  53. package/dist/extractor-DO-FDKkW.js.map +0 -1
  54. package/dist/extractor-cjruDqQ2.cjs +0 -1078
  55. package/dist/extractor-cjruDqQ2.cjs.map +0 -1
  56. package/dist/runner-0yr2HFay.cjs.map +0 -1
  57. package/dist/runner-BoZpJtIR.js.map +0 -1
  58. package/dist/schema-extractor-CHiBRT39.d.ts.map +0 -1
  59. package/dist/schema-extractor-DAkmmrOy.js.map +0 -1
  60. package/dist/schema-extractor-DyfK21m_.d.cts.map +0 -1
  61. package/dist/schema-extractor-Mk1MHBkQ.cjs.map +0 -1
  62. package/dist/subcommand-router-4d1Xbp8B.cjs.map +0 -1
  63. package/dist/value-completion-resolver-0xf8_07p.d.cts.map +0 -1
  64. package/dist/value-completion-resolver-CUKbibx-.d.ts.map +0 -1
@@ -1,1078 +0,0 @@
1
- const require_subcommand_router = require('./subcommand-router-4d1Xbp8B.cjs');
2
- const require_schema_extractor = require('./schema-extractor-Mk1MHBkQ.cjs');
3
- let zod = require("zod");
4
- let node_child_process = require("node:child_process");
5
-
6
- //#region src/core/command.ts
7
- function defineCommand(config) {
8
- return {
9
- name: config.name,
10
- description: config.description,
11
- args: config.args,
12
- subCommands: config.subCommands,
13
- setup: config.setup,
14
- run: config.run,
15
- cleanup: config.cleanup,
16
- notes: config.notes,
17
- examples: config.examples
18
- };
19
- }
20
-
21
- //#endregion
22
- //#region src/completion/bash.ts
23
- /**
24
- * Generate bash completion script for a command
25
- *
26
- * Generates a minimal script that delegates all logic to the CLI's __complete command.
27
- * The shell script only handles:
28
- * - Getting current command line tokens
29
- * - Calling __complete with --shell bash
30
- * - Setting COMPREPLY from the output
31
- * - Falling back to native file/directory completion when directed
32
- */
33
- function generateBashCompletion(_command, options) {
34
- const programName = options.programName;
35
- return {
36
- script: `# Bash completion for ${programName}
37
- # Generated by politty
38
-
39
- _${programName}_completions() {
40
- COMPREPLY=()
41
- local IFS=$'\\n'
42
-
43
- # Rejoin words split by '=' in COMP_WORDBREAKS (e.g. --opt=value)
44
- local -a _words=()
45
- local _i=1
46
- while (( _i <= COMP_CWORD )); do
47
- if [[ "\${COMP_WORDS[_i]}" == "=" && \${#_words[@]} -gt 0 ]]; then
48
- _words[\${#_words[@]}-1]+="=\${COMP_WORDS[_i+1]:-}"
49
- (( _i += 2 ))
50
- else
51
- _words+=("\${COMP_WORDS[_i]}")
52
- (( _i++ ))
53
- fi
54
- done
55
-
56
- local lines prev_opts="$-"
57
- set -f
58
- lines=($(${programName} __complete --shell bash -- "\${_words[@]}" 2>/dev/null))
59
- [[ "$prev_opts" != *f* ]] && set +f
60
-
61
- local count=\${#lines[@]}
62
- if (( count == 0 )); then
63
- return 0
64
- fi
65
-
66
- local last="\${lines[count-1]}"
67
- local directive=0
68
- if [[ "$last" == :* ]]; then
69
- directive="\${last:1}"
70
- unset 'lines[count-1]'
71
- (( count-- ))
72
- fi
73
-
74
- # Parse @ext: metadata (extension filter for native file completion)
75
- local extensions=""
76
- if (( count > 0 )); then
77
- local maybe_ext="\${lines[count-1]}"
78
- if [[ "$maybe_ext" == @ext:* ]]; then
79
- extensions="\${maybe_ext:5}"
80
- unset 'lines[count-1]'
81
- fi
82
- fi
83
-
84
- local cur=""
85
- (( \${#_words[@]} > 0 )) && cur="\${_words[\${#_words[@]}-1]}"
86
-
87
- # Strip --opt= prefix for native file/directory completion
88
- local inline_prefix=""
89
- if [[ "$cur" == --*=* ]]; then
90
- inline_prefix="\${cur%%=*}="
91
- cur="\${cur#*=}"
92
- fi
93
-
94
- # 16 = FileCompletion: delegate entirely to native file completion
95
- if (( directive & 16 )); then
96
- local -a entries=($(compgen -f -- "$cur"))
97
- if [[ -n "$inline_prefix" ]]; then
98
- local i
99
- for (( i=0; i<\${#entries[@]}; i++ )); do
100
- entries[$i]="\${inline_prefix}\${entries[$i]}"
101
- done
102
- fi
103
- COMPREPLY=("\${entries[@]}")
104
- compopt -o filenames
105
- return 0
106
- fi
107
-
108
- # Extension-filtered file completion: keep matching files + directories
109
- if [[ -n "$extensions" ]]; then
110
- local -a all_entries=($(compgen -f -- "$cur"))
111
- local IFS=','
112
- local -a ext_arr=($extensions)
113
- IFS=$'\\n'
114
- for f in "\${all_entries[@]}"; do
115
- if [[ -d "$f" ]]; then
116
- COMPREPLY+=("\${inline_prefix}$f")
117
- else
118
- for ext in "\${ext_arr[@]}"; do
119
- if [[ "$f" == *".$ext" ]]; then
120
- COMPREPLY+=("\${inline_prefix}$f")
121
- break
122
- fi
123
- done
124
- fi
125
- done
126
- compopt -o filenames
127
- compopt +o default 2>/dev/null
128
- return 0
129
- fi
130
-
131
- # Start with JS candidates
132
- if (( \${#lines[@]} > 0 )); then
133
- COMPREPLY=("\${lines[@]}")
134
- fi
135
-
136
- # 32 = DirectoryCompletion: merge native directory matches
137
- if (( directive & 32 )); then
138
- local -a dirs=($(compgen -d -- "$cur"))
139
- if [[ -n "$inline_prefix" ]]; then
140
- local i
141
- for (( i=0; i<\${#dirs[@]}; i++ )); do
142
- dirs[$i]="\${inline_prefix}\${dirs[$i]}"
143
- done
144
- fi
145
- COMPREPLY+=("\${dirs[@]}")
146
- compopt -o filenames
147
- fi
148
-
149
- # 2 = NoFileCompletion, 32 = DirectoryCompletion:
150
- # suppress -o default file fallback when completions are restricted
151
- if (( directive & 2 )) || (( directive & 32 )); then
152
- compopt +o default 2>/dev/null
153
- fi
154
-
155
- return 0
156
- }
157
-
158
- # Register the completion function
159
- complete -o default -F _${programName}_completions ${programName}
160
- `,
161
- shell: "bash",
162
- installInstructions: `# To enable completions, add the following to your ~/.bashrc:
163
-
164
- # Option 1: Source directly
165
- eval "$(${programName} completion bash)"
166
-
167
- # Option 2: Save to a file
168
- ${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
169
-
170
- # Then reload your shell or run:
171
- source ~/.bashrc`
172
- };
173
- }
174
-
175
- //#endregion
176
- //#region src/completion/dynamic/candidate-generator.ts
177
- /**
178
- * Generate completion candidates based on context
179
- */
180
- /**
181
- * Completion directive flags (bitwise)
182
- */
183
- const CompletionDirective = {
184
- Default: 0,
185
- NoSpace: 1,
186
- NoFileCompletion: 2,
187
- FilterPrefix: 4,
188
- KeepOrder: 8,
189
- FileCompletion: 16,
190
- DirectoryCompletion: 32,
191
- Error: 64
192
- };
193
- /**
194
- * Generate completion candidates based on context
195
- */
196
- function generateCandidates(context) {
197
- const candidates = [];
198
- let directive = CompletionDirective.Default;
199
- switch (context.completionType) {
200
- case "subcommand": return generateSubcommandCandidates(context);
201
- case "option-name": return generateOptionNameCandidates(context);
202
- case "option-value": return generateOptionValueCandidates(context);
203
- case "positional": return generatePositionalCandidates(context);
204
- default: return {
205
- candidates,
206
- directive
207
- };
208
- }
209
- }
210
- /**
211
- * Execute a shell command and return results as candidates
212
- */
213
- function executeShellCommand(command) {
214
- try {
215
- return (0, node_child_process.execSync)(command, {
216
- encoding: "utf-8",
217
- timeout: 5e3
218
- }).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => ({
219
- value: line,
220
- type: "value"
221
- }));
222
- } catch {
223
- return [];
224
- }
225
- }
226
- /**
227
- * Resolve value completion, executing shell commands and file lookups in JS
228
- */
229
- function resolveValueCandidates(vc, candidates, _currentWord, description) {
230
- let directive = CompletionDirective.FilterPrefix;
231
- let fileExtensions;
232
- switch (vc.type) {
233
- case "choices":
234
- if (vc.choices) for (const choice of vc.choices) candidates.push({
235
- value: choice,
236
- description,
237
- type: "value"
238
- });
239
- directive |= CompletionDirective.NoFileCompletion;
240
- break;
241
- case "file":
242
- if (vc.extensions && vc.extensions.length > 0) {
243
- fileExtensions = Array.from(new Set(vc.extensions.map((ext) => ext.trim().replace(/^\./, "")).filter((ext) => ext.length > 0)));
244
- if (fileExtensions.length === 0) {
245
- fileExtensions = void 0;
246
- directive |= CompletionDirective.FileCompletion;
247
- }
248
- } else directive |= CompletionDirective.FileCompletion;
249
- break;
250
- case "directory":
251
- directive |= CompletionDirective.DirectoryCompletion;
252
- break;
253
- case "command":
254
- if (vc.shellCommand) candidates.push(...executeShellCommand(vc.shellCommand));
255
- directive |= CompletionDirective.NoFileCompletion;
256
- break;
257
- case "none":
258
- directive |= CompletionDirective.NoFileCompletion;
259
- break;
260
- }
261
- return {
262
- directive,
263
- fileExtensions
264
- };
265
- }
266
- /**
267
- * Generate subcommand candidates
268
- */
269
- function generateSubcommandCandidates(context) {
270
- const candidates = [];
271
- let directive = CompletionDirective.FilterPrefix;
272
- for (const name of context.subcommands) {
273
- let description;
274
- if (context.currentCommand.subCommands) {
275
- const sub = context.currentCommand.subCommands[name];
276
- if (sub && typeof sub !== "function") description = sub.description;
277
- }
278
- candidates.push({
279
- value: name,
280
- description,
281
- type: "subcommand"
282
- });
283
- }
284
- if (candidates.length === 0 || context.currentWord.startsWith("-")) {
285
- const optionResult = generateOptionNameCandidates(context);
286
- candidates.push(...optionResult.candidates);
287
- }
288
- return {
289
- candidates,
290
- directive
291
- };
292
- }
293
- /**
294
- * Generate option name candidates
295
- */
296
- function generateOptionNameCandidates(context) {
297
- const candidates = [];
298
- const directive = CompletionDirective.FilterPrefix;
299
- const availableOptions = context.options.filter((opt) => {
300
- if (opt.valueType === "array") return true;
301
- return !context.usedOptions.has(opt.cliName) && !context.usedOptions.has(opt.alias || "");
302
- });
303
- for (const opt of availableOptions) candidates.push({
304
- value: `--${opt.cliName}`,
305
- description: opt.description,
306
- type: "option"
307
- });
308
- if (!context.usedOptions.has("help")) candidates.push({
309
- value: "--help",
310
- description: "Show help information",
311
- type: "option"
312
- });
313
- return {
314
- candidates,
315
- directive
316
- };
317
- }
318
- /**
319
- * Generate option value candidates
320
- */
321
- function generateOptionValueCandidates(context) {
322
- const candidates = [];
323
- if (!context.targetOption) return {
324
- candidates,
325
- directive: CompletionDirective.FilterPrefix
326
- };
327
- const vc = context.targetOption.valueCompletion;
328
- if (!vc) return {
329
- candidates,
330
- directive: CompletionDirective.FilterPrefix
331
- };
332
- const { directive, fileExtensions } = resolveValueCandidates(vc, candidates, context.currentWord);
333
- return {
334
- candidates,
335
- directive,
336
- fileExtensions
337
- };
338
- }
339
- /**
340
- * Generate positional argument candidates
341
- */
342
- function generatePositionalCandidates(context) {
343
- const candidates = [];
344
- const positionalIndex = context.positionalIndex ?? 0;
345
- const positional = context.positionals[positionalIndex] ?? (context.positionals.at(-1)?.variadic ? context.positionals.at(-1) : void 0);
346
- if (!positional) return {
347
- candidates,
348
- directive: CompletionDirective.FilterPrefix
349
- };
350
- const vc = positional.valueCompletion;
351
- if (!vc) return {
352
- candidates,
353
- directive: CompletionDirective.FilterPrefix
354
- };
355
- const { directive, fileExtensions } = resolveValueCandidates(vc, candidates, context.currentWord, positional.description);
356
- return {
357
- candidates,
358
- directive,
359
- fileExtensions
360
- };
361
- }
362
-
363
- //#endregion
364
- //#region src/completion/value-completion-resolver.ts
365
- /**
366
- * Resolve value completion from field metadata
367
- *
368
- * Priority:
369
- * 1. Explicit custom completion (choices or shellCommand)
370
- * 2. Explicit completion type (file, directory, none)
371
- * 3. Auto-detected enum values from schema
372
- */
373
- function resolveValueCompletion(field) {
374
- const meta = field.completion;
375
- if (meta?.custom) {
376
- if (meta.custom.choices && meta.custom.choices.length > 0) return {
377
- type: "choices",
378
- choices: meta.custom.choices
379
- };
380
- if (meta.custom.shellCommand) return {
381
- type: "command",
382
- shellCommand: meta.custom.shellCommand
383
- };
384
- }
385
- if (meta?.type) {
386
- if (meta.type === "file") return meta.extensions ? {
387
- type: "file",
388
- extensions: meta.extensions
389
- } : { type: "file" };
390
- if (meta.type === "directory") return { type: "directory" };
391
- if (meta.type === "none") return { type: "none" };
392
- }
393
- if (field.enumValues && field.enumValues.length > 0) return {
394
- type: "choices",
395
- choices: field.enumValues
396
- };
397
- }
398
-
399
- //#endregion
400
- //#region src/completion/dynamic/context-parser.ts
401
- /**
402
- * Parse completion context from partial command line
403
- */
404
- /**
405
- * Extract options from a command
406
- */
407
- function extractOptions$1(command) {
408
- if (!command.args) return [];
409
- return require_schema_extractor.extractFields(command.args).fields.filter((field) => !field.positional).map((field) => ({
410
- name: field.name,
411
- cliName: field.cliName,
412
- alias: field.alias,
413
- description: field.description,
414
- takesValue: field.type !== "boolean",
415
- valueType: field.type,
416
- required: field.required,
417
- valueCompletion: resolveValueCompletion(field)
418
- }));
419
- }
420
- /**
421
- * Extract positionals from a command
422
- */
423
- function extractPositionalsForContext(command) {
424
- if (!command.args) return [];
425
- return require_schema_extractor.extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
426
- name: field.name,
427
- cliName: field.cliName,
428
- position: index,
429
- description: field.description,
430
- required: field.required,
431
- variadic: field.type === "array",
432
- valueCompletion: resolveValueCompletion(field)
433
- }));
434
- }
435
- /**
436
- * Get subcommand names from a command
437
- */
438
- function getSubcommandNames(command) {
439
- if (!command.subCommands) return [];
440
- return Object.keys(command.subCommands).filter((name) => !name.startsWith("__"));
441
- }
442
- /**
443
- * Resolve subcommand by name
444
- */
445
- function resolveSubcommand(command, name) {
446
- if (!command.subCommands) return null;
447
- const sub = command.subCommands[name];
448
- if (!sub) return null;
449
- if (typeof sub === "function") return null;
450
- return sub;
451
- }
452
- /**
453
- * Check if a word is an option (starts with - or --)
454
- */
455
- function isOption(word) {
456
- return word.startsWith("-");
457
- }
458
- /**
459
- * Parse option name from word (e.g., "--foo=bar" -> "foo", "-v" -> "v")
460
- */
461
- function parseOptionName(word) {
462
- if (word.startsWith("--")) {
463
- const withoutPrefix = word.slice(2);
464
- const eqIndex = withoutPrefix.indexOf("=");
465
- return eqIndex >= 0 ? withoutPrefix.slice(0, eqIndex) : withoutPrefix;
466
- }
467
- if (word.startsWith("-")) return word.slice(1, 2);
468
- return word;
469
- }
470
- /**
471
- * Check if option has inline value (e.g., "--foo=bar")
472
- */
473
- function hasInlineValue(word) {
474
- return word.includes("=");
475
- }
476
- /**
477
- * Find option by name or alias
478
- */
479
- function findOption(options, nameOrAlias) {
480
- return options.find((opt) => opt.cliName === nameOrAlias || opt.alias === nameOrAlias);
481
- }
482
- /**
483
- * Parse completion context from command line arguments
484
- *
485
- * @param argv - Arguments after the program name (e.g., ["build", "--fo"])
486
- * @param rootCommand - The root command
487
- * @returns Completion context
488
- */
489
- function parseCompletionContext(argv, rootCommand) {
490
- let currentCommand = rootCommand;
491
- const subcommandPath = [];
492
- const usedOptions = /* @__PURE__ */ new Set();
493
- let positionalCount = 0;
494
- let i = 0;
495
- let options = extractOptions$1(currentCommand);
496
- let afterDoubleDash = false;
497
- while (i < argv.length - 1) {
498
- const word = argv[i];
499
- if (!afterDoubleDash && word === "--") {
500
- afterDoubleDash = true;
501
- i++;
502
- continue;
503
- }
504
- if (!afterDoubleDash && isOption(word)) {
505
- const optName = parseOptionName(word);
506
- const opt = findOption(options, optName);
507
- if (opt) {
508
- usedOptions.add(opt.cliName);
509
- if (opt.alias) usedOptions.add(opt.alias);
510
- if (opt.takesValue && !hasInlineValue(word)) i++;
511
- }
512
- i++;
513
- continue;
514
- }
515
- const subcommand = afterDoubleDash ? null : resolveSubcommand(currentCommand, word);
516
- if (subcommand) {
517
- subcommandPath.push(word);
518
- currentCommand = subcommand;
519
- options = extractOptions$1(currentCommand);
520
- usedOptions.clear();
521
- positionalCount = 0;
522
- i++;
523
- continue;
524
- }
525
- positionalCount++;
526
- i++;
527
- }
528
- const currentWord = argv[argv.length - 1] ?? "";
529
- const previousWord = argv[argv.length - 2] ?? "";
530
- const positionals = extractPositionalsForContext(currentCommand);
531
- const subcommands = getSubcommandNames(currentCommand);
532
- let completionType;
533
- let targetOption;
534
- let positionalIndex;
535
- if (!afterDoubleDash && previousWord && isOption(previousWord) && !hasInlineValue(previousWord)) {
536
- const optName = parseOptionName(previousWord);
537
- const opt = findOption(options, optName);
538
- if (opt && opt.takesValue) {
539
- completionType = "option-value";
540
- targetOption = opt;
541
- } else if (currentWord.startsWith("-")) completionType = "option-name";
542
- else {
543
- completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount);
544
- if (completionType === "positional") positionalIndex = positionalCount;
545
- }
546
- } else if (!afterDoubleDash && currentWord.startsWith("--") && hasInlineValue(currentWord)) {
547
- const optName = parseOptionName(currentWord);
548
- const opt = findOption(options, optName);
549
- if (opt && opt.takesValue) {
550
- completionType = "option-value";
551
- targetOption = opt;
552
- } else completionType = "option-name";
553
- } else if (!afterDoubleDash && currentWord.startsWith("-")) completionType = "option-name";
554
- else {
555
- completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash);
556
- if (completionType === "positional") positionalIndex = positionalCount;
557
- }
558
- return {
559
- subcommandPath,
560
- currentCommand,
561
- currentWord,
562
- previousWord,
563
- completionType,
564
- targetOption,
565
- positionalIndex,
566
- options,
567
- subcommands,
568
- positionals,
569
- usedOptions,
570
- providedPositionalCount: positionalCount
571
- };
572
- }
573
- /**
574
- * Determine default completion type when not completing an option
575
- */
576
- function determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash) {
577
- if (afterDoubleDash) return "positional";
578
- if (subcommands.length > 0) {
579
- if (subcommands.filter((s) => s.startsWith(currentWord)).length > 0 || currentWord === "") return "subcommand";
580
- }
581
- if (positionalCount < positionals.length) return "positional";
582
- if (positionals.length > 0 && positionals[positionals.length - 1].variadic) return "positional";
583
- return "subcommand";
584
- }
585
-
586
- //#endregion
587
- //#region src/completion/dynamic/shell-formatter.ts
588
- /**
589
- * Format completion candidates for the specified shell
590
- *
591
- * @returns Shell-ready output string (lines separated by newline, last line is :directive)
592
- */
593
- function formatForShell(result, options) {
594
- switch (options.shell) {
595
- case "bash": return formatForBash(result, options);
596
- case "zsh": return formatForZsh(result, options);
597
- case "fish": return formatForFish(result, options);
598
- }
599
- }
600
- /**
601
- * Check if the FilterPrefix directive is set
602
- */
603
- function shouldFilterPrefix(directive) {
604
- return (directive & CompletionDirective.FilterPrefix) !== 0;
605
- }
606
- /**
607
- * Filter candidates by prefix
608
- */
609
- function filterByPrefix(candidates, prefix) {
610
- if (!prefix) return candidates;
611
- return candidates.filter((c) => c.value.startsWith(prefix));
612
- }
613
- /**
614
- * Append extension metadata and directive to output lines
615
- */
616
- function appendMetadata(lines, result) {
617
- if (result.fileExtensions && result.fileExtensions.length > 0) lines.push(`@ext:${result.fileExtensions.join(",")}`);
618
- lines.push(`:${result.directive}`);
619
- }
620
- /**
621
- * Format for bash
622
- *
623
- * - Pre-filters candidates by currentWord prefix (replaces compgen -W)
624
- * - Handles --opt=value inline values by prepending prefix
625
- * - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
626
- * - Last line: :directive
627
- */
628
- function formatForBash(result, options) {
629
- let { candidates } = result;
630
- if (shouldFilterPrefix(result.directive)) candidates = filterByPrefix(candidates, options.currentWord);
631
- const lines = candidates.map((c) => {
632
- if (options.inlinePrefix) return `${options.inlinePrefix}${c.value}`;
633
- return c.value;
634
- });
635
- appendMetadata(lines, result);
636
- return lines.join("\n");
637
- }
638
- /**
639
- * Format for zsh
640
- *
641
- * - Outputs value:description pairs for _describe
642
- * - Colons in values/descriptions are escaped with backslash
643
- * - Last line: :directive
644
- */
645
- function formatForZsh(result, _options) {
646
- const lines = result.candidates.map((c) => {
647
- const escapedValue = c.value.replace(/:/g, "\\:");
648
- if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
649
- return escapedValue;
650
- });
651
- appendMetadata(lines, result);
652
- return lines.join("\n");
653
- }
654
- /**
655
- * Format for fish
656
- *
657
- * - Outputs value\tdescription pairs
658
- * - Last line: :directive
659
- */
660
- function formatForFish(result, _options) {
661
- const lines = result.candidates.map((c) => {
662
- if (c.description) return `${c.value}\t${c.description}`;
663
- return c.value;
664
- });
665
- appendMetadata(lines, result);
666
- return lines.join("\n");
667
- }
668
-
669
- //#endregion
670
- //#region src/completion/dynamic/complete-command.ts
671
- /**
672
- * Dynamic completion command implementation
673
- *
674
- * This creates a hidden `__complete` command that outputs completion candidates
675
- * for shell scripts to consume. Usage:
676
- *
677
- * mycli __complete --shell bash -- build --fo
678
- * mycli __complete --shell zsh -- plugin add
679
- *
680
- * Output format depends on the target shell:
681
- * bash: plain values (pre-filtered by prefix), last line :directive
682
- * zsh: value:description pairs, last line :directive
683
- * fish: value\tdescription pairs, last line :directive
684
- */
685
- /**
686
- * Detect inline option-value prefix (e.g., "--format=" from "--format=json")
687
- */
688
- function detectInlinePrefix(currentWord) {
689
- if (currentWord.startsWith("--") && currentWord.includes("=")) return currentWord.slice(0, currentWord.indexOf("=") + 1);
690
- }
691
- /**
692
- * Schema for the __complete command
693
- */
694
- const completeArgsSchema = zod.z.object({
695
- shell: require_schema_extractor.arg(zod.z.enum([
696
- "bash",
697
- "zsh",
698
- "fish"
699
- ]), { description: "Target shell for output formatting" }),
700
- args: require_schema_extractor.arg(zod.z.array(zod.z.string()).default([]), {
701
- positional: true,
702
- description: "Arguments to complete",
703
- variadic: true
704
- })
705
- });
706
- /**
707
- * Create the dynamic completion command
708
- *
709
- * @param rootCommand - The root command to generate completions for
710
- * @param programName - The program name (optional, defaults to rootCommand.name)
711
- * @returns A command that outputs completion candidates
712
- */
713
- function createDynamicCompleteCommand(rootCommand, _programName) {
714
- return defineCommand({
715
- name: "__complete",
716
- args: completeArgsSchema,
717
- run(args) {
718
- const context = parseCompletionContext(args.args, rootCommand);
719
- const result = generateCandidates(context);
720
- const inlinePrefix = detectInlinePrefix(context.currentWord);
721
- const output = formatForShell(result, {
722
- shell: args.shell,
723
- currentWord: inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord,
724
- inlinePrefix
725
- });
726
- console.log(output);
727
- }
728
- });
729
- }
730
- /**
731
- * Check if a command tree contains the __complete command
732
- */
733
- function hasCompleteCommand(command) {
734
- return Boolean(command.subCommands?.["__complete"]);
735
- }
736
-
737
- //#endregion
738
- //#region src/completion/fish.ts
739
- /**
740
- * Generate fish completion script for a command
741
- *
742
- * Generates a minimal script that delegates all logic to the CLI's __complete command.
743
- * The shell script only handles:
744
- * - Getting current command line tokens
745
- * - Calling __complete with --shell fish
746
- * - Echoing output as completions
747
- * - Falling back to native file/directory completion when directed
748
- */
749
- function generateFishCompletion(_command, options) {
750
- const programName = options.programName;
751
- return {
752
- script: `# Fish completion for ${programName}
753
- # Generated by politty
754
-
755
- function __fish_${programName}_complete
756
- set -l args (commandline -opc)
757
- set -e args[1]
758
- set -l directive 0
759
- set -l extensions ""
760
-
761
- # commandline -opc excludes the current token; always include it
762
- set -l ct (commandline -ct)
763
- if test (count $ct) -eq 0
764
- set -a args ""
765
- else
766
- set -a args $ct
767
- end
768
-
769
- for line in (${programName} __complete --shell fish -- $args 2>/dev/null)
770
- if string match -q ':*' -- $line
771
- set directive (string sub -s 2 -- $line)
772
- else if string match -q '@ext:*' -- $line
773
- set extensions (string sub -s 6 -- $line)
774
- else if test -n "$line"
775
- echo $line
776
- end
777
- end
778
-
779
- # 16 = FileCompletion: delegate entirely to native file completion
780
- if test (math "bitand($directive, 16)") -ne 0
781
- __fish_complete_path
782
- return
783
- end
784
-
785
- # Extension-filtered file completion: keep matching files + directories
786
- if test -n "$extensions"
787
- set -l cur (commandline -ct)
788
- test (count $cur) -eq 0; and set cur ""
789
- __fish_complete_directories "$cur"
790
- for ext in (string split "," -- $extensions)
791
- for f in "$cur"*.$ext
792
- if test -f "$f"
793
- echo $f
794
- end
795
- end
796
- end
797
- return
798
- end
799
-
800
- # 32 = DirectoryCompletion: add native directory matches
801
- if test (math "bitand($directive, 32)") -ne 0
802
- __fish_complete_directories
803
- end
804
- end
805
-
806
- # Clear existing completions
807
- complete -e -c ${programName}
808
-
809
- # Register completion
810
- complete -c ${programName} -f -a '(__fish_${programName}_complete)'
811
- `,
812
- shell: "fish",
813
- installInstructions: `# To enable completions, run one of the following:
814
-
815
- # Option 1: Source directly
816
- ${programName} completion fish | source
817
-
818
- # Option 2: Save to the fish completions directory
819
- ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
820
-
821
- # The completion will be available immediately in new shell sessions.
822
- # To use in the current session, run:
823
- source ~/.config/fish/completions/${programName}.fish`
824
- };
825
- }
826
-
827
- //#endregion
828
- //#region src/completion/zsh.ts
829
- /**
830
- * Generate zsh completion script for a command
831
- *
832
- * Generates a minimal script that delegates all logic to the CLI's __complete command.
833
- * The shell script only handles:
834
- * - Getting current command line tokens
835
- * - Calling __complete with --shell zsh
836
- * - Passing output directly to _describe
837
- * - Falling back to native file/directory completion when directed
838
- */
839
- function generateZshCompletion(_command, options) {
840
- const programName = options.programName;
841
- return {
842
- script: `#compdef ${programName}
843
-
844
- # Zsh completion for ${programName}
845
- # Generated by politty
846
-
847
- _${programName}() {
848
- local -a candidates
849
- local line directive=0
850
- local -a args=("\${words[@]:1}")
851
- local -a output=("\${(@f)$(${programName} __complete --shell zsh -- "\${args[@]}" 2>/dev/null)}")
852
-
853
- local extensions=""
854
- for line in "\${output[@]}"; do
855
- if [[ "$line" == :* ]]; then
856
- directive="\${line:1}"
857
- elif [[ "$line" == @ext:* ]]; then
858
- extensions="\${line:5}"
859
- elif [[ -n "$line" ]]; then
860
- candidates+=("$line")
861
- fi
862
- done
863
-
864
- # 16 = FileCompletion: delegate entirely to native file completion
865
- if (( directive & 16 )); then
866
- _files
867
- return 0
868
- fi
869
-
870
- # Extension-filtered file completion: call _files -g per extension
871
- if [[ -n "$extensions" ]]; then
872
- local ext
873
- for ext in \${(s:,:)extensions}; do
874
- _files -g "*.$ext"
875
- done
876
- return 0
877
- fi
878
-
879
- if (( \${#candidates[@]} > 0 )); then
880
- _describe 'completions' candidates
881
- fi
882
-
883
- # 32 = DirectoryCompletion: add native directory matches
884
- if (( directive & 32 )); then
885
- _files -/
886
- fi
887
-
888
- # 2 = NoFileCompletion, 32 = DirectoryCompletion:
889
- # prevent fallback to default completers (e.g. file completion)
890
- if (( directive & 2 )) || (( directive & 32 )); then
891
- return 0
892
- fi
893
- }
894
-
895
- # Prevent _files -g from falling back to showing all files when no pattern matches
896
- zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'
897
-
898
- compdef _${programName} ${programName}
899
- `,
900
- shell: "zsh",
901
- installInstructions: `# To enable completions, add the following to your ~/.zshrc:
902
-
903
- # Option 1: Source directly (add before compinit)
904
- eval "$(${programName} completion zsh)"
905
-
906
- # Option 2: Save to a file in your fpath
907
- ${programName} completion zsh > ~/.zsh/completions/_${programName}
908
-
909
- # Make sure your fpath includes the completions directory:
910
- # fpath=(~/.zsh/completions $fpath)
911
- # autoload -Uz compinit && compinit
912
-
913
- # Then reload your shell or run:
914
- source ~/.zshrc`
915
- };
916
- }
917
-
918
- //#endregion
919
- //#region src/completion/extractor.ts
920
- /**
921
- * Extract completion data from commands
922
- */
923
- /**
924
- * Convert a resolved field to a completable option
925
- */
926
- function fieldToOption(field) {
927
- return {
928
- name: field.name,
929
- cliName: field.cliName,
930
- alias: field.alias,
931
- description: field.description,
932
- takesValue: field.type !== "boolean",
933
- valueType: field.type,
934
- required: field.required,
935
- valueCompletion: resolveValueCompletion(field)
936
- };
937
- }
938
- /**
939
- * Extract options from a command's args schema
940
- */
941
- function extractOptions(command) {
942
- if (!command.args) return [];
943
- return require_schema_extractor.extractFields(command.args).fields.filter((field) => !field.positional).map(fieldToOption);
944
- }
945
- /**
946
- * Extract positional arguments from a command
947
- */
948
- function extractPositionals(command) {
949
- if (!command.args) return [];
950
- return require_schema_extractor.extractFields(command.args).fields.filter((field) => field.positional);
951
- }
952
- /**
953
- * Extract completable positional arguments from a command
954
- */
955
- function extractCompletablePositionals(command) {
956
- if (!command.args) return [];
957
- return require_schema_extractor.extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
958
- name: field.name,
959
- cliName: field.cliName,
960
- position: index,
961
- description: field.description,
962
- required: field.required,
963
- valueCompletion: resolveValueCompletion(field)
964
- }));
965
- }
966
- /**
967
- * Extract a completable subcommand from a command
968
- */
969
- function extractSubcommand(name, command) {
970
- const subcommands = [];
971
- if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) if (typeof subCommand === "function") subcommands.push({
972
- name: subName,
973
- description: "(lazy loaded)",
974
- subcommands: [],
975
- options: [],
976
- positionals: []
977
- });
978
- else subcommands.push(extractSubcommand(subName, subCommand));
979
- return {
980
- name,
981
- description: command.description,
982
- subcommands,
983
- options: extractOptions(command),
984
- positionals: extractCompletablePositionals(command)
985
- };
986
- }
987
- /**
988
- * Extract completion data from a command tree
989
- */
990
- function extractCompletionData(command, programName) {
991
- const rootSubcommand = extractSubcommand(programName, command);
992
- return {
993
- command: rootSubcommand,
994
- programName,
995
- globalOptions: rootSubcommand.options
996
- };
997
- }
998
-
999
- //#endregion
1000
- Object.defineProperty(exports, 'CompletionDirective', {
1001
- enumerable: true,
1002
- get: function () {
1003
- return CompletionDirective;
1004
- }
1005
- });
1006
- Object.defineProperty(exports, 'createDynamicCompleteCommand', {
1007
- enumerable: true,
1008
- get: function () {
1009
- return createDynamicCompleteCommand;
1010
- }
1011
- });
1012
- Object.defineProperty(exports, 'defineCommand', {
1013
- enumerable: true,
1014
- get: function () {
1015
- return defineCommand;
1016
- }
1017
- });
1018
- Object.defineProperty(exports, 'extractCompletionData', {
1019
- enumerable: true,
1020
- get: function () {
1021
- return extractCompletionData;
1022
- }
1023
- });
1024
- Object.defineProperty(exports, 'extractPositionals', {
1025
- enumerable: true,
1026
- get: function () {
1027
- return extractPositionals;
1028
- }
1029
- });
1030
- Object.defineProperty(exports, 'formatForShell', {
1031
- enumerable: true,
1032
- get: function () {
1033
- return formatForShell;
1034
- }
1035
- });
1036
- Object.defineProperty(exports, 'generateBashCompletion', {
1037
- enumerable: true,
1038
- get: function () {
1039
- return generateBashCompletion;
1040
- }
1041
- });
1042
- Object.defineProperty(exports, 'generateCandidates', {
1043
- enumerable: true,
1044
- get: function () {
1045
- return generateCandidates;
1046
- }
1047
- });
1048
- Object.defineProperty(exports, 'generateFishCompletion', {
1049
- enumerable: true,
1050
- get: function () {
1051
- return generateFishCompletion;
1052
- }
1053
- });
1054
- Object.defineProperty(exports, 'generateZshCompletion', {
1055
- enumerable: true,
1056
- get: function () {
1057
- return generateZshCompletion;
1058
- }
1059
- });
1060
- Object.defineProperty(exports, 'hasCompleteCommand', {
1061
- enumerable: true,
1062
- get: function () {
1063
- return hasCompleteCommand;
1064
- }
1065
- });
1066
- Object.defineProperty(exports, 'parseCompletionContext', {
1067
- enumerable: true,
1068
- get: function () {
1069
- return parseCompletionContext;
1070
- }
1071
- });
1072
- Object.defineProperty(exports, 'resolveValueCompletion', {
1073
- enumerable: true,
1074
- get: function () {
1075
- return resolveValueCompletion;
1076
- }
1077
- });
1078
- //# sourceMappingURL=extractor-cjruDqQ2.cjs.map