politty 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +47 -0
  2. package/dist/{arg-registry-6E0WHOh_.d.ts → arg-registry-DDJpsUea.d.cts} +47 -23
  3. package/dist/arg-registry-DDJpsUea.d.cts.map +1 -0
  4. package/dist/{arg-registry--NRaNFJM.d.cts → arg-registry-DDJpsUea.d.ts} +47 -23
  5. package/dist/arg-registry-DDJpsUea.d.ts.map +1 -0
  6. package/dist/augment.d.cts +1 -1
  7. package/dist/augment.d.ts +1 -1
  8. package/dist/cli.cjs +54 -0
  9. package/dist/cli.cjs.map +1 -0
  10. package/dist/cli.d.cts +1 -0
  11. package/dist/cli.d.ts +1 -0
  12. package/dist/cli.js +55 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/completion/index.cjs +6 -1
  15. package/dist/completion/index.d.cts +3 -3
  16. package/dist/completion/index.d.ts +3 -3
  17. package/dist/completion/index.js +2 -2
  18. package/dist/{completion-BFOAOg95.cjs → completion-CLHO3Xaz.cjs} +1812 -337
  19. package/dist/completion-CLHO3Xaz.cjs.map +1 -0
  20. package/dist/{completion-K5LGh1hO.js → completion-DHnVx9Zk.js} +1786 -340
  21. package/dist/completion-DHnVx9Zk.js.map +1 -0
  22. package/dist/docs/index.cjs +3 -3
  23. package/dist/docs/index.cjs.map +1 -1
  24. package/dist/docs/index.d.cts +1 -1
  25. package/dist/docs/index.d.cts.map +1 -1
  26. package/dist/docs/index.d.ts +1 -1
  27. package/dist/docs/index.d.ts.map +1 -1
  28. package/dist/docs/index.js +3 -3
  29. package/dist/docs/index.js.map +1 -1
  30. package/dist/{index-Cg8qstsT.d.cts → index-DKGn3lIl.d.ts} +119 -4
  31. package/dist/index-DKGn3lIl.d.ts.map +1 -0
  32. package/dist/{index-O3yn97Ed.d.ts → index-WyViqW59.d.cts} +119 -4
  33. package/dist/index-WyViqW59.d.cts.map +1 -0
  34. package/dist/index.cjs +4 -3
  35. package/dist/index.d.cts +3 -3
  36. package/dist/index.d.cts.map +1 -1
  37. package/dist/index.d.ts +3 -3
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +4 -4
  40. package/dist/{log-collector-Cu6MCtAx.js → log-collector-DK32-73m.js} +1 -1
  41. package/dist/{log-collector-Cu6MCtAx.js.map → log-collector-DK32-73m.js.map} +1 -1
  42. package/dist/{log-collector-Cd2_mv87.cjs → log-collector-DUqC427m.cjs} +1 -1
  43. package/dist/{log-collector-Cd2_mv87.cjs.map → log-collector-DUqC427m.cjs.map} +1 -1
  44. package/dist/prompt/clack/index.cjs +1 -2
  45. package/dist/prompt/clack/index.cjs.map +1 -1
  46. package/dist/prompt/clack/index.d.cts +1 -1
  47. package/dist/prompt/clack/index.d.ts +1 -1
  48. package/dist/prompt/clack/index.js +1 -1
  49. package/dist/prompt/index.cjs +1 -1
  50. package/dist/prompt/index.d.cts +1 -1
  51. package/dist/prompt/index.d.cts.map +1 -1
  52. package/dist/prompt/index.d.ts +1 -1
  53. package/dist/prompt/index.d.ts.map +1 -1
  54. package/dist/prompt/index.js +1 -1
  55. package/dist/prompt/inquirer/index.cjs +1 -2
  56. package/dist/prompt/inquirer/index.cjs.map +1 -1
  57. package/dist/prompt/inquirer/index.d.cts +1 -1
  58. package/dist/prompt/inquirer/index.d.ts +1 -1
  59. package/dist/prompt/inquirer/index.js +1 -1
  60. package/dist/{prompt-aXfSf27y.cjs → prompt-Bs9e-Em3.cjs} +1 -1
  61. package/dist/{prompt-aXfSf27y.cjs.map → prompt-Bs9e-Em3.cjs.map} +1 -1
  62. package/dist/{prompt-BKHqGrFw.js → prompt-Cc8Tfmdv.js} +1 -1
  63. package/dist/{prompt-BKHqGrFw.js.map → prompt-Cc8Tfmdv.js.map} +1 -1
  64. package/dist/{runner-BmSEiD9A.js → runner-D43SkHt5.js} +121 -20
  65. package/dist/runner-D43SkHt5.js.map +1 -0
  66. package/dist/{runner-CRZ_7Y9i.cjs → runner-DvFvokV6.cjs} +121 -20
  67. package/dist/runner-DvFvokV6.cjs.map +1 -0
  68. package/dist/{schema-extractor-SLPgBNgZ.cjs → schema-extractor-BxSRwLrx.cjs} +1 -2
  69. package/dist/schema-extractor-BxSRwLrx.cjs.map +1 -0
  70. package/dist/{schema-extractor-C50R-1re.js → schema-extractor-Dqe7_kyQ.js} +1 -1
  71. package/dist/schema-extractor-Dqe7_kyQ.js.map +1 -0
  72. package/package.json +15 -12
  73. package/dist/arg-registry--NRaNFJM.d.cts.map +0 -1
  74. package/dist/arg-registry-6E0WHOh_.d.ts.map +0 -1
  75. package/dist/completion-BFOAOg95.cjs.map +0 -1
  76. package/dist/completion-K5LGh1hO.js.map +0 -1
  77. package/dist/index-Cg8qstsT.d.cts.map +0 -1
  78. package/dist/index-O3yn97Ed.d.ts.map +0 -1
  79. package/dist/runner-BmSEiD9A.js.map +0 -1
  80. package/dist/runner-CRZ_7Y9i.cjs.map +0 -1
  81. package/dist/schema-extractor-C50R-1re.js.map +0 -1
  82. package/dist/schema-extractor-SLPgBNgZ.cjs.map +0 -1
@@ -1,9 +1,9 @@
1
- const require_log_collector = require('./log-collector-Cd2_mv87.cjs');
2
- const require_schema_extractor = require('./schema-extractor-SLPgBNgZ.cjs');
1
+ const require_schema_extractor = require('./schema-extractor-BxSRwLrx.cjs');
3
2
  let zod = require("zod");
4
3
  let node_child_process = require("node:child_process");
5
4
  let node_fs = require("node:fs");
6
5
  let node_path = require("node:path");
6
+ let node_util = require("node:util");
7
7
 
8
8
  //#region src/core/command.ts
9
9
  function defineCommand(config) {
@@ -133,6 +133,33 @@ function ansiC(s) {
133
133
  return out;
134
134
  }
135
135
  /**
136
+ * Single-quote escape for a POSIX shell literal: `'` -> `'\''`. Inside single
137
+ * quotes the shell performs no expansion at all, so `$`, backticks, and
138
+ * `$(...)` stay inert. Used for hardcoded paths that may originate from
139
+ * env/config so path metachars never execute as commands when the generated
140
+ * snippet is sourced. Safe for bash, zsh, and fish.
141
+ */
142
+ function shSingleQuote(s) {
143
+ return `'${s.replace(/'/g, "'\\''")}'`;
144
+ }
145
+ /**
146
+ * Shell sub-expression that prints a file's stat signature, trying GNU
147
+ * `stat -c` first (BSD `-f` is filesystem mode there) then falling back to
148
+ * BSD `stat -f`. `-L` follows symlinks so the shell-side mtime matches Node's
149
+ * `fs.statSync`. `posix` (bash/zsh) wraps it as `$(… || …)`; fish has no
150
+ * `$(…)` capture inside `(…)`, so it uses `(…; or …)`. With `withSize`, the
151
+ * signature is `mtime:size` so a worker cache rewritten within the same
152
+ * second still reads as stale; otherwise it is the bare mtime in whole
153
+ * seconds.
154
+ */
155
+ function statSigExpr(fileVar, opts) {
156
+ const gnuFmt = opts.withSize ? "%Y:%s" : "%Y";
157
+ const bsdFmt = opts.withSize ? "%m:%z" : "%m";
158
+ const gnu = `stat -L -c '${gnuFmt}' "${fileVar}" 2>/dev/null`;
159
+ const bsd = `stat -L -f '${bsdFmt}' "${fileVar}" 2>/dev/null`;
160
+ return opts.shell === "fish" ? `(${gnu}; or ${bsd})` : `$(${gnu} || ${bsd})`;
161
+ }
162
+ /**
136
163
  * Render an alias as its CLI token form: single-char aliases become `-x`,
137
164
  * multi-char aliases become `--long`. Mirrors the parser's accepted shapes
138
165
  * and is the bare-token form (no quoting) used inside generated case
@@ -294,13 +321,19 @@ function resolveValueCompletion(field) {
294
321
  * Parse completion context from partial command line
295
322
  */
296
323
  /**
297
- * The dynamic completion path runs `__complete` at TAB time and never sees
298
- * "expand" fields (those are handled inline by the static shell script).
299
- * Strip the transient pending sentinel here so the rest of the runtime path
300
- * can stay strict about handling only resolved `ValueCompletion` values.
324
+ * `completion.custom.expand` is baked into static scripts, but dispatcher
325
+ * scripts call `__complete` at TAB time. Convert the pending sentinel into a
326
+ * runtime form that can call `enumerate` with the dependency values already
327
+ * typed on the command line.
301
328
  */
302
- function stripPendingExpand(vc) {
303
- return vc?.type === "pending-expand" ? void 0 : vc;
329
+ function resolveRuntimeCompletion(vc) {
330
+ if (!vc) return void 0;
331
+ if (vc.type !== "pending-expand") return vc;
332
+ return {
333
+ type: "runtime-expand",
334
+ dependsOn: vc.spec.dependsOn,
335
+ enumerate: vc.spec.enumerate
336
+ };
304
337
  }
305
338
  /**
306
339
  * Extract options from a command
@@ -323,7 +356,7 @@ function extractOptionsFromSchema(schema) {
323
356
  valueType: field.type,
324
357
  required: field.required,
325
358
  defaultNegationAccepted: field.type === "boolean" && (field.negation === void 0 || field.negation === true),
326
- valueCompletion: stripPendingExpand(resolveValueCompletion(field))
359
+ valueCompletion: resolveRuntimeCompletion(resolveValueCompletion(field))
327
360
  };
328
361
  });
329
362
  }
@@ -454,7 +487,7 @@ function extractPositionalsForContext(command) {
454
487
  description: field.description,
455
488
  required: field.required,
456
489
  variadic: field.type === "array",
457
- valueCompletion: stripPendingExpand(resolveValueCompletion(field))
490
+ valueCompletion: resolveRuntimeCompletion(resolveValueCompletion(field))
458
491
  }));
459
492
  }
460
493
  /**
@@ -882,11 +915,15 @@ async function generateCandidates(context, options) {
882
915
  return generateValueCandidates(inlinePrefix ? {
883
916
  ...context,
884
917
  currentWord: context.currentWord.slice(inlinePrefix.length)
885
- } : context, options, opt?.name, opt?.valueCompletion);
918
+ } : context, options, opt?.name, opt?.valueCompletion, void 0, opt?.valueType === "array");
886
919
  }
887
920
  case "positional": {
888
921
  const positional = resolvePositionalTarget(context);
889
- return generateValueCandidates(context, options, positional?.name, positional?.valueCompletion, positional?.description);
922
+ if (!positional) return {
923
+ candidates: [],
924
+ directive: CompletionDirective.NoFileCompletion
925
+ };
926
+ return generateValueCandidates(context, options, positional.name, positional.valueCompletion, positional.description, false);
890
927
  }
891
928
  }
892
929
  }
@@ -921,6 +958,16 @@ function executeShellCommand(command) {
921
958
  return [];
922
959
  }
923
960
  }
961
+ function staticChoices(vc) {
962
+ return vc?.type === "choices" ? vc.choices : void 0;
963
+ }
964
+ function runtimeExpandDepChoices(context, dep) {
965
+ const localOption = context.options.find((opt) => opt.name === dep && opt.isGlobal !== true);
966
+ if (localOption) return staticChoices(localOption.valueCompletion);
967
+ const positional = context.positionals.find((pos) => pos.name === dep);
968
+ if (positional) return staticChoices(positional.valueCompletion);
969
+ return staticChoices(context.options.find((opt) => opt.name === dep && opt.isGlobal === true)?.valueCompletion);
970
+ }
924
971
  /**
925
972
  * Two-stage `key=value` post-processing. Returns the transformed candidate
926
973
  * list plus whether it contains a bare `key=` entry so the caller can flip
@@ -969,7 +1016,7 @@ function dropBareKeyEcho(candidates, currentWord) {
969
1016
  /**
970
1017
  * Resolve value completion, executing shell commands and file lookups in JS
971
1018
  */
972
- async function resolveValueCandidates(vc, ctx, description) {
1019
+ async function resolveValueCandidates(vc, ctx, completionContext, description, dedupeKeyValues) {
973
1020
  const candidates = [];
974
1021
  let directive = CompletionDirective.FilterPrefix;
975
1022
  let fileExtensions;
@@ -1026,9 +1073,40 @@ async function resolveValueCandidates(vc, ctx, description) {
1026
1073
  case "expand":
1027
1074
  directive |= CompletionDirective.NoFileCompletion;
1028
1075
  break;
1076
+ case "runtime-expand": {
1077
+ const deps = {};
1078
+ let missingDep = false;
1079
+ for (const dep of vc.dependsOn) {
1080
+ const raw = ctx.parsedArgs[dep];
1081
+ const value = Array.isArray(raw) ? raw[raw.length - 1] : raw;
1082
+ const allowedValues = runtimeExpandDepChoices(completionContext, dep);
1083
+ if (typeof value !== "string" || allowedValues === void 0 || !allowedValues.includes(value)) {
1084
+ missingDep = true;
1085
+ break;
1086
+ }
1087
+ deps[dep] = value;
1088
+ }
1089
+ if (!missingDep) try {
1090
+ const seen = /* @__PURE__ */ new Set();
1091
+ for (const item of vc.enumerate(deps)) {
1092
+ const normalized = typeof item === "string" ? { value: item } : item;
1093
+ if (seen.has(normalized.value)) continue;
1094
+ seen.add(normalized.value);
1095
+ candidates.push({
1096
+ ...normalized,
1097
+ type: "value"
1098
+ });
1099
+ }
1100
+ } catch {
1101
+ directive = CompletionDirective.NoFileCompletion | CompletionDirective.Error;
1102
+ break;
1103
+ }
1104
+ directive |= CompletionDirective.NoFileCompletion;
1105
+ break;
1106
+ }
1029
1107
  }
1030
- if (vc.type === "dynamic" || vc.type === "expand") {
1031
- const processed = applyKeyValuePostProcessing(candidates, ctx.currentWord);
1108
+ if (vc.type === "dynamic" || vc.type === "expand" || vc.type === "runtime-expand") {
1109
+ const processed = applyKeyValuePostProcessing(vc.type === "runtime-expand" && dedupeKeyValues === true ? dropAlreadyUsedKeyCandidates(candidates, ctx.previousValues) : candidates, ctx.currentWord);
1032
1110
  if (processed.hasEqSuffix) directive |= CompletionDirective.NoSpace;
1033
1111
  return {
1034
1112
  candidates: processed.candidates,
@@ -1044,6 +1122,19 @@ async function resolveValueCandidates(vc, ctx, description) {
1044
1122
  fileMatchers
1045
1123
  };
1046
1124
  }
1125
+ function dropAlreadyUsedKeyCandidates(candidates, previousValues) {
1126
+ const used = /* @__PURE__ */ new Set();
1127
+ for (const value of previousValues) {
1128
+ const eqIdx = value.indexOf("=");
1129
+ if (eqIdx > 0) used.add(value.slice(0, eqIdx));
1130
+ }
1131
+ if (used.size === 0) return [...candidates];
1132
+ return candidates.filter((candidate) => {
1133
+ const eqIdx = candidate.value.indexOf("=");
1134
+ if (eqIdx <= 0) return true;
1135
+ return !used.has(candidate.value.slice(0, eqIdx));
1136
+ });
1137
+ }
1047
1138
  /**
1048
1139
  * Generate subcommand candidates
1049
1140
  */
@@ -1067,7 +1158,7 @@ function generateSubcommandCandidates(context) {
1067
1158
  }
1068
1159
  return {
1069
1160
  candidates,
1070
- directive: CompletionDirective.FilterPrefix
1161
+ directive: CompletionDirective.FilterPrefix | CompletionDirective.NoFileCompletion
1071
1162
  };
1072
1163
  }
1073
1164
  /**
@@ -1101,7 +1192,7 @@ function generateOptionNameCandidates(context) {
1101
1192
  });
1102
1193
  return {
1103
1194
  candidates,
1104
- directive: CompletionDirective.FilterPrefix
1195
+ directive: CompletionDirective.FilterPrefix | CompletionDirective.NoFileCompletion
1105
1196
  };
1106
1197
  }
1107
1198
  /**
@@ -1137,12 +1228,12 @@ function parsedArgsWithoutTarget(parsedArgs, key) {
1137
1228
  * is propagated to choices candidates (positional path supplies it; option
1138
1229
  * path does not, mirroring the prior split implementations).
1139
1230
  */
1140
- async function generateValueCandidates(context, options, targetFieldName, vc, description) {
1231
+ async function generateValueCandidates(context, options, targetFieldName, vc, description, dedupeKeyValues) {
1141
1232
  if (!vc) return {
1142
1233
  candidates: [],
1143
1234
  directive: CompletionDirective.FilterPrefix
1144
1235
  };
1145
- return resolveValueCandidates(vc, resolverContext(context, options, targetFieldName), description);
1236
+ return resolveValueCandidates(vc, resolverContext(context, options, targetFieldName), context, description, dedupeKeyValues);
1146
1237
  }
1147
1238
 
1148
1239
  //#endregion
@@ -1153,10 +1244,10 @@ async function generateValueCandidates(context, options, targetFieldName, vc, de
1153
1244
  * sentinel via `resolveValueCompletion`) and passes them here once siblings
1154
1245
  * are known.
1155
1246
  */
1156
- function resolveExpandTargets(sub, targets, globalOptions = []) {
1247
+ function resolveExpandTargets(sub, targets, globalOptions = [], validateOnly = false) {
1157
1248
  if (targets.length === 0) return;
1158
1249
  const siblingIndex = buildSiblingIndex(sub, globalOptions);
1159
- for (const target of targets) target.set(resolveOne(target, siblingIndex));
1250
+ for (const target of targets) target.set(resolveOne(target, siblingIndex, validateOnly));
1160
1251
  }
1161
1252
  /**
1162
1253
  * Build a name → static-values map for siblings, using each field's already
@@ -1183,7 +1274,7 @@ function buildSiblingIndex(sub, globalOptions) {
1183
1274
  visit(globalOptions, true);
1184
1275
  return index;
1185
1276
  }
1186
- function resolveOne(target, siblings) {
1277
+ function resolveOne(target, siblings, validateOnly = false) {
1187
1278
  const { spec } = target;
1188
1279
  const deps = spec.dependsOn;
1189
1280
  if (deps.length === 0) throw new Error(`Field "${target.describe}": completion.custom.expand.dependsOn must list at least one sibling arg.`);
@@ -1194,6 +1285,11 @@ function resolveOne(target, siblings) {
1194
1285
  if (!values) throw new Error(`Field "${target.describe}": completion.custom.expand.dependsOn references "${dep}", which is not a sibling arg with a static \`choices\`/enum schema on the same command. Chaining expand specs is not supported.`);
1195
1286
  valueLists.push([...values]);
1196
1287
  }
1288
+ if (validateOnly) return {
1289
+ type: "expand",
1290
+ dependsOn: deps,
1291
+ table: []
1292
+ };
1197
1293
  const table = [];
1198
1294
  for (const combo of cartesian(valueLists)) {
1199
1295
  const depsRecord = {};
@@ -1404,11 +1500,11 @@ function fieldsToPositionals(fields, pending) {
1404
1500
  /**
1405
1501
  * Extract a completable subcommand from a command
1406
1502
  */
1407
- function extractSubcommand(name, command, globalOptions = []) {
1503
+ function extractSubcommand(name, command, globalOptions = [], validateOnly = false) {
1408
1504
  const subcommands = [];
1409
1505
  if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) {
1410
1506
  const resolved = require_schema_extractor.resolveSubCommandMeta(subCommand);
1411
- if (resolved) subcommands.push(extractSubcommand(subName, resolved, globalOptions));
1507
+ if (resolved) subcommands.push(extractSubcommand(subName, resolved, globalOptions, validateOnly));
1412
1508
  else subcommands.push({
1413
1509
  name: subName,
1414
1510
  description: "(lazy loaded)",
@@ -1427,7 +1523,7 @@ function extractSubcommand(name, command, globalOptions = []) {
1427
1523
  options: fieldsToOptions(fields, pending),
1428
1524
  positionals: fieldsToPositionals(fields, pending)
1429
1525
  };
1430
- resolveExpandTargets(node, pending, globalOptions);
1526
+ resolveExpandTargets(node, pending, globalOptions, validateOnly);
1431
1527
  return node;
1432
1528
  }
1433
1529
  /** Join parent and child with a separator, omitting separator when parent is empty. */
@@ -1826,7 +1922,7 @@ function propagateGlobalOptions(sub, globalOptions) {
1826
1922
  * @param globalArgsSchema - Optional global args schema. When provided, global options
1827
1923
  * are derived from this schema instead of the root command's options.
1828
1924
  */
1829
- function extractCompletionData(command, programName, globalArgsSchema) {
1925
+ function extractCompletionData(command, programName, globalArgsSchema, validateOnly = false) {
1830
1926
  let globalOptions = [];
1831
1927
  if (globalArgsSchema) {
1832
1928
  const globalPending = [];
@@ -1839,9 +1935,9 @@ function extractCompletionData(command, programName, globalArgsSchema) {
1839
1935
  subcommands: [],
1840
1936
  options: globalOptions,
1841
1937
  positionals: []
1842
- }, globalPending);
1938
+ }, globalPending, [], validateOnly);
1843
1939
  }
1844
- const rootSubcommand = extractSubcommand(programName, command, globalOptions);
1940
+ const rootSubcommand = extractSubcommand(programName, command, globalOptions, validateOnly);
1845
1941
  if (globalArgsSchema) propagateGlobalOptions(rootSubcommand, globalOptions);
1846
1942
  else globalOptions = rootSubcommand.options;
1847
1943
  return {
@@ -1858,8 +1954,9 @@ function extractCompletionData(command, programName, globalArgsSchema) {
1858
1954
  *
1859
1955
  * Every completion script generated by politty starts with a small
1860
1956
  * machine-readable header. The rc loader and the runMain background
1861
- * refresh path use the `# politty-bin-sig:` line to detect when the
1862
- * cached script is stale relative to the binary on disk.
1957
+ * refresh path use the `# politty-bin-sig:` and `# politty-bin-path:`
1958
+ * lines to detect when the cached script is stale relative to the
1959
+ * binary on disk.
1863
1960
  */
1864
1961
  /** Schema version of the header itself. Bump when the header layout changes. */
1865
1962
  const COMPLETION_VERSION = 1;
@@ -1875,6 +1972,9 @@ function computeBinSig(binPath) {
1875
1972
  return "0";
1876
1973
  }
1877
1974
  }
1975
+ function headerValue(value) {
1976
+ return value.replace(/\r?\n/g, " ");
1977
+ }
1878
1978
  /**
1879
1979
  * Walk `$PATH` looking for an executable named `programName`. Returns
1880
1980
  * the first match's full path, or `null` when not found. We mirror the
@@ -1905,6 +2005,8 @@ function findOnPath(programName) {
1905
2005
  */
1906
2006
  function resolveBinPath(programName, override) {
1907
2007
  if (override) return override;
2008
+ const envOverride = process.env[binEnvVarName(sanitize(programName))];
2009
+ if (envOverride) return envOverride;
1908
2010
  return findOnPath(programName) ?? process.argv[1] ?? "";
1909
2011
  }
1910
2012
  /**
@@ -1913,10 +2015,12 @@ function resolveBinPath(programName, override) {
1913
2015
  * marker.
1914
2016
  */
1915
2017
  function buildHeaderLines(opts) {
1916
- const sig = computeBinSig(resolveBinPath(opts.programName, opts.binPath));
2018
+ const binPath = resolveBinPath(opts.programName, opts.binPath);
2019
+ const sig = computeBinSig(binPath);
1917
2020
  const lines = [
1918
2021
  `# politty-completion-version: ${1}`,
1919
2022
  `# politty-bin-sig: ${sig}`,
2023
+ `# politty-bin-path: ${headerValue(binPath)}`,
1920
2024
  `# program: ${opts.programName}`
1921
2025
  ];
1922
2026
  if (opts.programVersion) lines.push(`# program-version: ${opts.programVersion}`);
@@ -1935,13 +2039,13 @@ function buildHeaderLines(opts) {
1935
2039
  * file in place, then sources the fresh file and stops executing the
1936
2040
  * stale body.
1937
2041
  */
1938
- function statSigExpr() {
1939
- return `$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null)`;
1940
- }
1941
2042
  function generateBashSelfRefresh(opts) {
1942
2043
  const { programName, binPath } = opts;
1943
2044
  const fn = sanitize(programName);
1944
- const sig = computeBinSig(resolveBinPath(programName, binPath));
2045
+ const envName = binEnvVarName(fn);
2046
+ const resolvedBinPath = resolveBinPath(programName, binPath);
2047
+ const sig = computeBinSig(resolvedBinPath);
2048
+ const quotedBinPath = shSingleQuote(resolvedBinPath);
1945
2049
  const refreshFn = `__${fn}_self_refresh`;
1946
2050
  return [
1947
2051
  `${refreshFn}() {`,
@@ -1949,14 +2053,15 @@ function generateBashSelfRefresh(opts) {
1949
2053
  ` _self=\${BASH_SOURCE[0]:-}`,
1950
2054
  ` [[ -n "$_self" && -f "$_self" ]] || return 1`,
1951
2055
  ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
1952
- ` head -n 8 "$_self" 2>/dev/null | grep -qF "# program: ${programName}" || return 1`,
1953
- ` head -n 8 "$_self" 2>/dev/null | grep -qF "# shell: bash" || return 1`,
1954
- ` _bin=$(type -P ${programName} 2>/dev/null)`,
2056
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# program: ${programName}" || return 1`,
2057
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# shell: bash" || return 1`,
2058
+ ` _bin="\${${envName}:-$(type -P ${programName} 2>/dev/null)}"`,
1955
2059
  ` [[ -n "$_bin" ]] || return 1`,
1956
- ` _sig=${statSigExpr()} || return 1`,
1957
- ` [[ "$_sig" != "${sig}" ]] || return 1`,
1958
- ` "$_bin" __refresh-completion bash "$_self" 2>/dev/null || return 1`,
1959
- ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-bin-sig: $_sig" || return 1`,
2060
+ ` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`,
2061
+ ` [[ "$_sig" != "${sig}" || "$_bin" != ${quotedBinPath} ]] || return 1`,
2062
+ ` "$_bin" __refresh-completion bash "$_self" --static 2>/dev/null || return 1`,
2063
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-sig: $_sig" || return 1`,
2064
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-path: $_bin" || return 1`,
1960
2065
  ` source "$_self" 2>/dev/null || return 1`,
1961
2066
  ` return 0`,
1962
2067
  `}`,
@@ -1972,8 +2077,11 @@ function generateBashSelfRefresh(opts) {
1972
2077
  function generateZshSelfRefresh(opts) {
1973
2078
  const { programName, binPath } = opts;
1974
2079
  const fn = sanitize(programName);
2080
+ const envName = binEnvVarName(fn);
1975
2081
  const completionFn = `_${programName}`;
1976
- const sig = computeBinSig(resolveBinPath(programName, binPath));
2082
+ const resolvedBinPath = resolveBinPath(programName, binPath);
2083
+ const sig = computeBinSig(resolvedBinPath);
2084
+ const quotedBinPath = shSingleQuote(resolvedBinPath);
1977
2085
  const refreshFn = `__${fn}_self_refresh`;
1978
2086
  return [
1979
2087
  `${refreshFn}() {`,
@@ -1983,14 +2091,15 @@ function generateZshSelfRefresh(opts) {
1983
2091
  ` _self="\${(%):-%x}"`,
1984
2092
  ` [[ -n "$_self" && -f "$_self" ]] || return 1`,
1985
2093
  ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
1986
- ` head -n 8 "$_self" 2>/dev/null | grep -qF "# program: ${programName}" || return 1`,
1987
- ` head -n 8 "$_self" 2>/dev/null | grep -qF "# shell: zsh" || return 1`,
1988
- ` _bin=$(whence -p ${programName} 2>/dev/null)`,
2094
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# program: ${programName}" || return 1`,
2095
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# shell: zsh" || return 1`,
2096
+ ` _bin="\${${envName}:-$(whence -p ${programName} 2>/dev/null)}"`,
1989
2097
  ` [[ -n "$_bin" ]] || return 1`,
1990
- ` _sig=${statSigExpr()} || return 1`,
1991
- ` [[ "$_sig" != "${sig}" ]] || return 1`,
1992
- ` "$_bin" __refresh-completion zsh "$_self" 2>/dev/null || return 1`,
1993
- ` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-bin-sig: $_sig" || return 1`,
2098
+ ` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`,
2099
+ ` [[ "$_sig" != "${sig}" || "$_bin" != ${quotedBinPath} ]] || return 1`,
2100
+ ` "$_bin" __refresh-completion zsh "$_self" --static 2>/dev/null || return 1`,
2101
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-sig: $_sig" || return 1`,
2102
+ ` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-path: $_bin" || return 1`,
1994
2103
  ` source "$_self" 2>/dev/null || return 1`,
1995
2104
  ` ${completionFn} "$@"`,
1996
2105
  ` return 0`,
@@ -2106,6 +2215,7 @@ function bashValueLines(vc, inline, fn, location) {
2106
2215
  case "directory": return [`COMPREPLY=($(compgen -P "$_inline_prefix" -d -- "$_cur"))`, `compopt -o filenames`];
2107
2216
  case "command": return [`COMPREPLY=($(compgen -P "$_inline_prefix" -W "$(${vc.shellCommand})" -- "$_cur"))`];
2108
2217
  case "none": return [`compopt +o default 2>/dev/null`];
2218
+ case "runtime-expand": return [];
2109
2219
  }
2110
2220
  }
2111
2221
  /**
@@ -2168,6 +2278,28 @@ function positionalBlock$2(positionals, fn, funcSuffix, options = []) {
2168
2278
  lines.push(` esac`);
2169
2279
  return lines;
2170
2280
  }
2281
+ /**
2282
+ * Subcommand-name completion. When the same node also has positionals, emit a
2283
+ * runtime check that completes subcommand names while the cursor still prefixes
2284
+ * one and falls through to positional completion otherwise. Returns lines at
2285
+ * base indentation; callers re-indent for their handler depth.
2286
+ */
2287
+ function subOrPositionalLines$2(subNames, positionals, fn, funcSuffix, options) {
2288
+ const subReply = [`COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`, `compopt +o default 2>/dev/null`];
2289
+ if (positionals.length === 0) return subReply;
2290
+ return [
2291
+ `local -a _sub_names=(${subNames})`,
2292
+ `local _sub_name _sub_match=0`,
2293
+ `for _sub_name in "\${_sub_names[@]}"; do`,
2294
+ ` [[ "$_sub_name" == "$_cur"* ]] && _sub_match=1 && break`,
2295
+ `done`,
2296
+ `if (( _sub_match )); then`,
2297
+ ...subReply.map((l) => ` ${l}`),
2298
+ `else`,
2299
+ ...positionalBlock$2(positionals, fn, funcSuffix, options),
2300
+ `fi`
2301
+ ];
2302
+ }
2171
2303
  /** Generate prev/inline value completion blocks for options */
2172
2304
  function valueCompletionBlocks(options, positionals, fn, funcSuffix) {
2173
2305
  if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
@@ -2243,8 +2375,7 @@ function generateSubHandler$2(sub, fn, path) {
2243
2375
  lines.push(` fi`);
2244
2376
  if (visibleSubs.length > 0) {
2245
2377
  const subNames = getSubNamesWithAliases(sub.subcommands).map((s) => s.name).join(" ");
2246
- lines.push(` COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`);
2247
- lines.push(` compopt +o default 2>/dev/null`);
2378
+ lines.push(...subOrPositionalLines$2(subNames, sub.positionals, fn, funcSuffix, sub.options).map((l) => ` ${l}`));
2248
2379
  } else if (sub.positionals.length > 0) lines.push(...positionalBlock$2(sub.positionals, fn, funcSuffix, sub.options));
2249
2380
  lines.push(`}`);
2250
2381
  lines.push(``);
@@ -2253,7 +2384,9 @@ function generateSubHandler$2(sub, fn, path) {
2253
2384
  function generateBashCompletion(command, options) {
2254
2385
  const { programName } = options;
2255
2386
  const data = extractCompletionData(command, programName, options.globalArgsSchema);
2256
- const fn = sanitize(programName);
2387
+ const baseFn = sanitize(programName);
2388
+ const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
2389
+ const isWorker = options.staticWorker !== void 0;
2257
2390
  const root = data.command;
2258
2391
  const visibleSubs = getVisibleSubs(root.subcommands);
2259
2392
  const expandSpecs = collectExpandSpecs(root);
@@ -2265,9 +2398,11 @@ function generateBashCompletion(command, options) {
2265
2398
  binPath: options.binPath,
2266
2399
  programVersion: options.programVersion
2267
2400
  }));
2401
+ lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
2402
+ if (isWorker) lines.push(`# politty-completion-worker: true`);
2268
2403
  lines.push(`# Generated by politty`);
2269
2404
  lines.push(``);
2270
- lines.push(...generateBashSelfRefresh({
2405
+ if (!isWorker) lines.push(...generateBashSelfRefresh({
2271
2406
  programName,
2272
2407
  binPath: options.binPath
2273
2408
  }));
@@ -2411,8 +2546,7 @@ function generateBashCompletion(command, options) {
2411
2546
  if (visibleSubs.length > 0) {
2412
2547
  lines.push(` else`);
2413
2548
  const subNames = getSubNamesWithAliases(root.subcommands).map((s) => s.name).join(" ");
2414
- lines.push(` COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`);
2415
- lines.push(` compopt +o default 2>/dev/null`);
2549
+ lines.push(...subOrPositionalLines$2(subNames, root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
2416
2550
  } else if (root.positionals.length > 0) {
2417
2551
  lines.push(` else`);
2418
2552
  lines.push(...positionalBlock$2(root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
@@ -2510,17 +2644,17 @@ function generateBashCompletion(command, options) {
2510
2644
  lines.push(` esac`);
2511
2645
  lines.push(`}`);
2512
2646
  lines.push(``);
2513
- lines.push(`complete -o default -F _${fn}_completions ${programName}`);
2647
+ if (!isWorker) lines.push(`complete -o default -F _${fn}_completions ${programName}`);
2514
2648
  lines.push(``);
2515
2649
  return {
2516
2650
  script: lines.join("\n"),
2517
2651
  shell: "bash",
2518
2652
  installInstructions: `# To enable auto-refreshing bash completions, add this to your ~/.bashrc:
2519
- eval "$(${programName} completion bash)"
2653
+ eval "$(${programName} completion bash --static)"
2520
2654
 
2521
2655
  # For faster shell startup, save the script instead:
2522
2656
  mkdir -p ~/.local/share/bash-completion/completions
2523
- ${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
2657
+ ${programName} completion bash --static > ~/.local/share/bash-completion/completions/${programName}
2524
2658
 
2525
2659
  # Then reload your shell or run:
2526
2660
  source ~/.bashrc`
@@ -2528,195 +2662,1330 @@ source ~/.bashrc`
2528
2662
  }
2529
2663
 
2530
2664
  //#endregion
2531
- //#region src/completion/dynamic/shell-formatter.ts
2532
- /**
2533
- * Format completion candidates for the specified shell
2534
- *
2535
- * @returns Shell-ready output string (lines separated by newline, last line is :directive)
2536
- */
2537
- function formatForShell(result, options) {
2538
- switch (options.shell) {
2539
- case "bash": return formatForBash(result, options);
2540
- case "zsh": return formatForZsh(result, options);
2541
- case "fish": return formatForFish(result, options);
2665
+ //#region src/completion/bundled-worker.ts
2666
+ const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
2667
+ const SHELL_EXT = {
2668
+ bash: "bash",
2669
+ zsh: "zsh",
2670
+ fish: "fish"
2671
+ };
2672
+ const REQUIRED_WORKER_HEADERS = [
2673
+ "# politty-completion-version: 1",
2674
+ "# politty-completion-mode: worker",
2675
+ "# politty-completion-worker: true"
2676
+ ];
2677
+ function bundledWorkerShellExtension(shell) {
2678
+ return SHELL_EXT[shell];
2679
+ }
2680
+ function defaultBundledWorkerOutputPath(shell) {
2681
+ return (0, node_path.join)("dist", "completion", `${shell}-worker.${bundledWorkerShellExtension(shell)}`);
2682
+ }
2683
+ function defaultBundledWorkerRelativePaths(shell) {
2684
+ const ext = SHELL_EXT[shell];
2685
+ return [
2686
+ `completion/${shell}-worker.${ext}`,
2687
+ `../completion/${shell}-worker.${ext}`,
2688
+ `dist/completion/${shell}-worker.${ext}`,
2689
+ `../dist/completion/${shell}-worker.${ext}`,
2690
+ `completion-worker.${shell}`,
2691
+ `../completion-worker.${shell}`
2692
+ ];
2693
+ }
2694
+ function bundledWorkerRelativePaths(programName, shell, options) {
2695
+ if (options?.disabled) return [];
2696
+ const configured = options?.relativePaths?.[shell];
2697
+ return (configured && configured.length > 0 ? configured : defaultBundledWorkerRelativePaths(shell)).map((p) => p.replaceAll("{shell}", shell).replaceAll("{ext}", SHELL_EXT[shell]).replaceAll("{program}", programName));
2698
+ }
2699
+ function readCmdShimTarget(path) {
2700
+ try {
2701
+ const content = (0, node_fs.readFileSync)(path, "utf8");
2702
+ let target = null;
2703
+ for (const line of content.split("\n")) {
2704
+ const match = line.match(/^# cmd-shim-target=(.+)$/);
2705
+ if (match) target = match[1];
2706
+ }
2707
+ return target;
2708
+ } catch {
2709
+ return null;
2542
2710
  }
2543
2711
  }
2544
- /**
2545
- * Append extension metadata and directive to output lines
2546
- */
2547
- function appendMetadata(lines, result) {
2548
- if (result.fileExtensions && result.fileExtensions.length > 0) lines.push(`@ext:${result.fileExtensions.join(",")}`);
2549
- if (result.fileMatchers && result.fileMatchers.length > 0) lines.push(`@matcher:${result.fileMatchers.join(",")}`);
2550
- lines.push(`:${result.directive}`);
2712
+ function addBaseDirs(out, path) {
2713
+ if (!path) return;
2714
+ out.add((0, node_path.dirname)((0, node_path.resolve)(path)));
2715
+ try {
2716
+ out.add((0, node_path.dirname)((0, node_fs.realpathSync)(path)));
2717
+ } catch {}
2718
+ const shimTarget = readCmdShimTarget(path);
2719
+ if (shimTarget) addBaseDirs(out, shimTarget);
2551
2720
  }
2552
- /**
2553
- * Format for bash
2554
- *
2555
- * - Pre-filters candidates by currentWord prefix (replaces compgen -W)
2556
- * - Handles --opt=value inline values by prepending prefix
2557
- * - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
2558
- * - Last line: :directive
2559
- */
2560
- function formatForBash(result, options) {
2561
- const lines = ((result.directive & CompletionDirective.FilterPrefix) !== 0 && options.currentWord ? result.candidates.filter((c) => c.value.startsWith(options.currentWord)) : result.candidates).map((c) => options.inlinePrefix ? `${options.inlinePrefix}${c.value}` : c.value);
2562
- appendMetadata(lines, result);
2563
- return lines.join("\n");
2721
+ function workerHead(path) {
2722
+ return (0, node_fs.readFileSync)(path, "utf8").split("\n", 24).join("\n");
2564
2723
  }
2565
- /**
2566
- * Format for zsh
2567
- *
2568
- * - Outputs value:description pairs for _describe
2569
- * - Colons in values/descriptions are escaped with backslash
2570
- * - Last line: :directive
2571
- */
2572
- function formatForZsh(result, _options) {
2573
- const lines = result.candidates.map((c) => {
2574
- const escapedValue = c.value.replace(/:/g, "\\:");
2575
- if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
2576
- return escapedValue;
2577
- });
2578
- appendMetadata(lines, result);
2579
- return lines.join("\n");
2724
+ function requiredBundledWorkerHeaders(programName, shell) {
2725
+ return [
2726
+ ...REQUIRED_WORKER_HEADERS,
2727
+ `# program: ${programName}`,
2728
+ `# shell: ${shell}`
2729
+ ];
2580
2730
  }
2581
- /**
2582
- * Format for fish
2583
- *
2584
- * - Outputs value\tdescription pairs
2585
- * - Last line: :directive
2586
- */
2587
- function formatForFish(result, _options) {
2588
- const lines = result.candidates.map((c) => {
2589
- if (c.description) return `${c.value}\t${c.description}`;
2590
- return c.value;
2731
+ function missingBundledWorkerHeaders(head, programName, shell) {
2732
+ const lines = head.split("\n").map((line) => line.trimEnd());
2733
+ return requiredBundledWorkerHeaders(programName, shell).filter((header) => !lines.includes(header));
2734
+ }
2735
+ function validateBundledWorkerFile(path, programName, shell) {
2736
+ if (!(0, node_fs.existsSync)(path)) throw new Error(`Bundled completion worker does not exist: ${path}`);
2737
+ const missing = missingBundledWorkerHeaders(workerHead(path), programName, shell);
2738
+ if (missing.length > 0) throw new Error(`Invalid bundled completion worker ${path}: missing ${missing.map((h) => JSON.stringify(h)).join(", ")}`);
2739
+ }
2740
+ function isBundledWorkerFile(path, programName, shell) {
2741
+ try {
2742
+ validateBundledWorkerFile(path, programName, shell);
2743
+ return true;
2744
+ } catch {
2745
+ return false;
2746
+ }
2747
+ }
2748
+ function resolveBundledWorkerPath(opts) {
2749
+ const rels = bundledWorkerRelativePaths(opts.programName, opts.shell, opts.bundledWorker);
2750
+ if (rels.length === 0) return null;
2751
+ const bases = /* @__PURE__ */ new Set();
2752
+ if (opts.binPath !== void 0) addBaseDirs(bases, opts.binPath);
2753
+ if (process.argv[1]) addBaseDirs(bases, process.argv[1]);
2754
+ addBaseDirs(bases, resolveBinPath(opts.programName, opts.binPath));
2755
+ for (const rel of rels) if ((0, node_path.isAbsolute)(rel) && isBundledWorkerFile(rel, opts.programName, opts.shell)) return rel;
2756
+ for (const base of bases) for (const rel of rels) {
2757
+ if ((0, node_path.isAbsolute)(rel)) continue;
2758
+ const candidate = (0, node_path.join)(base, rel);
2759
+ if (isBundledWorkerFile(candidate, opts.programName, opts.shell)) return candidate;
2760
+ }
2761
+ return null;
2762
+ }
2763
+ function resolvePathFromCwd(path, cwd) {
2764
+ return (0, node_path.isAbsolute)(path) ? path : (0, node_path.resolve)(cwd, path);
2765
+ }
2766
+ function executableCommand(bin, args, cwd) {
2767
+ const binPath = bin.startsWith(".") || bin.includes("/") || bin.includes("\\") ? resolvePathFromCwd(bin, cwd) : bin;
2768
+ const ext = (0, node_path.extname)(binPath).toLowerCase();
2769
+ if (ext === ".js" || ext === ".mjs" || ext === ".cjs") return {
2770
+ command: process.execPath,
2771
+ args: [binPath, ...args]
2772
+ };
2773
+ return {
2774
+ command: binPath,
2775
+ args: [...args]
2776
+ };
2777
+ }
2778
+ async function runTargetBin(bin, args, opts) {
2779
+ const command = executableCommand(bin, args, opts.cwd);
2780
+ try {
2781
+ const { stdout, stderr } = await execFileAsync(command.command, command.args, {
2782
+ cwd: opts.cwd,
2783
+ env: {
2784
+ ...process.env,
2785
+ ...opts.env
2786
+ },
2787
+ encoding: "utf8",
2788
+ maxBuffer: 10 * 1024 * 1024
2789
+ });
2790
+ return {
2791
+ stdout,
2792
+ stderr
2793
+ };
2794
+ } catch (error) {
2795
+ const stderr = typeof error === "object" && error !== null && "stderr" in error ? String(error.stderr ?? "").trim() : "";
2796
+ const detail = stderr ? `\n${stderr}` : "";
2797
+ throw new Error(`Command failed: ${command.command} ${command.args.join(" ")}${detail}`, error instanceof Error ? { cause: error } : void 0);
2798
+ }
2799
+ }
2800
+ function assertNonEmptyFile(path) {
2801
+ const stat = (0, node_fs.statSync)(path);
2802
+ if (!stat.isFile() || stat.size === 0) throw new Error(`Generated bundled completion worker is empty: ${path}`);
2803
+ return stat.size;
2804
+ }
2805
+ function formatSize(size) {
2806
+ if (size < 1024) return `${size} B`;
2807
+ return `${(size / 1024).toFixed(1)} KiB`;
2808
+ }
2809
+ function printSuccess(path, size, cwd) {
2810
+ const rel = (0, node_path.relative)(cwd, path);
2811
+ console.log(`Generated bundled completion worker: ${rel && !rel.startsWith("..") ? rel : path} (${formatSize(size)})`);
2812
+ }
2813
+ async function generateBundledCompletionWorker(options) {
2814
+ const cwd = options.cwd ?? process.cwd();
2815
+ const outputPath = resolvePathFromCwd(options.outputPath ?? defaultBundledWorkerOutputPath(options.shell), cwd);
2816
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(outputPath), { recursive: true });
2817
+ (0, node_fs.rmSync)(outputPath, { force: true });
2818
+ await runTargetBin(options.bin, [
2819
+ "__refresh-completion",
2820
+ options.shell,
2821
+ outputPath,
2822
+ "--static",
2823
+ "--worker"
2824
+ ], {
2825
+ cwd,
2826
+ env: options.env
2591
2827
  });
2592
- appendMetadata(lines, result);
2593
- return lines.join("\n");
2828
+ const size = assertNonEmptyFile(outputPath);
2829
+ validateBundledWorkerFile(outputPath, options.programName, options.shell);
2830
+ let reportedPath;
2831
+ if (options.verify) {
2832
+ const lines = (await runTargetBin(options.bin, ["__completion-worker-path", options.shell], {
2833
+ cwd,
2834
+ env: options.env
2835
+ })).stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2836
+ if (lines.length !== 1) throw new Error(`Expected __completion-worker-path ${options.shell} to print exactly one path, got ${lines.length}.`);
2837
+ reportedPath = resolvePathFromCwd(lines[0], cwd);
2838
+ const generatedReal = (0, node_fs.realpathSync)(outputPath);
2839
+ const reportedReal = (0, node_fs.realpathSync)(reportedPath);
2840
+ if (reportedReal !== generatedReal) throw new Error(`Bundled completion worker path mismatch: generated ${generatedReal}, reported ${reportedReal}`);
2841
+ }
2842
+ if (!options.quiet) printSuccess(outputPath, size, cwd);
2843
+ return {
2844
+ outputPath,
2845
+ size,
2846
+ ...reportedPath !== void 0 && { reportedPath }
2847
+ };
2594
2848
  }
2595
2849
 
2596
2850
  //#endregion
2597
- //#region src/completion/dynamic/complete-command.ts
2598
- /**
2599
- * Dynamic completion command implementation
2600
- *
2601
- * This creates a hidden `__complete` command that outputs completion candidates
2602
- * for shell scripts to consume. Usage:
2603
- *
2604
- * mycli __complete --shell bash -- build --fo
2605
- * mycli __complete --shell zsh -- plugin add
2606
- *
2607
- * Output format depends on the target shell:
2608
- * bash: plain values (pre-filtered by prefix), last line :directive
2609
- * zsh: value:description pairs, last line :directive
2610
- * fish: value\tdescription pairs, last line :directive
2611
- */
2612
- /**
2613
- * Schema for the __complete command
2614
- */
2615
- const completeArgsSchema = zod.z.object({
2616
- shell: require_schema_extractor.arg(zod.z.enum([
2617
- "bash",
2618
- "zsh",
2619
- "fish"
2620
- ]), { description: "Target shell for output formatting" }),
2621
- args: require_schema_extractor.arg(zod.z.array(zod.z.string()).default([]), {
2622
- positional: true,
2623
- description: "Arguments to complete",
2624
- variadic: true
2625
- })
2626
- });
2627
- /**
2628
- * Create the dynamic completion command
2629
- *
2630
- * @param rootCommand - The root command to generate completions for
2631
- * @param programName - The program name (optional, defaults to rootCommand.name)
2632
- * @param globalArgsSchema - Global args schema. Forwarded to
2633
- * `parseCompletionContext` so resolvers attached to global options remain
2634
- * reachable at every subcommand level.
2635
- * @returns A command that outputs completion candidates
2636
- */
2637
- function createDynamicCompleteCommand(rootCommand, _programName, globalArgsSchema) {
2638
- return defineCommand({
2639
- name: "__complete",
2640
- args: completeArgsSchema,
2641
- async run(args) {
2642
- const context = parseCompletionContext(args.args, rootCommand, globalArgsSchema);
2643
- const inlinePrefix = context.completionType === "option-value" && context.targetOption ? detectInlineOptionPrefix(context.currentWord) : void 0;
2644
- const effectiveWord = inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord;
2645
- const output = formatForShell(await generateCandidates(context, { shell: args.shell }), {
2646
- shell: args.shell,
2647
- currentWord: effectiveWord,
2648
- inlinePrefix
2649
- });
2650
- console.log(output);
2651
- }
2652
- });
2851
+ //#region src/completion/dispatcher.ts
2852
+ function compileCacheSuffix(programName) {
2853
+ return shSingleQuote(`/${programName}/node-compile-cache`);
2653
2854
  }
2654
- /**
2655
- * Check if a command tree contains the __complete command
2656
- */
2657
- function hasCompleteCommand(command) {
2658
- return Boolean(command.subCommands?.["__complete"]);
2855
+ function hardcodedCompileCacheDir(cacheDir) {
2856
+ return cacheDir ? shSingleQuote(`${cacheDir}/node-compile-cache`) : void 0;
2659
2857
  }
2660
-
2661
- //#endregion
2662
- //#region src/completion/fish.ts
2663
- /** Escape shell-special characters for fish double-quoted strings */
2664
- function escapeDesc$1(s) {
2665
- return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
2858
+ function hardcodedWorkerPath(cacheDir, shell) {
2859
+ return cacheDir ? shSingleQuote(`${cacheDir}/completion-worker.${shell}`) : void 0;
2860
+ }
2861
+ function workerPathSuffix(programName, shell) {
2862
+ return shSingleQuote(`/${programName}/completion-worker.${shell}`);
2666
2863
  }
2667
2864
  /**
2668
- * Escape a fish `switch` case pattern. Fish's `case` interprets its
2669
- * arguments as globs even when double-quoted, so glob metacharacters
2670
- * (`*`, `?`, `[`, `]`) must be backslash-escaped to keep the comparison
2671
- * literal — otherwise a key like `prod*` would also match a runtime
2672
- * value of `production`. Quote/dollar/backslash are escaped first so the
2673
- * resulting string remains valid inside a double-quoted literal.
2865
+ * `<printCmd> <dir>` line resolving a cache path: the hardcoded `cacheDir` when
2866
+ * configured, else `${XDG_CACHE_HOME:-$HOME/.cache}` joined with `suffix`. Used
2867
+ * by bash and zsh, which both expand the XDG default inline.
2674
2868
  */
2675
- function fishCaseEscape(s) {
2676
- return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/\*/g, "\\*").replace(/\?/g, "\\?").replace(/\[/g, "\\[").replace(/]/g, "\\]");
2869
+ function posixCacheDefault(printCmd, hardcoded, suffix) {
2870
+ return hardcoded ? ` ${printCmd} ${hardcoded}` : ` ${printCmd} "\${XDG_CACHE_HOME:-$HOME/.cache}"${suffix}`;
2677
2871
  }
2678
2872
  /**
2679
- * Generate fish value completion lines for a ValueCompletion spec.
2680
- * Each line outputs candidates via echo (tab-separated value\tdescription).
2681
- *
2682
- * `location` is required for the expand variant (carries fieldName +
2683
- * isArrayOption); other variants ignore it.
2873
+ * fish equivalent of {@link posixCacheDefault}. fish has no `${VAR:-default}`
2874
+ * form, so the XDG fallback is materialized over three lines.
2684
2875
  */
2685
- function fishValueLines(vc, fn, location) {
2686
- if (!vc) return [];
2687
- switch (vc.type) {
2688
- case "expand": {
2689
- if (!location) throw new Error("fishValueLines: expand variant requires a location");
2690
- const depExpr = (d) => {
2691
- const safe = sanitize(d.name);
2692
- return d.isGlobal ? `$_global_arg_values_${safe}` : `$_arg_values_${safe}`;
2693
- };
2694
- const depKey = location.resolvedDeps.map((d) => `"${depExpr(d)}"`).join(`\\x1f`);
2695
- const bucket = sanitize(location.fieldName);
2696
- const bucketList = location.isGlobal ? `$_global_used_field_keys_${bucket}` : `$_used_field_keys_${bucket}`;
2697
- const out = [`switch ${depKey}`];
2698
- for (const entry of vc.table) {
2699
- const casePattern = entry.key.map((k) => `"${fishCaseEscape(k)}"`).join(`\\x1f`);
2700
- out.push(` case ${casePattern}`);
2701
- const keyOnlyLines = [];
2702
- const fullLines = [];
2703
- const seenKeys = /* @__PURE__ */ new Set();
2704
- const printfLine = (value, description) => description ? `printf '%s\\t%s\\n' "${escapeDesc$1(value)}" "${escapeDesc$1(description)}"` : `printf '%s\\n' "${escapeDesc$1(value)}"`;
2705
- const wrapWithDedup = (echoLine, keyPart) => location.isArrayOption && keyPart.length > 0 ? [
2706
- ` if not contains -- "${escapeDesc$1(keyPart)}" ${bucketList}`,
2707
- ` ${echoLine}`,
2708
- ` end`
2709
- ] : [` ${echoLine}`];
2710
- for (const c of entry.candidates) {
2711
- const eqIdx = c.value.indexOf("=");
2712
- const keyPart = eqIdx > 0 ? c.value.slice(0, eqIdx) : "";
2713
- const echoLine = printfLine(c.value, c.description);
2714
- if (!(keyPart.length > 0 && c.value.length === eqIdx + 1)) fullLines.push(...wrapWithDedup(echoLine, keyPart));
2715
- if (keyPart.length === 0) keyOnlyLines.push(` ${echoLine}`);
2716
- else if (!seenKeys.has(keyPart)) {
2717
- seenKeys.add(keyPart);
2718
- keyOnlyLines.push(...wrapWithDedup(printfLine(`${keyPart}=`, c.description), keyPart));
2719
- }
2876
+ function fishCacheDefault(hardcoded, suffix) {
2877
+ if (hardcoded) return ` printf '%s\\n' ${hardcoded}`;
2878
+ return [
2879
+ ` set -l _cache_root "$XDG_CACHE_HOME"`,
2880
+ ` test -n "$_cache_root"; or set _cache_root "$HOME/.cache"`,
2881
+ ` printf '%s\\n' "$_cache_root"${suffix}`
2882
+ ].join("\n");
2883
+ }
2884
+ function shellWorkerRelList(options, shell) {
2885
+ return bundledWorkerRelativePaths(options.programName, shell, options.bundledWorker).map(shSingleQuote).join(" ");
2886
+ }
2887
+ function fishDQ(s) {
2888
+ return `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$")}"`;
2889
+ }
2890
+ function fishWorkerRelList(options) {
2891
+ return bundledWorkerRelativePaths(options.programName, "fish", options.bundledWorker).map(fishDQ).join(" ");
2892
+ }
2893
+ function bundledWorkerPathCommandEnabled(options) {
2894
+ return options.bundledWorker?.queryCommand === true && !options.bundledWorker.disabled;
2895
+ }
2896
+ function bashDispatcher(_command, options) {
2897
+ const { programName } = options;
2898
+ const fn = sanitize(programName);
2899
+ const workerFn = `${fn}_worker`;
2900
+ const workerBinEnvName = binEnvVarName(workerFn);
2901
+ const envName = binEnvVarName(fn);
2902
+ const programLookup = shSingleQuote(programName);
2903
+ const compileCacheDefault = posixCacheDefault("printf '%s\\n'", hardcodedCompileCacheDir(options.cacheDir), compileCacheSuffix(programName));
2904
+ const workerPathDefault = posixCacheDefault("printf '%s\\n'", hardcodedWorkerPath(options.cacheDir, "bash"), workerPathSuffix(programName, "bash"));
2905
+ const workerRelList = shellWorkerRelList(options, "bash");
2906
+ const canQueryBundledWorkerPath = bundledWorkerPathCommandEnabled(options);
2907
+ const lines = [];
2908
+ lines.push(...buildHeaderLines({
2909
+ programName,
2910
+ shell: "bash",
2911
+ binPath: options.binPath,
2912
+ programVersion: options.programVersion
2913
+ }));
2914
+ lines.push(`# Generated by politty`);
2915
+ lines.push(`# politty-completion-mode: dispatcher`);
2916
+ lines.push(``);
2917
+ lines.push(`__${fn}_resolve_bin() {`);
2918
+ lines.push(` if [[ -n "\${${envName}:-}" ]]; then`);
2919
+ lines.push(` printf '%s\\n' "\${${envName}}"`);
2920
+ lines.push(` return 0`);
2921
+ lines.push(` fi`);
2922
+ lines.push(` type -P ${programLookup} 2>/dev/null`);
2923
+ lines.push(`}`);
2924
+ lines.push(``);
2925
+ lines.push(`__${fn}_node_compile_cache_dir() {`);
2926
+ lines.push(` if [[ -n "\${NODE_COMPILE_CACHE:-}" ]]; then`);
2927
+ lines.push(` printf '%s\\n' "$NODE_COMPILE_CACHE"`);
2928
+ lines.push(` return 0`);
2929
+ lines.push(` fi`);
2930
+ lines.push(compileCacheDefault);
2931
+ lines.push(`}`);
2932
+ lines.push(``);
2933
+ lines.push(`__${fn}_static_worker_path() {`);
2934
+ lines.push(workerPathDefault);
2935
+ lines.push(`}`);
2936
+ lines.push(``);
2937
+ lines.push(`__${fn}_worker_file_sig() {`);
2938
+ lines.push(` local _worker="$1" _sig`);
2939
+ lines.push(` _sig=${statSigExpr("$_worker", {
2940
+ shell: "posix",
2941
+ withSize: true
2942
+ })} || return 1`);
2943
+ lines.push(` printf '%s\\n' "$_sig"`);
2944
+ lines.push(`}`);
2945
+ lines.push(``);
2946
+ lines.push(`__${fn}_is_worker_file() {`);
2947
+ lines.push(` local _worker="$1" _head`);
2948
+ lines.push(` [[ -f "$_worker" ]] || return 1`);
2949
+ lines.push(` _head="$(head -n 24 "$_worker" 2>/dev/null)" || return 1`);
2950
+ lines.push(` _head=$'\\n'"$_head"$'\\n'`);
2951
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-version: 1"$'\\n'* ]] || return 1`);
2952
+ lines.push(` [[ "$_head" == *$'\\n'"# program: ${programName}"$'\\n'* ]] || return 1`);
2953
+ lines.push(` [[ "$_head" == *$'\\n'"# shell: bash"$'\\n'* ]] || return 1`);
2954
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-mode: worker"$'\\n'* ]] || return 1`);
2955
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-worker: true"$'\\n'* ]] || return 1`);
2956
+ lines.push(`}`);
2957
+ lines.push(``);
2958
+ lines.push(`__${fn}_load_worker() {`);
2959
+ lines.push(` local _worker="$1" _sig _key`);
2960
+ lines.push(` __${fn}_is_worker_file "$_worker" || return 1`);
2961
+ lines.push(` _sig="$(__${fn}_worker_file_sig "$_worker")" || return 1`);
2962
+ lines.push(` _key="$_worker:$_sig"`);
2963
+ lines.push(` if [[ "\${__${fn}_loaded_worker_key:-}" != "$_key" ]]; then`);
2964
+ lines.push(` source "$_worker" 2>/dev/null || return 1`);
2965
+ lines.push(` __${fn}_loaded_worker_key="$_key"`);
2966
+ lines.push(` fi`);
2967
+ lines.push(` declare -F _${workerFn}_completions >/dev/null 2>&1`);
2968
+ lines.push(`}`);
2969
+ lines.push(``);
2970
+ lines.push(`__${fn}_realpath() {`);
2971
+ lines.push(` local _path="$1" _dir _target _limit=0`);
2972
+ lines.push(` while [[ -L "$_path" && $_limit -lt 40 ]]; do`);
2973
+ lines.push(` _dir="$(cd -P "$(dirname "$_path")" 2>/dev/null && pwd)" || return 1`);
2974
+ lines.push(` _target="$(readlink "$_path")" || return 1`);
2975
+ lines.push(` if [[ "$_target" == /* ]]; then _path="$_target"; else _path="$_dir/$_target"; fi`);
2976
+ lines.push(` (( _limit++ ))`);
2977
+ lines.push(` done`);
2978
+ lines.push(` _dir="$(cd -P "$(dirname "$_path")" 2>/dev/null && pwd)" || return 1`);
2979
+ lines.push(` printf '%s\\n' "$_dir/$(basename "$_path")"`);
2980
+ lines.push(`}`);
2981
+ lines.push(``);
2982
+ lines.push(`__${fn}_worker_from_dir() {`);
2983
+ lines.push(` local _dir="$1" _rel _candidate`);
2984
+ if (workerRelList.length > 0) {
2985
+ lines.push(` for _rel in ${workerRelList}; do`);
2986
+ lines.push(` [[ "$_rel" == /* ]] && _candidate="$_rel" || _candidate="$_dir/$_rel"`);
2987
+ lines.push(` if __${fn}_is_worker_file "$_candidate"; then`);
2988
+ lines.push(` printf '%s\\n' "$_candidate"`);
2989
+ lines.push(` return 0`);
2990
+ lines.push(` fi`);
2991
+ lines.push(` done`);
2992
+ }
2993
+ lines.push(` return 1`);
2994
+ lines.push(`}`);
2995
+ lines.push(``);
2996
+ lines.push(`__${fn}_cmd_shim_target() {`);
2997
+ lines.push(` sed -n 's/^# cmd-shim-target=//p' "$1" 2>/dev/null | tail -n 1`);
2998
+ lines.push(`}`);
2999
+ lines.push(``);
3000
+ lines.push(`__${fn}_bundled_worker_path() {`);
3001
+ lines.push(` local _bin="$1" _node_compile_cache="\${2:-}" _real _dir _target _reported`);
3002
+ lines.push(` _real="$(__${fn}_realpath "$_bin" 2>/dev/null)" || _real=""`);
3003
+ lines.push(` if [[ -n "$_real" ]]; then`);
3004
+ lines.push(` _dir="$(dirname "$_real")"`);
3005
+ lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
3006
+ lines.push(` fi`);
3007
+ lines.push(` _dir="$(dirname "$_bin")"`);
3008
+ lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
3009
+ lines.push(` _target="$(__${fn}_cmd_shim_target "$_bin")"`);
3010
+ lines.push(` if [[ -n "$_target" ]]; then`);
3011
+ lines.push(` _real="$(__${fn}_realpath "$_target" 2>/dev/null)" || _real="$_target"`);
3012
+ lines.push(` _dir="$(dirname "$_real")"`);
3013
+ lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
3014
+ lines.push(` fi`);
3015
+ if (canQueryBundledWorkerPath) {
3016
+ lines.push(` if [[ "\${__${fn}_queried_bin:-}" == "$_bin" && -n "\${__${fn}_queried_worker:-}" ]] && __${fn}_is_worker_file "\${__${fn}_queried_worker}"; then`);
3017
+ lines.push(` printf '%s\\n' "\${__${fn}_queried_worker}"`);
3018
+ lines.push(` return 0`);
3019
+ lines.push(` fi`);
3020
+ lines.push(` _reported="$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __completion-worker-path bash 2>/dev/null)" || _reported=""`);
3021
+ lines.push(` if [[ -n "$_reported" ]] && __${fn}_is_worker_file "$_reported"; then`);
3022
+ lines.push(` __${fn}_queried_bin="$_bin"`);
3023
+ lines.push(` __${fn}_queried_worker="$_reported"`);
3024
+ lines.push(` printf '%s\\n' "$_reported"`);
3025
+ lines.push(` return 0`);
3026
+ lines.push(` fi`);
3027
+ }
3028
+ lines.push(` return 1`);
3029
+ lines.push(`}`);
3030
+ lines.push(``);
3031
+ lines.push(`__${fn}_bin_sig() {`);
3032
+ lines.push(` local _bin="$1" _sig`);
3033
+ lines.push(` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`);
3034
+ lines.push(` printf '%s\\n' "$_sig"`);
3035
+ lines.push(`}`);
3036
+ lines.push(``);
3037
+ lines.push(`__${fn}_worker_matches_bin() {`);
3038
+ lines.push(` local _worker="$1" _sig="$2" _bin="$3" _head`);
3039
+ lines.push(` [[ -f "$_worker" ]] || return 1`);
3040
+ lines.push(` _head="$(head -n 12 "$_worker" 2>/dev/null)" || return 1`);
3041
+ lines.push(` grep -qxF "# politty-bin-sig: $_sig" <<< "$_head" || return 1`);
3042
+ lines.push(` grep -qxF "# politty-bin-path: $_bin" <<< "$_head" || return 1`);
3043
+ lines.push(`}`);
3044
+ lines.push(``);
3045
+ lines.push(`__${fn}_apply_dynamic_output() {`);
3046
+ lines.push(` local _raw="$1" _cur="$2" _inline_prefix="$3"`);
3047
+ lines.push(` COMPREPLY=()`);
3048
+ lines.push(` local _directive=0`);
3049
+ lines.push(` local -a _lines=() _exts=() _matchers=()`);
3050
+ lines.push(` local _line`);
3051
+ lines.push(` while IFS= read -r _line; do _lines+=("$_line"); done <<< "$_raw"`);
3052
+ lines.push(` local _last=$((\${#_lines[@]} - 1))`);
3053
+ lines.push(` if (( _last >= 0 )) && [[ "\${_lines[$_last]}" == :[0-9]* ]]; then`);
3054
+ lines.push(` local -a _meta=()`);
3055
+ lines.push(` IFS=$'\\t' read -r -a _meta <<< "\${_lines[$_last]}"`);
3056
+ lines.push(` unset '_lines[_last]'`);
3057
+ lines.push(` _directive="\${_meta[0]#:}"`);
3058
+ lines.push(` local _m`);
3059
+ lines.push(` for _m in "\${_meta[@]:1}"; do`);
3060
+ lines.push(` if [[ "$_m" == @ext:* ]]; then`);
3061
+ lines.push(` IFS=',' read -r -a _exts <<< "\${_m#@ext:}"`);
3062
+ lines.push(` elif [[ "$_m" == @matcher:* ]]; then`);
3063
+ lines.push(` IFS=',' read -r -a _matchers <<< "\${_m#@matcher:}"`);
3064
+ lines.push(` fi`);
3065
+ lines.push(` done`);
3066
+ lines.push(` fi`);
3067
+ lines.push(` for _line in "\${_lines[@]}"; do`);
3068
+ lines.push(` [[ -z "$_line" ]] && continue`);
3069
+ lines.push(` COMPREPLY+=("$_line")`);
3070
+ lines.push(` done`);
3071
+ lines.push(` local _ip="\${_inline_prefix:-}"`);
3072
+ lines.push(` if (( \${#_exts[@]} > 0 || \${#_matchers[@]} > 0 )); then`);
3073
+ lines.push(` local _need_empty=0`);
3074
+ lines.push(` compopt +o default 2>/dev/null || _need_empty=1`);
3075
+ lines.push(` compopt -o filenames 2>/dev/null`);
3076
+ lines.push(` local _f _ext _pat _base _ok`);
3077
+ lines.push(` while IFS= read -r _f; do`);
3078
+ lines.push(` if [[ -d "$_f" ]]; then`);
3079
+ lines.push(` COMPREPLY+=("\${_ip}\${_f}")`);
3080
+ lines.push(` continue`);
3081
+ lines.push(` fi`);
3082
+ lines.push(` _ok=0`);
3083
+ lines.push(` if (( \${#_exts[@]} > 0 )); then`);
3084
+ lines.push(` for _ext in "\${_exts[@]}"; do`);
3085
+ lines.push(` [[ -n "$_ext" && "$_f" == *."$_ext" ]] && _ok=1`);
3086
+ lines.push(` done`);
3087
+ lines.push(` fi`);
3088
+ lines.push(` if (( \${#_matchers[@]} > 0 )); then`);
3089
+ lines.push(` _base="\${_f##*/}"`);
3090
+ lines.push(` for _pat in "\${_matchers[@]}"; do`);
3091
+ lines.push(` [[ -n "$_pat" && "$_base" == $_pat ]] && _ok=1`);
3092
+ lines.push(` done`);
3093
+ lines.push(` fi`);
3094
+ lines.push(` (( _ok )) && COMPREPLY+=("\${_ip}\${_f}")`);
3095
+ lines.push(` done < <(compgen -f -- "$_cur")`);
3096
+ lines.push(` if (( _need_empty && \${#COMPREPLY[@]} == 0 )); then COMPREPLY=( "" ); fi`);
3097
+ lines.push(` elif (( _directive & ${CompletionDirective.DirectoryCompletion} )); then`);
3098
+ lines.push(` local _need_empty=0`);
3099
+ lines.push(` compopt +o default 2>/dev/null || _need_empty=1`);
3100
+ lines.push(` compopt -o filenames 2>/dev/null`);
3101
+ lines.push(` local _d`);
3102
+ lines.push(` while IFS= read -r _d; do COMPREPLY+=("\${_ip}\${_d}"); done < <(compgen -d -- "$_cur")`);
3103
+ lines.push(` if (( _need_empty && \${#COMPREPLY[@]} == 0 )); then COMPREPLY=( "" ); fi`);
3104
+ lines.push(` elif (( _directive & ${CompletionDirective.FileCompletion} )); then`);
3105
+ lines.push(` compopt -o filenames 2>/dev/null`);
3106
+ lines.push(` local _p`);
3107
+ lines.push(` while IFS= read -r _p; do COMPREPLY+=("\${_ip}\${_p}"); done < <(compgen -f -- "$_cur")`);
3108
+ lines.push(` elif (( _directive & ${CompletionDirective.NoFileCompletion} )); then`);
3109
+ lines.push(` local _need_empty=0`);
3110
+ lines.push(` compopt +o default 2>/dev/null || _need_empty=1`);
3111
+ lines.push(` if (( _need_empty && \${#COMPREPLY[@]} == 0 )); then COMPREPLY=( "" ); fi`);
3112
+ lines.push(` fi`);
3113
+ lines.push(` if (( _directive & ${CompletionDirective.NoSpace} )); then`);
3114
+ lines.push(` compopt -o nospace 2>/dev/null`);
3115
+ lines.push(` fi`);
3116
+ lines.push(`}`);
3117
+ lines.push(``);
3118
+ lines.push(`_${fn}_completions() {`);
3119
+ lines.push(` COMPREPLY=()`);
3120
+ lines.push(` local -a _words=()`);
3121
+ lines.push(` local _i=1`);
3122
+ lines.push(` while (( _i <= COMP_CWORD )); do`);
3123
+ lines.push(` if [[ "\${COMP_WORDS[_i]}" == "=" && \${#_words[@]} -gt 0 ]]; then`);
3124
+ lines.push(` _words[\${#_words[@]}-1]+="=\${COMP_WORDS[_i+1]:-}"`);
3125
+ lines.push(` (( _i += 2 ))`);
3126
+ lines.push(` else`);
3127
+ lines.push(` _words+=("\${COMP_WORDS[_i]}")`);
3128
+ lines.push(` (( _i++ ))`);
3129
+ lines.push(` fi`);
3130
+ lines.push(` done`);
3131
+ lines.push(` local _cur="" _inline_prefix=""`);
3132
+ lines.push(` (( \${#_words[@]} > 0 )) && _cur="\${_words[\${#_words[@]}-1]}"`);
3133
+ lines.push(` local _after_dd=0 _di=0`);
3134
+ lines.push(` while (( _di < \${#_words[@]} - 1 )); do`);
3135
+ lines.push(` [[ "\${_words[_di]}" == "--" ]] && { _after_dd=1; break; }`);
3136
+ lines.push(` (( _di++ ))`);
3137
+ lines.push(` done`);
3138
+ lines.push(` if (( ! _after_dd )) && [[ "$_cur" == -*=* ]]; then`);
3139
+ lines.push(` _inline_prefix="\${_cur%%=*}="`);
3140
+ lines.push(` _cur="\${_cur#*=}"`);
3141
+ lines.push(` fi`);
3142
+ lines.push(` local _bin _out _node_compile_cache _worker _bundled_worker _sig`);
3143
+ lines.push(` _bin="$(__${fn}_resolve_bin)" || _bin=""`);
3144
+ lines.push(` [[ -n "$_bin" ]] || return 0`);
3145
+ lines.push(` _node_compile_cache="$(__${fn}_node_compile_cache_dir)" || _node_compile_cache=""`);
3146
+ lines.push(` _bundled_worker="$(__${fn}_bundled_worker_path "$_bin" "$_node_compile_cache")" || _bundled_worker=""`);
3147
+ lines.push(` if [[ -n "$_bundled_worker" ]] && __${fn}_load_worker "$_bundled_worker"; then`);
3148
+ lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions`);
3149
+ lines.push(` return 0`);
3150
+ lines.push(` fi`);
3151
+ lines.push(` _worker="$(__${fn}_static_worker_path)" || _worker=""`);
3152
+ lines.push(` _sig="$(__${fn}_bin_sig "$_bin")" || _sig=""`);
3153
+ lines.push(` if [[ -n "$_worker" && -n "$_sig" ]]; then`);
3154
+ lines.push(` if ! __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"; then`);
3155
+ lines.push(` mkdir -p "\${_worker%/*}" 2>/dev/null`);
3156
+ lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${envName}="$_bin" "$_bin" __refresh-completion bash "$_worker" --static --worker 2>/dev/null`);
3157
+ lines.push(` fi`);
3158
+ lines.push(` if __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin" && __${fn}_load_worker "$_worker"; then`);
3159
+ lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions`);
3160
+ lines.push(` return 0`);
3161
+ lines.push(` fi`);
3162
+ lines.push(` fi`);
3163
+ lines.push(` _out=$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __complete --shell bash -- "\${_words[@]}" 2>/dev/null) || return 0`);
3164
+ lines.push(` __${fn}_apply_dynamic_output "$_out" "$_cur" "$_inline_prefix"`);
3165
+ lines.push(`}`);
3166
+ lines.push(``);
3167
+ lines.push(`complete -o default -F _${fn}_completions ${programName}`);
3168
+ lines.push(``);
3169
+ return {
3170
+ script: lines.join("\n"),
3171
+ shell: "bash",
3172
+ installInstructions: `# To enable dispatcher bash completions, add this to your ~/.bashrc:
3173
+ eval "$(${programName} completion bash)"
3174
+
3175
+ # To save the dispatcher script instead:
3176
+ mkdir -p ~/.local/share/bash-completion/completions
3177
+ ${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
3178
+
3179
+ # To generate the older static script:
3180
+ ${programName} completion bash --static`
3181
+ };
3182
+ }
3183
+ function zshDispatcher(_command, options) {
3184
+ const { programName } = options;
3185
+ const fn = sanitize(programName);
3186
+ const workerFn = `${fn}_worker`;
3187
+ const workerBinEnvName = binEnvVarName(workerFn);
3188
+ const envName = binEnvVarName(fn);
3189
+ const programLookup = shSingleQuote(programName);
3190
+ const completionFn = `_${programName}`;
3191
+ const autoloadCheck = `"\${funcstack[1]:-}" == "${completionFn}"`;
3192
+ const compileCacheDefault = posixCacheDefault("print -r --", hardcodedCompileCacheDir(options.cacheDir), compileCacheSuffix(programName));
3193
+ const workerPathDefault = posixCacheDefault("print -r --", hardcodedWorkerPath(options.cacheDir, "zsh"), workerPathSuffix(programName, "zsh"));
3194
+ const workerRelList = shellWorkerRelList(options, "zsh");
3195
+ const canQueryBundledWorkerPath = bundledWorkerPathCommandEnabled(options);
3196
+ const lines = [];
3197
+ lines.push(`#compdef ${programName}`);
3198
+ lines.push(...buildHeaderLines({
3199
+ programName,
3200
+ shell: "zsh",
3201
+ binPath: options.binPath,
3202
+ programVersion: options.programVersion
3203
+ }));
3204
+ lines.push(`# Generated by politty`);
3205
+ lines.push(`# politty-completion-mode: dispatcher`);
3206
+ lines.push(``);
3207
+ lines.push(`__${fn}_resolve_bin() {`);
3208
+ lines.push(` emulate -L zsh`);
3209
+ lines.push(` setopt local_options no_aliases`);
3210
+ lines.push(` if [[ -n "\${${envName}:-}" ]]; then`);
3211
+ lines.push(` print -r -- "\${${envName}}"`);
3212
+ lines.push(` return 0`);
3213
+ lines.push(` fi`);
3214
+ lines.push(` whence -p ${programLookup} 2>/dev/null`);
3215
+ lines.push(`}`);
3216
+ lines.push(``);
3217
+ lines.push(`__${fn}_node_compile_cache_dir() {`);
3218
+ lines.push(` emulate -L zsh`);
3219
+ lines.push(` setopt local_options no_aliases`);
3220
+ lines.push(` if [[ -n "\${NODE_COMPILE_CACHE:-}" ]]; then`);
3221
+ lines.push(` print -r -- "$NODE_COMPILE_CACHE"`);
3222
+ lines.push(` return 0`);
3223
+ lines.push(` fi`);
3224
+ lines.push(compileCacheDefault);
3225
+ lines.push(`}`);
3226
+ lines.push(``);
3227
+ lines.push(`__${fn}_static_worker_path() {`);
3228
+ lines.push(` emulate -L zsh`);
3229
+ lines.push(` setopt local_options no_aliases`);
3230
+ lines.push(workerPathDefault);
3231
+ lines.push(`}`);
3232
+ lines.push(``);
3233
+ lines.push(`__${fn}_worker_file_sig() {`);
3234
+ lines.push(` emulate -L zsh`);
3235
+ lines.push(` setopt local_options no_aliases`);
3236
+ lines.push(` local _worker="$1" _sig`);
3237
+ lines.push(` _sig=${statSigExpr("$_worker", {
3238
+ shell: "posix",
3239
+ withSize: true
3240
+ })} || return 1`);
3241
+ lines.push(` print -r -- "$_sig"`);
3242
+ lines.push(`}`);
3243
+ lines.push(``);
3244
+ lines.push(`__${fn}_is_worker_file() {`);
3245
+ lines.push(` emulate -L zsh`);
3246
+ lines.push(` setopt local_options no_aliases`);
3247
+ lines.push(` local _worker="$1" _head`);
3248
+ lines.push(` [[ -f "$_worker" ]] || return 1`);
3249
+ lines.push(` _head="$(<$_worker)" || return 1`);
3250
+ lines.push(` _head=$'\\n'"$_head"$'\\n'`);
3251
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-version: 1"$'\\n'* ]] || return 1`);
3252
+ lines.push(` [[ "$_head" == *$'\\n'"# program: ${programName}"$'\\n'* ]] || return 1`);
3253
+ lines.push(` [[ "$_head" == *$'\\n'"# shell: zsh"$'\\n'* ]] || return 1`);
3254
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-mode: worker"$'\\n'* ]] || return 1`);
3255
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-worker: true"$'\\n'* ]] || return 1`);
3256
+ lines.push(`}`);
3257
+ lines.push(``);
3258
+ lines.push(`__${fn}_load_worker() {`);
3259
+ lines.push(` emulate -L zsh`);
3260
+ lines.push(` setopt local_options no_aliases`);
3261
+ lines.push(` local _worker="$1" _sig _key`);
3262
+ lines.push(` __${fn}_is_worker_file "$_worker" || return 1`);
3263
+ lines.push(` _sig="$(__${fn}_worker_file_sig "$_worker")" || return 1`);
3264
+ lines.push(` _key="$_worker:$_sig"`);
3265
+ lines.push(` if [[ "\${__${fn}_loaded_worker_key:-}" != "$_key" ]]; then`);
3266
+ lines.push(` source "$_worker" 2>/dev/null || return 1`);
3267
+ lines.push(` typeset -g __${fn}_loaded_worker_key="$_key"`);
3268
+ lines.push(` fi`);
3269
+ lines.push(` (( $+functions[_${workerFn}_completions] ))`);
3270
+ lines.push(`}`);
3271
+ lines.push(``);
3272
+ lines.push(`__${fn}_realpath() {`);
3273
+ lines.push(` emulate -L zsh`);
3274
+ lines.push(` setopt local_options no_aliases`);
3275
+ lines.push(` local _path="$1"`);
3276
+ lines.push(` [[ -n "$_path" ]] || return 1`);
3277
+ lines.push(` print -r -- "\${_path:A}"`);
3278
+ lines.push(`}`);
3279
+ lines.push(``);
3280
+ lines.push(`__${fn}_worker_from_dir() {`);
3281
+ lines.push(` emulate -L zsh`);
3282
+ lines.push(` setopt local_options no_aliases`);
3283
+ lines.push(` local _dir="$1" _rel _candidate`);
3284
+ if (workerRelList.length > 0) {
3285
+ lines.push(` for _rel in ${workerRelList}; do`);
3286
+ lines.push(` [[ "$_rel" == /* ]] && _candidate="$_rel" || _candidate="$_dir/$_rel"`);
3287
+ lines.push(` if __${fn}_is_worker_file "$_candidate"; then`);
3288
+ lines.push(` print -r -- "$_candidate"`);
3289
+ lines.push(` return 0`);
3290
+ lines.push(` fi`);
3291
+ lines.push(` done`);
3292
+ }
3293
+ lines.push(` return 1`);
3294
+ lines.push(`}`);
3295
+ lines.push(``);
3296
+ lines.push(`__${fn}_cmd_shim_target() {`);
3297
+ lines.push(` emulate -L zsh`);
3298
+ lines.push(` setopt local_options no_aliases`);
3299
+ lines.push(` [[ -f "$1" ]] || return 0`);
3300
+ lines.push(` local _l _t=""`);
3301
+ lines.push(` for _l in "\${(@f)$(<$1)}"; do`);
3302
+ lines.push(` [[ "$_l" == "# cmd-shim-target="* ]] && _t="\${_l#\\# cmd-shim-target=}"`);
3303
+ lines.push(` done`);
3304
+ lines.push(` [[ -n "$_t" ]] && print -r -- "$_t"`);
3305
+ lines.push(`}`);
3306
+ lines.push(``);
3307
+ lines.push(`__${fn}_bundled_worker_path() {`);
3308
+ lines.push(` emulate -L zsh`);
3309
+ lines.push(` setopt local_options no_aliases`);
3310
+ lines.push(` local _bin="$1" _node_compile_cache="\${2:-}" _real _dir _target _reported`);
3311
+ lines.push(` _real="$(__${fn}_realpath "$_bin" 2>/dev/null)" || _real=""`);
3312
+ lines.push(` if [[ -n "$_real" ]]; then`);
3313
+ lines.push(` _dir="\${_real:h}"`);
3314
+ lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
3315
+ lines.push(` fi`);
3316
+ lines.push(` _dir="\${_bin:h}"`);
3317
+ lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
3318
+ lines.push(` _target="$(__${fn}_cmd_shim_target "$_bin")"`);
3319
+ lines.push(` if [[ -n "$_target" ]]; then`);
3320
+ lines.push(` _real="$(__${fn}_realpath "$_target" 2>/dev/null)" || _real="$_target"`);
3321
+ lines.push(` _dir="\${_real:h}"`);
3322
+ lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
3323
+ lines.push(` fi`);
3324
+ if (canQueryBundledWorkerPath) {
3325
+ lines.push(` if [[ "\${__${fn}_queried_bin:-}" == "$_bin" && -n "\${__${fn}_queried_worker:-}" ]] && __${fn}_is_worker_file "\${__${fn}_queried_worker}"; then`);
3326
+ lines.push(` print -r -- "\${__${fn}_queried_worker}"`);
3327
+ lines.push(` return 0`);
3328
+ lines.push(` fi`);
3329
+ lines.push(` _reported="$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __completion-worker-path zsh 2>/dev/null)" || _reported=""`);
3330
+ lines.push(` if [[ -n "$_reported" ]] && __${fn}_is_worker_file "$_reported"; then`);
3331
+ lines.push(` typeset -g __${fn}_queried_bin="$_bin"`);
3332
+ lines.push(` typeset -g __${fn}_queried_worker="$_reported"`);
3333
+ lines.push(` print -r -- "$_reported"`);
3334
+ lines.push(` return 0`);
3335
+ lines.push(` fi`);
3336
+ }
3337
+ lines.push(` return 1`);
3338
+ lines.push(`}`);
3339
+ lines.push(``);
3340
+ lines.push(`__${fn}_bin_sig() {`);
3341
+ lines.push(` emulate -L zsh`);
3342
+ lines.push(` setopt local_options no_aliases`);
3343
+ lines.push(` local _bin="$1" _sig`);
3344
+ lines.push(` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`);
3345
+ lines.push(` print -r -- "$_sig"`);
3346
+ lines.push(`}`);
3347
+ lines.push(``);
3348
+ lines.push(`__${fn}_worker_matches_bin() {`);
3349
+ lines.push(` emulate -L zsh`);
3350
+ lines.push(` setopt local_options no_aliases`);
3351
+ lines.push(` local _worker="$1" _sig="$2" _bin="$3" _head`);
3352
+ lines.push(` [[ -f "$_worker" ]] || return 1`);
3353
+ lines.push(` _head=$'\\n'"$(<$_worker)"$'\\n'`);
3354
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-bin-sig: $_sig"$'\\n'* ]] || return 1`);
3355
+ lines.push(` [[ "$_head" == *$'\\n'"# politty-bin-path: $_bin"$'\\n'* ]] || return 1`);
3356
+ lines.push(`}`);
3357
+ lines.push(``);
3358
+ lines.push(`__${fn}_cdescribe() {`);
3359
+ lines.push(` _describe "$@" 2>/dev/null && return 0`);
3360
+ lines.push(` shift`);
3361
+ lines.push(` local _cd_arr="$1"`);
3362
+ lines.push(` shift`);
3363
+ lines.push(` local -a _cd_vals=("\${(@)\${(P)_cd_arr}%%:*}")`);
3364
+ lines.push(` compadd "$@" -a _cd_vals 2>/dev/null`);
3365
+ lines.push(` return 0`);
3366
+ lines.push(`}`);
3367
+ lines.push(``);
3368
+ lines.push(`__${fn}_add_path_candidate() {`);
3369
+ lines.push(` compadd -f -- "$1" 2>/dev/null && return 0`);
3370
+ lines.push(` print -r -- "$1"`);
3371
+ lines.push(`}`);
3372
+ lines.push(``);
3373
+ lines.push(`__${fn}_apply_dynamic_output() {`);
3374
+ lines.push(` local _raw="$1"`);
3375
+ lines.push(` local _directive=0`);
3376
+ lines.push(` local -a _vals _lines _exts _matchers`);
3377
+ lines.push(` local _meta`);
3378
+ lines.push(` _lines=("\${(@f)_raw}")`);
3379
+ lines.push(` local _last=$#_lines`);
3380
+ lines.push(` if (( _last >= 1 )) && [[ "\${_lines[$_last]}" == :<->* ]]; then`);
3381
+ lines.push(` local _dline="\${_lines[$_last]}"`);
3382
+ lines.push(` _lines[$_last]=()`);
3383
+ lines.push(` local -a _dfields`);
3384
+ lines.push(` _dfields=("\${(@ps:\\t:)_dline}")`);
3385
+ lines.push(` _directive="\${_dfields[1]#:}"`);
3386
+ lines.push(` local _m`);
3387
+ lines.push(` for _m in "\${(@)_dfields[2,-1]}"; do`);
3388
+ lines.push(` if [[ "$_m" == @ext:* ]]; then`);
3389
+ lines.push(` _meta="\${_m#@ext:}"`);
3390
+ lines.push(` _exts=("\${(@s:,:)_meta}")`);
3391
+ lines.push(` elif [[ "$_m" == @matcher:* ]]; then`);
3392
+ lines.push(` _meta="\${_m#@matcher:}"`);
3393
+ lines.push(` _matchers=("\${(@s:,:)_meta}")`);
3394
+ lines.push(` fi`);
3395
+ lines.push(` done`);
3396
+ lines.push(` fi`);
3397
+ lines.push(` local _l`);
3398
+ lines.push(` for _l in "\${_lines[@]}"; do`);
3399
+ lines.push(` [[ -z "$_l" ]] && continue`);
3400
+ lines.push(` _vals+=("$_l")`);
3401
+ lines.push(` done`);
3402
+ lines.push(` if (( \${#_vals[@]} > 0 )); then`);
3403
+ lines.push(` if (( _directive & ${CompletionDirective.NoSpace} )); then`);
3404
+ lines.push(` __${fn}_cdescribe 'completions' _vals -S ''`);
3405
+ lines.push(` else`);
3406
+ lines.push(` __${fn}_cdescribe 'completions' _vals`);
3407
+ lines.push(` fi`);
3408
+ lines.push(` fi`);
3409
+ lines.push(` if (( \${#_exts[@]} > 0 || \${#_matchers[@]} > 0 )); then`);
3410
+ lines.push(` setopt local_options null_glob`);
3411
+ lines.push(` local _cur="\${words[CURRENT]:-}" _dir _prefix _f _out _ext _pat`);
3412
+ lines.push(` if [[ -z "$_cur" || "$_cur" != */* ]]; then`);
3413
+ lines.push(` _dir="."`);
3414
+ lines.push(` _prefix="$_cur"`);
3415
+ lines.push(` elif [[ "$_cur" == */ ]]; then`);
3416
+ lines.push(` _dir="\${_cur%/}"`);
3417
+ lines.push(` _prefix=""`);
3418
+ lines.push(` else`);
3419
+ lines.push(` _dir="\${_cur%/*}"`);
3420
+ lines.push(` _prefix="\${_cur##*/}"`);
3421
+ lines.push(` fi`);
3422
+ lines.push(` for _f in "$_dir"/"$_prefix"*(N/); do`);
3423
+ lines.push(` _out="\${_f#./}"`);
3424
+ lines.push(` __${fn}_add_path_candidate "$_out"`);
3425
+ lines.push(` done`);
3426
+ lines.push(` for _f in "$_dir"/"$_prefix"*(N.); do`);
3427
+ lines.push(` _out="\${_f#./}"`);
3428
+ lines.push(` for _ext in "\${_exts[@]}"; do`);
3429
+ lines.push(` [[ -n "$_ext" && "$_out" == *."$_ext" ]] && { __${fn}_add_path_candidate "$_out"; break; }`);
3430
+ lines.push(` done`);
3431
+ lines.push(` done`);
3432
+ lines.push(` for _pat in "\${_matchers[@]}"; do`);
3433
+ lines.push(` [[ -n "$_pat" ]] || continue`);
3434
+ lines.push(` for _f in "$_dir"/\${~_pat}(N.); do`);
3435
+ lines.push(` _out="\${_f#./}"`);
3436
+ lines.push(` [[ "$_out" == "$_cur"* ]] && __${fn}_add_path_candidate "$_out"`);
3437
+ lines.push(` done`);
3438
+ lines.push(` done`);
3439
+ lines.push(` elif (( _directive & ${CompletionDirective.DirectoryCompletion} )); then`);
3440
+ lines.push(` _files -/`);
3441
+ lines.push(` elif (( _directive & ${CompletionDirective.FileCompletion} )); then`);
3442
+ lines.push(` _files`);
3443
+ lines.push(` elif (( \${#_vals[@]} == 0 )) && ! (( _directive & ${CompletionDirective.NoFileCompletion} )); then`);
3444
+ lines.push(` _files`);
3445
+ lines.push(` fi`);
3446
+ lines.push(`}`);
3447
+ lines.push(``);
3448
+ lines.push(`${completionFn}() {`);
3449
+ lines.push(` emulate -L zsh`);
3450
+ lines.push(` setopt local_options no_aliases`);
3451
+ lines.push(` local _bin _out _node_compile_cache _worker _bundled_worker _sig`);
3452
+ lines.push(` _bin="$(__${fn}_resolve_bin)"`);
3453
+ lines.push(` [[ -n "$_bin" ]] || return 1`);
3454
+ lines.push(` _node_compile_cache="$(__${fn}_node_compile_cache_dir)"`);
3455
+ lines.push(` _bundled_worker="$(__${fn}_bundled_worker_path "$_bin" "$_node_compile_cache")"`);
3456
+ lines.push(` if [[ -n "$_bundled_worker" ]] && __${fn}_load_worker "$_bundled_worker"; then`);
3457
+ lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions "$@"`);
3458
+ lines.push(` return 0`);
3459
+ lines.push(` fi`);
3460
+ lines.push(` _worker="$(__${fn}_static_worker_path)"`);
3461
+ lines.push(` _sig="$(__${fn}_bin_sig "$_bin")"`);
3462
+ lines.push(` if [[ -n "$_worker" && -n "$_sig" ]]; then`);
3463
+ lines.push(` if ! __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"; then`);
3464
+ lines.push(` mkdir -p "\${_worker%/*}" 2>/dev/null`);
3465
+ lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${envName}="$_bin" "$_bin" __refresh-completion zsh "$_worker" --static --worker 2>/dev/null`);
3466
+ lines.push(` fi`);
3467
+ lines.push(` if __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin" && __${fn}_load_worker "$_worker"; then`);
3468
+ lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions "$@"`);
3469
+ lines.push(` return 0`);
3470
+ lines.push(` fi`);
3471
+ lines.push(` fi`);
3472
+ lines.push(` _out=$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __complete --shell zsh -- "\${(@)words[2,CURRENT]}" 2>/dev/null) || return 1`);
3473
+ lines.push(` __${fn}_apply_dynamic_output "$_out"`);
3474
+ lines.push(`}`);
3475
+ lines.push(``);
3476
+ lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
3477
+ lines.push(``);
3478
+ lines.push(`if [[ ${autoloadCheck} ]]; then`);
3479
+ lines.push(` ${completionFn} "$@"`);
3480
+ lines.push(`else`);
3481
+ lines.push(` compdef ${completionFn} ${programName}`);
3482
+ lines.push(`fi`);
3483
+ lines.push(``);
3484
+ return {
3485
+ script: lines.join("\n"),
3486
+ shell: "zsh",
3487
+ installInstructions: `# To enable dispatcher zsh completions, add this to your ~/.zshrc after compinit:
3488
+ eval "$(${programName} completion zsh)"
3489
+
3490
+ # To save the dispatcher script in your fpath:
3491
+ mkdir -p ~/.zsh/completions
3492
+ ${programName} completion zsh > ~/.zsh/completions/_${programName}
3493
+
3494
+ # Make sure your ~/.zshrc includes the fpath line before compinit:
3495
+ fpath=(~/.zsh/completions $fpath)
3496
+ autoload -Uz compinit && compinit
3497
+
3498
+ # To generate the older static script:
3499
+ ${programName} completion zsh --static`
3500
+ };
3501
+ }
3502
+ function fishDispatcher(_command, options) {
3503
+ const { programName } = options;
3504
+ const fn = sanitize(programName);
3505
+ const workerFn = `${fn}_worker`;
3506
+ const workerBinEnvName = binEnvVarName(workerFn);
3507
+ const envName = binEnvVarName(fn);
3508
+ const programLookup = shSingleQuote(programName);
3509
+ const compileCacheDefault = fishCacheDefault(hardcodedCompileCacheDir(options.cacheDir), compileCacheSuffix(programName));
3510
+ const workerPathDefault = fishCacheDefault(hardcodedWorkerPath(options.cacheDir, "fish"), workerPathSuffix(programName, "fish"));
3511
+ const workerRelList = fishWorkerRelList(options);
3512
+ const canQueryBundledWorkerPath = bundledWorkerPathCommandEnabled(options);
3513
+ const lines = [];
3514
+ lines.push(...buildHeaderLines({
3515
+ programName,
3516
+ shell: "fish",
3517
+ binPath: options.binPath,
3518
+ programVersion: options.programVersion
3519
+ }));
3520
+ lines.push(`# Generated by politty`);
3521
+ lines.push(`# politty-completion-mode: dispatcher`);
3522
+ lines.push(``);
3523
+ lines.push(`function __${fn}_resolve_bin`);
3524
+ lines.push(` if set -q ${envName}; and test -n "$${envName}"`);
3525
+ lines.push(` printf '%s\\n' "$${envName}"`);
3526
+ lines.push(` return 0`);
3527
+ lines.push(` end`);
3528
+ lines.push(` set -l _bin (command -v ${programLookup})`);
3529
+ lines.push(` test -n "$_bin"; and test -x "$_bin"; and not test -d "$_bin"; and printf '%s\\n' "$_bin"`);
3530
+ lines.push(`end`);
3531
+ lines.push(``);
3532
+ lines.push(`function __${fn}_node_compile_cache_dir`);
3533
+ lines.push(` if set -q NODE_COMPILE_CACHE; and test -n "$NODE_COMPILE_CACHE"`);
3534
+ lines.push(` printf '%s\\n' "$NODE_COMPILE_CACHE"`);
3535
+ lines.push(` return 0`);
3536
+ lines.push(` end`);
3537
+ lines.push(compileCacheDefault);
3538
+ lines.push(`end`);
3539
+ lines.push(``);
3540
+ lines.push(`function __${fn}_static_worker_path`);
3541
+ lines.push(workerPathDefault);
3542
+ lines.push(`end`);
3543
+ lines.push(``);
3544
+ lines.push(`function __${fn}_worker_file_sig`);
3545
+ lines.push(` set -l _worker $argv[1]`);
3546
+ lines.push(` set -l _sig ${statSigExpr("$_worker", {
3547
+ shell: "fish",
3548
+ withSize: true
3549
+ })}`);
3550
+ lines.push(` test -n "$_sig"; and printf '%s\\n' "$_sig"`);
3551
+ lines.push(`end`);
3552
+ lines.push(``);
3553
+ lines.push(`function __${fn}_is_worker_file`);
3554
+ lines.push(` set -l _worker $argv[1]`);
3555
+ lines.push(` test -f "$_worker"; or return 1`);
3556
+ lines.push(` set -l _head (head -n 24 "$_worker" 2>/dev/null)`);
3557
+ lines.push(` string match -q -- "# politty-completion-version: 1" $_head; or return 1`);
3558
+ lines.push(` string match -q -- "# program: ${programName}" $_head; or return 1`);
3559
+ lines.push(` string match -q -- "# shell: fish" $_head; or return 1`);
3560
+ lines.push(` string match -q -- "# politty-completion-mode: worker" $_head; or return 1`);
3561
+ lines.push(` string match -q -- "# politty-completion-worker: true" $_head; or return 1`);
3562
+ lines.push(`end`);
3563
+ lines.push(``);
3564
+ lines.push(`function __${fn}_load_worker`);
3565
+ lines.push(` set -l _worker $argv[1]`);
3566
+ lines.push(` __${fn}_is_worker_file "$_worker"; or return 1`);
3567
+ lines.push(` set -l _sig (__${fn}_worker_file_sig "$_worker")`);
3568
+ lines.push(` test -n "$_sig"; or return 1`);
3569
+ lines.push(` set -l _key "$_worker:$_sig"`);
3570
+ lines.push(` if not set -q __${fn}_loaded_worker_key; or test "$__${fn}_loaded_worker_key" != "$_key"`);
3571
+ lines.push(` source "$_worker" 2>/dev/null; or return 1`);
3572
+ lines.push(` set -g __${fn}_loaded_worker_key "$_key"`);
3573
+ lines.push(` end`);
3574
+ lines.push(` functions -q __fish_${workerFn}_complete`);
3575
+ lines.push(`end`);
3576
+ lines.push(``);
3577
+ lines.push(`function __${fn}_realpath`);
3578
+ lines.push(` set -l _path $argv[1]`);
3579
+ lines.push(` set -l _old_pwd (pwd)`);
3580
+ lines.push(` set -l _limit 0`);
3581
+ lines.push(` while test -L "$_path"; and test $_limit -lt 40`);
3582
+ lines.push(` set -l _dir (dirname "$_path")`);
3583
+ lines.push(` if not cd "$_dir" 2>/dev/null`);
3584
+ lines.push(` cd "$_old_pwd" 2>/dev/null`);
3585
+ lines.push(` return 1`);
3586
+ lines.push(` end`);
3587
+ lines.push(` set -l _absdir (pwd -P)`);
3588
+ lines.push(` cd "$_old_pwd" 2>/dev/null`);
3589
+ lines.push(` test -n "$_absdir"; or return 1`);
3590
+ lines.push(` set -l _target (readlink "$_path")`);
3591
+ lines.push(` test -n "$_target"; or return 1`);
3592
+ lines.push(` if string match -q '/*' -- "$_target"`);
3593
+ lines.push(` set _path "$_target"`);
3594
+ lines.push(` else`);
3595
+ lines.push(` set _path "$_absdir/$_target"`);
3596
+ lines.push(` end`);
3597
+ lines.push(` set _limit (math $_limit + 1)`);
3598
+ lines.push(` end`);
3599
+ lines.push(` set -l _dir (dirname "$_path")`);
3600
+ lines.push(` if not cd "$_dir" 2>/dev/null`);
3601
+ lines.push(` cd "$_old_pwd" 2>/dev/null`);
3602
+ lines.push(` return 1`);
3603
+ lines.push(` end`);
3604
+ lines.push(` set -l _absdir (pwd -P)`);
3605
+ lines.push(` cd "$_old_pwd" 2>/dev/null`);
3606
+ lines.push(` test -n "$_absdir"; or return 1`);
3607
+ lines.push(` set -l _base (basename "$_path")`);
3608
+ lines.push(` printf '%s\\n' "$_absdir/$_base"`);
3609
+ lines.push(`end`);
3610
+ lines.push(``);
3611
+ lines.push(`function __${fn}_worker_from_dir`);
3612
+ lines.push(` set -l _dir $argv[1]`);
3613
+ if (workerRelList.length > 0) {
3614
+ lines.push(` for _rel in ${workerRelList}`);
3615
+ lines.push(` if string match -q '/*' -- "$_rel"`);
3616
+ lines.push(` set _candidate "$_rel"`);
3617
+ lines.push(` else`);
3618
+ lines.push(` set _candidate "$_dir/$_rel"`);
3619
+ lines.push(` end`);
3620
+ lines.push(` if __${fn}_is_worker_file "$_candidate"`);
3621
+ lines.push(` printf '%s\\n' "$_candidate"`);
3622
+ lines.push(` return 0`);
3623
+ lines.push(` end`);
3624
+ lines.push(` end`);
3625
+ }
3626
+ lines.push(` return 1`);
3627
+ lines.push(`end`);
3628
+ lines.push(``);
3629
+ lines.push(`function __${fn}_cmd_shim_target`);
3630
+ lines.push(` sed -n 's/^# cmd-shim-target=//p' "$argv[1]" 2>/dev/null | tail -n 1`);
3631
+ lines.push(`end`);
3632
+ lines.push(``);
3633
+ lines.push(`function __${fn}_bundled_worker_path`);
3634
+ lines.push(` set -l _bin $argv[1]`);
3635
+ lines.push(` set -l _node_compile_cache $argv[2]`);
3636
+ lines.push(` set -l _real (__${fn}_realpath "$_bin" 2>/dev/null)`);
3637
+ lines.push(` if test -n "$_real"`);
3638
+ lines.push(` set -l _dir (dirname "$_real")`);
3639
+ lines.push(` __${fn}_worker_from_dir "$_dir"; and return 0`);
3640
+ lines.push(` end`);
3641
+ lines.push(` set -l _dir (dirname "$_bin")`);
3642
+ lines.push(` __${fn}_worker_from_dir "$_dir"; and return 0`);
3643
+ lines.push(` set -l _target (__${fn}_cmd_shim_target "$_bin")`);
3644
+ lines.push(` if test -n "$_target"`);
3645
+ lines.push(` set _real (__${fn}_realpath "$_target" 2>/dev/null)`);
3646
+ lines.push(` test -n "$_real"; or set _real "$_target"`);
3647
+ lines.push(` set _dir (dirname "$_real")`);
3648
+ lines.push(` __${fn}_worker_from_dir "$_dir"; and return 0`);
3649
+ lines.push(` end`);
3650
+ if (canQueryBundledWorkerPath) {
3651
+ lines.push(` if set -q __${fn}_queried_bin; and test "$__${fn}_queried_bin" = "$_bin"; and test -n "$__${fn}_queried_worker"; and __${fn}_is_worker_file "$__${fn}_queried_worker"`);
3652
+ lines.push(` printf '%s\\n' "$__${fn}_queried_worker"`);
3653
+ lines.push(` return 0`);
3654
+ lines.push(` end`);
3655
+ lines.push(` set -l _reported (env NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __completion-worker-path fish 2>/dev/null)`);
3656
+ lines.push(` if test -n "$_reported"; and __${fn}_is_worker_file "$_reported"`);
3657
+ lines.push(` set -g __${fn}_queried_bin "$_bin"`);
3658
+ lines.push(` set -g __${fn}_queried_worker "$_reported"`);
3659
+ lines.push(` printf '%s\\n' "$_reported"`);
3660
+ lines.push(` return 0`);
3661
+ lines.push(` end`);
3662
+ }
3663
+ lines.push(` return 1`);
3664
+ lines.push(`end`);
3665
+ lines.push(``);
3666
+ lines.push(`function __${fn}_bin_sig`);
3667
+ lines.push(` set -l _bin $argv[1]`);
3668
+ lines.push(` set -l _sig ${statSigExpr("$_bin", { shell: "fish" })}`);
3669
+ lines.push(` test -n "$_sig"; and printf '%s\\n' "$_sig"`);
3670
+ lines.push(`end`);
3671
+ lines.push(``);
3672
+ lines.push(`function __${fn}_worker_matches_bin`);
3673
+ lines.push(` set -l _worker $argv[1]`);
3674
+ lines.push(` set -l _sig $argv[2]`);
3675
+ lines.push(` set -l _bin $argv[3]`);
3676
+ lines.push(` test -f "$_worker"; or return 1`);
3677
+ lines.push(` set -l _head (head -n 12 "$_worker" 2>/dev/null)`);
3678
+ lines.push(` contains -- "# politty-bin-sig: $_sig" $_head; or return 1`);
3679
+ lines.push(` contains -- "# politty-bin-path: $_bin" $_head; or return 1`);
3680
+ lines.push(`end`);
3681
+ lines.push(``);
3682
+ lines.push(`function __${fn}_apply_dynamic_output`);
3683
+ lines.push(` set -l _cur $argv[1]`);
3684
+ lines.push(` set -l _directive 0`);
3685
+ lines.push(` set -l _emitted 0`);
3686
+ lines.push(` set -l _exts`);
3687
+ lines.push(` set -l _matchers`);
3688
+ lines.push(` set -l _lines`);
3689
+ lines.push(` while read -l _l`);
3690
+ lines.push(` set -a _lines $_l`);
3691
+ lines.push(` end`);
3692
+ lines.push(` set -l _n (count $_lines)`);
3693
+ lines.push(` if test $_n -ge 1; and string match -qr '^:[0-9]+' -- $_lines[$_n]`);
3694
+ lines.push(` set -l _fields (string split \\t -- $_lines[$_n])`);
3695
+ lines.push(` set -e _lines[$_n]`);
3696
+ lines.push(` set _directive (string sub -s 2 -- $_fields[1])`);
3697
+ lines.push(` for _f in $_fields[2..-1]`);
3698
+ lines.push(` if string match -q '@ext:*' -- $_f`);
3699
+ lines.push(` set _exts (string split , -- (string sub -s 6 -- $_f))`);
3700
+ lines.push(` else if string match -q '@matcher:*' -- $_f`);
3701
+ lines.push(` set _matchers (string split , -- (string sub -s 10 -- $_f))`);
3702
+ lines.push(` end`);
3703
+ lines.push(` end`);
3704
+ lines.push(` end`);
3705
+ lines.push(` for _l in $_lines`);
3706
+ lines.push(` if test -n "$_l"`);
3707
+ lines.push(` printf '%s\\n' $_l`);
3708
+ lines.push(` set _emitted 1`);
3709
+ lines.push(` end`);
3710
+ lines.push(` end`);
3711
+ lines.push(` if test (count $_exts) -gt 0; or test (count $_matchers) -gt 0`);
3712
+ lines.push(` __fish_complete_directories "$_cur"`);
3713
+ lines.push(` set -l _dir ""`);
3714
+ lines.push(` if string match -q '*/*' "$_cur"`);
3715
+ lines.push(` set _dir (string replace -r '[^/]*$' '' "$_cur")`);
3716
+ lines.push(` end`);
3717
+ lines.push(` for _f in "$_cur"*`);
3718
+ lines.push(` test -f "$_f"; or continue`);
3719
+ lines.push(` for _ext in $_exts`);
3720
+ lines.push(` if string match -q -- "*.$_ext" "$_f"`);
3721
+ lines.push(` printf '%s\\n' "$_f"`);
3722
+ lines.push(` break`);
3723
+ lines.push(` end`);
3724
+ lines.push(` end`);
3725
+ lines.push(` end`);
3726
+ lines.push(` for _pat in $_matchers`);
3727
+ lines.push(` for _f in "$_dir"$_pat`);
3728
+ lines.push(` test -f "$_f"; and string match -q "$_cur*" "$_f"; and printf '%s\\n' "$_f"`);
3729
+ lines.push(` end`);
3730
+ lines.push(` end`);
3731
+ lines.push(` else if test (math "bitand($_directive, ${CompletionDirective.DirectoryCompletion})") -ne 0`);
3732
+ lines.push(` __fish_complete_directories "$_cur"`);
3733
+ lines.push(` else if test (math "bitand($_directive, ${CompletionDirective.FileCompletion})") -ne 0`);
3734
+ lines.push(` __fish_complete_path "$_cur"`);
3735
+ lines.push(` else if test $_emitted -eq 0; and test (math "bitand($_directive, ${CompletionDirective.NoFileCompletion})") -eq 0`);
3736
+ lines.push(` __fish_complete_path "$_cur"`);
3737
+ lines.push(` end`);
3738
+ lines.push(`end`);
3739
+ lines.push(``);
3740
+ lines.push(`function __fish_${fn}_complete`);
3741
+ lines.push(` set -l _bin (__${fn}_resolve_bin)`);
3742
+ lines.push(` test -n "$_bin"; or return 0`);
3743
+ lines.push(` set -l _node_compile_cache (__${fn}_node_compile_cache_dir)`);
3744
+ lines.push(` set -l _bundled_worker (__${fn}_bundled_worker_path "$_bin" "$_node_compile_cache")`);
3745
+ lines.push(` if test -n "$_bundled_worker"; and __${fn}_load_worker "$_bundled_worker"`);
3746
+ lines.push(` set -lx ${workerBinEnvName} "$_bin"`);
3747
+ lines.push(` set -lx NODE_COMPILE_CACHE "$_node_compile_cache"`);
3748
+ lines.push(` __fish_${workerFn}_complete`);
3749
+ lines.push(` return 0`);
3750
+ lines.push(` end`);
3751
+ lines.push(` set -l _worker (__${fn}_static_worker_path)`);
3752
+ lines.push(` set -l _sig (__${fn}_bin_sig "$_bin")`);
3753
+ lines.push(` if test -n "$_worker"; and test -n "$_sig"`);
3754
+ lines.push(` if not __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"`);
3755
+ lines.push(` mkdir -p (dirname "$_worker") 2>/dev/null`);
3756
+ lines.push(` env NODE_COMPILE_CACHE="$_node_compile_cache" ${envName}="$_bin" $_bin __refresh-completion fish "$_worker" --static --worker 2>/dev/null`);
3757
+ lines.push(` end`);
3758
+ lines.push(` if __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"`);
3759
+ lines.push(` if __${fn}_load_worker "$_worker"`);
3760
+ lines.push(` set -lx ${workerBinEnvName} "$_bin"`);
3761
+ lines.push(` set -lx NODE_COMPILE_CACHE "$_node_compile_cache"`);
3762
+ lines.push(` __fish_${workerFn}_complete`);
3763
+ lines.push(` return 0`);
3764
+ lines.push(` end`);
3765
+ lines.push(` end`);
3766
+ lines.push(` end`);
3767
+ lines.push(` set -l _args (commandline -opc)`);
3768
+ lines.push(` set -l _cur (commandline -ct)`);
3769
+ lines.push(` if test (count $_args) -gt 0`);
3770
+ lines.push(` set _args $_args[2..]`);
3771
+ lines.push(` end`);
3772
+ lines.push(` env NODE_COMPILE_CACHE="$_node_compile_cache" $_bin __complete --shell fish -- $_args "$_cur" 2>/dev/null | __${fn}_apply_dynamic_output "$_cur"`);
3773
+ lines.push(`end`);
3774
+ lines.push(``);
3775
+ lines.push(`complete -e -c ${programName}`);
3776
+ lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
3777
+ lines.push(``);
3778
+ return {
3779
+ script: lines.join("\n"),
3780
+ shell: "fish",
3781
+ installInstructions: `# To enable dispatcher fish completions, run:
3782
+ ${programName} completion fish --install
3783
+
3784
+ # To generate the older static script:
3785
+ ${programName} completion fish --static`
3786
+ };
3787
+ }
3788
+ function generateDispatcherCompletion(command, options) {
3789
+ extractCompletionData(command, options.programName, options.globalArgsSchema, true);
3790
+ switch (options.shell) {
3791
+ case "bash": return bashDispatcher(command, options);
3792
+ case "zsh": return zshDispatcher(command, options);
3793
+ case "fish": return fishDispatcher(command, options);
3794
+ default: throw new Error(`Unsupported shell: ${options.shell}`);
3795
+ }
3796
+ }
3797
+
3798
+ //#endregion
3799
+ //#region src/completion/dynamic/shell-formatter.ts
3800
+ /**
3801
+ * Format completion candidates for the specified shell
3802
+ *
3803
+ * @returns Shell-ready output string (lines separated by newline, last line is :directive)
3804
+ */
3805
+ function formatForShell(result, options) {
3806
+ switch (options.shell) {
3807
+ case "bash": return formatForBash(result, options);
3808
+ case "zsh": return formatForZsh(result, options);
3809
+ case "fish": return formatForFish(result, options);
3810
+ }
3811
+ }
3812
+ /**
3813
+ * Append extension metadata and directive to output lines
3814
+ */
3815
+ function appendMetadata(lines, result) {
3816
+ let directiveLine = `:${result.directive}`;
3817
+ if (result.fileExtensions && result.fileExtensions.length > 0) directiveLine += `\t@ext:${result.fileExtensions.join(",")}`;
3818
+ if (result.fileMatchers && result.fileMatchers.length > 0) directiveLine += `\t@matcher:${result.fileMatchers.join(",")}`;
3819
+ lines.push(directiveLine);
3820
+ }
3821
+ /**
3822
+ * Format for bash
3823
+ *
3824
+ * - Pre-filters candidates by currentWord prefix (replaces compgen -W)
3825
+ * - Handles --opt=value inline values by prepending prefix
3826
+ * - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
3827
+ * - Last line: :directive
3828
+ */
3829
+ function formatForBash(result, options) {
3830
+ const lines = ((result.directive & CompletionDirective.FilterPrefix) !== 0 && options.currentWord ? result.candidates.filter((c) => c.value.startsWith(options.currentWord)) : result.candidates).map((c) => options.inlinePrefix ? `${options.inlinePrefix}${c.value}` : c.value);
3831
+ appendMetadata(lines, result);
3832
+ return lines.join("\n");
3833
+ }
3834
+ /**
3835
+ * Format for zsh
3836
+ *
3837
+ * - Outputs value:description pairs for _describe
3838
+ * - Colons in values/descriptions are escaped with backslash
3839
+ * - Last line: :directive
3840
+ */
3841
+ function formatForZsh(result, _options) {
3842
+ const lines = result.candidates.map((c) => {
3843
+ const escapedValue = c.value.replace(/:/g, "\\:");
3844
+ if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
3845
+ return escapedValue;
3846
+ });
3847
+ appendMetadata(lines, result);
3848
+ return lines.join("\n");
3849
+ }
3850
+ /**
3851
+ * Format for fish
3852
+ *
3853
+ * - Outputs value\tdescription pairs
3854
+ * - Last line: :directive
3855
+ */
3856
+ function formatForFish(result, _options) {
3857
+ const lines = result.candidates.map((c) => {
3858
+ if (c.description) return `${c.value}\t${c.description}`;
3859
+ return c.value;
3860
+ });
3861
+ appendMetadata(lines, result);
3862
+ return lines.join("\n");
3863
+ }
3864
+
3865
+ //#endregion
3866
+ //#region src/completion/dynamic/complete-command.ts
3867
+ /**
3868
+ * Dynamic completion command implementation
3869
+ *
3870
+ * This creates a hidden `__complete` command that outputs completion candidates
3871
+ * for shell scripts to consume. Usage:
3872
+ *
3873
+ * mycli __complete --shell bash -- build --fo
3874
+ * mycli __complete --shell zsh -- plugin add
3875
+ *
3876
+ * Output format depends on the target shell:
3877
+ * bash: plain values (pre-filtered by prefix), last line :directive
3878
+ * zsh: value:description pairs, last line :directive
3879
+ * fish: value\tdescription pairs, last line :directive
3880
+ */
3881
+ /**
3882
+ * Schema for the __complete command
3883
+ */
3884
+ const completeArgsSchema = zod.z.object({
3885
+ shell: require_schema_extractor.arg(zod.z.enum([
3886
+ "bash",
3887
+ "zsh",
3888
+ "fish"
3889
+ ]), { description: "Target shell for output formatting" }),
3890
+ args: require_schema_extractor.arg(zod.z.array(zod.z.string()).default([]), {
3891
+ positional: true,
3892
+ description: "Arguments to complete",
3893
+ variadic: true
3894
+ })
3895
+ });
3896
+ /**
3897
+ * Create the dynamic completion command
3898
+ *
3899
+ * @param rootCommand - The root command to generate completions for
3900
+ * @param programName - The program name (optional, defaults to rootCommand.name)
3901
+ * @param globalArgsSchema - Global args schema. Forwarded to
3902
+ * `parseCompletionContext` so resolvers attached to global options remain
3903
+ * reachable at every subcommand level.
3904
+ * @returns A command that outputs completion candidates
3905
+ */
3906
+ function createDynamicCompleteCommand(rootCommand, _programName, globalArgsSchema) {
3907
+ return defineCommand({
3908
+ name: "__complete",
3909
+ args: completeArgsSchema,
3910
+ async run(args) {
3911
+ const context = parseCompletionContext(args.args, rootCommand, globalArgsSchema);
3912
+ const inlinePrefix = context.completionType === "option-value" && context.targetOption ? detectInlineOptionPrefix(context.currentWord) : void 0;
3913
+ const effectiveWord = inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord;
3914
+ const output = formatForShell(await generateCandidates(context, { shell: args.shell }), {
3915
+ shell: args.shell,
3916
+ currentWord: effectiveWord,
3917
+ inlinePrefix
3918
+ });
3919
+ console.log(output);
3920
+ }
3921
+ });
3922
+ }
3923
+ /**
3924
+ * Check if a command tree contains the __complete command
3925
+ */
3926
+ function hasCompleteCommand(command) {
3927
+ return Boolean(command.subCommands?.["__complete"]);
3928
+ }
3929
+
3930
+ //#endregion
3931
+ //#region src/completion/fish.ts
3932
+ /** Escape shell-special characters for fish double-quoted strings */
3933
+ function escapeDesc$1(s) {
3934
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
3935
+ }
3936
+ /**
3937
+ * Escape a fish `switch` case pattern. Fish's `case` interprets its
3938
+ * arguments as globs even when double-quoted, so glob metacharacters
3939
+ * (`*`, `?`, `[`, `]`) must be backslash-escaped to keep the comparison
3940
+ * literal — otherwise a key like `prod*` would also match a runtime
3941
+ * value of `production`. Quote/dollar/backslash are escaped first so the
3942
+ * resulting string remains valid inside a double-quoted literal.
3943
+ */
3944
+ function fishCaseEscape(s) {
3945
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/\*/g, "\\*").replace(/\?/g, "\\?").replace(/\[/g, "\\[").replace(/]/g, "\\]");
3946
+ }
3947
+ /**
3948
+ * Generate fish value completion lines for a ValueCompletion spec.
3949
+ * Each line outputs candidates via echo (tab-separated value\tdescription).
3950
+ *
3951
+ * `location` is required for the expand variant (carries fieldName +
3952
+ * isArrayOption); other variants ignore it.
3953
+ */
3954
+ function fishValueLines(vc, fn, location) {
3955
+ if (!vc) return [];
3956
+ switch (vc.type) {
3957
+ case "expand": {
3958
+ if (!location) throw new Error("fishValueLines: expand variant requires a location");
3959
+ const depExpr = (d) => {
3960
+ const safe = sanitize(d.name);
3961
+ return d.isGlobal ? `$_global_arg_values_${safe}` : `$_arg_values_${safe}`;
3962
+ };
3963
+ const depKey = location.resolvedDeps.map((d) => `"${depExpr(d)}"`).join(`\\x1f`);
3964
+ const bucket = sanitize(location.fieldName);
3965
+ const bucketList = location.isGlobal ? `$_global_used_field_keys_${bucket}` : `$_used_field_keys_${bucket}`;
3966
+ const out = [`switch ${depKey}`];
3967
+ for (const entry of vc.table) {
3968
+ const casePattern = entry.key.map((k) => `"${fishCaseEscape(k)}"`).join(`\\x1f`);
3969
+ out.push(` case ${casePattern}`);
3970
+ const keyOnlyLines = [];
3971
+ const fullLines = [];
3972
+ const seenKeys = /* @__PURE__ */ new Set();
3973
+ const printfLine = (value, description) => description ? `printf '%s\\t%s\\n' "${escapeDesc$1(value)}" "${escapeDesc$1(description)}"` : `printf '%s\\n' "${escapeDesc$1(value)}"`;
3974
+ const wrapWithDedup = (echoLine, keyPart) => location.isArrayOption && keyPart.length > 0 ? [
3975
+ ` if not contains -- "${escapeDesc$1(keyPart)}" ${bucketList}`,
3976
+ ` ${echoLine}`,
3977
+ ` end`
3978
+ ] : [` ${echoLine}`];
3979
+ for (const c of entry.candidates) {
3980
+ const eqIdx = c.value.indexOf("=");
3981
+ const keyPart = eqIdx > 0 ? c.value.slice(0, eqIdx) : "";
3982
+ const echoLine = printfLine(c.value, c.description);
3983
+ if (!(keyPart.length > 0 && c.value.length === eqIdx + 1)) fullLines.push(...wrapWithDedup(echoLine, keyPart));
3984
+ if (keyPart.length === 0) keyOnlyLines.push(` ${echoLine}`);
3985
+ else if (!seenKeys.has(keyPart)) {
3986
+ seenKeys.add(keyPart);
3987
+ keyOnlyLines.push(...wrapWithDedup(printfLine(`${keyPart}=`, c.description), keyPart));
3988
+ }
2720
3989
  }
2721
3990
  if (fullLines.length !== keyOnlyLines.length || fullLines.some((l, i) => l !== keyOnlyLines[i])) {
2722
3991
  out.push(` if string match -q '*=*' -- "$_cur"`);
@@ -2742,6 +4011,7 @@ function fishValueLines(vc, fn, location) {
2742
4011
  `end`
2743
4012
  ];
2744
4013
  case "none": return [];
4014
+ case "runtime-expand": return [];
2745
4015
  }
2746
4016
  }
2747
4017
  /** Generate fish matcher-filtered file completion */
@@ -2763,9 +4033,13 @@ function fishMatcherLines(patterns) {
2763
4033
  function fishExtensionLines(extensions) {
2764
4034
  const lines = [];
2765
4035
  lines.push(`__fish_complete_directories "$_cur"`);
2766
- for (const ext of extensions) {
2767
- lines.push(`for _f in "$_cur"*.${ext}`);
2768
- lines.push(` test -f "$_f"; and echo "$_f"`);
4036
+ if (extensions.length > 0) {
4037
+ const extMatch = extensions.map((ext) => `string match -q -- "*.${ext}" "$_f"`).join("; or ");
4038
+ lines.push(`for _f in "$_cur"*`);
4039
+ lines.push(` test -f "$_f"; or continue`);
4040
+ lines.push(` if ${extMatch}`);
4041
+ lines.push(` echo "$_f"`);
4042
+ lines.push(` end`);
2769
4043
  lines.push(`end`);
2770
4044
  }
2771
4045
  return lines;
@@ -2802,6 +4076,27 @@ function positionalBlock$1(positionals, fn, options = []) {
2802
4076
  }
2803
4077
  return lines;
2804
4078
  }
4079
+ /**
4080
+ * Subcommand-name echoes. When the same node also has positionals, complete
4081
+ * subcommand names only while the cursor still prefixes one and fall through to
4082
+ * positional completion otherwise. Returns lines at base indentation; callers
4083
+ * re-indent for their handler depth.
4084
+ */
4085
+ function subOrPositionalLines$1(subItems, positionals, fn, options) {
4086
+ const echoSubs = subItems.map((s) => `echo "${s.name}\t${escapeDesc$1(s.description ?? "")}"`);
4087
+ if (positionals.length === 0) return echoSubs;
4088
+ return [
4089
+ `set -l _sub_match 0`,
4090
+ `for _sub_name in ${subItems.map((s) => `"${escapeDesc$1(s.name)}"`).join(" ")}`,
4091
+ ` test (string sub -l (string length -- "$_cur") -- "$_sub_name") = "$_cur"; and set _sub_match 1; and break`,
4092
+ `end`,
4093
+ `if test $_sub_match -eq 1`,
4094
+ ...echoSubs.map((l) => ` ${l}`),
4095
+ `else`,
4096
+ ...positionalBlock$1(positionals, fn, options),
4097
+ `end`
4098
+ ];
4099
+ }
2805
4100
  /** Generate available-option echo lines for fish */
2806
4101
  function availableOptionLines$1(options, fn) {
2807
4102
  const lines = [];
@@ -2857,11 +4152,10 @@ function generateSubHandler$1(sub, fn, path) {
2857
4152
  lines.push(...availableOptionLines$1(sub.options, fn));
2858
4153
  lines.push(` return`);
2859
4154
  lines.push(` end`);
2860
- if (visibleSubs.length > 0) for (const s of getSubNamesWithAliases(sub.subcommands)) {
2861
- const desc = escapeDesc$1(s.description ?? "");
2862
- lines.push(` echo "${s.name}\t${desc}"`);
2863
- }
2864
- else if (sub.positionals.length > 0) lines.push(...positionalBlock$1(sub.positionals, fn, sub.options));
4155
+ if (visibleSubs.length > 0) {
4156
+ const subItems = getSubNamesWithAliases(sub.subcommands);
4157
+ lines.push(...subOrPositionalLines$1(subItems, sub.positionals, fn, sub.options).map((l) => ` ${l}`));
4158
+ } else if (sub.positionals.length > 0) lines.push(...positionalBlock$1(sub.positionals, fn, sub.options));
2865
4159
  lines.push(`end`);
2866
4160
  lines.push(``);
2867
4161
  return lines;
@@ -2879,7 +4173,9 @@ function optTakesValueCases(sub, parentPath) {
2879
4173
  function generateFishCompletion(command, options) {
2880
4174
  const { programName } = options;
2881
4175
  const data = extractCompletionData(command, programName, options.globalArgsSchema);
2882
- const fn = sanitize(programName);
4176
+ const baseFn = sanitize(programName);
4177
+ const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
4178
+ const isWorker = options.staticWorker !== void 0;
2883
4179
  const root = data.command;
2884
4180
  const visibleSubs = getVisibleSubs(root.subcommands);
2885
4181
  const expandSpecs = collectExpandSpecs(root);
@@ -2894,27 +4190,34 @@ function generateFishCompletion(command, options) {
2894
4190
  binPath: options.binPath,
2895
4191
  programVersion: options.programVersion
2896
4192
  }));
4193
+ lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
4194
+ if (isWorker) lines.push(`# politty-completion-worker: true`);
2897
4195
  lines.push(`# Generated by politty`);
2898
4196
  lines.push(``);
2899
- const sig = computeBinSig(resolveBinPath(programName, options.binPath));
2900
- const refreshFn = `__${fn}_refresh_completion`;
2901
- lines.push(`function ${refreshFn} --no-scope-shadowing`);
2902
- lines.push(` set -l _bin (command -v ${programName})`);
2903
- lines.push(` test -z "$_bin"; and return 1`);
2904
- lines.push(` set -l _sig (stat -L -c '%Y' "$_bin" 2>/dev/null; or stat -L -f '%m' "$_bin" 2>/dev/null)`);
2905
- lines.push(` test "$_sig" = "${sig}"; and return 1`);
2906
- lines.push(` set -l _target (status current-filename)`);
2907
- lines.push(` test -n "$_target"; and test -f "$_target"; or return 1`);
2908
- lines.push(` "$_bin" __refresh-completion fish "$_target" 2>/dev/null`);
2909
- lines.push(` and source "$_target" 2>/dev/null`);
2910
- lines.push(` and return 0`);
2911
- lines.push(` return 1`);
2912
- lines.push(`end`);
2913
- lines.push(`${refreshFn}`);
2914
- lines.push(`set -l _politty_refreshed $status`);
2915
- lines.push(`functions -e ${refreshFn}`);
2916
- lines.push(`test $_politty_refreshed -eq 0; and return`);
2917
- lines.push(``);
4197
+ if (!isWorker) {
4198
+ const resolvedBinPath = resolveBinPath(programName, options.binPath);
4199
+ const sig = computeBinSig(resolvedBinPath);
4200
+ const refreshEnvName = binEnvVarName(baseFn);
4201
+ const refreshFn = `__${fn}_refresh_completion`;
4202
+ lines.push(`function ${refreshFn} --no-scope-shadowing`);
4203
+ lines.push(` set -l _bin $${refreshEnvName}`);
4204
+ lines.push(` test -z "$_bin"; and set _bin (command -v ${programName})`);
4205
+ lines.push(` test -z "$_bin"; and return 1`);
4206
+ lines.push(` set -l _sig ${statSigExpr("$_bin", { shell: "fish" })}`);
4207
+ lines.push(` test "$_sig" = "${sig}"; and test "$_bin" = "${escapeDesc$1(resolvedBinPath)}"; and return 1`);
4208
+ lines.push(` set -l _target (status current-filename)`);
4209
+ lines.push(` test -n "$_target"; and test -f "$_target"; or return 1`);
4210
+ lines.push(` "$_bin" __refresh-completion fish "$_target" --static 2>/dev/null`);
4211
+ lines.push(` and source "$_target" 2>/dev/null`);
4212
+ lines.push(` and return 0`);
4213
+ lines.push(` return 1`);
4214
+ lines.push(`end`);
4215
+ lines.push(`${refreshFn}`);
4216
+ lines.push(`set -l _politty_refreshed $status`);
4217
+ lines.push(`functions -e ${refreshFn}`);
4218
+ lines.push(`test $_politty_refreshed -eq 0; and return`);
4219
+ lines.push(``);
4220
+ }
2918
4221
  if (hasDynamicCompletion(root)) {
2919
4222
  lines.push(`function __${fn}_invoke_complete`);
2920
4223
  lines.push(` set -l _shell $argv[1]`);
@@ -3061,10 +4364,8 @@ function generateFishCompletion(command, options) {
3061
4364
  lines.push(...availableOptionLines$1(root.options, fn));
3062
4365
  if (visibleSubs.length > 0) {
3063
4366
  lines.push(` else`);
3064
- for (const s of getSubNamesWithAliases(root.subcommands)) {
3065
- const desc = escapeDesc$1(s.description ?? "");
3066
- lines.push(` echo "${s.name}\t${desc}"`);
3067
- }
4367
+ const subItems = getSubNamesWithAliases(root.subcommands);
4368
+ lines.push(...subOrPositionalLines$1(subItems, root.positionals, fn, root.options).map((l) => ` ${l}`));
3068
4369
  } else if (root.positionals.length > 0) {
3069
4370
  lines.push(` else`);
3070
4371
  lines.push(...positionalBlock$1(root.positionals, fn, root.options));
@@ -3173,17 +4474,19 @@ function generateFishCompletion(command, options) {
3173
4474
  lines.push(` end`);
3174
4475
  lines.push(`end`);
3175
4476
  lines.push(``);
3176
- lines.push(`# Clear existing completions`);
3177
- lines.push(`complete -e -c ${programName}`);
3178
- lines.push(``);
3179
- lines.push(`# Register completion`);
3180
- lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
4477
+ if (!isWorker) {
4478
+ lines.push(`# Clear existing completions`);
4479
+ lines.push(`complete -e -c ${programName}`);
4480
+ lines.push(``);
4481
+ lines.push(`# Register completion`);
4482
+ lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
4483
+ }
3181
4484
  lines.push(``);
3182
4485
  return {
3183
4486
  script: lines.join("\n"),
3184
4487
  shell: "fish",
3185
4488
  installInstructions: `# To enable auto-refreshing fish completions, run:
3186
- ${programName} completion fish --install`
4489
+ ${programName} completion fish --install --static`
3187
4490
  };
3188
4491
  }
3189
4492
 
@@ -3198,38 +4501,30 @@ ${programName} completion fish --install`
3198
4501
  * 1. Looks up the binary on $PATH.
3199
4502
  * 2. Reads its mtime.
3200
4503
  * 3. If the on-disk completion cache is missing or its
3201
- * `# politty-bin-sig:` header differs, regenerates the cache by
3202
- * spawning the binary once.
4504
+ * `# politty-bin-sig:` / `# politty-bin-path:` headers differ,
4505
+ * regenerates the cache by spawning the binary once.
3203
4506
  * 4. Sources the cache.
3204
4507
  *
3205
4508
  * All failure modes are silent no-ops so a broken / missing CLI never
3206
4509
  * blocks shell startup.
3207
4510
  */
3208
- /**
3209
- * Single-quote escape: `'` -> `'\''`. Inside single quotes the shell
3210
- * performs no expansion at all, so `$`, backticks, and `$(...)` are
3211
- * inert. Used for hardcoded paths because callers may sources them
3212
- * from env / config — we must not let metachars in the path execute as
3213
- * commands when the rc snippet is sourced.
3214
- */
3215
- function shSingleQuote$1(s) {
3216
- return `'${s.replace(/'/g, "'\\''")}'`;
3217
- }
3218
4511
  function bashCachePathExpr(programName, cacheDir, shell) {
3219
- if (cacheDir) return shSingleQuote$1(`${cacheDir}/completion.${shell}`);
4512
+ if (cacheDir) return shSingleQuote(`${cacheDir}/completion.${shell}`);
3220
4513
  return `"\${XDG_CACHE_HOME:-$HOME/.cache}/${programName}/completion.${shell}"`;
3221
4514
  }
3222
4515
  function generateBashLoader(opts) {
3223
4516
  const fn = sanitize(opts.programName);
4517
+ const envName = binEnvVarName(fn);
3224
4518
  const cache = bashCachePathExpr(opts.programName, opts.cacheDir, "bash");
3225
4519
  return `__${fn}_load_completion() {
3226
- local _bin _cache _sig _hdr
3227
- _bin=$(type -P ${opts.programName} 2>/dev/null)
4520
+ local _bin _cache _sig _sig_hdr _path_hdr
4521
+ _bin="\${${envName}:-$(type -P ${opts.programName} 2>/dev/null)}"
3228
4522
  [[ -n "$_bin" ]] || return 0
3229
4523
  _cache=${cache}
3230
4524
  _sig=$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null) || return 0
3231
- _hdr="# politty-bin-sig: $_sig"
3232
- if [[ ! -f "$_cache" ]] || ! head -5 "$_cache" 2>/dev/null | grep -qF "$_hdr"; then
4525
+ _sig_hdr="# politty-bin-sig: $_sig"
4526
+ _path_hdr="# politty-bin-path: $_bin"
4527
+ if [[ ! -f "$_cache" ]] || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_sig_hdr" || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_path_hdr"; then
3233
4528
  # Use the hidden __refresh-completion subcommand instead of
3234
4529
  # \`$_bin completion bash\`: the foreground completion command
3235
4530
  # is subject to user setup/cleanup/prompt and required
@@ -3251,17 +4546,19 @@ unset -f __${fn}_load_completion
3251
4546
  }
3252
4547
  function generateZshLoader(opts) {
3253
4548
  const fn = sanitize(opts.programName);
4549
+ const envName = binEnvVarName(fn);
3254
4550
  const cache = bashCachePathExpr(opts.programName, opts.cacheDir, "zsh");
3255
4551
  return `__${fn}_load_completion() {
3256
4552
  emulate -L zsh
3257
4553
  setopt local_options no_aliases
3258
- local _bin _cache _sig _hdr
3259
- _bin=$(whence -p ${opts.programName} 2>/dev/null)
4554
+ local _bin _cache _sig _sig_hdr _path_hdr
4555
+ _bin="\${${envName}:-$(whence -p ${opts.programName} 2>/dev/null)}"
3260
4556
  [[ -n "$_bin" ]] || return 0
3261
4557
  _cache=${cache}
3262
4558
  _sig=$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null) || return 0
3263
- _hdr="# politty-bin-sig: $_sig"
3264
- if [[ ! -f "$_cache" ]] || ! head -5 "$_cache" 2>/dev/null | grep -qF "$_hdr"; then
4559
+ _sig_hdr="# politty-bin-sig: $_sig"
4560
+ _path_hdr="# politty-bin-path: $_bin"
4561
+ if [[ ! -f "$_cache" ]] || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_sig_hdr" || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_path_hdr"; then
3265
4562
  # See bash loader for why we use __refresh-completion instead
3266
4563
  # of \`$_bin completion zsh\`.
3267
4564
  "$_bin" __refresh-completion zsh 2>/dev/null
@@ -3333,11 +4630,14 @@ function generateScript(ctx, shell) {
3333
4630
  return generateCompletion(ctx.rootCommand, {
3334
4631
  shell,
3335
4632
  programName: ctx.programName,
4633
+ mode: ctx.completionMode ?? "dispatcher",
3336
4634
  includeDescriptions: true,
3337
4635
  ...ctx.programVersion !== void 0 && { programVersion: ctx.programVersion },
3338
4636
  ...ctx.binPath !== void 0 && { binPath: ctx.binPath },
3339
4637
  ...ctx.cacheDir !== void 0 && { cacheDir: ctx.cacheDir },
3340
- ...ctx.globalArgsSchema !== void 0 && { globalArgsSchema: ctx.globalArgsSchema }
4638
+ ...ctx.globalArgsSchema !== void 0 && { globalArgsSchema: ctx.globalArgsSchema },
4639
+ ...ctx.bundledWorker !== void 0 && { bundledWorker: ctx.bundledWorker },
4640
+ ...ctx.staticWorker !== void 0 && { staticWorker: ctx.staticWorker }
3341
4641
  }).script;
3342
4642
  }
3343
4643
  /** Write the script for `shell` to its install path. Returns the path. */
@@ -3360,11 +4660,30 @@ function readCachedSig(path) {
3360
4660
  return null;
3361
4661
  }
3362
4662
  }
4663
+ function readCachedMode(path) {
4664
+ try {
4665
+ if (!(0, node_fs.existsSync)(path)) return void 0;
4666
+ const m = (0, node_fs.readFileSync)(path, "utf8").split("\n", 10).join("\n").match(/^# politty-completion-mode: (dispatcher|static)$/m);
4667
+ if (m) return m[1];
4668
+ return;
4669
+ } catch {
4670
+ return;
4671
+ }
4672
+ }
4673
+ function readCachedBinPath(path) {
4674
+ try {
4675
+ if (!(0, node_fs.existsSync)(path)) return null;
4676
+ const m = (0, node_fs.readFileSync)(path, "utf8").split("\n", 10).join("\n").match(/^# politty-bin-path: (.*)$/m);
4677
+ return m ? m[1] : null;
4678
+ } catch {
4679
+ return null;
4680
+ }
4681
+ }
3363
4682
  function isManagedTarget(path, programName, shell) {
3364
4683
  try {
3365
4684
  if (!(0, node_fs.existsSync)(path)) return false;
3366
- const head = (0, node_fs.readFileSync)(path, "utf8").split("\n", 8).join("\n");
3367
- return /^# politty-completion-version: \S+/m.test(head) && head.includes(`# program: ${programName}`) && head.includes(`# shell: ${shell}`);
4685
+ const lines = (0, node_fs.readFileSync)(path, "utf8").split("\n", 8).map((line) => line.trimEnd());
4686
+ return lines.some((line) => /^# politty-completion-version: \S+$/.test(line)) && lines.includes(`# program: ${programName}`) && lines.includes(`# shell: ${shell}`);
3368
4687
  } catch {
3369
4688
  return false;
3370
4689
  }
@@ -3384,8 +4703,9 @@ function isManagedTarget(path, programName, shell) {
3384
4703
  */
3385
4704
  function refreshIfStale(ctx, shell) {
3386
4705
  try {
3387
- const target = ctx.targetPath ? (0, node_fs.realpathSync)(ctx.targetPath) : installPath(ctx.programName, shell, ctx.cacheDir);
3388
- if (ctx.targetPath && !isManagedTarget(target, ctx.programName, shell)) return;
4706
+ const target = ctx.targetPath ? (0, node_fs.existsSync)(ctx.targetPath) ? (0, node_fs.realpathSync)(ctx.targetPath) : ctx.targetPath : installPath(ctx.programName, shell, ctx.cacheDir);
4707
+ if (ctx.targetPath && (0, node_fs.existsSync)(target) && !isManagedTarget(target, ctx.programName, shell)) return;
4708
+ if (ctx.targetPath && !(0, node_fs.existsSync)(target) && !ctx.allowTargetCreate) return;
3389
4709
  const binPath = resolveBinPath(ctx.programName, ctx.binPath);
3390
4710
  if (!binPath) return;
3391
4711
  let currentSig;
@@ -3394,8 +4714,12 @@ function refreshIfStale(ctx, shell) {
3394
4714
  } catch {
3395
4715
  return;
3396
4716
  }
3397
- if (readCachedSig(target) === currentSig) return;
3398
- writeAtomic(target, generateScript(ctx, shell));
4717
+ if (readCachedSig(target) === currentSig && readCachedBinPath(target) === binPath) return;
4718
+ const completionMode = ctx.completionMode ?? readCachedMode(target) ?? ((0, node_fs.existsSync)(target) ? "static" : "dispatcher");
4719
+ writeAtomic(target, generateScript({
4720
+ ...ctx,
4721
+ completionMode
4722
+ }, shell));
3399
4723
  } catch {}
3400
4724
  }
3401
4725
  /**
@@ -3451,6 +4775,49 @@ function escapeDescribeValue(s) {
3451
4775
  function escapeZshDQ(s) {
3452
4776
  return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
3453
4777
  }
4778
+ function zshArrayLiteral(values) {
4779
+ return values.map((v) => `"${escapeZshDQ(v)}"`).join(" ");
4780
+ }
4781
+ function zshFilteredFileLines(fn, kind, values) {
4782
+ const lines = [
4783
+ `setopt local_options null_glob`,
4784
+ `local _cur="\${words[CURRENT]:-}" _dir _prefix _f _out`,
4785
+ `local -a ${kind === "extensions" ? "_exts" : "_matchers"}=(${zshArrayLiteral(values)})`,
4786
+ `if [[ -z "$_cur" || "$_cur" != */* ]]; then`,
4787
+ ` _dir="."`,
4788
+ ` _prefix="$_cur"`,
4789
+ `elif [[ "$_cur" == */ ]]; then`,
4790
+ ` _dir="\${_cur%/}"`,
4791
+ ` _prefix=""`,
4792
+ `else`,
4793
+ ` _dir="\${_cur%/*}"`,
4794
+ ` _prefix="\${_cur##*/}"`,
4795
+ `fi`,
4796
+ `for _f in "$_dir"/"$_prefix"*(N/); do`,
4797
+ ` _out="\${_f#./}"`,
4798
+ ` __${fn}_add_path_candidate "$_out"`,
4799
+ `done`
4800
+ ];
4801
+ if (kind === "extensions") {
4802
+ lines.push(`local _ext`);
4803
+ lines.push(`for _f in "$_dir"/"$_prefix"*(N.); do`);
4804
+ lines.push(` _out="\${_f#./}"`);
4805
+ lines.push(` for _ext in "\${_exts[@]}"; do`);
4806
+ lines.push(` [[ -n "$_ext" && "$_out" == *."$_ext" ]] && { __${fn}_add_path_candidate "$_out"; break; }`);
4807
+ lines.push(` done`);
4808
+ lines.push(`done`);
4809
+ } else {
4810
+ lines.push(`local _pat`);
4811
+ lines.push(`for _pat in "\${_matchers[@]}"; do`);
4812
+ lines.push(` [[ -n "$_pat" ]] || continue`);
4813
+ lines.push(` for _f in "$_dir"/\${~_pat}(N.); do`);
4814
+ lines.push(` _out="\${_f#./}"`);
4815
+ lines.push(` [[ "$_out" == "$_cur"* ]] && __${fn}_add_path_candidate "$_out"`);
4816
+ lines.push(` done`);
4817
+ lines.push(`done`);
4818
+ }
4819
+ return lines;
4820
+ }
3454
4821
  /**
3455
4822
  * Generate zsh value completion lines for a ValueCompletion spec.
3456
4823
  * Uses `_vals` array (must be declared in the calling function scope).
@@ -3507,12 +4874,13 @@ function zshValueLines(vc, fn, location) {
3507
4874
  case "dynamic": return [`__${fn}_apply_dynamic_output "$(__${fn}_invoke_complete zsh "\${(@)words[2,CURRENT]}")"`];
3508
4875
  case "choices": return [`_vals=(${vc.choices.map((c) => `"${escapeDesc(c)}"`).join(" ")})`, `__${fn}_cdescribe 'completions' _vals`];
3509
4876
  case "file":
3510
- if (vc.matcher?.length) return vc.matcher.map((p) => `_files -g "${p}"`);
3511
- if (vc.extensions?.length) return vc.extensions.map((ext) => `_files -g "*.${ext}"`);
4877
+ if (vc.matcher?.length) return zshFilteredFileLines(fn, "matchers", vc.matcher);
4878
+ if (vc.extensions?.length) return zshFilteredFileLines(fn, "extensions", vc.extensions);
3512
4879
  return [`_files`];
3513
4880
  case "directory": return [`_files -/`];
3514
4881
  case "command": return [`_vals=("\${(@f)$(${vc.shellCommand})}")`, `__${fn}_cdescribe 'completions' _vals`];
3515
4882
  case "none": return [];
4883
+ case "runtime-expand": return [];
3516
4884
  }
3517
4885
  }
3518
4886
  /** Generate option-value case branches */
@@ -3551,6 +4919,31 @@ function positionalBlock(positionals, fn, funcSuffix, options = []) {
3551
4919
  lines.push(` esac`);
3552
4920
  return lines;
3553
4921
  }
4922
+ /**
4923
+ * Subcommand completion via `_describe`. When the same node also has
4924
+ * positionals, complete subcommand names only while the cursor still prefixes
4925
+ * one and fall through to positional completion otherwise. Returns lines at
4926
+ * base indentation; callers re-indent for their handler depth.
4927
+ */
4928
+ function subOrPositionalLines(subItems, positionals, fn, funcSuffix, options) {
4929
+ const describe = [`local -a _subs=(${subItems.map((s) => {
4930
+ const desc = s.description ? `:${escapeDesc(s.description)}` : "";
4931
+ return `"${s.name}${desc}"`;
4932
+ }).join(" ")})`, `__${fn}_cdescribe 'subcommands' _subs`];
4933
+ if (positionals.length === 0) return describe;
4934
+ return [
4935
+ `local -a _sub_names=(${subItems.map((s) => `"${escapeZshDQ(s.name)}"`).join(" ")})`,
4936
+ `local _cur_word="\${words[CURRENT]:-}" _sub_name _sub_match=0`,
4937
+ `for _sub_name in "\${_sub_names[@]}"; do`,
4938
+ ` [[ "$_sub_name" == "$_cur_word"* ]] && _sub_match=1 && break`,
4939
+ `done`,
4940
+ `if (( _sub_match )); then`,
4941
+ ...describe.map((l) => ` ${l}`),
4942
+ `else`,
4943
+ ...positionalBlock(positionals, fn, funcSuffix, options),
4944
+ `fi`
4945
+ ];
4946
+ }
3554
4947
  /** Generate prev-word value completion case block */
3555
4948
  function valueCompletionBlock(options, positionals, fn, funcSuffix) {
3556
4949
  if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
@@ -3622,12 +5015,8 @@ function generateSubHandler(sub, fn, path) {
3622
5015
  lines.push(` return 0`);
3623
5016
  lines.push(` fi`);
3624
5017
  if (visibleSubs.length > 0) {
3625
- const subItems = getSubNamesWithAliases(sub.subcommands).map((s) => {
3626
- const desc = s.description ? `:${escapeDesc(s.description)}` : "";
3627
- return `"${s.name}${desc}"`;
3628
- }).join(" ");
3629
- lines.push(` local -a _subs=(${subItems})`);
3630
- lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
5018
+ const subItems = getSubNamesWithAliases(sub.subcommands);
5019
+ lines.push(...subOrPositionalLines(subItems, sub.positionals, fn, funcSuffix, sub.options).map((l) => ` ${l}`));
3631
5020
  } else if (sub.positionals.length > 0) lines.push(...positionalBlock(sub.positionals, fn, funcSuffix, sub.options));
3632
5021
  lines.push(`}`);
3633
5022
  lines.push(``);
@@ -3636,8 +5025,10 @@ function generateSubHandler(sub, fn, path) {
3636
5025
  function generateZshCompletion(command, options) {
3637
5026
  const { programName } = options;
3638
5027
  const data = extractCompletionData(command, programName, options.globalArgsSchema);
3639
- const fn = sanitize(programName);
3640
- const completionFn = `_${programName}`;
5028
+ const baseFn = sanitize(programName);
5029
+ const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
5030
+ const isWorker = options.staticWorker !== void 0;
5031
+ const completionFn = isWorker ? `_${fn}_completions` : `_${programName}`;
3641
5032
  const autoloadCheck = `"\${funcstack[1]:-}" == "${escapeZshDQ(completionFn)}"`;
3642
5033
  const root = data.command;
3643
5034
  const visibleSubs = getVisibleSubs(root.subcommands);
@@ -3647,17 +5038,21 @@ function generateZshCompletion(command, options) {
3647
5038
  const arrayExpandSpecs = expandSpecs.filter((s) => s.isArrayOption);
3648
5039
  const hasArrayExpand = arrayExpandSpecs.length > 0;
3649
5040
  const lines = [];
3650
- lines.push(`#compdef ${programName}`);
3651
- lines.push(``);
5041
+ if (!isWorker) {
5042
+ lines.push(`#compdef ${programName}`);
5043
+ lines.push(``);
5044
+ }
3652
5045
  lines.push(...buildHeaderLines({
3653
5046
  programName,
3654
5047
  shell: "zsh",
3655
5048
  binPath: options.binPath,
3656
5049
  programVersion: options.programVersion
3657
5050
  }));
5051
+ lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
5052
+ if (isWorker) lines.push(`# politty-completion-worker: true`);
3658
5053
  lines.push(`# Generated by politty`);
3659
5054
  lines.push(``);
3660
- lines.push(...generateZshSelfRefresh({
5055
+ if (!isWorker) lines.push(...generateZshSelfRefresh({
3661
5056
  programName,
3662
5057
  binPath: options.binPath
3663
5058
  }));
@@ -3733,6 +5128,11 @@ function generateZshCompletion(command, options) {
3733
5128
  lines.push(` return 0`);
3734
5129
  lines.push(`}`);
3735
5130
  lines.push(``);
5131
+ lines.push(`__${fn}_add_path_candidate() {`);
5132
+ lines.push(` compadd -f -- "$1" 2>/dev/null && return 0`);
5133
+ lines.push(` print -r -- "$1"`);
5134
+ lines.push(`}`);
5135
+ lines.push(``);
3736
5136
  lines.push(`__${fn}_opt_takes_value() {`);
3737
5137
  lines.push(` case "$1:$2" in`);
3738
5138
  lines.push(...optTakesValueEntries(root, ""));
@@ -3789,12 +5189,8 @@ function generateZshCompletion(command, options) {
3789
5189
  lines.push(` __${fn}_cdescribe 'options' _opts`);
3790
5190
  if (visibleSubs.length > 0) {
3791
5191
  lines.push(` else`);
3792
- const subItems = getSubNamesWithAliases(root.subcommands).map((s) => {
3793
- const desc = s.description ? `:${escapeDesc(s.description)}` : "";
3794
- return `"${s.name}${desc}"`;
3795
- }).join(" ");
3796
- lines.push(` local -a _subs=(${subItems})`);
3797
- lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
5192
+ const subItems = getSubNamesWithAliases(root.subcommands);
5193
+ lines.push(...subOrPositionalLines(subItems, root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
3798
5194
  } else if (root.positionals.length > 0) {
3799
5195
  lines.push(` else`);
3800
5196
  lines.push(...positionalBlock(root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
@@ -3869,21 +5265,23 @@ function generateZshCompletion(command, options) {
3869
5265
  lines.push(``);
3870
5266
  lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
3871
5267
  lines.push(``);
3872
- lines.push(`if [[ ${autoloadCheck} ]]; then`);
3873
- lines.push(` ${completionFn} "$@"`);
3874
- lines.push(`else`);
3875
- lines.push(` compdef ${completionFn} ${programName}`);
3876
- lines.push(`fi`);
3877
- lines.push(``);
5268
+ if (!isWorker) {
5269
+ lines.push(`if [[ ${autoloadCheck} ]]; then`);
5270
+ lines.push(` ${completionFn} "$@"`);
5271
+ lines.push(`else`);
5272
+ lines.push(` compdef ${completionFn} ${programName}`);
5273
+ lines.push(`fi`);
5274
+ lines.push(``);
5275
+ }
3878
5276
  return {
3879
5277
  script: lines.join("\n"),
3880
5278
  shell: "zsh",
3881
5279
  installInstructions: `# To enable auto-refreshing zsh completions, add this to your ~/.zshrc after compinit:
3882
- eval "$(${programName} completion zsh)"
5280
+ eval "$(${programName} completion zsh --static)"
3883
5281
 
3884
5282
  # For faster shell startup, save the script in your fpath:
3885
5283
  mkdir -p ~/.zsh/completions
3886
- ${programName} completion zsh > ~/.zsh/completions/_${programName}
5284
+ ${programName} completion zsh --static > ~/.zsh/completions/_${programName}
3887
5285
 
3888
5286
  # Make sure your ~/.zshrc includes the fpath line before compinit:
3889
5287
  fpath=(~/.zsh/completions $fpath)
@@ -3928,6 +5326,7 @@ source ~/.zshrc`
3928
5326
  * Generate completion script for the specified shell
3929
5327
  */
3930
5328
  function generateCompletion(command, options) {
5329
+ if (options.mode === "dispatcher") return generateDispatcherCompletion(command, options);
3931
5330
  switch (options.shell) {
3932
5331
  case "bash": return generateBashCompletion(command, options);
3933
5332
  case "zsh": return generateZshCompletion(command, options);
@@ -3945,9 +5344,6 @@ function getSupportedShells() {
3945
5344
  "fish"
3946
5345
  ];
3947
5346
  }
3948
- function shSingleQuote(s) {
3949
- return `'${s.replace(/'/g, "'\\''")}'`;
3950
- }
3951
5347
  function printZshFpathSetup(programName, target) {
3952
5348
  console.error("");
3953
5349
  console.error("Configure zsh fpath with:");
@@ -3988,7 +5384,10 @@ const completionArgsSchema = zod.z.object({
3988
5384
  description: "Show installation instructions"
3989
5385
  }),
3990
5386
  loader: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Print just the rc loader snippet (bash/zsh). Add it to ~/.bashrc or ~/.zshrc; it auto-regenerates the cache when the binary changes." }),
3991
- install: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Write the completion script to its on-disk cache (bash/zsh) or autoload location (fish) instead of printing it." })
5387
+ install: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Write the completion script to its on-disk cache (bash/zsh) or autoload location (fish) instead of printing it." }),
5388
+ static: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Generate the legacy static completion script with command metadata baked in." }),
5389
+ dispatcher: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Generate the runtime dispatcher completion script. This is the default." }),
5390
+ worker: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Generate an internal static worker artifact for dispatcher mode." })
3992
5391
  });
3993
5392
  const refreshArgsSchema = zod.z.object({
3994
5393
  shell: require_schema_extractor.arg(zod.z.enum([
@@ -4004,8 +5403,19 @@ const refreshArgsSchema = zod.z.object({
4004
5403
  positional: true,
4005
5404
  description: "Existing politty-generated completion file to refresh",
4006
5405
  placeholder: "TARGET"
4007
- })
5406
+ }),
5407
+ static: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Refresh using the legacy static completion script mode." }),
5408
+ worker: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Refresh an internal static worker completion script." })
4008
5409
  });
5410
+ const workerPathArgsSchema = zod.z.object({ shell: require_schema_extractor.arg(zod.z.enum([
5411
+ "bash",
5412
+ "zsh",
5413
+ "fish"
5414
+ ]), {
5415
+ positional: true,
5416
+ description: "Shell worker to locate",
5417
+ placeholder: "SHELL"
5418
+ }) });
4009
5419
  /**
4010
5420
  * Create a completion subcommand for your CLI
4011
5421
  *
@@ -4027,7 +5437,8 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
4027
5437
  const refreshExtra = {
4028
5438
  ...cacheDir !== void 0 && { cacheDir },
4029
5439
  ...programVersion !== void 0 && { programVersion },
4030
- ...globalArgsSchema !== void 0 && { globalArgsSchema }
5440
+ ...globalArgsSchema !== void 0 && { globalArgsSchema },
5441
+ ...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker }
4031
5442
  };
4032
5443
  const installCtxBase = {
4033
5444
  programName: resolvedProgramName,
@@ -4045,6 +5456,10 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
4045
5456
  ...rootCommand.subCommands,
4046
5457
  "__refresh-completion": createRefreshCompletionCommand(rootCommand, resolvedProgramName, refreshExtra)
4047
5458
  };
5459
+ if (!rootCommand.subCommands?.["__completion-worker-path"]) rootCommand.subCommands = {
5460
+ ...rootCommand.subCommands,
5461
+ "__completion-worker-path": createCompletionWorkerPathCommand(resolvedProgramName, refreshExtra)
5462
+ };
4048
5463
  return defineCommand({
4049
5464
  name: "completion",
4050
5465
  description: "Generate shell completion script",
@@ -4056,12 +5471,17 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
4056
5471
  process.exitCode = 1;
4057
5472
  return;
4058
5473
  }
5474
+ if (args.static && args.dispatcher) throw new Error("Choose only one completion mode: --dispatcher or --static.");
5475
+ if (args.worker && !args.static) throw new Error("`--worker` requires `--static`.");
5476
+ if (args.worker && (args.install || args.loader || args.instructions)) throw new Error("`--worker` can only print a worker artifact.");
5477
+ const completionMode = args.static ? "static" : "dispatcher";
4059
5478
  if (args.install) {
4060
5479
  let target;
4061
5480
  try {
4062
5481
  target = install({
4063
5482
  rootCommand,
4064
- ...installCtxBase
5483
+ ...installCtxBase,
5484
+ completionMode
4065
5485
  }, shellType);
4066
5486
  } catch (e) {
4067
5487
  throw new Error(`install failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -4089,10 +5509,13 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
4089
5509
  const result = generateCompletion(rootCommand, {
4090
5510
  shell: shellType,
4091
5511
  programName: resolvedProgramName,
5512
+ mode: completionMode,
4092
5513
  includeDescriptions: true,
4093
5514
  ...globalArgsSchema !== void 0 && { globalArgsSchema },
4094
5515
  ...programVersion !== void 0 && { programVersion },
4095
- ...cacheDir !== void 0 && { cacheDir }
5516
+ ...cacheDir !== void 0 && { cacheDir },
5517
+ ...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker },
5518
+ ...args.worker && { staticWorker: { functionSuffix: "worker" } }
4096
5519
  });
4097
5520
  if (args.instructions) console.log(result.installInstructions);
4098
5521
  else console.log(result.script);
@@ -4114,11 +5537,31 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
4114
5537
  rootCommand,
4115
5538
  programName,
4116
5539
  ...extra,
5540
+ completionMode: args.static || args.worker ? "static" : void 0,
5541
+ ...args.worker && { staticWorker: { functionSuffix: "worker" } },
5542
+ ...args.worker && { allowTargetCreate: true },
4117
5543
  ...args.target !== void 0 && { targetPath: args.target }
4118
5544
  }, args.shell);
4119
5545
  }
4120
5546
  });
4121
5547
  }
5548
+ function createCompletionWorkerPathCommand(programName, extra = {}) {
5549
+ return defineCommand({
5550
+ name: "__completion-worker-path",
5551
+ description: "(internal) Print the bundled completion worker path when available.",
5552
+ args: workerPathArgsSchema,
5553
+ run(args) {
5554
+ const path = resolveBundledWorkerPath({
5555
+ programName,
5556
+ shell: args.shell,
5557
+ ...extra.binPath !== void 0 && { binPath: extra.binPath },
5558
+ ...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker }
5559
+ });
5560
+ if (!path) throw new Error(`No bundled completion worker found for ${programName} (${args.shell}).`);
5561
+ console.log(path);
5562
+ }
5563
+ });
5564
+ }
4122
5565
  /**
4123
5566
  * Wrap a command with a completion subcommand
4124
5567
  *
@@ -4140,19 +5583,21 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
4140
5583
  * ```
4141
5584
  */
4142
5585
  function withCompletionCommand(command, options) {
4143
- const { programName, globalArgsSchema, cacheDir, programVersion } = typeof options === "string" ? { programName: options } : options ?? {};
5586
+ const { programName, globalArgsSchema, cacheDir, programVersion, bundledWorker } = typeof options === "string" ? { programName: options } : options ?? {};
4144
5587
  const resolvedProgramName = programName ?? command.name;
4145
5588
  const extra = {
4146
5589
  ...cacheDir !== void 0 && { cacheDir },
4147
5590
  ...programVersion !== void 0 && { programVersion },
4148
- ...globalArgsSchema !== void 0 && { globalArgsSchema }
5591
+ ...globalArgsSchema !== void 0 && { globalArgsSchema },
5592
+ ...bundledWorker !== void 0 && { bundledWorker }
4149
5593
  };
4150
5594
  const wrappedCommand = { ...command };
4151
5595
  wrappedCommand.subCommands = {
4152
5596
  ...command.subCommands,
4153
5597
  completion: createCompletionCommand(wrappedCommand, programName, globalArgsSchema, extra),
4154
5598
  __complete: createDynamicCompleteCommand(wrappedCommand, programName, globalArgsSchema),
4155
- "__refresh-completion": createRefreshCompletionCommand(wrappedCommand, resolvedProgramName, extra)
5599
+ "__refresh-completion": createRefreshCompletionCommand(wrappedCommand, resolvedProgramName, extra),
5600
+ "__completion-worker-path": createCompletionWorkerPathCommand(resolvedProgramName, extra)
4156
5601
  };
4157
5602
  wrappedCommand.runMainHook = (argv) => {
4158
5603
  maybeSpawnRefresh(argv, {
@@ -4179,7 +5624,7 @@ function withCompletionCommand(command, options) {
4179
5624
  function maybeSpawnRefresh(argv, ctx) {
4180
5625
  if (process.env.POLITTY_NO_COMPLETION_REFRESH) return;
4181
5626
  const firstPositional = argv.find((a) => !a.startsWith("-"));
4182
- if (firstPositional === "__complete" || firstPositional === "__refresh-completion" || firstPositional === "completion") return;
5627
+ if (firstPositional === "__complete" || firstPositional === "__refresh-completion" || firstPositional === "__completion-worker-path" || firstPositional === "completion") return;
4183
5628
  const shell = detectShell();
4184
5629
  if (!shell) return;
4185
5630
  const argv0 = process.argv[1];
@@ -4195,12 +5640,24 @@ Object.defineProperty(exports, 'CompletionDirective', {
4195
5640
  return CompletionDirective;
4196
5641
  }
4197
5642
  });
5643
+ Object.defineProperty(exports, 'bundledWorkerShellExtension', {
5644
+ enumerable: true,
5645
+ get: function () {
5646
+ return bundledWorkerShellExtension;
5647
+ }
5648
+ });
4198
5649
  Object.defineProperty(exports, 'createCompletionCommand', {
4199
5650
  enumerable: true,
4200
5651
  get: function () {
4201
5652
  return createCompletionCommand;
4202
5653
  }
4203
5654
  });
5655
+ Object.defineProperty(exports, 'createCompletionWorkerPathCommand', {
5656
+ enumerable: true,
5657
+ get: function () {
5658
+ return createCompletionWorkerPathCommand;
5659
+ }
5660
+ });
4204
5661
  Object.defineProperty(exports, 'createDefineCommand', {
4205
5662
  enumerable: true,
4206
5663
  get: function () {
@@ -4219,6 +5676,12 @@ Object.defineProperty(exports, 'createRefreshCompletionCommand', {
4219
5676
  return createRefreshCompletionCommand;
4220
5677
  }
4221
5678
  });
5679
+ Object.defineProperty(exports, 'defaultBundledWorkerOutputPath', {
5680
+ enumerable: true,
5681
+ get: function () {
5682
+ return defaultBundledWorkerOutputPath;
5683
+ }
5684
+ });
4222
5685
  Object.defineProperty(exports, 'defineCommand', {
4223
5686
  enumerable: true,
4224
5687
  get: function () {
@@ -4249,6 +5712,12 @@ Object.defineProperty(exports, 'formatForShell', {
4249
5712
  return formatForShell;
4250
5713
  }
4251
5714
  });
5715
+ Object.defineProperty(exports, 'generateBundledCompletionWorker', {
5716
+ enumerable: true,
5717
+ get: function () {
5718
+ return generateBundledCompletionWorker;
5719
+ }
5720
+ });
4252
5721
  Object.defineProperty(exports, 'generateCandidates', {
4253
5722
  enumerable: true,
4254
5723
  get: function () {
@@ -4285,10 +5754,16 @@ Object.defineProperty(exports, 'resolveValueCompletion', {
4285
5754
  return resolveValueCompletion;
4286
5755
  }
4287
5756
  });
5757
+ Object.defineProperty(exports, 'validateBundledWorkerFile', {
5758
+ enumerable: true,
5759
+ get: function () {
5760
+ return validateBundledWorkerFile;
5761
+ }
5762
+ });
4288
5763
  Object.defineProperty(exports, 'withCompletionCommand', {
4289
5764
  enumerable: true,
4290
5765
  get: function () {
4291
5766
  return withCompletionCommand;
4292
5767
  }
4293
5768
  });
4294
- //# sourceMappingURL=completion-BFOAOg95.cjs.map
5769
+ //# sourceMappingURL=completion-CLHO3Xaz.cjs.map