@optique/core 1.3.5 → 1.3.7-dev.3001

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.
@@ -1019,12 +1019,16 @@ function analyzeNoMatchContext(parsers) {
1019
1019
  * construction. This is a programmer error, not a user error.
1020
1020
  */
1021
1021
  var DuplicateOptionError = class extends Error {
1022
+ /** The option name shared by multiple fields. */
1023
+ optionName;
1024
+ /** The fields declaring the option. */
1025
+ sources;
1022
1026
  constructor(optionName$1, sources) {
1023
1027
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
1024
1028
  super(`Duplicate option name "${optionName$1}" found in fields: ${sourceNames.join(", ")}. Each option name must be unique within a parser combinator.`);
1029
+ this.name = "DuplicateOptionError";
1025
1030
  this.optionName = optionName$1;
1026
1031
  this.sources = sources;
1027
- this.name = "DuplicateOptionError";
1028
1032
  }
1029
1033
  };
1030
1034
  /**
@@ -1032,6 +1036,8 @@ var DuplicateOptionError = class extends Error {
1032
1036
  * during parser construction. This is a programmer error, not a user error.
1033
1037
  */
1034
1038
  var DuplicateCommandNameError = class extends TypeError {
1039
+ commandName;
1040
+ sources;
1035
1041
  constructor(commandName, sources) {
1036
1042
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
1037
1043
  super(`Duplicate command name "${commandName}" found in parsers: ${sourceNames.join(", ")}. Each command name or alias must be unique within active parser alternatives.`);
@@ -103,7 +103,9 @@ interface OrErrorOptions {
103
103
  * construction. This is a programmer error, not a user error.
104
104
  */
105
105
  declare class DuplicateOptionError extends Error {
106
+ /** The option name shared by multiple fields. */
106
107
  readonly optionName: string;
108
+ /** The fields declaring the option. */
107
109
  readonly sources: readonly (string | symbol)[];
108
110
  constructor(optionName: string, sources: readonly (string | symbol)[]);
109
111
  }
@@ -103,7 +103,9 @@ interface OrErrorOptions {
103
103
  * construction. This is a programmer error, not a user error.
104
104
  */
105
105
  declare class DuplicateOptionError extends Error {
106
+ /** The option name shared by multiple fields. */
106
107
  readonly optionName: string;
108
+ /** The fields declaring the option. */
107
109
  readonly sources: readonly (string | symbol)[];
108
110
  constructor(optionName: string, sources: readonly (string | symbol)[]);
109
111
  }
@@ -1019,12 +1019,16 @@ function analyzeNoMatchContext(parsers) {
1019
1019
  * construction. This is a programmer error, not a user error.
1020
1020
  */
1021
1021
  var DuplicateOptionError = class extends Error {
1022
+ /** The option name shared by multiple fields. */
1023
+ optionName;
1024
+ /** The fields declaring the option. */
1025
+ sources;
1022
1026
  constructor(optionName$1, sources) {
1023
1027
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
1024
1028
  super(`Duplicate option name "${optionName$1}" found in fields: ${sourceNames.join(", ")}. Each option name must be unique within a parser combinator.`);
1029
+ this.name = "DuplicateOptionError";
1025
1030
  this.optionName = optionName$1;
1026
1031
  this.sources = sources;
1027
- this.name = "DuplicateOptionError";
1028
1032
  }
1029
1033
  };
1030
1034
  /**
@@ -1032,6 +1036,8 @@ var DuplicateOptionError = class extends Error {
1032
1036
  * during parser construction. This is a programmer error, not a user error.
1033
1037
  */
1034
1038
  var DuplicateCommandNameError = class extends TypeError {
1039
+ commandName;
1040
+ sources;
1035
1041
  constructor(commandName, sources) {
1036
1042
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
1037
1043
  super(`Duplicate command name "${commandName}" found in parsers: ${sourceNames.join(", ")}. Each command name or alias must be unique within active parser alternatives.`);
@@ -0,0 +1,73 @@
1
+
2
+ //#region src/internal/joined-file-suggestion.ts
3
+ const joinedFiles = /* @__PURE__ */ new WeakMap();
4
+ /**
5
+ * Keeps a joined file fallback's origin available to the Bash encoder.
6
+ * Other shells retain the existing literal representation.
7
+ * @param prefix The option name and delimiter.
8
+ * @param file The original file suggestion.
9
+ * @param value The value prefix being completed.
10
+ * @returns The literal fallback with private file metadata.
11
+ * @internal
12
+ */
13
+ function joinedFileSuggestion(prefix, file, value) {
14
+ const literal = {
15
+ kind: "literal",
16
+ text: prefix + (file.pattern ?? ""),
17
+ description: file.description
18
+ };
19
+ joinedFiles.set(literal, {
20
+ files: [{
21
+ prefix,
22
+ file: file.pattern === value && !/[?*]/.test(value) && !(file.prefix != null && file.prefix !== "" && value.startsWith(file.prefix)) ? {
23
+ ...file,
24
+ pattern: void 0
25
+ } : file
26
+ }],
27
+ literal: false
28
+ });
29
+ return literal;
30
+ }
31
+ /**
32
+ * Retrieves the originating file suggestion for a joined literal fallback.
33
+ * @param suggestion The suggestion to inspect.
34
+ * @returns Its private file metadata, if present.
35
+ * @internal
36
+ */
37
+ function getJoinedFileSuggestion(suggestion) {
38
+ return joinedFiles.get(suggestion);
39
+ }
40
+ /**
41
+ * Preserves file origins when equal literal fallbacks are deduplicated.
42
+ * @param target The retained suggestion.
43
+ * @param source The duplicate suggestion.
44
+ * @internal
45
+ */
46
+ function mergeJoinedFileSuggestions(target, source) {
47
+ const a = joinedFiles.get(target);
48
+ const b = joinedFiles.get(source);
49
+ if (a === void 0 && b === void 0) return;
50
+ const files = [...a?.files ?? [], ...b?.files ?? []];
51
+ const seen = /* @__PURE__ */ new Set();
52
+ joinedFiles.set(target, {
53
+ literal: (a?.literal ?? true) || (b?.literal ?? true),
54
+ files: files.filter(({ prefix, file }) => {
55
+ const key = JSON.stringify([
56
+ prefix,
57
+ file.type,
58
+ file.pattern,
59
+ file.prefix,
60
+ file.includeHidden === true,
61
+ file.extensions?.toSorted()
62
+ ]);
63
+ if (seen.has(key)) return false;
64
+ seen.add(key);
65
+ return true;
66
+ })
67
+ });
68
+ }
69
+
70
+ //#endregion
71
+ exports.getJoinedFileSuggestion = getJoinedFileSuggestion;
72
+ exports.joinedFileSuggestion = joinedFileSuggestion;
73
+ exports.mergeJoinedFileSuggestions = mergeJoinedFileSuggestions;
@@ -0,0 +1,70 @@
1
+ //#region src/internal/joined-file-suggestion.ts
2
+ const joinedFiles = /* @__PURE__ */ new WeakMap();
3
+ /**
4
+ * Keeps a joined file fallback's origin available to the Bash encoder.
5
+ * Other shells retain the existing literal representation.
6
+ * @param prefix The option name and delimiter.
7
+ * @param file The original file suggestion.
8
+ * @param value The value prefix being completed.
9
+ * @returns The literal fallback with private file metadata.
10
+ * @internal
11
+ */
12
+ function joinedFileSuggestion(prefix, file, value) {
13
+ const literal = {
14
+ kind: "literal",
15
+ text: prefix + (file.pattern ?? ""),
16
+ description: file.description
17
+ };
18
+ joinedFiles.set(literal, {
19
+ files: [{
20
+ prefix,
21
+ file: file.pattern === value && !/[?*]/.test(value) && !(file.prefix != null && file.prefix !== "" && value.startsWith(file.prefix)) ? {
22
+ ...file,
23
+ pattern: void 0
24
+ } : file
25
+ }],
26
+ literal: false
27
+ });
28
+ return literal;
29
+ }
30
+ /**
31
+ * Retrieves the originating file suggestion for a joined literal fallback.
32
+ * @param suggestion The suggestion to inspect.
33
+ * @returns Its private file metadata, if present.
34
+ * @internal
35
+ */
36
+ function getJoinedFileSuggestion(suggestion) {
37
+ return joinedFiles.get(suggestion);
38
+ }
39
+ /**
40
+ * Preserves file origins when equal literal fallbacks are deduplicated.
41
+ * @param target The retained suggestion.
42
+ * @param source The duplicate suggestion.
43
+ * @internal
44
+ */
45
+ function mergeJoinedFileSuggestions(target, source) {
46
+ const a = joinedFiles.get(target);
47
+ const b = joinedFiles.get(source);
48
+ if (a === void 0 && b === void 0) return;
49
+ const files = [...a?.files ?? [], ...b?.files ?? []];
50
+ const seen = /* @__PURE__ */ new Set();
51
+ joinedFiles.set(target, {
52
+ literal: (a?.literal ?? true) || (b?.literal ?? true),
53
+ files: files.filter(({ prefix, file }) => {
54
+ const key = JSON.stringify([
55
+ prefix,
56
+ file.type,
57
+ file.pattern,
58
+ file.prefix,
59
+ file.includeHidden === true,
60
+ file.extensions?.toSorted()
61
+ ]);
62
+ if (seen.has(key)) return false;
63
+ seen.add(key);
64
+ return true;
65
+ })
66
+ });
67
+ }
68
+
69
+ //#endregion
70
+ export { getJoinedFileSuggestion, joinedFileSuggestion, mergeJoinedFileSuggestions };
@@ -758,6 +758,14 @@ type Suggestion = {
758
758
  * The current prefix/pattern for fallback when native completion is unavailable.
759
759
  */
760
760
  readonly pattern?: string;
761
+ /**
762
+ * Literal text at the start of `pattern` that is preserved on insertion
763
+ * but excluded from native filesystem matching, such as `output=`.
764
+ * Shell adapters ignore it if it does not lead both the pattern and
765
+ * the current word. Regenerate installed scripts to use this field.
766
+ * @since 1.1.13
767
+ */
768
+ readonly prefix?: string;
761
769
  /**
762
770
  * The type of file system entries to complete.
763
771
  */
@@ -758,6 +758,14 @@ type Suggestion = {
758
758
  * The current prefix/pattern for fallback when native completion is unavailable.
759
759
  */
760
760
  readonly pattern?: string;
761
+ /**
762
+ * Literal text at the start of `pattern` that is preserved on insertion
763
+ * but excluded from native filesystem matching, such as `output=`.
764
+ * Shell adapters ignore it if it does not lead both the pattern and
765
+ * the current word. Regenerate installed scripts to use this field.
766
+ * @since 1.1.13
767
+ */
768
+ readonly prefix?: string;
761
769
  /**
762
770
  * The type of file system entries to complete.
763
771
  */
@@ -4,6 +4,7 @@ const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
4
4
  const require_dependency_runtime = require('./dependency-runtime.cjs');
5
5
  const require_internal_parser = require('./internal/parser.cjs');
6
6
  const require_annotation_state = require('./annotation-state.cjs');
7
+ const require_joined_file_suggestion = require('./internal/joined-file-suggestion.cjs');
7
8
  const require_command_doc_metadata = require('./internal/command-doc-metadata.cjs');
8
9
  const require_passthrough = require('./internal/passthrough.cjs');
9
10
  const require_execution_context = require('./execution-context.cjs');
@@ -1516,6 +1517,7 @@ function multiple(parser, options = {}) {
1516
1517
  "file",
1517
1518
  suggestion.type,
1518
1519
  suggestion.pattern ?? "",
1520
+ suggestion.prefix ?? "",
1519
1521
  suggestion.includeHidden === true,
1520
1522
  suggestion.extensions == null ? "" : suggestion.extensions.toSorted().join("\0"),
1521
1523
  description
@@ -1523,14 +1525,16 @@ function multiple(parser, options = {}) {
1523
1525
  };
1524
1526
  return require_mode_dispatch.dispatchIterableByMode(parser.mode, function* () {
1525
1527
  const selectedValues = collectSelectedValuesSync();
1526
- const emitted = /* @__PURE__ */ new Set();
1528
+ const emitted = /* @__PURE__ */ new Map();
1527
1529
  const yieldUnique = function* (suggestions) {
1528
1530
  for (const s of suggestions) {
1529
1531
  const key = suggestionKey(s);
1530
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1531
- emitted.add(key);
1532
+ if (!shouldInclude(selectedValues, s)) continue;
1533
+ const existing = emitted.get(key);
1534
+ if (existing === void 0) {
1535
+ emitted.set(key, s);
1532
1536
  yield s;
1533
- }
1537
+ } else require_joined_file_suggestion.mergeJoinedFileSuggestions(existing, s);
1534
1538
  }
1535
1539
  };
1536
1540
  let shouldTryFallback = false;
@@ -1543,14 +1547,16 @@ function multiple(parser, options = {}) {
1543
1547
  if (shouldTryFallback) yield* yieldUnique(syncParser.suggest(require_execution_context.withChildContext(context, itemIndex, suggestFallbackState), prefix));
1544
1548
  }, async function* () {
1545
1549
  const selectedValues = await collectSelectedValuesAsync();
1546
- const emitted = /* @__PURE__ */ new Set();
1550
+ const emitted = /* @__PURE__ */ new Map();
1547
1551
  const yieldUnique = async function* (suggestions) {
1548
1552
  for await (const s of suggestions) {
1549
1553
  const key = suggestionKey(s);
1550
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1551
- emitted.add(key);
1554
+ if (!shouldInclude(selectedValues, s)) continue;
1555
+ const existing = emitted.get(key);
1556
+ if (existing === void 0) {
1557
+ emitted.set(key, s);
1552
1558
  yield s;
1553
- }
1559
+ } else require_joined_file_suggestion.mergeJoinedFileSuggestions(existing, s);
1554
1560
  }
1555
1561
  };
1556
1562
  let shouldTryFallback = false;
package/dist/modifiers.js CHANGED
@@ -4,6 +4,7 @@ import { dispatchByMode, dispatchIterableByMode, mapModeValue, wrapForMode } fro
4
4
  import { defineForwardedEffectfulSchedulingNodes } from "./dependency-runtime.js";
5
5
  import { defineInheritedAnnotationParser, defineParseLanes, defineSourceBindingOnlyAnnotationCompletionParser, getOwnParseLanes, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
6
6
  import { getDelegatedAnnotationState, hasDelegatedAnnotationCarrier, isAnnotationWrappedInitialState, normalizeDelegatedAnnotationState, normalizeNestedDelegatedAnnotationState } from "./annotation-state.js";
7
+ import { mergeJoinedFileSuggestions } from "./internal/joined-file-suggestion.js";
7
8
  import { getCommandDocMetadata, withCommandDocMetadata } from "./internal/command-doc-metadata.js";
8
9
  import { defineKnownCompletionLookup, defineOptionMatch, definePassThroughPriority, getKnownCompletion, getOptionMatch, getPassThroughPriority } from "./internal/passthrough.js";
9
10
  import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
@@ -1516,6 +1517,7 @@ function multiple(parser, options = {}) {
1516
1517
  "file",
1517
1518
  suggestion.type,
1518
1519
  suggestion.pattern ?? "",
1520
+ suggestion.prefix ?? "",
1519
1521
  suggestion.includeHidden === true,
1520
1522
  suggestion.extensions == null ? "" : suggestion.extensions.toSorted().join("\0"),
1521
1523
  description
@@ -1523,14 +1525,16 @@ function multiple(parser, options = {}) {
1523
1525
  };
1524
1526
  return dispatchIterableByMode(parser.mode, function* () {
1525
1527
  const selectedValues = collectSelectedValuesSync();
1526
- const emitted = /* @__PURE__ */ new Set();
1528
+ const emitted = /* @__PURE__ */ new Map();
1527
1529
  const yieldUnique = function* (suggestions) {
1528
1530
  for (const s of suggestions) {
1529
1531
  const key = suggestionKey(s);
1530
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1531
- emitted.add(key);
1532
+ if (!shouldInclude(selectedValues, s)) continue;
1533
+ const existing = emitted.get(key);
1534
+ if (existing === void 0) {
1535
+ emitted.set(key, s);
1532
1536
  yield s;
1533
- }
1537
+ } else mergeJoinedFileSuggestions(existing, s);
1534
1538
  }
1535
1539
  };
1536
1540
  let shouldTryFallback = false;
@@ -1543,14 +1547,16 @@ function multiple(parser, options = {}) {
1543
1547
  if (shouldTryFallback) yield* yieldUnique(syncParser.suggest(withChildContext(context, itemIndex, suggestFallbackState), prefix));
1544
1548
  }, async function* () {
1545
1549
  const selectedValues = await collectSelectedValuesAsync();
1546
- const emitted = /* @__PURE__ */ new Set();
1550
+ const emitted = /* @__PURE__ */ new Map();
1547
1551
  const yieldUnique = async function* (suggestions) {
1548
1552
  for await (const s of suggestions) {
1549
1553
  const key = suggestionKey(s);
1550
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1551
- emitted.add(key);
1554
+ if (!shouldInclude(selectedValues, s)) continue;
1555
+ const existing = emitted.get(key);
1556
+ if (existing === void 0) {
1557
+ emitted.set(key, s);
1552
1558
  yield s;
1553
- }
1559
+ } else mergeJoinedFileSuggestions(existing, s);
1554
1560
  }
1555
1561
  };
1556
1562
  let shouldTryFallback = false;
@@ -6,6 +6,7 @@ const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
6
6
  const require_internal_dependency = require('./internal/dependency.cjs');
7
7
  const require_dependency_runtime = require('./dependency-runtime.cjs');
8
8
  const require_annotation_state = require('./annotation-state.cjs');
9
+ const require_joined_file_suggestion = require('./internal/joined-file-suggestion.cjs');
9
10
  const require_command_doc_metadata = require('./internal/command-doc-metadata.cjs');
10
11
  const require_passthrough = require('./internal/passthrough.cjs');
11
12
  const require_command_alias = require('./internal/command-alias.cjs');
@@ -253,33 +254,33 @@ function* getSuggestionsWithDependency(valueParser, prefix, dependencyRegistry,
253
254
  }
254
255
  yield* valueParser.suggest(prefix);
255
256
  }
257
+ /** Returns the joined prefix accepted by option parsing. */
258
+ function joinedOptionPrefix(names, input) {
259
+ return names.filter((name) => name.startsWith("--") || name.startsWith("/") || name.startsWith("-") && name.length > 2).map((name) => name + (name.startsWith("/") ? ":" : "=")).find((prefix) => input.startsWith(prefix));
260
+ }
256
261
  /**
257
262
  * Internal sync helper for option suggest functionality.
258
263
  * @internal
259
264
  */
260
265
  function* suggestOptionSync(optionNames$1, valueParser, hidden, description, context, prefix) {
261
266
  if (hidden || context.optionsTerminated) return;
262
- const equalsIndex = prefix.indexOf("=");
263
- if (equalsIndex >= 0) {
264
- const optionPart = prefix.slice(0, equalsIndex);
265
- const valuePart = prefix.slice(equalsIndex + 1);
267
+ const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
268
+ const joinedPrefix = joinedOptionPrefix(optionNames$1, prefix);
269
+ if (joinedPrefix !== void 0 && !expectingValue) {
270
+ const optionPart = joinedPrefix.slice(0, -1);
271
+ const valuePart = prefix.slice(joinedPrefix.length);
266
272
  if (optionNames$1.includes(optionPart)) {
267
273
  if (valueParser && valueParser.suggest) {
268
274
  const valueSuggestions = getSuggestionsWithDependency(valueParser, valuePart, context.dependencyRegistry, context.exec);
269
275
  for (const suggestion of valueSuggestions) if (suggestion.kind === "literal") yield {
270
276
  kind: "literal",
271
- text: `${optionPart}=${suggestion.text}`,
272
- description: suggestion.description
273
- };
274
- else yield {
275
- kind: "literal",
276
- text: `${optionPart}=${suggestion.pattern || ""}`,
277
+ text: `${joinedPrefix}${suggestion.text}`,
277
278
  description: suggestion.description
278
279
  };
280
+ else yield require_joined_file_suggestion.joinedFileSuggestion(joinedPrefix, suggestion, valuePart);
279
281
  }
280
282
  }
281
283
  } else {
282
- const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
283
284
  if (!expectingValue && (prefix.startsWith("--") || prefix.startsWith("-") || prefix.startsWith("/") || prefix.startsWith("+"))) {
284
285
  for (const optionName$1 of optionNames$1) if (optionName$1.startsWith(prefix)) {
285
286
  if (prefix === "-" && optionName$1.length !== 2) continue;
@@ -344,27 +345,23 @@ async function* getSuggestionsWithDependencyAsync(valueParser, prefix, dependenc
344
345
  */
345
346
  async function* suggestOptionAsync(optionNames$1, valueParser, hidden, description, context, prefix) {
346
347
  if (hidden || context.optionsTerminated) return;
347
- const equalsIndex = prefix.indexOf("=");
348
- if (equalsIndex >= 0) {
349
- const optionPart = prefix.slice(0, equalsIndex);
350
- const valuePart = prefix.slice(equalsIndex + 1);
348
+ const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
349
+ const joinedPrefix = joinedOptionPrefix(optionNames$1, prefix);
350
+ if (joinedPrefix !== void 0 && !expectingValue) {
351
+ const optionPart = joinedPrefix.slice(0, -1);
352
+ const valuePart = prefix.slice(joinedPrefix.length);
351
353
  if (optionNames$1.includes(optionPart)) {
352
354
  if (valueParser && valueParser.suggest) {
353
355
  const valueSuggestions = getSuggestionsWithDependencyAsync(valueParser, valuePart, context.dependencyRegistry, context.exec);
354
356
  for await (const suggestion of valueSuggestions) if (suggestion.kind === "literal") yield {
355
357
  kind: "literal",
356
- text: `${optionPart}=${suggestion.text}`,
357
- description: suggestion.description
358
- };
359
- else yield {
360
- kind: "literal",
361
- text: `${optionPart}=${suggestion.pattern || ""}`,
358
+ text: `${joinedPrefix}${suggestion.text}`,
362
359
  description: suggestion.description
363
360
  };
361
+ else yield require_joined_file_suggestion.joinedFileSuggestion(joinedPrefix, suggestion, valuePart);
364
362
  }
365
363
  }
366
364
  } else {
367
- const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
368
365
  if (!expectingValue && (prefix.startsWith("--") || prefix.startsWith("-") || prefix.startsWith("/") || prefix.startsWith("+"))) {
369
366
  for (const optionName$1 of optionNames$1) if (optionName$1.startsWith(prefix)) {
370
367
  if (prefix === "-" && optionName$1.length !== 2) continue;
@@ -1806,16 +1803,29 @@ function command(name, parser, options = {}) {
1806
1803
  * // mycli --debug --foo bar
1807
1804
  * // → { debug: true, extra: ["--foo", "bar"] }
1808
1805
  *
1809
- * // greedy format: captures all remaining tokens
1810
- * const parser = command("exec", object({
1811
- * container: argument(string()),
1812
- * args: passThrough({ format: "greedy" }),
1813
- * }));
1806
+ * // greedy format: require a program name before capturing the rest
1807
+ * const parser = command("exec", map(
1808
+ * seq(
1809
+ * argument(string({ metavar: "PROGRAM" })),
1810
+ * passThrough({ format: "greedy" }),
1811
+ * ),
1812
+ * ([program, args]) => ({ program, args }),
1813
+ * ));
1814
1814
  *
1815
- * // myproxy exec mycontainer --verbose -it bash
1816
- * // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
1815
+ * // myproxy exec my-program --help
1816
+ * // → { program: "my-program", args: ["--help"] }
1817
1817
  * ```
1818
1818
  *
1819
+ * With built-in help enabled, `myproxy exec --help` displays help because `seq()`
1820
+ * does not reach pass-through until the program name is parsed. After the
1821
+ * program name, greedy capture can consume remaining tokens, including
1822
+ * `--help`, so they can be forwarded. Putting this parser last in an `object()`
1823
+ * does not establish the same boundary: fields are tried by priority, not
1824
+ * declaration order.
1825
+ * An immediate `--` after the program name is consumed by `seq()` as an
1826
+ * options terminator before greedy capture starts. Once capture starts,
1827
+ * subsequent `--` tokens are kept.
1828
+ *
1819
1829
  * @since 0.8.0
1820
1830
  */
1821
1831
  function passThrough(options = {}) {
@@ -584,16 +584,29 @@ interface PassThroughOptions {
584
584
  * // mycli --debug --foo bar
585
585
  * // → { debug: true, extra: ["--foo", "bar"] }
586
586
  *
587
- * // greedy format: captures all remaining tokens
588
- * const parser = command("exec", object({
589
- * container: argument(string()),
590
- * args: passThrough({ format: "greedy" }),
591
- * }));
587
+ * // greedy format: require a program name before capturing the rest
588
+ * const parser = command("exec", map(
589
+ * seq(
590
+ * argument(string({ metavar: "PROGRAM" })),
591
+ * passThrough({ format: "greedy" }),
592
+ * ),
593
+ * ([program, args]) => ({ program, args }),
594
+ * ));
592
595
  *
593
- * // myproxy exec mycontainer --verbose -it bash
594
- * // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
596
+ * // myproxy exec my-program --help
597
+ * // → { program: "my-program", args: ["--help"] }
595
598
  * ```
596
599
  *
600
+ * With built-in help enabled, `myproxy exec --help` displays help because `seq()`
601
+ * does not reach pass-through until the program name is parsed. After the
602
+ * program name, greedy capture can consume remaining tokens, including
603
+ * `--help`, so they can be forwarded. Putting this parser last in an `object()`
604
+ * does not establish the same boundary: fields are tried by priority, not
605
+ * declaration order.
606
+ * An immediate `--` after the program name is consumed by `seq()` as an
607
+ * options terminator before greedy capture starts. Once capture starts,
608
+ * subsequent `--` tokens are kept.
609
+ *
597
610
  * @since 0.8.0
598
611
  */
599
612
  declare function passThrough(options?: PassThroughOptions): FluentParser<"sync", readonly string[], readonly string[]>;
@@ -584,16 +584,29 @@ interface PassThroughOptions {
584
584
  * // mycli --debug --foo bar
585
585
  * // → { debug: true, extra: ["--foo", "bar"] }
586
586
  *
587
- * // greedy format: captures all remaining tokens
588
- * const parser = command("exec", object({
589
- * container: argument(string()),
590
- * args: passThrough({ format: "greedy" }),
591
- * }));
587
+ * // greedy format: require a program name before capturing the rest
588
+ * const parser = command("exec", map(
589
+ * seq(
590
+ * argument(string({ metavar: "PROGRAM" })),
591
+ * passThrough({ format: "greedy" }),
592
+ * ),
593
+ * ([program, args]) => ({ program, args }),
594
+ * ));
592
595
  *
593
- * // myproxy exec mycontainer --verbose -it bash
594
- * // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
596
+ * // myproxy exec my-program --help
597
+ * // → { program: "my-program", args: ["--help"] }
595
598
  * ```
596
599
  *
600
+ * With built-in help enabled, `myproxy exec --help` displays help because `seq()`
601
+ * does not reach pass-through until the program name is parsed. After the
602
+ * program name, greedy capture can consume remaining tokens, including
603
+ * `--help`, so they can be forwarded. Putting this parser last in an `object()`
604
+ * does not establish the same boundary: fields are tried by priority, not
605
+ * declaration order.
606
+ * An immediate `--` after the program name is consumed by `seq()` as an
607
+ * options terminator before greedy capture starts. Once capture starts,
608
+ * subsequent `--` tokens are kept.
609
+ *
597
610
  * @since 0.8.0
598
611
  */
599
612
  declare function passThrough(options?: PassThroughOptions): FluentParser<"sync", readonly string[], readonly string[]>;