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