@clo4/cli 0.0.0-stage → 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,1705 @@
1
+ // \0rolldown/runtime.js
2
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
3
+ // src/util.ts
4
+ function isArray(o) {
5
+ return Array.isArray(o);
6
+ }
7
+ /** Convert the value, if any, to an array with 0 or more elements */
8
+ function makeArray(thing) {
9
+ if (thing === void 0) return [];
10
+ if (isArray(thing)) return thing.slice();
11
+ return [thing];
12
+ }
13
+ /**
14
+ * Like {@link makeArray}, but without copying when the input is already an
15
+ * array. Only safe when the caller treats the result as read-only — use it on
16
+ * the hot path to avoid slicing read-only spec metadata.
17
+ */
18
+ function asArray(thing) {
19
+ if (thing === void 0) return [];
20
+ if (isArray(thing)) return thing;
21
+ return [thing];
22
+ }
23
+ /** Convert the value to an array with at least one element */
24
+ function makeArray1(thing) {
25
+ if (isArray(thing)) return thing.slice();
26
+ return [thing];
27
+ }
28
+ /** Set many keys in a Map to the same value */
29
+ function setEach(map, keys, value) {
30
+ if (isArray(keys)) for (const key of keys) map.set(key, value);
31
+ else map.set(keys, value);
32
+ }
33
+ function assert(expr, msg = "") {
34
+ if (!expr) throw new Error(msg);
35
+ }
36
+ /** Print a message to stdout, trimming trailing whitespace. */
37
+ function print(text) {
38
+ console.log(text.trimEnd());
39
+ }
40
+ /** Print an error message to stderr with the shared `Error:` prefix. */
41
+ function printActionError(message) {
42
+ console.error("Error:", message);
43
+ }
44
+ // src/analyze.ts
45
+ const $optionArg = (index, start, end, literal, option, separator) => ({
46
+ kind: "option-arg",
47
+ index,
48
+ literal,
49
+ start,
50
+ end,
51
+ option,
52
+ separator
53
+ });
54
+ const $unknownOption = (index, start, end, literal, validOptions) => ({
55
+ kind: "unknown-option",
56
+ index,
57
+ start,
58
+ end,
59
+ literal,
60
+ validOptions
61
+ });
62
+ const $arg = (index, start, end, literal) => ({
63
+ kind: "arg",
64
+ index,
65
+ start,
66
+ end,
67
+ literal
68
+ });
69
+ const $argSeparator = (index, start, end, literal) => ({
70
+ kind: "arg-separator",
71
+ index,
72
+ start,
73
+ end,
74
+ literal
75
+ });
76
+ const $subcommand = (index, start, end, literal, subcommand) => {
77
+ return {
78
+ kind: "subcommand",
79
+ index,
80
+ start,
81
+ end,
82
+ literal,
83
+ subcommand
84
+ };
85
+ };
86
+ const $option = (index, start, end, literal, option) => {
87
+ return {
88
+ kind: "option",
89
+ index,
90
+ start,
91
+ end,
92
+ literal,
93
+ option
94
+ };
95
+ };
96
+ /**
97
+ * Lex the input, output some tokens
98
+ *
99
+ * This generator uses the options and subcommands to categorize the
100
+ * input tokens. It will not track or reject arguments - tokens that
101
+ * don't match an option or subcommand are yielded as arguments.
102
+ */
103
+ function analyze(input, spec) {
104
+ const tokens = [];
105
+ if (input.length === 0) {
106
+ const localOptions = /* @__PURE__ */ new Map();
107
+ const localRequiredOptions = /* @__PURE__ */ new Map();
108
+ const persistentOptions = /* @__PURE__ */ new Map();
109
+ const persistentRequiredOptions = /* @__PURE__ */ new Map();
110
+ if (spec.options) for (const option of spec.options) if (option.isPersistent) {
111
+ setEach(persistentOptions, option.name, option);
112
+ if (option.isRequired) setEach(persistentRequiredOptions, option.name, option);
113
+ } else {
114
+ setEach(localOptions, option.name, option);
115
+ if (option.isRequired) setEach(localRequiredOptions, option.name, option);
116
+ }
117
+ return {
118
+ finalState: {
119
+ localOptions,
120
+ persistentOptions,
121
+ localRequiredOptions,
122
+ persistentRequiredOptions
123
+ },
124
+ tokens
125
+ };
126
+ }
127
+ let localSubcommands;
128
+ const localOptions = /* @__PURE__ */ new Map();
129
+ const persistentOptions = /* @__PURE__ */ new Map();
130
+ const localRequiredOptions = /* @__PURE__ */ new Map();
131
+ const persistentRequiredOptions = /* @__PURE__ */ new Map();
132
+ let separators = ["="];
133
+ let hasFoundArg = false;
134
+ let posixCompliantOptions = true;
135
+ let subcommandsMatchUniquePrefix = false;
136
+ let optionsMustPrecedeArguments = false;
137
+ let hasUsedNonPersistentOption = false;
138
+ const getOption = (name) => localOptions.get(name) ?? persistentOptions.get(name) ?? null;
139
+ const hasOption = (name) => localOptions.has(name) || persistentOptions.has(name);
140
+ const getSubcommand = (name) => {
141
+ if (!localSubcommands) return null;
142
+ let prefixMatch = null;
143
+ let prefixCount = 0;
144
+ for (const command of localSubcommands) {
145
+ const names = typeof command.name === "string" ? [command.name] : command.name;
146
+ for (const cmdName of names) {
147
+ if (cmdName === name) return command;
148
+ if (subcommandsMatchUniquePrefix && cmdName.startsWith(name)) {
149
+ prefixMatch = command;
150
+ prefixCount += 1;
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ return prefixCount === 1 ? prefixMatch : null;
156
+ };
157
+ const updateCurrentCommand = (command) => {
158
+ if (command.options) for (const option of command.options) if (option.isPersistent) {
159
+ setEach(persistentOptions, option.name, option);
160
+ if (option.isRequired) setEach(persistentRequiredOptions, option.name, option);
161
+ } else {
162
+ setEach(localOptions, option.name, option);
163
+ if (option.isRequired) setEach(localRequiredOptions, option.name, option);
164
+ }
165
+ localSubcommands = command.subcommands;
166
+ const directives = command.parserDirectives;
167
+ if (directives?.optionArgSeparators) separators = asArray(directives.optionArgSeparators);
168
+ if (directives?.subcommandsMatchUniquePrefix) subcommandsMatchUniquePrefix = directives.subcommandsMatchUniquePrefix;
169
+ if (directives?.optionsMustPrecedeArguments) optionsMustPrecedeArguments = directives.optionsMustPrecedeArguments;
170
+ if (typeof directives?.flagsArePosixNoncompliant === "boolean") posixCompliantOptions = !directives.flagsArePosixNoncompliant;
171
+ };
172
+ updateCurrentCommand(spec);
173
+ tokens: for (let index = 0; index < input.length; index++) {
174
+ const token = input[index];
175
+ if (token === "--") {
176
+ tokens.push($argSeparator(index, 0, token.length, token));
177
+ index += 1;
178
+ for (; index < input.length; index++) {
179
+ const tok = input[index];
180
+ tokens.push($arg(index, 0, tok.length, tok));
181
+ }
182
+ break tokens;
183
+ }
184
+ if (!hasFoundArg && !hasUsedNonPersistentOption && localSubcommands) {
185
+ const command = getSubcommand(token);
186
+ if (command) {
187
+ localOptions.clear();
188
+ updateCurrentCommand(command);
189
+ tokens.push($subcommand(index, 0, token.length, token, command));
190
+ continue tokens;
191
+ }
192
+ }
193
+ if (!(optionsMustPrecedeArguments && hasFoundArg)) {
194
+ if (posixCompliantOptions && token.length > 1 && (token.startsWith("-") || token.startsWith("+")) && !token.startsWith("--")) {
195
+ const leadingChar = token[0];
196
+ const dashOption = getOption(leadingChar);
197
+ if (dashOption?.args && !hasOption(token.slice(0, 2))) {
198
+ tokens.push($option(index, 0, 1, leadingChar, dashOption));
199
+ tokens.push($optionArg(index, 1, token.length, token.slice(1), leadingChar, ""));
200
+ continue tokens;
201
+ }
202
+ chars: for (let char = 1; char < token.length; char++) {
203
+ const optionName = `${leadingChar}${token[char]}`;
204
+ const option = getOption(optionName);
205
+ if (!option) {
206
+ tokens.push($unknownOption(index, char, char + 1, optionName, [...localOptions.keys(), ...persistentOptions.keys()]));
207
+ continue chars;
208
+ }
209
+ if (!hasUsedNonPersistentOption && !option.isPersistent) hasUsedNonPersistentOption = true;
210
+ tokens.push($option(index, char, char + 1, optionName, option));
211
+ if (option.args && char < token.length - 1) {
212
+ const remainingChars = token.slice(char + 1);
213
+ const sep = separators.find((sep) => remainingChars.startsWith(sep)) || "";
214
+ const argStartOffset = char + sep.length;
215
+ tokens.push($optionArg(index, argStartOffset + 1, token.length, token.slice(argStartOffset + 1), optionName, sep));
216
+ break;
217
+ }
218
+ }
219
+ continue tokens;
220
+ }
221
+ if (!posixCompliantOptions || token.length > 2 && token.startsWith("--")) {
222
+ let foundSep = null;
223
+ let foundSepIndex = -1;
224
+ separators: for (const sep of separators) {
225
+ const index = token.indexOf(sep);
226
+ if (index === -1) continue separators;
227
+ foundSep = sep;
228
+ foundSepIndex = index;
229
+ }
230
+ if (foundSep) {
231
+ const optionName = token.slice(0, foundSepIndex);
232
+ const option = getOption(optionName);
233
+ if (option) {
234
+ if (!hasUsedNonPersistentOption && !option.isPersistent) hasUsedNonPersistentOption = true;
235
+ tokens.push($option(index, 0, foundSepIndex, token.slice(0, foundSepIndex), option));
236
+ tokens.push($optionArg(index, foundSepIndex + foundSep.length, token.length, token.slice(foundSepIndex + foundSep.length), optionName, foundSep));
237
+ continue tokens;
238
+ } else if (posixCompliantOptions) {
239
+ const optionName = token.slice(0, foundSepIndex);
240
+ tokens.push($unknownOption(index, 0, foundSepIndex, optionName, [...localOptions.keys(), ...persistentOptions.keys()]));
241
+ tokens.push($optionArg(index, foundSepIndex + foundSep.length, token.length, token.slice(foundSepIndex + foundSep.length), optionName, foundSep));
242
+ continue tokens;
243
+ }
244
+ } else {
245
+ const option = getOption(token);
246
+ if (option) {
247
+ if (!hasUsedNonPersistentOption && !option.isPersistent) hasUsedNonPersistentOption = true;
248
+ tokens.push($option(index, 0, token.length, token, option));
249
+ continue tokens;
250
+ } else if (posixCompliantOptions) {
251
+ tokens.push($unknownOption(index, 0, token.length, token, [...localOptions.keys(), ...persistentOptions.keys()]));
252
+ continue tokens;
253
+ }
254
+ }
255
+ }
256
+ }
257
+ if (!hasFoundArg) hasFoundArg = true;
258
+ tokens.push($arg(index, 0, token.length, token));
259
+ }
260
+ return {
261
+ finalState: {
262
+ localOptions,
263
+ persistentOptions,
264
+ localRequiredOptions,
265
+ persistentRequiredOptions
266
+ },
267
+ tokens
268
+ };
269
+ }
270
+ // src/errors.ts
271
+ const repr = JSON.stringify;
272
+ var ParseError = class extends Error {
273
+ context;
274
+ constructor(context, ...args) {
275
+ super(...args);
276
+ this.context = context;
277
+ this.name = this.constructor.name;
278
+ }
279
+ };
280
+ var UnknownOption = class extends ParseError {
281
+ index;
282
+ start;
283
+ end;
284
+ option;
285
+ validOptions;
286
+ constructor(token, context) {
287
+ const msg = `${repr(token.literal)} looks like a flag, but is unknown in this context`;
288
+ super(context, msg);
289
+ this.option = token.literal;
290
+ this.validOptions = token.validOptions;
291
+ this.index = token.index;
292
+ this.start = token.start;
293
+ this.end = token.end;
294
+ }
295
+ };
296
+ var InvalidOptionArg = class extends ParseError {
297
+ index;
298
+ start;
299
+ end;
300
+ arg;
301
+ option;
302
+ constructor(token, context) {
303
+ const msg = `Option ${token.option} doesn't take arguments`;
304
+ super(context, msg);
305
+ this.index = token.index;
306
+ this.start = token.start;
307
+ this.end = token.end;
308
+ this.option = token.option;
309
+ this.arg = token.literal;
310
+ }
311
+ };
312
+ var TooManyArguments = class extends ParseError {
313
+ index;
314
+ value;
315
+ constructor(token, context) {
316
+ const msg = `Too many arguments, ${repr(token.literal)} was unexpected`;
317
+ super(context, msg);
318
+ this.index = token.index;
319
+ this.value = token.literal;
320
+ }
321
+ };
322
+ var TooFewOptionArguments = class extends ParseError {
323
+ constructor(min, max, context) {
324
+ const msg = min === max ? `Option needs ${min} argument${min === 1 ? "" : "s"}` : !Number.isFinite(max) ? `Option needs at least ${min} argument${min === 1 ? "" : "s"}` : `Option needs between ${min} and ${max} arguments`;
325
+ super(context, msg);
326
+ }
327
+ };
328
+ var TooFewArguments = class extends ParseError {
329
+ constructor(min, max, context) {
330
+ const msg = min === max ? `Expected ${min} argument${min === 1 ? "" : "s"}` : !Number.isFinite(max) ? `Expected at least ${min} argument${min === 1 ? "" : "s"}` : `Expected between ${min} and ${max} arguments`;
331
+ super(context, msg);
332
+ }
333
+ };
334
+ var MissingRequiredOption = class extends ParseError {
335
+ constructor(name, context) {
336
+ const msg = `Option ${name} is required, but wasn't found`;
337
+ super(context, msg);
338
+ }
339
+ };
340
+ // src/help.ts
341
+ var import_fastest_levenshtein = (/* @__PURE__ */ __commonJSMin(((exports) => {
342
+ exports.__esModule = true;
343
+ exports.distance = exports.closest = void 0;
344
+ var peq = /* @__PURE__ */ new Uint32Array(65536);
345
+ var myers_32 = function(a, b) {
346
+ var n = a.length;
347
+ var m = b.length;
348
+ var lst = 1 << n - 1;
349
+ var pv = -1;
350
+ var mv = 0;
351
+ var sc = n;
352
+ var i = n;
353
+ while (i--) peq[a.charCodeAt(i)] |= 1 << i;
354
+ for (i = 0; i < m; i++) {
355
+ var eq = peq[b.charCodeAt(i)];
356
+ var xv = eq | mv;
357
+ eq |= (eq & pv) + pv ^ pv;
358
+ mv |= ~(eq | pv);
359
+ pv &= eq;
360
+ if (mv & lst) sc++;
361
+ if (pv & lst) sc--;
362
+ mv = mv << 1 | 1;
363
+ pv = pv << 1 | ~(xv | mv);
364
+ mv &= xv;
365
+ }
366
+ i = n;
367
+ while (i--) peq[a.charCodeAt(i)] = 0;
368
+ return sc;
369
+ };
370
+ var myers_x = function(b, a) {
371
+ var n = a.length;
372
+ var m = b.length;
373
+ var mhc = [];
374
+ var phc = [];
375
+ var hsize = Math.ceil(n / 32);
376
+ var vsize = Math.ceil(m / 32);
377
+ for (var i = 0; i < hsize; i++) {
378
+ phc[i] = -1;
379
+ mhc[i] = 0;
380
+ }
381
+ var j = 0;
382
+ for (; j < vsize - 1; j++) {
383
+ var mv_1 = 0;
384
+ var pv_1 = -1;
385
+ var start_1 = j * 32;
386
+ var vlen_1 = Math.min(32, m) + start_1;
387
+ for (var k = start_1; k < vlen_1; k++) peq[b.charCodeAt(k)] |= 1 << k;
388
+ for (var i = 0; i < n; i++) {
389
+ var eq = peq[a.charCodeAt(i)];
390
+ var pb = phc[i / 32 | 0] >>> i & 1;
391
+ var mb = mhc[i / 32 | 0] >>> i & 1;
392
+ var xv = eq | mv_1;
393
+ var xh = ((eq | mb) & pv_1) + pv_1 ^ pv_1 | eq | mb;
394
+ var ph = mv_1 | ~(xh | pv_1);
395
+ var mh = pv_1 & xh;
396
+ if (ph >>> 31 ^ pb) phc[i / 32 | 0] ^= 1 << i;
397
+ if (mh >>> 31 ^ mb) mhc[i / 32 | 0] ^= 1 << i;
398
+ ph = ph << 1 | pb;
399
+ mh = mh << 1 | mb;
400
+ pv_1 = mh | ~(xv | ph);
401
+ mv_1 = ph & xv;
402
+ }
403
+ for (var k = start_1; k < vlen_1; k++) peq[b.charCodeAt(k)] = 0;
404
+ }
405
+ var mv = 0;
406
+ var pv = -1;
407
+ var start = j * 32;
408
+ var vlen = Math.min(32, m - start) + start;
409
+ for (var k = start; k < vlen; k++) peq[b.charCodeAt(k)] |= 1 << k;
410
+ var score = m;
411
+ for (var i = 0; i < n; i++) {
412
+ var eq = peq[a.charCodeAt(i)];
413
+ var pb = phc[i / 32 | 0] >>> i & 1;
414
+ var mb = mhc[i / 32 | 0] >>> i & 1;
415
+ var xv = eq | mv;
416
+ var xh = ((eq | mb) & pv) + pv ^ pv | eq | mb;
417
+ var ph = mv | ~(xh | pv);
418
+ var mh = pv & xh;
419
+ score += ph >>> m - 1 & 1;
420
+ score -= mh >>> m - 1 & 1;
421
+ if (ph >>> 31 ^ pb) phc[i / 32 | 0] ^= 1 << i;
422
+ if (mh >>> 31 ^ mb) mhc[i / 32 | 0] ^= 1 << i;
423
+ ph = ph << 1 | pb;
424
+ mh = mh << 1 | mb;
425
+ pv = mh | ~(xv | ph);
426
+ mv = ph & xv;
427
+ }
428
+ for (var k = start; k < vlen; k++) peq[b.charCodeAt(k)] = 0;
429
+ return score;
430
+ };
431
+ var distance = function(a, b) {
432
+ if (a.length < b.length) {
433
+ var tmp = b;
434
+ b = a;
435
+ a = tmp;
436
+ }
437
+ if (b.length === 0) return a.length;
438
+ if (a.length <= 32) return myers_32(a, b);
439
+ return myers_x(a, b);
440
+ };
441
+ exports.distance = distance;
442
+ var closest = function(str, arr) {
443
+ var min_distance = Infinity;
444
+ var min_index = 0;
445
+ for (var i = 0; i < arr.length; i++) {
446
+ var dist = distance(str, arr[i]);
447
+ if (dist < min_distance) {
448
+ min_distance = dist;
449
+ min_index = i;
450
+ }
451
+ }
452
+ return arr[min_index];
453
+ };
454
+ exports.closest = closest;
455
+ })))();
456
+ function getNamed(named, name) {
457
+ return named.find((obj) => makeArray(obj.name).includes(name)) ?? null;
458
+ }
459
+ function getParserDirective(path, key) {
460
+ for (let index = path.length - 1; index >= 0; index -= 1) {
461
+ const command = path[index];
462
+ if (command?.parserDirectives && key in command.parserDirectives) return command.parserDirectives[key];
463
+ }
464
+ }
465
+ function dedent(str) {
466
+ if (str[0] !== "\n") return str;
467
+ const lines = str.split("\n").slice(1, -1);
468
+ const margin = lines[0].length - lines[0].trimStart().length;
469
+ return lines.map((line) => line.slice(margin)).join("\n");
470
+ }
471
+ /** Get the help message used by the built-in help option and command. */
472
+ function getHelp(path, config = {}) {
473
+ const { description = true, usage = true, didYouMean } = config;
474
+ const sections = getSections(path);
475
+ if (didYouMean) sections.didYouMean = (0, import_fastest_levenshtein.closest)(didYouMean.input, didYouMean.choices);
476
+ if (!description) sections.description = void 0;
477
+ if (!usage) sections.usage = void 0;
478
+ return formatHelpSections(sections);
479
+ }
480
+ /**
481
+ * Action that prints usage for commands where a subcommand must be provided.
482
+ */
483
+ const usage = ({ path, args: [command], help }) => {
484
+ if (command === void 0) {
485
+ print(help());
486
+ return 0;
487
+ }
488
+ if (command === "") {
489
+ printActionError(`Expected a command, but the value was empty\n\n${help({ description: false })}`);
490
+ return 1;
491
+ }
492
+ const subcommands = path.at(-1)?.subcommands?.filter((cmd) => !cmd.hidden).flatMap((cmd) => cmd.name);
493
+ if (subcommands && subcommands.length > 0) {
494
+ if (getParserDirective(path, "subcommandsMatchUniquePrefix")) {
495
+ const possible = subcommands.filter((name) => name.startsWith(command));
496
+ if (possible.length > 0) {
497
+ printActionError(`Ambiguous command '${command}', could be: ${possible.join(", ")}\n\n${help({ description: false })}`);
498
+ return 1;
499
+ }
500
+ }
501
+ printActionError(`Unknown command '${command}'\n\n${help({
502
+ description: false,
503
+ didYouMean: {
504
+ input: command,
505
+ choices: subcommands
506
+ }
507
+ })}`);
508
+ return 1;
509
+ }
510
+ printActionError(`Unknown command '${command}'\n\n${help({ description: false })}`);
511
+ return 1;
512
+ };
513
+ /** A subcommand that prints help for its parent or a sibling command. */
514
+ const helpCommand = {
515
+ name: "help",
516
+ description: "Print a help message",
517
+ args: {
518
+ name: "command",
519
+ isOptional: true
520
+ },
521
+ action({ path, args: [commandName], help }) {
522
+ const helpRoot = path.slice(0, -1);
523
+ if (!commandName) {
524
+ print(help({ path: helpRoot }));
525
+ return 0;
526
+ }
527
+ const parent = path[path.length - 2];
528
+ if (!parent.subcommands) {
529
+ printActionError(`No subcommands, try using 'help' with nothing after it\n\n${help({
530
+ description: false,
531
+ path: helpRoot
532
+ })}`);
533
+ return 1;
534
+ }
535
+ const command = getNamed(parent.subcommands, commandName);
536
+ if (!command) {
537
+ const subcommands = parent.subcommands.filter((cmd) => !cmd.hidden).flatMap((cmd) => cmd.name);
538
+ if (subcommands.length > 0) {
539
+ printActionError(`There is no subcommand named '${commandName}'\n\n${help({
540
+ path: helpRoot,
541
+ didYouMean: {
542
+ input: commandName,
543
+ choices: subcommands
544
+ }
545
+ })}`);
546
+ return 1;
547
+ }
548
+ printActionError(`There is no subcommand named '${commandName}'\n\n${help({
549
+ description: false,
550
+ path: helpRoot
551
+ })}`);
552
+ return 1;
553
+ }
554
+ print(help({ path: [...helpRoot, command] }));
555
+ }
556
+ };
557
+ /** A persistent help option that prints help for the selected command. */
558
+ const help = {
559
+ name: ["-h", "--help"],
560
+ description: "Print a help message",
561
+ isPersistent: true,
562
+ action({ path }) {
563
+ print(formatHelpSections(getSections(path)));
564
+ return 0;
565
+ }
566
+ };
567
+ function getSubcommandPathString(commands) {
568
+ return commands.map((command) => getLongestString(makeArray1(command.name))).join(" ");
569
+ }
570
+ function nonNullable(value) {
571
+ return value !== null && value !== void 0;
572
+ }
573
+ function getSections(commands) {
574
+ const command = commands[commands.length - 1];
575
+ const description = command.description;
576
+ const defaultDesc = "No description";
577
+ const subcommands = command.subcommands ? command.subcommands.filter((subcommand) => !subcommand.hidden).map((subcommand) => [makeArray1(subcommand.name).sort((a, b) => a.length - b.length).join(", "), subcommand.description ?? defaultDesc]) : [];
578
+ let persistentOptionObjects = [];
579
+ let optionObjects = [];
580
+ let persistentOptions = [];
581
+ let options = [];
582
+ if (commands.length === 1 && subcommands.length === 0) {
583
+ optionObjects = command.options ? command.options.filter((option) => !option.hidden) : [];
584
+ options = optionObjects.map((option) => [optionToString(option), option.description ?? defaultDesc]);
585
+ } else {
586
+ persistentOptionObjects = commands.map((command) => command.options).filter(nonNullable).flat().filter((option) => option.isPersistent && !option.hidden);
587
+ persistentOptions = persistentOptionObjects.map((option) => [optionToString(option), option.description ?? defaultDesc]);
588
+ optionObjects = (command.options ?? []).filter((option) => !option.hidden && !option.isPersistent);
589
+ options = optionObjects.map((option) => [optionToString(option), option.description ?? defaultDesc]);
590
+ }
591
+ return {
592
+ usage: (() => {
593
+ const parts = [getSubcommandPathString(commands)];
594
+ const required = [...optionObjects.filter((option) => option.isRequired), ...persistentOptionObjects.filter((option) => option.isRequired)];
595
+ for (const option of required) parts.push(optionToString(option));
596
+ if (options.length || persistentOptions.length) parts.push("[flags]");
597
+ if (command.requiresSubcommand) parts.push("<command>");
598
+ else {
599
+ const args = makeArray(command.args);
600
+ if (args.length > 0) parts.push(summarizeArguments(args));
601
+ }
602
+ return parts.join(" ");
603
+ })(),
604
+ description,
605
+ persistentOptions,
606
+ options,
607
+ subcommands
608
+ };
609
+ }
610
+ function formatHelpSections(sections) {
611
+ const parts = [];
612
+ const indent = " ";
613
+ if (sections.didYouMean) parts.push(` Did you mean '${sections.didYouMean}'?`);
614
+ if (sections.description) parts.push(dedent(sections.description));
615
+ if (sections.usage) parts.push(`Usage:\n${indent}${sections.usage}`);
616
+ const longestOptionName = Math.max(0, ...sections.options.map(([name]) => name.length), ...sections.persistentOptions.map(([name]) => name.length));
617
+ const minSpacingBetweenNameAndDesc = 2;
618
+ const formatOptions = (options) => {
619
+ const lines = [];
620
+ const newline = indent + " ".repeat(longestOptionName + minSpacingBetweenNameAndDesc);
621
+ for (const [name, desc] of options) {
622
+ const spaces = " ".repeat(longestOptionName - name.length + minSpacingBetweenNameAndDesc);
623
+ const [head, ...tail] = desc.split("\n");
624
+ const description = [head, ...tail.map((line) => newline + line)].join("\n");
625
+ lines.push(`${indent}${name}${spaces}${description}`);
626
+ }
627
+ return lines;
628
+ };
629
+ if (sections.subcommands.length > 0) {
630
+ const lines = ["Commands:"];
631
+ const longestName = Math.max(...sections.subcommands.map(([name]) => name.length));
632
+ const newline = indent + " ".repeat(longestName + minSpacingBetweenNameAndDesc);
633
+ for (const [name, desc] of sections.subcommands) {
634
+ const spaces = " ".repeat(longestName - name.length + minSpacingBetweenNameAndDesc);
635
+ const [head, ...tail] = desc.split("\n\n")[0].split("\n");
636
+ const description = [head, ...tail.map((line) => newline + line)].join("\n");
637
+ lines.push(`${indent}${name}${spaces}${description}`);
638
+ }
639
+ parts.push(lines.join("\n"));
640
+ }
641
+ if (sections.options.length > 0) {
642
+ const lines = ["Flags:", ...formatOptions(sections.options)];
643
+ parts.push(lines.join("\n"));
644
+ }
645
+ if (sections.persistentOptions.length > 0) {
646
+ const lines = ["Global flags:", ...formatOptions(sections.persistentOptions)];
647
+ parts.push(lines.join("\n"));
648
+ }
649
+ return parts.join("\n\n") + "\n";
650
+ }
651
+ function getLongestString(strings) {
652
+ let longest = strings[0];
653
+ for (let i = 1; i < strings.length; i++) {
654
+ const string = strings[i];
655
+ if (string.length > longest.length) longest = string;
656
+ }
657
+ return longest;
658
+ }
659
+ function optionToString(option) {
660
+ const name = makeArray1(option.name).sort((a, b) => a.length - b.length).join(", ");
661
+ const optionArgs = makeArray(option.args);
662
+ if (optionArgs.length === 0) return name;
663
+ if (option.requiresSeparator) {
664
+ const separator = option.requiresSeparator === true ? "=" : option.requiresSeparator;
665
+ if (optionArgs[0].isOptional) return `${name}[${separator}${optionArgs[0].name || "argument"}]`;
666
+ return `${name}${separator}<${optionArgs[0].name || "argument"}>`;
667
+ }
668
+ return `${name} ${summarizeArguments(optionArgs)}`;
669
+ }
670
+ function summarizeArguments(args) {
671
+ return args.map((arg) => argToString(arg)).join(" ");
672
+ }
673
+ function argToString(arg) {
674
+ const name = arg.name || "argument";
675
+ let lhs = "<";
676
+ let rhs = ">";
677
+ let suffix = "";
678
+ if (arg.isOptional) {
679
+ lhs = "[";
680
+ rhs = "]";
681
+ }
682
+ if (arg.isVariadic) suffix = "...";
683
+ return `${lhs}${name}${rhs}${suffix}`;
684
+ }
685
+ // src/parse.ts
686
+ /**
687
+ * Returns the minimum number of arguments that satisfies the args.
688
+ *
689
+ * Accepts the raw spec value (a single arg, an array, or `undefined`) so the
690
+ * hot path doesn't have to copy it into an array first.
691
+ */
692
+ function getMinArgs(args) {
693
+ if (args === void 0) return 0;
694
+ if (!isArray(args)) return args.isOptional ? 0 : 1;
695
+ let count = 0;
696
+ for (let i = 0; i < args.length; i++) {
697
+ if (args[i].isOptional) break;
698
+ count++;
699
+ }
700
+ return count;
701
+ }
702
+ /** Returns the maximum number of arguments that satisfies the args. */
703
+ function getMaxArgs(args) {
704
+ if (args === void 0) return 0;
705
+ if (!isArray(args)) return args.isVariadic ? Infinity : 1;
706
+ for (let i = 0; i < args.length; i++) if (args[args.length - 1 - i].isVariadic) return Infinity;
707
+ return args.length;
708
+ }
709
+ /** Check if a map has a key or any key from an array */
710
+ function has(map, key) {
711
+ if (isArray(key)) return map.has(key[0]);
712
+ return map.has(key);
713
+ }
714
+ /** Get a value from a map by a key or any key from an array */
715
+ function get(map, key) {
716
+ if (isArray(key)) return map.get(key[0]);
717
+ return map.get(key);
718
+ }
719
+ /**
720
+ * Parse the input tokens using the given CLI spec.
721
+ *
722
+ * This returns a `ParseResult`, which contains the array of
723
+ * subcommands used, the provided options, and the arguments.
724
+ *
725
+ * If parsing fails, this function throws.
726
+ *
727
+ * This function is exposed to make it easier to build a custom runner.
728
+ */
729
+ function parse(input, spec) {
730
+ const path = [spec];
731
+ const actions = [];
732
+ const optionActions = [];
733
+ if (spec.action) actions.push(spec.action);
734
+ else if (spec.requiresSubcommand) actions.push(usage);
735
+ let commandArgsMin = getMinArgs(spec.args);
736
+ let commandArgsMax = getMaxArgs(spec.args);
737
+ const foundArgs = [];
738
+ const foundOptions = /* @__PURE__ */ new Map();
739
+ let state = 0;
740
+ let optionArgs = null;
741
+ let optionArgsMin = 0;
742
+ let optionArgsMax = 0;
743
+ let requiredSeparator = false;
744
+ let argSeparatorIndex = -1;
745
+ const dependsOnOptions = [];
746
+ const exclusiveOnOptions = [];
747
+ const parseOption = (token) => {
748
+ const option = token.option;
749
+ if (!option.isRepeatable && has(foundOptions, option.name)) throw new ParseError(ctx(), "Repeated option");
750
+ if (option.exclusiveOn) exclusiveOnOptions.push(option);
751
+ if (option.dependsOn) dependsOnOptions.push(option);
752
+ if (option.action) optionActions.push(option.action);
753
+ if (option.isRepeatable) {
754
+ const maxRepeat = option.isRepeatable === true ? Infinity : option.isRepeatable;
755
+ let arr = get(foundOptions, option.name);
756
+ if (!arr) {
757
+ arr = [];
758
+ setEach(foundOptions, option.name, arr);
759
+ }
760
+ if (arr.length >= maxRepeat) throw new ParseError(ctx(), "Too many repetitions");
761
+ arr.push("");
762
+ return;
763
+ }
764
+ optionArgsMin = getMinArgs(option.args);
765
+ optionArgsMax = getMaxArgs(option.args);
766
+ optionArgs = [];
767
+ setEach(foundOptions, option.name, optionArgs);
768
+ if (optionArgsMax === 0) {
769
+ optionArgs = null;
770
+ state = 0;
771
+ return;
772
+ }
773
+ if (option.requiresSeparator) {
774
+ state = 2;
775
+ requiredSeparator = option.requiresSeparator;
776
+ } else state = 1;
777
+ };
778
+ const parseArg = (token) => {
779
+ if (!path[path.length - 1].requiresSubcommand && foundArgs.length >= commandArgsMax) throw new TooManyArguments(token, ctx());
780
+ foundArgs.push(token.literal);
781
+ };
782
+ const parseArgSeparator = () => {
783
+ if (foundArgs.length >= commandArgsMax) throw new ParseError(ctx(), "Unexpected argument '--', did you mean to use an option instead?");
784
+ argSeparatorIndex = foundArgs.length;
785
+ state = 0;
786
+ };
787
+ const parseSubcommand = (token) => {
788
+ path.push(token.subcommand);
789
+ commandArgsMin = getMinArgs(token.subcommand.args);
790
+ commandArgsMax = getMaxArgs(token.subcommand.args);
791
+ if (token.subcommand.action) actions.push(token.subcommand.action);
792
+ else if (token.subcommand.requiresSubcommand) actions.push(usage);
793
+ };
794
+ const ctx = () => ({ path });
795
+ const { finalState, tokens } = analyze(input, spec);
796
+ for (const token of tokens) switch (state) {
797
+ case 0:
798
+ switch (token.kind) {
799
+ case "subcommand":
800
+ parseSubcommand(token);
801
+ break;
802
+ case "arg":
803
+ parseArg(token);
804
+ break;
805
+ case "option":
806
+ parseOption(token);
807
+ break;
808
+ case "unknown-option": throw new UnknownOption(token, ctx());
809
+ case "option-arg": throw new InvalidOptionArg(token, ctx());
810
+ case "arg-separator":
811
+ parseArgSeparator();
812
+ break;
813
+ default: throw new ParseError(ctx(), "Unreachable");
814
+ }
815
+ break;
816
+ case 1:
817
+ assert(Array.isArray(optionArgs), "Invalid state, must have an array to store option arguments");
818
+ switch (token.kind) {
819
+ case "option-arg":
820
+ case "arg":
821
+ if (optionArgs.length < optionArgsMax) optionArgs.push(token.literal);
822
+ else {
823
+ optionArgs = null;
824
+ state = 0;
825
+ parseArg(token);
826
+ }
827
+ break;
828
+ case "option":
829
+ if (optionArgs.length < optionArgsMin) throw new TooFewOptionArguments(optionArgsMin, optionArgsMax, ctx());
830
+ parseOption(token);
831
+ break;
832
+ case "unknown-option":
833
+ if (optionArgs.length < optionArgsMax) optionArgs.push(token.literal);
834
+ else throw new UnknownOption(token, ctx());
835
+ break;
836
+ case "arg-separator":
837
+ if (optionArgs.length < optionArgsMin) throw new TooFewOptionArguments(optionArgsMin, optionArgsMax, ctx());
838
+ parseArgSeparator();
839
+ break;
840
+ case "subcommand": throw new ParseError(ctx(), "Unexpected token");
841
+ default: throw new ParseError(ctx(), "Unreachable");
842
+ }
843
+ break;
844
+ case 2:
845
+ assert(Array.isArray(optionArgs), "Invalid state, must have an array to store option arguments");
846
+ assert(requiredSeparator, "Invalid state, a separator must be set");
847
+ switch (token.kind) {
848
+ case "option-arg":
849
+ if (typeof requiredSeparator === "string" && token.separator !== requiredSeparator) throw new ParseError(ctx(), `Incorrect separator, use '${requiredSeparator}' instead of '${token.separator}'`);
850
+ optionArgs.push(token.literal);
851
+ state = 0;
852
+ break;
853
+ case "arg":
854
+ if (optionArgsMin > 0) throw new TooFewOptionArguments(optionArgsMin, optionArgsMax, ctx());
855
+ optionArgs = null;
856
+ state = 0;
857
+ parseArg(token);
858
+ break;
859
+ case "option":
860
+ if (optionArgsMin > 0) throw new TooFewOptionArguments(optionArgsMin, optionArgsMax, ctx());
861
+ parseOption(token);
862
+ break;
863
+ case "unknown-option": throw new UnknownOption(token, ctx());
864
+ case "arg-separator": throw new ParseError(ctx(), "Unexpected token");
865
+ case "subcommand":
866
+ if (optionArgsMin > 0) throw new TooFewOptionArguments(optionArgsMin, optionArgsMax, ctx());
867
+ parseSubcommand(token);
868
+ state = 0;
869
+ break;
870
+ default: throw new ParseError(ctx(), "Unreachable");
871
+ }
872
+ }
873
+ const hasOptionAction = optionActions.length > 0;
874
+ if (!hasOptionAction && foundArgs.length < commandArgsMin) throw new TooFewArguments(commandArgsMin, commandArgsMax, ctx());
875
+ if (optionArgs && optionArgs.length < optionArgsMin) throw new TooFewOptionArguments(optionArgsMin, optionArgsMax, ctx());
876
+ for (const option of dependsOnOptions) for (const name of option.dependsOn) if (!foundOptions.has(name)) throw new ParseError(ctx(), `${option.name} requires ${name}, add it to fix this error`);
877
+ for (const option of exclusiveOnOptions) for (const name of option.exclusiveOn) if (foundOptions.has(name)) throw new ParseError(ctx(), `${option.name} can't be used together with ${name}`);
878
+ if (!hasOptionAction) {
879
+ for (const name of finalState.localRequiredOptions.keys()) if (!foundOptions.has(name)) throw new MissingRequiredOption(name, ctx());
880
+ for (const name of finalState.persistentRequiredOptions.keys()) if (!foundOptions.has(name)) throw new MissingRequiredOption(name, ctx());
881
+ }
882
+ return {
883
+ path,
884
+ actions,
885
+ optionActions,
886
+ argSeparatorIndex,
887
+ args: foundArgs,
888
+ options: foundOptions
889
+ };
890
+ }
891
+ // src/completion.ts
892
+ const SHELLS = [
893
+ "bash",
894
+ "zsh",
895
+ "fish"
896
+ ];
897
+ function isShell(value) {
898
+ return SHELLS.includes(value);
899
+ }
900
+ const firstLine = (description) => description ? description.split("\n", 1)[0].trim() : "";
901
+ /** Turn an arbitrary name into a safe shell identifier fragment. */
902
+ const sanitize = (value) => value.replace(/[^A-Za-z0-9]+/g, "_");
903
+ function encodeLocation(location) {
904
+ const bytes = new TextEncoder().encode(JSON.stringify(location));
905
+ let binary = "";
906
+ for (const byte of bytes) binary += String.fromCharCode(byte);
907
+ return btoa(binary);
908
+ }
909
+ function decodeLocation(encoded) {
910
+ const binary = atob(encoded);
911
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
912
+ return JSON.parse(new TextDecoder().decode(bytes));
913
+ }
914
+ /** Build {@link ArgComplete} for one argument at a known spec location. */
915
+ function argCompleteFor(arg, location) {
916
+ const staticValues = (arg.suggestions ?? []).map((suggestion) => typeof suggestion === "string" ? suggestion : makeArray(suggestion.name)[0]);
917
+ const genIndex = makeArray(arg.generators).findIndex((generator) => generator.custom);
918
+ const customLocation = genIndex >= 0 ? encodeLocation({
919
+ ...location,
920
+ genIndex
921
+ }) : void 0;
922
+ if (!arg.template && staticValues.length === 0 && !customLocation) return;
923
+ return {
924
+ template: arg.template,
925
+ staticValues,
926
+ customLocation
927
+ };
928
+ }
929
+ /** Completion for a option's first argument, if it has any metadata. */
930
+ function optionArgComplete(option, path) {
931
+ const arg = asArray(option.args)[0];
932
+ if (!arg) return void 0;
933
+ return argCompleteFor(arg, {
934
+ path,
935
+ option: makeArray(option.name)[0],
936
+ argIndex: 0
937
+ });
938
+ }
939
+ /** Completion for a command's first positional arg that has metadata. */
940
+ function positionalComplete(command, path) {
941
+ const args = asArray(command.args);
942
+ for (let index = 0; index < args.length; index += 1) {
943
+ const ac = argCompleteFor(args[index], {
944
+ path,
945
+ argIndex: index
946
+ });
947
+ if (ac) return ac;
948
+ }
949
+ }
950
+ function toOptionInfo(option, path) {
951
+ return {
952
+ tokens: makeArray(option.name),
953
+ description: firstLine(option.description),
954
+ takesArg: asArray(option.args).length > 0,
955
+ argComplete: optionArgComplete(option, path)
956
+ };
957
+ }
958
+ /**
959
+ * Flatten the spec into a list of command nodes. Hidden options and subcommands
960
+ * are excluded; persistent options are propagated to descendants.
961
+ */
962
+ function walk(spec) {
963
+ const nodes = [];
964
+ const recurse = (command, key, path, inheritedPersistent) => {
965
+ const ownOptions = (command.options ?? []).filter((option) => !option.hidden);
966
+ const children = (command.subcommands ?? []).filter((sub) => !sub.hidden);
967
+ const node = {
968
+ key,
969
+ path,
970
+ subcommands: [],
971
+ options: [...inheritedPersistent, ...ownOptions].map((option) => toOptionInfo(option, path)),
972
+ positional: positionalComplete(command, path),
973
+ transitions: []
974
+ };
975
+ const childPersistent = [...inheritedPersistent, ...ownOptions.filter((option) => option.isPersistent)];
976
+ for (const child of children) {
977
+ const tokens = makeArray(child.name);
978
+ const childKey = `${key}_${sanitize(tokens[0])}`;
979
+ node.subcommands.push({
980
+ tokens,
981
+ description: firstLine(child.description)
982
+ });
983
+ for (const token of tokens) node.transitions.push({
984
+ token,
985
+ childKey
986
+ });
987
+ recurse(child, childKey, [...path, tokens[0]], childPersistent);
988
+ }
989
+ nodes.push(node);
990
+ };
991
+ recurse(spec, "cmd", [], []);
992
+ return nodes;
993
+ }
994
+ /** Single-quote a string for bash/zsh. */
995
+ function shQuote(value) {
996
+ return `'${value.replaceAll("'", "'\\''")}'`;
997
+ }
998
+ /** Single-quote a string for fish (only `\` and `'` are special). */
999
+ function fishQuote(value) {
1000
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
1001
+ }
1002
+ /** Every candidate token offered at a node (subcommand aliases + option aliases). */
1003
+ function candidateTokens(node) {
1004
+ const tokens = [];
1005
+ for (const sub of node.subcommands) tokens.push(...sub.tokens);
1006
+ for (const option of node.options) tokens.push(...option.tokens);
1007
+ return tokens;
1008
+ }
1009
+ function renderTransitionCases(nodes, indent) {
1010
+ const lines = [];
1011
+ for (const node of nodes) for (const { token, childKey } of node.transitions) lines.push(`${indent}${shQuote(`${node.key}::${token}`)}) cmd=${shQuote(childKey)} ;;`);
1012
+ return lines;
1013
+ }
1014
+ /**
1015
+ * bash candidate lines for an arg. `append` keeps existing COMPREPLY (for a
1016
+ * positional, which coexists with subcommands/options); otherwise it replaces.
1017
+ * The generator callback uses the typed command and is guarded for resilience.
1018
+ */
1019
+ function bashArgLines(ac, indent, append) {
1020
+ const lines = [];
1021
+ const op = append ? "COMPREPLY+=" : "COMPREPLY=";
1022
+ const hasWords = ac.staticValues.length > 0 || ac.customLocation !== void 0;
1023
+ if (hasWords) {
1024
+ lines.push(`${indent}local words=${ac.staticValues.length > 0 ? shQuote(ac.staticValues.join(" ")) : "\"\""}`);
1025
+ if (ac.customLocation) {
1026
+ lines.push(`${indent}if command -v "\${COMP_WORDS[0]}" >/dev/null 2>&1; then`);
1027
+ lines.push(`${indent} words="$words $("\${COMP_WORDS[0]}" completion generate-suggestions ${shQuote(ac.customLocation)} -- "\${COMP_WORDS[@]:1}" 2>/dev/null)"`);
1028
+ lines.push(`${indent}fi`);
1029
+ }
1030
+ lines.push(`${indent}${op}( $(compgen -W "$words" -- "$cur") )`);
1031
+ }
1032
+ if (ac.template) {
1033
+ const option = ac.template === "folders" ? "-d" : "-f";
1034
+ const fileOp = hasWords || append ? "COMPREPLY+=" : "COMPREPLY=";
1035
+ lines.push(`${indent}${fileOp}( $(compgen ${option} -- "$cur") )`);
1036
+ }
1037
+ return lines;
1038
+ }
1039
+ function renderBash(spec, nodes) {
1040
+ const id = sanitize(spec.name);
1041
+ const lines = [];
1042
+ lines.push(`_${id}() {`);
1043
+ lines.push(` local cur="\${COMP_WORDS[COMP_CWORD]}"`);
1044
+ lines.push(` local prev="\${COMP_WORDS[COMP_CWORD-1]}"`);
1045
+ lines.push(` local cmd="cmd" i w`);
1046
+ lines.push(` for (( i=1; i < COMP_CWORD; i++ )); do`);
1047
+ lines.push(` w="\${COMP_WORDS[i]}"`);
1048
+ lines.push(` case "$w" in -* ) continue ;; esac`);
1049
+ lines.push(` case "\${cmd}::\${w}" in`);
1050
+ lines.push(...renderTransitionCases(nodes, " "));
1051
+ lines.push(` esac`);
1052
+ lines.push(` done`);
1053
+ const argCases = [];
1054
+ for (const node of nodes) for (const option of node.options) {
1055
+ if (!option.argComplete) continue;
1056
+ for (const token of option.tokens) {
1057
+ argCases.push(` ${shQuote(`${node.key}::${token}`)})`);
1058
+ argCases.push(...bashArgLines(option.argComplete, " ", false));
1059
+ argCases.push(` return ;;`);
1060
+ }
1061
+ }
1062
+ if (argCases.length > 0) {
1063
+ lines.push(` case "\${cmd}::\${prev}" in`);
1064
+ lines.push(...argCases);
1065
+ lines.push(` esac`);
1066
+ }
1067
+ lines.push(` local opts=""`);
1068
+ lines.push(` case "$cmd" in`);
1069
+ for (const node of nodes) lines.push(` ${shQuote(node.key)}) opts=${shQuote(candidateTokens(node).join(" "))} ;;`);
1070
+ lines.push(` esac`);
1071
+ lines.push(` COMPREPLY=( $(compgen -W "$opts" -- "$cur") )`);
1072
+ const posCases = [];
1073
+ for (const node of nodes) {
1074
+ if (!node.positional) continue;
1075
+ posCases.push(` ${shQuote(node.key)})`);
1076
+ posCases.push(...bashArgLines(node.positional, " ", true));
1077
+ posCases.push(` ;;`);
1078
+ }
1079
+ if (posCases.length > 0) {
1080
+ lines.push(` case "$cmd" in`);
1081
+ lines.push(...posCases);
1082
+ lines.push(` esac`);
1083
+ }
1084
+ lines.push(`}`);
1085
+ lines.push(`complete -F _${id} ${shQuote(spec.name)}`);
1086
+ return lines.join("\n");
1087
+ }
1088
+ /** zsh candidate lines for an arg (used for both option values and positionals). */
1089
+ function zshArgLines(ac, indent) {
1090
+ const lines = [];
1091
+ if (ac.staticValues.length > 0 || ac.customLocation !== void 0) {
1092
+ lines.push(`${indent}local -a vals`);
1093
+ lines.push(`${indent}vals=( ${ac.staticValues.map(shQuote).join(" ")} )`);
1094
+ if (ac.customLocation) {
1095
+ lines.push(`${indent}if command -v "$words[1]" >/dev/null 2>&1; then`);
1096
+ lines.push(`${indent} vals+=( \${(f)"$("$words[1]" completion generate-suggestions ${shQuote(ac.customLocation)} -- \${words[2,-1]} 2>/dev/null)"} )`);
1097
+ lines.push(`${indent}fi`);
1098
+ }
1099
+ lines.push(`${indent}compadd -a vals`);
1100
+ }
1101
+ if (ac.template) lines.push(`${indent}${ac.template === "folders" ? "_files -/" : "_files"}`);
1102
+ return lines;
1103
+ }
1104
+ function renderZsh(spec, nodes) {
1105
+ const id = sanitize(spec.name);
1106
+ const lines = [];
1107
+ lines.push(`#compdef ${spec.name}`);
1108
+ lines.push(`_${id}() {`);
1109
+ lines.push(` local cmd=cmd i w prev`);
1110
+ lines.push(` for (( i=2; i < CURRENT; i++ )); do`);
1111
+ lines.push(` w="\${words[i]}"`);
1112
+ lines.push(` case "$w" in -* ) continue ;; esac`);
1113
+ lines.push(` case "\${cmd}::\${w}" in`);
1114
+ lines.push(...renderTransitionCases(nodes, " "));
1115
+ lines.push(` esac`);
1116
+ lines.push(` done`);
1117
+ lines.push(` prev="\${words[CURRENT-1]}"`);
1118
+ const argCases = [];
1119
+ for (const node of nodes) for (const option of node.options) {
1120
+ if (!option.argComplete) continue;
1121
+ for (const token of option.tokens) {
1122
+ argCases.push(` ${shQuote(`${node.key}::${token}`)})`);
1123
+ argCases.push(...zshArgLines(option.argComplete, " "));
1124
+ argCases.push(` return ;;`);
1125
+ }
1126
+ }
1127
+ if (argCases.length > 0) {
1128
+ lines.push(` case "\${cmd}::\${prev}" in`);
1129
+ lines.push(...argCases);
1130
+ lines.push(` esac`);
1131
+ }
1132
+ lines.push(` local -a opts`);
1133
+ lines.push(` case "$cmd" in`);
1134
+ for (const node of nodes) {
1135
+ const entries = [];
1136
+ for (const sub of node.subcommands) for (const token of sub.tokens) entries.push(shQuote(`${token}:${sub.description}`));
1137
+ for (const option of node.options) for (const token of option.tokens) entries.push(shQuote(`${token}:${option.description}`));
1138
+ lines.push(` ${shQuote(node.key)}) opts=( ${entries.join(" ")} ) ;;`);
1139
+ }
1140
+ lines.push(` esac`);
1141
+ lines.push(` _describe -t values ${shQuote(spec.name)} opts`);
1142
+ const posCases = [];
1143
+ for (const node of nodes) {
1144
+ if (!node.positional) continue;
1145
+ posCases.push(` ${shQuote(node.key)})`);
1146
+ posCases.push(...zshArgLines(node.positional, " "));
1147
+ posCases.push(` ;;`);
1148
+ }
1149
+ if (posCases.length > 0) {
1150
+ lines.push(` case "$cmd" in`);
1151
+ lines.push(...posCases);
1152
+ lines.push(` esac`);
1153
+ }
1154
+ lines.push(`}`);
1155
+ lines.push(`_${id} "$@"`);
1156
+ return lines.join("\n");
1157
+ }
1158
+ /** Build the fish `complete` option selectors (`-s x`, `-l long`, `-o old`). */
1159
+ function fishOptionSelectors(tokens) {
1160
+ const parts = [];
1161
+ for (const token of tokens) if (token.startsWith("--")) parts.push(`-l ${fishQuote(token.slice(2))}`);
1162
+ else if (token.startsWith("-") && token.length === 2) parts.push(`-s ${fishQuote(token.slice(1))}`);
1163
+ else if (token.startsWith("-") && token.length > 2) parts.push(`-o ${fishQuote(token.slice(1))}`);
1164
+ else parts.push(`-a ${fishQuote(token)}`);
1165
+ return parts.join(" ");
1166
+ }
1167
+ /** A guarded fish command substitution that runs a generator by location. */
1168
+ function fishGuardedCallback(location) {
1169
+ return `(set -l c (commandline -opc)[1]; if command -q -- $c; or test -x "$c"; $c completion generate-suggestions ${location} -- (commandline -opc) 2>/dev/null; end)`;
1170
+ }
1171
+ /** The `-a` candidate bits and whether the shell's file completion is wanted. */
1172
+ function fishValueBits(ac) {
1173
+ const parts = [...ac.staticValues];
1174
+ if (ac.template === "folders" || Array.isArray(ac.template)) parts.push("(__fish_complete_directories)");
1175
+ if (ac.customLocation) parts.push(fishGuardedCallback(ac.customLocation));
1176
+ return {
1177
+ aBits: parts.length > 0 ? ` -a ${fishQuote(parts.join(" "))}` : "",
1178
+ wantsFiles: ac.template === "filepaths" || Array.isArray(ac.template)
1179
+ };
1180
+ }
1181
+ /**
1182
+ * The suffix after a option's selectors. Uses `-x` (exclusive: requires a value
1183
+ * and suppresses fish's file fallback) when the arg has its own candidates and
1184
+ * isn't a filepaths template; `-r` lets files through when they're wanted.
1185
+ */
1186
+ function fishOptionArgSuffix(option) {
1187
+ if (!option.takesArg) return "";
1188
+ if (!option.argComplete) return " -r";
1189
+ const { aBits, wantsFiles } = fishValueBits(option.argComplete);
1190
+ return `${wantsFiles ? " -r" : " -x"}${aBits}`;
1191
+ }
1192
+ function renderFish(spec, nodes) {
1193
+ const id = sanitize(spec.name);
1194
+ const name = spec.name;
1195
+ const lines = [];
1196
+ lines.push(`function __fish_${id}_at`);
1197
+ lines.push(` set -l cmd cmd`);
1198
+ lines.push(` set -l toks (commandline -opc)`);
1199
+ lines.push(` for w in $toks[2..-1]`);
1200
+ lines.push(` if string match -qr '^-' -- $w`);
1201
+ lines.push(` continue`);
1202
+ lines.push(` end`);
1203
+ lines.push(` switch "$cmd $w"`);
1204
+ for (const node of nodes) for (const { token, childKey } of node.transitions) {
1205
+ lines.push(` case ${fishQuote(`${node.key} ${token}`)}`);
1206
+ lines.push(` set cmd ${fishQuote(childKey)}`);
1207
+ }
1208
+ lines.push(` end`);
1209
+ lines.push(` end`);
1210
+ lines.push(` test "$cmd" = "$argv[1]"`);
1211
+ lines.push(`end`);
1212
+ lines.push("");
1213
+ lines.push(`complete -c ${fishQuote(name)} -f`);
1214
+ for (const node of nodes) {
1215
+ const guard = `-n ${fishQuote(`__fish_${id}_at ${node.key}`)}`;
1216
+ for (const sub of node.subcommands) for (const token of sub.tokens) {
1217
+ const desc = sub.description ? ` -d ${fishQuote(sub.description)}` : "";
1218
+ lines.push(`complete -c ${fishQuote(name)} ${guard} -a ${fishQuote(token)}${desc}`);
1219
+ }
1220
+ for (const option of node.options) {
1221
+ const desc = option.description ? ` -d ${fishQuote(option.description)}` : "";
1222
+ const suffix = fishOptionArgSuffix(option);
1223
+ lines.push(`complete -c ${fishQuote(name)} ${guard} ${fishOptionSelectors(option.tokens)}${suffix}${desc}`);
1224
+ }
1225
+ if (node.positional) {
1226
+ const { aBits, wantsFiles } = fishValueBits(node.positional);
1227
+ lines.push(`complete -c ${fishQuote(name)} ${guard}${aBits}${wantsFiles ? " -F" : ""}`);
1228
+ }
1229
+ }
1230
+ return lines.join("\n");
1231
+ }
1232
+ function analyzeState(spec, before) {
1233
+ const { tokens, finalState } = analyze(before, spec);
1234
+ const path = [spec];
1235
+ let current = spec;
1236
+ let pending = null;
1237
+ let pendingFilled = 0;
1238
+ let pendingMax = 0;
1239
+ let positionalIndex = 0;
1240
+ let forceArgs = false;
1241
+ let sawPositional = false;
1242
+ let usedNonPersistentOption = false;
1243
+ const presentNames = /* @__PURE__ */ new Set();
1244
+ const countByOption = /* @__PURE__ */ new Map();
1245
+ for (const token of tokens) switch (token.kind) {
1246
+ case "subcommand":
1247
+ current = token.subcommand;
1248
+ path.push(current);
1249
+ positionalIndex = 0;
1250
+ sawPositional = false;
1251
+ pending = null;
1252
+ break;
1253
+ case "option": {
1254
+ const option = token.option;
1255
+ for (const name of makeArray(option.name)) presentNames.add(name);
1256
+ countByOption.set(option, (countByOption.get(option) ?? 0) + 1);
1257
+ if (!option.isPersistent) usedNonPersistentOption = true;
1258
+ const max = getMaxArgs(option.args);
1259
+ if (max > 0 && !option.requiresSeparator) {
1260
+ pending = {
1261
+ option,
1262
+ argIndex: 0
1263
+ };
1264
+ pendingFilled = 0;
1265
+ pendingMax = max;
1266
+ } else pending = null;
1267
+ break;
1268
+ }
1269
+ case "option-arg":
1270
+ pending = null;
1271
+ break;
1272
+ case "arg":
1273
+ if (pending && pendingFilled < pendingMax) {
1274
+ pendingFilled += 1;
1275
+ pending = {
1276
+ option: pending.option,
1277
+ argIndex: pendingFilled
1278
+ };
1279
+ if (pendingFilled >= pendingMax) pending = null;
1280
+ } else {
1281
+ positionalIndex += 1;
1282
+ sawPositional = true;
1283
+ pending = null;
1284
+ }
1285
+ break;
1286
+ case "arg-separator":
1287
+ forceArgs = true;
1288
+ pending = null;
1289
+ break;
1290
+ case "unknown-option": pending = null;
1291
+ }
1292
+ const availableOptions = [];
1293
+ const seen = /* @__PURE__ */ new Set();
1294
+ for (const map of [finalState.localOptions, finalState.persistentOptions]) for (const option of map.values()) if (!seen.has(option)) {
1295
+ seen.add(option);
1296
+ availableOptions.push(option);
1297
+ }
1298
+ return {
1299
+ path,
1300
+ current,
1301
+ availableOptions,
1302
+ pending,
1303
+ positionalIndex,
1304
+ presentNames,
1305
+ countByOption,
1306
+ forceArgs,
1307
+ sawPositional,
1308
+ usedNonPersistentOption
1309
+ };
1310
+ }
1311
+ /** Does any command on the path enable a sticky parser directive? */
1312
+ function directiveOnPath(path, key) {
1313
+ return path.some((command) => command.parserDirectives?.[key]);
1314
+ }
1315
+ /** The option-arg separators in effect (the deepest command that sets them). */
1316
+ function separatorsOnPath(path) {
1317
+ let separators = ["="];
1318
+ for (const command of path) {
1319
+ const configured = command.parserDirectives?.optionArgSeparators;
1320
+ if (configured) separators = makeArray(configured);
1321
+ }
1322
+ return separators;
1323
+ }
1324
+ /** Enumerate filesystem entries matching `partial` for a path template. */
1325
+ async function enumeratePaths(partial, template) {
1326
+ const { readdirSync } = await import("node:fs");
1327
+ const slash = partial.lastIndexOf("/");
1328
+ const dir = slash >= 0 ? partial.slice(0, slash + 1) : "";
1329
+ const base = slash >= 0 ? partial.slice(slash + 1) : partial;
1330
+ const foldersOnly = template === "folders";
1331
+ let entries;
1332
+ try {
1333
+ entries = readdirSync(dir || ".", { withFileTypes: true });
1334
+ } catch {
1335
+ return [];
1336
+ }
1337
+ const candidates = [];
1338
+ for (const entry of entries) {
1339
+ if (!entry.name.startsWith(base)) continue;
1340
+ const isDir = entry.isDirectory();
1341
+ if (foldersOnly && !isDir) continue;
1342
+ candidates.push({ value: `${dir}${entry.name}${isDir ? "/" : ""}` });
1343
+ }
1344
+ return candidates;
1345
+ }
1346
+ /** All candidate values for a single argument (suggestions/generators/template). */
1347
+ async function argValues(arg, tokens, partial) {
1348
+ const candidates = [];
1349
+ for (const suggestion of arg.suggestions ?? []) candidates.push(typeof suggestion === "string" ? { value: suggestion } : {
1350
+ value: makeArray(suggestion.name)[0],
1351
+ description: suggestion.description
1352
+ });
1353
+ for (const generator of makeArray(arg.generators)) {
1354
+ if (!generator.custom) continue;
1355
+ const produced = await generator.custom([...tokens, partial]);
1356
+ for (const suggestion of produced) candidates.push(typeof suggestion === "string" ? { value: suggestion } : {
1357
+ value: makeArray(suggestion.name)[0],
1358
+ description: suggestion.description
1359
+ });
1360
+ }
1361
+ if (arg.template) candidates.push(...await enumeratePaths(partial, arg.template));
1362
+ return candidates;
1363
+ }
1364
+ /** Pick the argument being completed at a positional index (honors variadic). */
1365
+ function positionalArg(command, index) {
1366
+ const args = asArray(command.args);
1367
+ if (args.length === 0) return void 0;
1368
+ if (index < args.length) return args[index];
1369
+ const last = args[args.length - 1];
1370
+ return last?.isVariadic ? last : void 0;
1371
+ }
1372
+ /**
1373
+ * Resolve completion candidates for a partial command line, reusing the parser
1374
+ * (`analyze`) for context. Dispatches between completing a option's value and
1375
+ * completing a fresh token (subcommand / positional / option).
1376
+ */
1377
+ async function completeRuntime(req) {
1378
+ const state = analyzeState(req.spec, req.before);
1379
+ const separators = separatorsOnPath(state.path);
1380
+ const optionValue = await resolveOptionValue(state, req, separators);
1381
+ if (optionValue) return optionValue;
1382
+ return freshTokenCandidates(state, req, separators);
1383
+ }
1384
+ /**
1385
+ * Candidates when the word being completed is a option's value — either attached
1386
+ * (`--option=value`) or the separate token after a option that expects one. Returns
1387
+ * null when no option value is being completed.
1388
+ */
1389
+ async function resolveOptionValue(state, req, separators) {
1390
+ const tokens = [...req.before];
1391
+ for (const separator of separators) {
1392
+ const at = req.partial.indexOf(separator);
1393
+ if (at <= 0) continue;
1394
+ const optionName = req.partial.slice(0, at);
1395
+ const option = state.availableOptions.find((candidate) => makeArray(candidate.name).includes(optionName));
1396
+ const arg = option && asArray(option.args)[0];
1397
+ if (option && arg) return (await argValues(arg, tokens, req.partial.slice(at + separator.length))).map((candidate) => ({
1398
+ value: `${optionName}${separator}${candidate.value}`,
1399
+ description: candidate.description
1400
+ }));
1401
+ }
1402
+ if (state.pending) {
1403
+ const arg = asArray(state.pending.option.args)[state.pending.argIndex];
1404
+ return arg ? argValues(arg, tokens, req.partial) : [];
1405
+ }
1406
+ return null;
1407
+ }
1408
+ /** Candidates when completing a fresh token: subcommand / positional / option. */
1409
+ async function freshTokenCandidates(state, req, separators) {
1410
+ const candidates = [];
1411
+ const tokens = [...req.before];
1412
+ const posixNoncompliant = directiveOnPath(state.path, "flagsArePosixNoncompliant");
1413
+ const partialIsOption = !posixNoncompliant && (req.partial.startsWith("-") || req.partial.startsWith("+"));
1414
+ const wantValues = !(req.gateOptions && partialIsOption);
1415
+ const wantOptions = !req.gateOptions || partialIsOption || posixNoncompliant;
1416
+ if (wantValues) {
1417
+ const positional = positionalArg(state.current, state.positionalIndex);
1418
+ if (positional) candidates.push(...await argValues(positional, tokens, req.partial));
1419
+ if (state.forceArgs) return candidates;
1420
+ if (!state.sawPositional && !state.usedNonPersistentOption) for (const sub of state.current.subcommands ?? []) {
1421
+ if (sub.hidden) continue;
1422
+ const description = firstLine(sub.description);
1423
+ for (const token of makeArray(sub.name)) candidates.push({
1424
+ value: token,
1425
+ description
1426
+ });
1427
+ }
1428
+ } else if (state.forceArgs) return candidates;
1429
+ const optionsBlocked = state.sawPositional && directiveOnPath(state.path, "optionsMustPrecedeArguments");
1430
+ if (wantOptions && !optionsBlocked) candidates.push(...optionCandidates(state, separators));
1431
+ return candidates;
1432
+ }
1433
+ /** Eligible option candidates after relationship filtering. */
1434
+ function optionCandidates(state, separators) {
1435
+ const required = [];
1436
+ const optional = [];
1437
+ for (const option of state.availableOptions) {
1438
+ if (option.hidden) continue;
1439
+ const count = state.countByOption.get(option) ?? 0;
1440
+ if (count > 0) {
1441
+ if (!option.isRepeatable) continue;
1442
+ if (typeof option.isRepeatable === "number" && count >= option.isRepeatable) continue;
1443
+ }
1444
+ if (option.exclusiveOn?.some((name) => state.presentNames.has(name))) continue;
1445
+ if (state.availableOptions.some((other) => other !== option && makeArray(other.name).some((name) => state.presentNames.has(name)) && other.exclusiveOn?.some((name) => makeArray(option.name).includes(name)))) continue;
1446
+ let description = firstLine(option.description);
1447
+ if (option.dependsOn) {
1448
+ const needs = option.dependsOn.join(", ");
1449
+ description = description ? `${description} (requires ${needs})` : `requires ${needs}`;
1450
+ }
1451
+ if (option.isRequired) description = description ? `${description} [required]` : "[required]";
1452
+ const sep = option.requiresSeparator === true ? separators[0] : typeof option.requiresSeparator === "string" ? option.requiresSeparator : void 0;
1453
+ const bucket = option.isRequired ? required : optional;
1454
+ for (const token of makeArray(option.name)) bucket.push({
1455
+ value: sep ? `${token}${sep}` : token,
1456
+ description
1457
+ });
1458
+ }
1459
+ return [...required, ...optional];
1460
+ }
1461
+ /**
1462
+ * Render the thin shim that forwards the line to `completion complete`. The
1463
+ * binary is invoked as typed (so `./pack` works) and guarded so a missing
1464
+ * command degrades to no completion instead of an error.
1465
+ */
1466
+ function renderRuntimeShim(spec, shell) {
1467
+ const id = sanitize(spec.name);
1468
+ const name = spec.name;
1469
+ if (shell === "bash") return [
1470
+ `_${id}_complete() {`,
1471
+ ` local bin="\${COMP_WORDS[0]}"`,
1472
+ ` command -v "$bin" >/dev/null 2>&1 || return`,
1473
+ ` local cur="\${COMP_WORDS[COMP_CWORD]}"`,
1474
+ ` local out`,
1475
+ ` out="$("$bin" completion complete --gate-options -- "\${COMP_WORDS[@]:1:COMP_CWORD-1}" "$cur" 2>/dev/null | cut -f1)"`,
1476
+ ` local IFS=$'\\n'`,
1477
+ ` COMPREPLY=( $(compgen -W "$out" -- "$cur") )`,
1478
+ `}`,
1479
+ `complete -F _${id}_complete ${shQuote(name)}`
1480
+ ].join("\n");
1481
+ if (shell === "zsh") return [
1482
+ `#compdef ${name}`,
1483
+ `_${id}_complete() {`,
1484
+ ` local bin="$words[1]"`,
1485
+ ` command -v "$bin" >/dev/null 2>&1 || return`,
1486
+ ` local -a lines`,
1487
+ ` lines=( "\${(@f)$("$bin" completion complete -- "\${words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}" )`,
1488
+ ` local -a disp`,
1489
+ ` local line`,
1490
+ ` for line in $lines; do disp+=( "\${line//$'\\t'/:}" ); done`,
1491
+ ` _describe -t values ${shQuote(name)} disp`,
1492
+ `}`,
1493
+ `_${id}_complete "$@"`
1494
+ ].join("\n");
1495
+ return [
1496
+ `function __${id}_complete`,
1497
+ ` set -l toks (commandline -opc)`,
1498
+ ` set -l cur (commandline -ct)`,
1499
+ ` set -l bin $toks[1]`,
1500
+ ` command -q -- $bin; or test -x "$bin"; or return`,
1501
+ ` $bin completion complete --gate-options -- $toks[2..-1] $cur 2>/dev/null`,
1502
+ `end`,
1503
+ `complete -c ${fishQuote(name)} -f -a '(__${id}_complete)'`
1504
+ ].join("\n");
1505
+ }
1506
+ /** Generate a completion script for `shell` from a CLI spec. */
1507
+ function getCompletion(spec, shell, mode = "runtime") {
1508
+ if (mode === "runtime") return renderRuntimeShim(spec, shell);
1509
+ const nodes = walk(spec);
1510
+ switch (shell) {
1511
+ case "bash": return renderBash(spec, nodes);
1512
+ case "zsh": return renderZsh(spec, nodes);
1513
+ case "fish": return renderFish(spec, nodes);
1514
+ }
1515
+ }
1516
+ /** Resolve the argument a {@link Location} points at, or null if it's stale. */
1517
+ function resolveArg(spec, location) {
1518
+ let command = spec;
1519
+ let ancestorPersistent = [];
1520
+ for (const segment of location.path) {
1521
+ ancestorPersistent = [...ancestorPersistent, ...(command.options ?? []).filter((option) => option.isPersistent)];
1522
+ const child = (command.subcommands ?? []).find((sub) => makeArray(sub.name).includes(segment));
1523
+ if (!child) return null;
1524
+ command = child;
1525
+ }
1526
+ let args;
1527
+ if (location.option !== void 0) {
1528
+ const option = [...ancestorPersistent, ...command.options ?? []].find((candidate) => makeArray(candidate.name).includes(location.option));
1529
+ if (!option) return null;
1530
+ args = asArray(option.args);
1531
+ } else args = asArray(command.args);
1532
+ return args[location.argIndex] ?? null;
1533
+ }
1534
+ /**
1535
+ * Run the generator a base64 location points at and return its suggestions as
1536
+ * insertable strings, or null if the location is invalid.
1537
+ */
1538
+ async function generateSuggestions(spec, encoded, tokens) {
1539
+ let location;
1540
+ try {
1541
+ location = decodeLocation(encoded);
1542
+ } catch {
1543
+ return null;
1544
+ }
1545
+ const arg = resolveArg(spec, location);
1546
+ if (!arg) return null;
1547
+ const generator = makeArray(arg.generators)[location.genIndex];
1548
+ if (!generator?.custom) return null;
1549
+ return (await generator.custom(tokens)).map((suggestion) => typeof suggestion === "string" ? suggestion : makeArray(suggestion.name)[0]);
1550
+ }
1551
+ /** A subcommand that prints a shell completion script for the whole CLI. */
1552
+ const completion = {
1553
+ name: "completion",
1554
+ description: "Print a shell completion script",
1555
+ args: {
1556
+ name: "shell",
1557
+ isOptional: true,
1558
+ suggestions: SHELLS
1559
+ },
1560
+ options: [{
1561
+ name: "--runtime",
1562
+ description: "Resolve candidates by invoking the CLI (default)",
1563
+ exclusiveOn: ["--shell"]
1564
+ }, {
1565
+ name: "--shell",
1566
+ description: "Emit a self-contained script with no per-Tab CLI call",
1567
+ exclusiveOn: ["--runtime"]
1568
+ }],
1569
+ subcommands: [{
1570
+ name: "complete",
1571
+ description: "Resolve completion candidates for a partial command line",
1572
+ hidden: true,
1573
+ options: [{
1574
+ name: "--gate-options",
1575
+ description: "Only suggest options after a leading dash"
1576
+ }],
1577
+ args: {
1578
+ name: "tokens",
1579
+ isVariadic: true,
1580
+ isOptional: true
1581
+ },
1582
+ async action({ path, args, options }) {
1583
+ const spec = path[0];
1584
+ const partial = args.length > 0 ? args[args.length - 1] : "";
1585
+ const lines = (await completeRuntime({
1586
+ spec,
1587
+ before: args.slice(0, -1),
1588
+ partial,
1589
+ gateOptions: options.has("--gate-options")
1590
+ })).map((candidate) => candidate.description ? `${candidate.value}\t${candidate.description}` : candidate.value);
1591
+ if (lines.length > 0) print(lines.join("\n"));
1592
+ return 0;
1593
+ }
1594
+ }, {
1595
+ name: "generate-suggestions",
1596
+ description: "Print dynamic suggestions for a completion generator",
1597
+ hidden: true,
1598
+ args: [{ name: "location" }, {
1599
+ name: "tokens",
1600
+ isVariadic: true,
1601
+ isOptional: true
1602
+ }],
1603
+ async action({ path, args: [encoded, ...tokens] }) {
1604
+ const spec = path[0];
1605
+ if (encoded === void 0) {
1606
+ printActionError("Missing generator location");
1607
+ return 1;
1608
+ }
1609
+ const suggestions = await generateSuggestions(spec, encoded, tokens);
1610
+ if (suggestions === null) {
1611
+ printActionError(`Unknown generator location '${encoded}'`);
1612
+ return 1;
1613
+ }
1614
+ if (suggestions.length > 0) print(suggestions.join("\n"));
1615
+ return 0;
1616
+ }
1617
+ }],
1618
+ action({ path: [spec], args: [shell], options, help }) {
1619
+ if (shell === void 0) {
1620
+ print(`Supported shells: ${SHELLS.join(", ")}`);
1621
+ return 0;
1622
+ }
1623
+ if (!isShell(shell)) {
1624
+ printActionError(`Unsupported shell '${shell}'\n\n${help({
1625
+ description: false,
1626
+ didYouMean: {
1627
+ input: shell,
1628
+ choices: SHELLS
1629
+ }
1630
+ })}`);
1631
+ return 1;
1632
+ }
1633
+ print(getCompletion(spec, shell, options.has("--shell") ? "shell" : "runtime"));
1634
+ return 0;
1635
+ }
1636
+ };
1637
+ // src/run.ts
1638
+ /**
1639
+ * Parse argv and execute the selected action.
1640
+ *
1641
+ * The final command action runs unless a parsed option has an action. Option
1642
+ * actions take precedence so helpers like `--help` can run without satisfying
1643
+ * the selected command's required arguments.
1644
+ */
1645
+ async function run(spec, args = defaultArgs()) {
1646
+ let result;
1647
+ try {
1648
+ result = parse(args, spec);
1649
+ } catch (error) {
1650
+ if (error instanceof UnknownOption) {
1651
+ const helpMessage = getHelp(error.context.path, {
1652
+ description: false,
1653
+ didYouMean: {
1654
+ input: error.option,
1655
+ choices: error.validOptions
1656
+ }
1657
+ });
1658
+ printError(`${error.message}\n\n${helpMessage}`);
1659
+ } else if (error instanceof ParseError) printError(`${error.message}\n\n${getHelp(error.context.path, { description: false })}`);
1660
+ else printError(`${error}`);
1661
+ return 1;
1662
+ }
1663
+ const action = result.optionActions.at(-1) ?? result.actions.at(-1);
1664
+ if (!action) return 1;
1665
+ const options = result.options;
1666
+ const actionInit = {
1667
+ help: (helpOptions) => getHelp(helpOptions?.path ?? result.path, helpOptions),
1668
+ args: result.args,
1669
+ path: result.path,
1670
+ argSeparatorIndex: result.argSeparatorIndex,
1671
+ options: {
1672
+ options,
1673
+ has: (name) => options.has(name),
1674
+ get: (name) => options.get(name)?.[0],
1675
+ all: (name) => options.get(name),
1676
+ count: (name) => options.get(name)?.length ?? 0
1677
+ }
1678
+ };
1679
+ let exitCode;
1680
+ try {
1681
+ const actionResult = action(actionInit);
1682
+ exitCode = typeof actionResult === "object" ? await actionResult : actionResult;
1683
+ } catch (error) {
1684
+ printError(error instanceof Error ? error.message : String(error));
1685
+ return 1;
1686
+ }
1687
+ return Math.min(Math.max(exitCode ?? 0, 0), 255);
1688
+ }
1689
+ function printError(...strings) {
1690
+ console.error("Error:", ...strings);
1691
+ }
1692
+ function defaultArgs() {
1693
+ return globalThis.process?.argv?.slice(2) ?? [];
1694
+ }
1695
+ // src/index.ts
1696
+ var src_default = {
1697
+ completion,
1698
+ help,
1699
+ helpCommand,
1700
+ printError,
1701
+ run
1702
+ };
1703
+ export { completion, src_default as default, help, helpCommand, printError, run };
1704
+
1705
+ //# sourceMappingURL=index.js.map