politty 0.4.3 → 0.4.6

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 (60) hide show
  1. package/dist/{arg-registry-BUUhZ7JR.d.ts → arg-registry-2m40k1Et.d.ts} +1 -1
  2. package/dist/{arg-registry-BUUhZ7JR.d.ts.map → arg-registry-2m40k1Et.d.ts.map} +1 -1
  3. package/dist/augment.d.ts +1 -1
  4. package/dist/completion/index.cjs +16 -168
  5. package/dist/completion/index.d.cts +2 -77
  6. package/dist/completion/index.d.ts +2 -77
  7. package/dist/completion/index.js +3 -154
  8. package/dist/{zsh-CASZWn0o.cjs → completion-Df0eZ70u.cjs} +326 -37
  9. package/dist/completion-Df0eZ70u.cjs.map +1 -0
  10. package/dist/{zsh-hjvdI8uZ.js → completion-_AnQsWh9.js} +298 -27
  11. package/dist/completion-_AnQsWh9.js.map +1 -0
  12. package/dist/docs/index.cjs +276 -29
  13. package/dist/docs/index.cjs.map +1 -1
  14. package/dist/docs/index.d.cts +62 -7
  15. package/dist/docs/index.d.cts.map +1 -1
  16. package/dist/docs/index.d.ts +62 -7
  17. package/dist/docs/index.d.ts.map +1 -1
  18. package/dist/docs/index.js +271 -30
  19. package/dist/docs/index.js.map +1 -1
  20. package/dist/{value-completion-resolver-BQgHsX7b.d.cts → index-BZalbMeu.d.ts} +83 -4
  21. package/dist/index-BZalbMeu.d.ts.map +1 -0
  22. package/dist/{value-completion-resolver-C9LTGr0O.d.ts → index-C5-0RXiH.d.cts} +83 -4
  23. package/dist/index-C5-0RXiH.d.cts.map +1 -0
  24. package/dist/index.cjs +8 -7
  25. package/dist/index.d.cts +67 -13
  26. package/dist/index.d.cts.map +1 -1
  27. package/dist/index.d.ts +68 -14
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -5
  30. package/dist/{lazy-BEDnSR0m.cjs → lazy-DHlvJiQQ.cjs} +44 -8
  31. package/dist/lazy-DHlvJiQQ.cjs.map +1 -0
  32. package/dist/{lazy-BrEg8SgI.js → lazy-DSyfzR-F.js} +38 -8
  33. package/dist/lazy-DSyfzR-F.js.map +1 -0
  34. package/dist/{runner-C4fSHJMe.cjs → runner-C1Aah5c5.cjs} +365 -23
  35. package/dist/runner-C1Aah5c5.cjs.map +1 -0
  36. package/dist/{runner-D6k4BgB4.js → runner-DjG0uBxQ.js} +365 -23
  37. package/dist/runner-DjG0uBxQ.js.map +1 -0
  38. package/dist/{schema-extractor-n9288WJ6.d.ts → schema-extractor-C9APqeSL.d.ts} +30 -3
  39. package/dist/schema-extractor-C9APqeSL.d.ts.map +1 -0
  40. package/dist/{schema-extractor-DFaAZzaY.d.cts → schema-extractor-CVf0J4An.d.cts} +29 -2
  41. package/dist/schema-extractor-CVf0J4An.d.cts.map +1 -0
  42. package/dist/{subcommand-router-CAzBsLSI.js → subcommand-router-CKuy6D2b.js} +2 -2
  43. package/dist/{subcommand-router-CAzBsLSI.js.map → subcommand-router-CKuy6D2b.js.map} +1 -1
  44. package/dist/{subcommand-router-ZjNjFaUL.cjs → subcommand-router-sZHhUP7b.cjs} +2 -2
  45. package/dist/{subcommand-router-ZjNjFaUL.cjs.map → subcommand-router-sZHhUP7b.cjs.map} +1 -1
  46. package/package.json +9 -9
  47. package/dist/completion/index.cjs.map +0 -1
  48. package/dist/completion/index.d.cts.map +0 -1
  49. package/dist/completion/index.d.ts.map +0 -1
  50. package/dist/completion/index.js.map +0 -1
  51. package/dist/lazy-BEDnSR0m.cjs.map +0 -1
  52. package/dist/lazy-BrEg8SgI.js.map +0 -1
  53. package/dist/runner-C4fSHJMe.cjs.map +0 -1
  54. package/dist/runner-D6k4BgB4.js.map +0 -1
  55. package/dist/schema-extractor-DFaAZzaY.d.cts.map +0 -1
  56. package/dist/schema-extractor-n9288WJ6.d.ts.map +0 -1
  57. package/dist/value-completion-resolver-BQgHsX7b.d.cts.map +0 -1
  58. package/dist/value-completion-resolver-C9LTGr0O.d.ts.map +0 -1
  59. package/dist/zsh-CASZWn0o.cjs.map +0 -1
  60. package/dist/zsh-hjvdI8uZ.js.map +0 -1
@@ -1,5 +1,5 @@
1
- const require_subcommand_router = require('./subcommand-router-ZjNjFaUL.cjs');
2
- const require_lazy = require('./lazy-BEDnSR0m.cjs');
1
+ const require_subcommand_router = require('./subcommand-router-sZHhUP7b.cjs');
2
+ const require_lazy = require('./lazy-DHlvJiQQ.cjs');
3
3
  let zod = require("zod");
4
4
  let node_child_process = require("node:child_process");
5
5
 
@@ -17,6 +17,30 @@ function defineCommand(config) {
17
17
  examples: config.examples
18
18
  };
19
19
  }
20
+ /**
21
+ * Create a typed defineCommand factory with pre-bound global args type.
22
+ * This is the recommended pattern for type-safe global options.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // global-args.ts
27
+ * type GlobalArgsType = { verbose: boolean; config?: string };
28
+ * export const defineAppCommand = createDefineCommand<GlobalArgsType>();
29
+ *
30
+ * // commands/build.ts
31
+ * export const buildCommand = defineAppCommand({
32
+ * name: "build",
33
+ * args: z.object({ output: arg(z.string().default("dist")) }),
34
+ * run: (args) => {
35
+ * args.verbose; // typed via GlobalArgsType
36
+ * args.output; // typed via local args
37
+ * },
38
+ * });
39
+ * ```
40
+ */
41
+ function createDefineCommand() {
42
+ return defineCommand;
43
+ }
20
44
 
21
45
  //#endregion
22
46
  //#region src/completion/value-completion-resolver.ts
@@ -69,6 +93,13 @@ function resolveValueCompletion(field) {
69
93
  /**
70
94
  * Sanitize a name for use as a shell function/variable identifier.
71
95
  * Replaces any character that is not alphanumeric or underscore with underscore.
96
+ *
97
+ * Note: This is not injective -- distinct names may produce the same output
98
+ * (e.g., "foo-bar" and "foo_bar" both become "foo_bar"). When used for nested
99
+ * path encoding (`path.map(sanitize).join("_")`), cross-level collisions are
100
+ * theoretically possible (e.g., "foo-bar:baz" vs "foo:bar-baz") but extremely
101
+ * unlikely in real CLI designs. If collision-safety is needed, sanitize must be
102
+ * replaced with an injective encoding.
72
103
  */
73
104
  function sanitize(name) {
74
105
  return name.replace(/[^a-zA-Z0-9_]/g, "_");
@@ -148,29 +179,80 @@ function extractSubcommand(name, command) {
148
179
  positionals: extractCompletablePositionals(command)
149
180
  };
150
181
  }
182
+ /** Join parent and child with a separator, omitting separator when parent is empty. */
183
+ function joinPrefix(parent, child, sep) {
184
+ return parent ? `${parent}${sep}${child}` : child;
185
+ }
151
186
  /**
152
187
  * Collect opt-takes-value case entries for a subcommand tree.
153
- * Used by bash and zsh generators (identical case syntax: `subcmd:--opt) return 0 ;;`).
188
+ * Used by bash and zsh generators (identical case syntax: `path:--opt) return 0 ;;`).
189
+ * parentPath is a colon-delimited path (e.g., "" for root, "workspace:user" for nested).
154
190
  */
155
- function optTakesValueEntries(sub, subcmdName) {
191
+ function optTakesValueEntries(sub, parentPath) {
156
192
  const lines = [];
157
193
  for (const opt of sub.options) if (opt.takesValue) {
158
- const patterns = [`${subcmdName}:--${opt.cliName}`];
159
- if (opt.alias) patterns.push(`${subcmdName}:-${opt.alias}`);
194
+ const patterns = [`${parentPath}:--${opt.cliName}`];
195
+ if (opt.alias) patterns.push(`${parentPath}:-${opt.alias}`);
160
196
  lines.push(` ${patterns.join("|")}) return 0 ;;`);
161
197
  }
162
- for (const child of getVisibleSubs(sub.subcommands)) lines.push(...optTakesValueEntries(child, child.name));
198
+ for (const child of getVisibleSubs(sub.subcommands)) lines.push(...optTakesValueEntries(child, joinPrefix(parentPath, child.name, ":")));
163
199
  return lines;
164
200
  }
165
201
  /**
202
+ * Recursively collect all subcommand route entries.
203
+ * Returns entries used by all shell generators for both dispatch routing
204
+ * and subcommand lookup (is_subcmd) tables.
205
+ */
206
+ function collectRouteEntries(sub, parentPath = "", parentFunc = "") {
207
+ const entries = [];
208
+ for (const child of getVisibleSubs(sub.subcommands)) {
209
+ const pathStr = joinPrefix(parentPath, child.name, ":");
210
+ const funcSuffix = joinPrefix(parentFunc, sanitize(child.name), "_");
211
+ entries.push(...collectRouteEntries(child, pathStr, funcSuffix));
212
+ entries.push({
213
+ pathStr,
214
+ funcSuffix,
215
+ lookupPattern: `${parentPath}:${child.name}`
216
+ });
217
+ }
218
+ return entries;
219
+ }
220
+ /**
221
+ * Generate is_subcmd case/switch body lines (bash/zsh case syntax).
222
+ * Returns lines for the case statement body only (caller wraps in function).
223
+ */
224
+ function isSubcmdCaseLines(routeEntries) {
225
+ return routeEntries.map((r) => ` ${r.lookupPattern}) return 0 ;;`);
226
+ }
227
+ /**
228
+ * Recursively merge global options into a subcommand and all its descendants.
229
+ * Avoids duplicates by checking existing option names.
230
+ */
231
+ function propagateGlobalOptions(sub, globalOptions) {
232
+ const existingNames = new Set(sub.options.map((o) => o.name));
233
+ const newOpts = globalOptions.filter((o) => !existingNames.has(o.name));
234
+ sub.options = [...sub.options, ...newOpts];
235
+ for (const child of sub.subcommands) propagateGlobalOptions(child, globalOptions);
236
+ }
237
+ /**
166
238
  * Extract completion data from a command tree
239
+ *
240
+ * @param command - The root command
241
+ * @param programName - Program name for completion scripts
242
+ * @param globalArgsSchema - Optional global args schema. When provided, global options
243
+ * are derived from this schema instead of the root command's options.
167
244
  */
168
- function extractCompletionData(command, programName) {
245
+ function extractCompletionData(command, programName, globalArgsSchema) {
169
246
  const rootSubcommand = extractSubcommand(programName, command);
247
+ let globalOptions;
248
+ if (globalArgsSchema) {
249
+ globalOptions = require_lazy.extractFields(globalArgsSchema).fields.filter((field) => !field.positional).map(fieldToOption);
250
+ propagateGlobalOptions(rootSubcommand, globalOptions);
251
+ } else globalOptions = rootSubcommand.options;
170
252
  return {
171
253
  command: rootSubcommand,
172
254
  programName,
173
- globalOptions: rootSubcommand.options
255
+ globalOptions
174
256
  };
175
257
  }
176
258
 
@@ -318,8 +400,9 @@ function generateSubHandler$2(sub, fn, path) {
318
400
  for (const child of visibleSubs) lines.push(...generateSubHandler$2(child, fn, fullPath));
319
401
  lines.push(`${funcName}() {`);
320
402
  lines.push(...valueCompletionBlocks(sub.options));
321
- lines.push(` if [[ -z "$_inline_prefix" ]] && __${fn}_opt_takes_value "${sub.name}" "$_prev"; then return; fi`);
322
- lines.push(` if [[ -n "$_inline_prefix" ]] && __${fn}_opt_takes_value "${sub.name}" "\${_inline_prefix%=}"; then return; fi`);
403
+ const fullPathStr = fullPath.join(":");
404
+ lines.push(` if [[ -z "$_inline_prefix" ]] && __${fn}_opt_takes_value "${fullPathStr}" "$_prev"; then return; fi`);
405
+ lines.push(` if [[ -n "$_inline_prefix" ]] && __${fn}_opt_takes_value "${fullPathStr}" "\${_inline_prefix%=}"; then return; fi`);
323
406
  if (sub.positionals.length > 0) {
324
407
  lines.push(` if (( _after_dd )); then`);
325
408
  lines.push(...positionalBlock$2(sub.positionals).map((l) => ` ${l}`));
@@ -344,7 +427,7 @@ function generateSubHandler$2(sub, fn, path) {
344
427
  }
345
428
  function generateBashCompletion(command, options) {
346
429
  const { programName } = options;
347
- const data = extractCompletionData(command, programName);
430
+ const data = extractCompletionData(command, programName, options.globalArgsSchema);
348
431
  const fn = sanitize(programName);
349
432
  const root = data.command;
350
433
  const visibleSubs = getVisibleSubs(root.subcommands);
@@ -368,6 +451,16 @@ function generateBashCompletion(command, options) {
368
451
  lines.push(` return 1`);
369
452
  lines.push(`}`);
370
453
  lines.push(``);
454
+ const routeEntries = collectRouteEntries(root);
455
+ if (routeEntries.length > 0) {
456
+ lines.push(`__${fn}_is_subcmd() {`);
457
+ lines.push(` case "$1:$2" in`);
458
+ lines.push(...isSubcmdCaseLines(routeEntries));
459
+ lines.push(` esac`);
460
+ lines.push(` return 1`);
461
+ lines.push(`}`);
462
+ lines.push(``);
463
+ }
371
464
  for (const sub of visibleSubs) lines.push(...generateSubHandler$2(sub, fn, []));
372
465
  lines.push(`__${fn}_complete_root() {`);
373
466
  lines.push(...valueCompletionBlocks(root.options));
@@ -396,7 +489,7 @@ function generateBashCompletion(command, options) {
396
489
  lines.push(` fi`);
397
490
  lines.push(`}`);
398
491
  lines.push(``);
399
- const subRouting = visibleSubs.map((s) => ` ${s.name}) __${fn}_complete_${sanitize(s.name)} ;;`).join("\n");
492
+ const subRouting = routeEntries.map((r) => ` ${r.pathStr}) __${fn}_complete_${r.funcSuffix} ;;`).join("\n");
400
493
  lines.push(`_${fn}_completions() {`);
401
494
  lines.push(` COMPREPLY=()`);
402
495
  lines.push(``);
@@ -440,7 +533,7 @@ function generateBashCompletion(command, options) {
440
533
  lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w" && _skip_next=1`);
441
534
  lines.push(` (( _j++ )); continue`);
442
535
  lines.push(` fi`);
443
- if (visibleSubs.length > 0) lines.push(` if [[ -z "$_subcmd" ]]; then _subcmd="$_w"; _used_opts=(); else (( _pos_count++ )); fi`);
536
+ if (routeEntries.length > 0) lines.push(` if __${fn}_is_subcmd "$_subcmd" "$_w"; then _subcmd="\${_subcmd:+\${_subcmd}:}$_w"; _used_opts=(); _pos_count=0; else (( _pos_count++ )); fi`);
444
537
  else lines.push(` (( _pos_count++ ))`);
445
538
  lines.push(` (( _j++ ))`);
446
539
  lines.push(` done`);
@@ -1113,7 +1206,8 @@ function generateSubHandler$1(sub, fn, path) {
1113
1206
  for (const child of visibleSubs) lines.push(...generateSubHandler$1(child, fn, fullPath));
1114
1207
  lines.push(`function ${funcName} --no-scope-shadowing`);
1115
1208
  lines.push(...valueCompletionBlock$1(sub.options));
1116
- lines.push(` if __${fn}_opt_takes_value "${sub.name}" "$_prev"; return; end`);
1209
+ const fullPathStr = fullPath.join(":");
1210
+ lines.push(` if __${fn}_opt_takes_value "${fullPathStr}" "$_prev"; return; end`);
1117
1211
  if (sub.positionals.length > 0) {
1118
1212
  lines.push(` if test $_after_dd -eq 1`);
1119
1213
  lines.push(...positionalBlock$1(sub.positionals).map((l) => ` ${l}`));
@@ -1134,20 +1228,23 @@ function generateSubHandler$1(sub, fn, path) {
1134
1228
  return lines;
1135
1229
  }
1136
1230
  /** Generate opt-takes-value entries for fish switch cases */
1137
- function optTakesValueCases(sub, subcmdName) {
1231
+ function optTakesValueCases(sub, parentPath) {
1138
1232
  const lines = [];
1139
1233
  for (const opt of sub.options) if (opt.takesValue) {
1140
- const patterns = [`"${subcmdName}:--${opt.cliName}"`];
1141
- if (opt.alias) patterns.push(`"${subcmdName}:-${opt.alias}"`);
1234
+ const patterns = [`"${parentPath}:--${opt.cliName}"`];
1235
+ if (opt.alias) patterns.push(`"${parentPath}:-${opt.alias}"`);
1142
1236
  lines.push(` case ${patterns.join(" ")}`);
1143
1237
  lines.push(` return 0`);
1144
1238
  }
1145
- for (const child of getVisibleSubs(sub.subcommands)) lines.push(...optTakesValueCases(child, child.name));
1239
+ for (const child of getVisibleSubs(sub.subcommands)) {
1240
+ const childPath = parentPath ? `${parentPath}:${child.name}` : child.name;
1241
+ lines.push(...optTakesValueCases(child, childPath));
1242
+ }
1146
1243
  return lines;
1147
1244
  }
1148
1245
  function generateFishCompletion(command, options) {
1149
1246
  const { programName } = options;
1150
- const data = extractCompletionData(command, programName);
1247
+ const data = extractCompletionData(command, programName, options.globalArgsSchema);
1151
1248
  const fn = sanitize(programName);
1152
1249
  const root = data.command;
1153
1250
  const visibleSubs = getVisibleSubs(root.subcommands);
@@ -1171,6 +1268,19 @@ function generateFishCompletion(command, options) {
1171
1268
  lines.push(` return 1`);
1172
1269
  lines.push(`end`);
1173
1270
  lines.push(``);
1271
+ const routeEntries = collectRouteEntries(root);
1272
+ if (routeEntries.length > 0) {
1273
+ lines.push(`function __${fn}_is_subcmd`);
1274
+ lines.push(` switch "$argv[1]:$argv[2]"`);
1275
+ for (const r of routeEntries) {
1276
+ lines.push(` case "${r.lookupPattern}"`);
1277
+ lines.push(` return 0`);
1278
+ }
1279
+ lines.push(` end`);
1280
+ lines.push(` return 1`);
1281
+ lines.push(`end`);
1282
+ lines.push(``);
1283
+ }
1174
1284
  for (const sub of visibleSubs) lines.push(...generateSubHandler$1(sub, fn, []));
1175
1285
  lines.push(`function __${fn}_complete_root --no-scope-shadowing`);
1176
1286
  lines.push(...valueCompletionBlock$1(root.options));
@@ -1233,13 +1343,13 @@ function generateFishCompletion(command, options) {
1233
1343
  lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w"; and set _skip_next 1`);
1234
1344
  lines.push(` set _j (math $_j + 1); continue`);
1235
1345
  lines.push(` end`);
1236
- 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`);
1346
+ if (routeEntries.length > 0) lines.push(` if __${fn}_is_subcmd "$_subcmd" "$_w"; test -n "$_subcmd"; and set _subcmd "$_subcmd:$_w"; or set _subcmd "$_w"; set _used_opts; set _pos_count 0; else; set _pos_count (math $_pos_count + 1); end`);
1237
1347
  else lines.push(` set _pos_count (math $_pos_count + 1)`);
1238
1348
  lines.push(` set _j (math $_j + 1)`);
1239
1349
  lines.push(` end`);
1240
1350
  lines.push(``);
1241
1351
  lines.push(` switch "$_subcmd"`);
1242
- for (const s of visibleSubs) lines.push(` case "${s.name}"; __${fn}_complete_${sanitize(s.name)}`);
1352
+ for (const r of routeEntries) lines.push(` case "${r.pathStr}"; __${fn}_complete_${r.funcSuffix}`);
1243
1353
  lines.push(` case '*'; __${fn}_complete_root`);
1244
1354
  lines.push(` end`);
1245
1355
  lines.push(`end`);
@@ -1358,7 +1468,8 @@ function generateSubHandler(sub, fn, path) {
1358
1468
  lines.push(`${funcName}() {`);
1359
1469
  lines.push(` local -a _vals=()`);
1360
1470
  lines.push(...valueCompletionBlock(sub.options, fn));
1361
- lines.push(` if __${fn}_opt_takes_value "${sub.name}" "\${words[CURRENT-1]}"; then return 0; fi`);
1471
+ const fullPathStr = fullPath.join(":");
1472
+ lines.push(` if __${fn}_opt_takes_value "${fullPathStr}" "\${words[CURRENT-1]}"; then return 0; fi`);
1362
1473
  if (sub.positionals.length > 0) {
1363
1474
  lines.push(` if (( _after_dd )); then`);
1364
1475
  lines.push(...positionalBlock(sub.positionals, fn).map((l) => ` ${l}`));
@@ -1385,7 +1496,7 @@ function generateSubHandler(sub, fn, path) {
1385
1496
  }
1386
1497
  function generateZshCompletion(command, options) {
1387
1498
  const { programName } = options;
1388
- const data = extractCompletionData(command, programName);
1499
+ const data = extractCompletionData(command, programName, options.globalArgsSchema);
1389
1500
  const fn = sanitize(programName);
1390
1501
  const root = data.command;
1391
1502
  const visibleSubs = getVisibleSubs(root.subcommands);
@@ -1420,6 +1531,16 @@ function generateZshCompletion(command, options) {
1420
1531
  lines.push(` return 1`);
1421
1532
  lines.push(`}`);
1422
1533
  lines.push(``);
1534
+ const routeEntries = collectRouteEntries(root);
1535
+ if (routeEntries.length > 0) {
1536
+ lines.push(`__${fn}_is_subcmd() {`);
1537
+ lines.push(` case "$1:$2" in`);
1538
+ lines.push(...isSubcmdCaseLines(routeEntries));
1539
+ lines.push(` esac`);
1540
+ lines.push(` return 1`);
1541
+ lines.push(`}`);
1542
+ lines.push(``);
1543
+ }
1423
1544
  for (const sub of visibleSubs) lines.push(...generateSubHandler(sub, fn, []));
1424
1545
  lines.push(`__${fn}_complete_root() {`);
1425
1546
  lines.push(` local -a _vals=()`);
@@ -1450,7 +1571,7 @@ function generateZshCompletion(command, options) {
1450
1571
  lines.push(` fi`);
1451
1572
  lines.push(`}`);
1452
1573
  lines.push(``);
1453
- const subRouting = visibleSubs.map((s) => ` ${s.name}) __${fn}_complete_${sanitize(s.name)} ;;`).join("\n");
1574
+ const subRouting = routeEntries.map((r) => ` ${r.pathStr}) __${fn}_complete_${r.funcSuffix} ;;`).join("\n");
1454
1575
  lines.push(`_${fn}() {`);
1455
1576
  lines.push(` (( CURRENT )) || CURRENT=\${#words}`);
1456
1577
  lines.push(``);
@@ -1469,7 +1590,7 @@ function generateZshCompletion(command, options) {
1469
1590
  lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w" && _skip_next=1`);
1470
1591
  lines.push(` (( _j++ )); continue`);
1471
1592
  lines.push(` fi`);
1472
- if (visibleSubs.length > 0) lines.push(` if [[ -z "$_subcmd" ]]; then _subcmd="$_w"; _used_opts=(); else (( _pos_count++ )); fi`);
1593
+ if (routeEntries.length > 0) lines.push(` if __${fn}_is_subcmd "$_subcmd" "$_w"; then _subcmd="\${_subcmd:+\${_subcmd}:}$_w"; _used_opts=(); _pos_count=0; else (( _pos_count++ )); fi`);
1473
1594
  else lines.push(` (( _pos_count++ ))`);
1474
1595
  lines.push(` (( _j++ ))`);
1475
1596
  lines.push(` done`);
@@ -1504,6 +1625,156 @@ source ~/.zshrc`
1504
1625
  };
1505
1626
  }
1506
1627
 
1628
+ //#endregion
1629
+ //#region src/completion/index.ts
1630
+ /**
1631
+ * Shell completion generation module
1632
+ *
1633
+ * Provides utilities to generate shell completion scripts for bash, zsh, and fish.
1634
+ *
1635
+ * @example
1636
+ * ```typescript
1637
+ * import { generateCompletion, createCompletionCommand } from "politty/completion";
1638
+ *
1639
+ * // Generate completion script directly
1640
+ * const result = generateCompletion(myCommand, {
1641
+ * shell: "bash",
1642
+ * programName: "mycli"
1643
+ * });
1644
+ * console.log(result.script);
1645
+ *
1646
+ * // Or add a completion subcommand to your CLI
1647
+ * const mainCommand = withCompletionCommand(
1648
+ * defineCommand({
1649
+ * name: "mycli",
1650
+ * subCommands: { ... },
1651
+ * }),
1652
+ * );
1653
+ * ```
1654
+ */
1655
+ /**
1656
+ * Generate completion script for the specified shell
1657
+ */
1658
+ function generateCompletion(command, options) {
1659
+ switch (options.shell) {
1660
+ case "bash": return generateBashCompletion(command, options);
1661
+ case "zsh": return generateZshCompletion(command, options);
1662
+ case "fish": return generateFishCompletion(command, options);
1663
+ default: throw new Error(`Unsupported shell: ${options.shell}`);
1664
+ }
1665
+ }
1666
+ /**
1667
+ * Get the list of supported shells
1668
+ */
1669
+ function getSupportedShells() {
1670
+ return [
1671
+ "bash",
1672
+ "zsh",
1673
+ "fish"
1674
+ ];
1675
+ }
1676
+ /**
1677
+ * Detect the current shell from environment
1678
+ */
1679
+ function detectShell() {
1680
+ const shellName = (process.env.SHELL || "").split("/").pop()?.toLowerCase() || "";
1681
+ if (shellName.includes("bash")) return "bash";
1682
+ if (shellName.includes("zsh")) return "zsh";
1683
+ if (shellName.includes("fish")) return "fish";
1684
+ return null;
1685
+ }
1686
+ /**
1687
+ * Schema for the completion command arguments
1688
+ */
1689
+ const completionArgsSchema = zod.z.object({
1690
+ shell: require_lazy.arg(zod.z.enum([
1691
+ "bash",
1692
+ "zsh",
1693
+ "fish"
1694
+ ]).optional().describe("Shell type (auto-detected if not specified)"), {
1695
+ positional: true,
1696
+ description: "Shell type (bash, zsh, or fish)",
1697
+ placeholder: "SHELL"
1698
+ }),
1699
+ instructions: require_lazy.arg(zod.z.boolean().default(false), {
1700
+ alias: "i",
1701
+ description: "Show installation instructions"
1702
+ })
1703
+ });
1704
+ /**
1705
+ * Create a completion subcommand for your CLI
1706
+ *
1707
+ * This creates a ready-to-use subcommand that generates completion scripts.
1708
+ *
1709
+ * @example
1710
+ * ```typescript
1711
+ * const mainCommand = defineCommand({
1712
+ * name: "mycli",
1713
+ * subCommands: {
1714
+ * completion: createCompletionCommand(mainCommand)
1715
+ * }
1716
+ * });
1717
+ * ```
1718
+ */
1719
+ function createCompletionCommand(rootCommand, programName, globalArgsSchema) {
1720
+ const resolvedProgramName = programName ?? rootCommand.name;
1721
+ if (!rootCommand.subCommands?.__complete) rootCommand.subCommands = {
1722
+ ...rootCommand.subCommands,
1723
+ __complete: createDynamicCompleteCommand(rootCommand, resolvedProgramName)
1724
+ };
1725
+ return defineCommand({
1726
+ name: "completion",
1727
+ description: "Generate shell completion script",
1728
+ args: completionArgsSchema,
1729
+ run(args) {
1730
+ const shellType = args.shell || detectShell();
1731
+ if (!shellType) {
1732
+ console.error("Could not detect shell type. Please specify one of: bash, zsh, fish");
1733
+ process.exitCode = 1;
1734
+ return;
1735
+ }
1736
+ const result = generateCompletion(rootCommand, {
1737
+ shell: shellType,
1738
+ programName: resolvedProgramName,
1739
+ includeDescriptions: true,
1740
+ ...globalArgsSchema !== void 0 && { globalArgsSchema }
1741
+ });
1742
+ if (args.instructions) console.log(result.installInstructions);
1743
+ else console.log(result.script);
1744
+ }
1745
+ });
1746
+ }
1747
+ /**
1748
+ * Wrap a command with a completion subcommand
1749
+ *
1750
+ * This avoids circular references that occur when a command references itself
1751
+ * in its subCommands (e.g., for completion generation).
1752
+ *
1753
+ * @param command - The command to wrap
1754
+ * @param options - Options including programName
1755
+ * @returns A new command with the completion subcommand added
1756
+ *
1757
+ * @example
1758
+ * ```typescript
1759
+ * const mainCommand = withCompletionCommand(
1760
+ * defineCommand({
1761
+ * name: "mycli",
1762
+ * subCommands: { ... },
1763
+ * }),
1764
+ * );
1765
+ * ```
1766
+ */
1767
+ function withCompletionCommand(command, options) {
1768
+ const { programName, globalArgsSchema } = typeof options === "string" ? { programName: options } : options ?? {};
1769
+ const wrappedCommand = { ...command };
1770
+ wrappedCommand.subCommands = {
1771
+ ...command.subCommands,
1772
+ completion: createCompletionCommand(wrappedCommand, programName, globalArgsSchema),
1773
+ __complete: createDynamicCompleteCommand(wrappedCommand, programName)
1774
+ };
1775
+ return wrappedCommand;
1776
+ }
1777
+
1507
1778
  //#endregion
1508
1779
  Object.defineProperty(exports, 'CompletionDirective', {
1509
1780
  enumerable: true,
@@ -1511,6 +1782,18 @@ Object.defineProperty(exports, 'CompletionDirective', {
1511
1782
  return CompletionDirective;
1512
1783
  }
1513
1784
  });
1785
+ Object.defineProperty(exports, 'createCompletionCommand', {
1786
+ enumerable: true,
1787
+ get: function () {
1788
+ return createCompletionCommand;
1789
+ }
1790
+ });
1791
+ Object.defineProperty(exports, 'createDefineCommand', {
1792
+ enumerable: true,
1793
+ get: function () {
1794
+ return createDefineCommand;
1795
+ }
1796
+ });
1514
1797
  Object.defineProperty(exports, 'createDynamicCompleteCommand', {
1515
1798
  enumerable: true,
1516
1799
  get: function () {
@@ -1523,6 +1806,12 @@ Object.defineProperty(exports, 'defineCommand', {
1523
1806
  return defineCommand;
1524
1807
  }
1525
1808
  });
1809
+ Object.defineProperty(exports, 'detectShell', {
1810
+ enumerable: true,
1811
+ get: function () {
1812
+ return detectShell;
1813
+ }
1814
+ });
1526
1815
  Object.defineProperty(exports, 'extractCompletionData', {
1527
1816
  enumerable: true,
1528
1817
  get: function () {
@@ -1541,28 +1830,22 @@ Object.defineProperty(exports, 'formatForShell', {
1541
1830
  return formatForShell;
1542
1831
  }
1543
1832
  });
1544
- Object.defineProperty(exports, 'generateBashCompletion', {
1545
- enumerable: true,
1546
- get: function () {
1547
- return generateBashCompletion;
1548
- }
1549
- });
1550
1833
  Object.defineProperty(exports, 'generateCandidates', {
1551
1834
  enumerable: true,
1552
1835
  get: function () {
1553
1836
  return generateCandidates;
1554
1837
  }
1555
1838
  });
1556
- Object.defineProperty(exports, 'generateFishCompletion', {
1839
+ Object.defineProperty(exports, 'generateCompletion', {
1557
1840
  enumerable: true,
1558
1841
  get: function () {
1559
- return generateFishCompletion;
1842
+ return generateCompletion;
1560
1843
  }
1561
1844
  });
1562
- Object.defineProperty(exports, 'generateZshCompletion', {
1845
+ Object.defineProperty(exports, 'getSupportedShells', {
1563
1846
  enumerable: true,
1564
1847
  get: function () {
1565
- return generateZshCompletion;
1848
+ return getSupportedShells;
1566
1849
  }
1567
1850
  });
1568
1851
  Object.defineProperty(exports, 'hasCompleteCommand', {
@@ -1583,4 +1866,10 @@ Object.defineProperty(exports, 'resolveValueCompletion', {
1583
1866
  return resolveValueCompletion;
1584
1867
  }
1585
1868
  });
1586
- //# sourceMappingURL=zsh-CASZWn0o.cjs.map
1869
+ Object.defineProperty(exports, 'withCompletionCommand', {
1870
+ enumerable: true,
1871
+ get: function () {
1872
+ return withCompletionCommand;
1873
+ }
1874
+ });
1875
+ //# sourceMappingURL=completion-Df0eZ70u.cjs.map