@optique/core 0.8.0-dev.164 → 0.8.0-dev.166

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/modifiers.js CHANGED
@@ -2,6 +2,45 @@ import { formatMessage, message, text } from "./message.js";
2
2
 
3
3
  //#region src/modifiers.ts
4
4
  /**
5
+ * Internal helper for optional-style parsing logic shared by optional()
6
+ * and withDefault(). Handles the common pattern of:
7
+ * - Unwrapping optional state to inner parser state
8
+ * - Detecting if inner parser actually matched (state changed or no consumption)
9
+ * - Returning success with undefined state when inner parser fails without consuming
10
+ * @internal
11
+ */
12
+ function parseOptionalStyle(context, parser) {
13
+ const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
14
+ const result = parser.parse({
15
+ ...context,
16
+ state: innerState
17
+ });
18
+ if (result.success) {
19
+ if (result.next.state !== innerState || result.consumed.length === 0) return {
20
+ success: true,
21
+ next: {
22
+ ...result.next,
23
+ state: [result.next.state]
24
+ },
25
+ consumed: result.consumed
26
+ };
27
+ return {
28
+ success: true,
29
+ next: {
30
+ ...result.next,
31
+ state: context.state
32
+ },
33
+ consumed: result.consumed
34
+ };
35
+ }
36
+ if (result.consumed === 0) return {
37
+ success: true,
38
+ next: context,
39
+ consumed: []
40
+ };
41
+ return result;
42
+ }
43
+ /**
5
44
  * Creates a parser that makes another parser optional, allowing it to succeed
6
45
  * without consuming input if the wrapped parser fails to match.
7
46
  * If the wrapped parser succeeds, this returns its value.
@@ -23,35 +62,7 @@ function optional(parser) {
23
62
  }],
24
63
  initialState: void 0,
25
64
  parse(context) {
26
- const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
27
- const result = parser.parse({
28
- ...context,
29
- state: innerState
30
- });
31
- if (result.success) {
32
- if (result.next.state !== innerState || result.consumed.length === 0) return {
33
- success: true,
34
- next: {
35
- ...result.next,
36
- state: [result.next.state]
37
- },
38
- consumed: result.consumed
39
- };
40
- return {
41
- success: true,
42
- next: {
43
- ...result.next,
44
- state: context.state
45
- },
46
- consumed: result.consumed
47
- };
48
- }
49
- if (result.consumed === 0) return {
50
- success: true,
51
- next: context,
52
- consumed: []
53
- };
54
- return result;
65
+ return parseOptionalStyle(context, parser);
55
66
  },
56
67
  complete(state) {
57
68
  if (typeof state === "undefined") return {
@@ -121,35 +132,7 @@ function withDefault(parser, defaultValue, options) {
121
132
  }],
122
133
  initialState: void 0,
123
134
  parse(context) {
124
- const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
125
- const result = parser.parse({
126
- ...context,
127
- state: innerState
128
- });
129
- if (result.success) {
130
- if (result.next.state !== innerState || result.consumed.length === 0) return {
131
- success: true,
132
- next: {
133
- ...result.next,
134
- state: [result.next.state]
135
- },
136
- consumed: result.consumed
137
- };
138
- return {
139
- success: true,
140
- next: {
141
- ...result.next,
142
- state: context.state
143
- },
144
- consumed: result.consumed
145
- };
146
- }
147
- if (result.consumed === 0) return {
148
- success: true,
149
- next: context,
150
- consumed: []
151
- };
152
- return result;
135
+ return parseOptionalStyle(context, parser);
153
136
  },
154
137
  complete(state) {
155
138
  if (typeof state === "undefined") try {
package/dist/parser.cjs CHANGED
@@ -34,7 +34,7 @@ function parse(parser, args) {
34
34
  };
35
35
  const previousBuffer = context.buffer;
36
36
  context = result.next;
37
- if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer[0] === previousBuffer[0]) return {
37
+ if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer.every((item, i) => item === previousBuffer[i])) return {
38
38
  success: false,
39
39
  error: require_message.message`Unexpected option or argument: ${context.buffer[0]}.`
40
40
  };
@@ -90,7 +90,7 @@ function suggest(parser, args) {
90
90
  if (!result.success) return Array.from(parser.suggest(context, prefix));
91
91
  const previousBuffer = context.buffer;
92
92
  context = result.next;
93
- if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer[0] === previousBuffer[0]) return [];
93
+ if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer.every((item, i) => item === previousBuffer[i])) return [];
94
94
  }
95
95
  return Array.from(parser.suggest(context, prefix));
96
96
  }
@@ -154,14 +154,18 @@ function getDocPage(parser, args = []) {
154
154
  const usage = [...require_usage.normalizeUsage(parser.usage)];
155
155
  let i = 0;
156
156
  for (const arg of args) {
157
+ if (i >= usage.length) break;
157
158
  const term = usage[i];
158
- if (usage.length > i && term.type === "exclusive") for (const termGroup of term.terms) {
159
- const firstTerm = termGroup[0];
160
- if (firstTerm?.type !== "command" || firstTerm.name !== arg) continue;
161
- usage.splice(i, 1, ...termGroup);
162
- break;
163
- }
164
- i++;
159
+ if (term.type === "exclusive") {
160
+ for (const termGroup of term.terms) {
161
+ const firstTerm = termGroup[0];
162
+ if (firstTerm?.type !== "command" || firstTerm.name !== arg) continue;
163
+ usage.splice(i, 1, ...termGroup);
164
+ i += termGroup.length;
165
+ break;
166
+ }
167
+ if (usage[i] === term) i++;
168
+ } else i++;
165
169
  }
166
170
  return {
167
171
  usage,
@@ -172,6 +176,7 @@ function getDocPage(parser, args = []) {
172
176
  }
173
177
 
174
178
  //#endregion
179
+ exports.DuplicateOptionError = require_constructs.DuplicateOptionError;
175
180
  exports.WithDefaultError = require_modifiers.WithDefaultError;
176
181
  exports.argument = require_primitives.argument;
177
182
  exports.command = require_primitives.command;
@@ -190,6 +195,7 @@ exports.option = require_primitives.option;
190
195
  exports.optional = require_modifiers.optional;
191
196
  exports.or = require_constructs.or;
192
197
  exports.parse = parse;
198
+ exports.passThrough = require_primitives.passThrough;
193
199
  exports.suggest = suggest;
194
200
  exports.tuple = require_constructs.tuple;
195
201
  exports.withDefault = require_modifiers.withDefault;
package/dist/parser.d.cts CHANGED
@@ -3,8 +3,8 @@ import { Usage } from "./usage.cjs";
3
3
  import { DocFragments, DocPage } from "./doc.cjs";
4
4
  import { ValueParserResult } from "./valueparser.cjs";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
6
- import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.cjs";
7
- import { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
6
+ import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough } from "./primitives.cjs";
7
+ import { ConditionalErrorOptions, ConditionalOptions, DuplicateOptionError, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
8
8
 
9
9
  //#region src/parser.d.ts
10
10
 
@@ -323,4 +323,4 @@ declare function suggest<T>(parser: Parser<T, unknown>, args: readonly [string,
323
323
  */
324
324
  declare function getDocPage(parser: Parser<unknown, unknown>, args?: readonly string[]): DocPage | undefined;
325
325
  //#endregion
326
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
326
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, passThrough, suggest, tuple, withDefault };
package/dist/parser.d.ts CHANGED
@@ -3,8 +3,8 @@ import { Usage } from "./usage.js";
3
3
  import { DocFragments, DocPage } from "./doc.js";
4
4
  import { ValueParserResult } from "./valueparser.js";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
6
- import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.js";
7
- import { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
6
+ import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough } from "./primitives.js";
7
+ import { ConditionalErrorOptions, ConditionalOptions, DuplicateOptionError, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
8
8
 
9
9
  //#region src/parser.d.ts
10
10
 
@@ -323,4 +323,4 @@ declare function suggest<T>(parser: Parser<T, unknown>, args: readonly [string,
323
323
  */
324
324
  declare function getDocPage(parser: Parser<unknown, unknown>, args?: readonly string[]): DocPage | undefined;
325
325
  //#endregion
326
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
326
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, passThrough, suggest, tuple, withDefault };
package/dist/parser.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { message } from "./message.js";
2
2
  import { normalizeUsage } from "./usage.js";
3
- import { concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
3
+ import { DuplicateOptionError, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
4
4
  import { WithDefaultError, map, multiple, optional, withDefault } from "./modifiers.js";
5
- import { argument, command, constant, flag, option } from "./primitives.js";
5
+ import { argument, command, constant, flag, option, passThrough } from "./primitives.js";
6
6
 
7
7
  //#region src/parser.ts
8
8
  /**
@@ -34,7 +34,7 @@ function parse(parser, args) {
34
34
  };
35
35
  const previousBuffer = context.buffer;
36
36
  context = result.next;
37
- if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer[0] === previousBuffer[0]) return {
37
+ if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer.every((item, i) => item === previousBuffer[i])) return {
38
38
  success: false,
39
39
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
40
40
  };
@@ -90,7 +90,7 @@ function suggest(parser, args) {
90
90
  if (!result.success) return Array.from(parser.suggest(context, prefix));
91
91
  const previousBuffer = context.buffer;
92
92
  context = result.next;
93
- if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer[0] === previousBuffer[0]) return [];
93
+ if (context.buffer.length > 0 && context.buffer.length === previousBuffer.length && context.buffer.every((item, i) => item === previousBuffer[i])) return [];
94
94
  }
95
95
  return Array.from(parser.suggest(context, prefix));
96
96
  }
@@ -154,14 +154,18 @@ function getDocPage(parser, args = []) {
154
154
  const usage = [...normalizeUsage(parser.usage)];
155
155
  let i = 0;
156
156
  for (const arg of args) {
157
+ if (i >= usage.length) break;
157
158
  const term = usage[i];
158
- if (usage.length > i && term.type === "exclusive") for (const termGroup of term.terms) {
159
- const firstTerm = termGroup[0];
160
- if (firstTerm?.type !== "command" || firstTerm.name !== arg) continue;
161
- usage.splice(i, 1, ...termGroup);
162
- break;
163
- }
164
- i++;
159
+ if (term.type === "exclusive") {
160
+ for (const termGroup of term.terms) {
161
+ const firstTerm = termGroup[0];
162
+ if (firstTerm?.type !== "command" || firstTerm.name !== arg) continue;
163
+ usage.splice(i, 1, ...termGroup);
164
+ i += termGroup.length;
165
+ break;
166
+ }
167
+ if (usage[i] === term) i++;
168
+ } else i++;
165
169
  }
166
170
  return {
167
171
  usage,
@@ -172,4 +176,4 @@ function getDocPage(parser, args = []) {
172
176
  }
173
177
 
174
178
  //#endregion
175
- export { WithDefaultError, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
179
+ export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, passThrough, suggest, tuple, withDefault };
@@ -723,10 +723,176 @@ function command(name, parser, options = {}) {
723
723
  }
724
724
  };
725
725
  }
726
+ /**
727
+ * Creates a parser that collects unrecognized options and passes them through.
728
+ * This is useful for building wrapper CLI tools that need to forward unknown
729
+ * options to an underlying tool.
730
+ *
731
+ * **Important**: This parser intentionally weakens Optique's strict parsing
732
+ * philosophy where "all input must be recognized." The benefit is enabling
733
+ * legitimate wrapper/proxy tool patterns, but the trade-off is that typos
734
+ * in pass-through options won't be caught.
735
+ *
736
+ * @param options Configuration for how to capture options.
737
+ * @returns A {@link Parser} that captures unrecognized options as an array
738
+ * of strings.
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * // Default format: only captures --opt=val
743
+ * const parser = object({
744
+ * debug: option("--debug"),
745
+ * extra: passThrough(),
746
+ * });
747
+ *
748
+ * // mycli --debug --foo=bar --baz=qux
749
+ * // → { debug: true, extra: ["--foo=bar", "--baz=qux"] }
750
+ *
751
+ * // nextToken format: captures --opt val pairs
752
+ * const parser = object({
753
+ * debug: option("--debug"),
754
+ * extra: passThrough({ format: "nextToken" }),
755
+ * });
756
+ *
757
+ * // mycli --debug --foo bar
758
+ * // → { debug: true, extra: ["--foo", "bar"] }
759
+ *
760
+ * // greedy format: captures all remaining tokens
761
+ * const parser = command("exec", object({
762
+ * container: argument(string()),
763
+ * args: passThrough({ format: "greedy" }),
764
+ * }));
765
+ *
766
+ * // myproxy exec mycontainer --verbose -it bash
767
+ * // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
768
+ * ```
769
+ *
770
+ * @since 0.8.0
771
+ */
772
+ function passThrough(options = {}) {
773
+ const format = options.format ?? "equalsOnly";
774
+ const optionPattern = /^-[a-z0-9-]|^--[a-z0-9-]+/i;
775
+ const equalsOptionPattern = /^--[a-z0-9-]+=/i;
776
+ return {
777
+ $valueType: [],
778
+ $stateType: [],
779
+ priority: -10,
780
+ usage: [{ type: "passthrough" }],
781
+ initialState: [],
782
+ parse(context) {
783
+ if (context.buffer.length < 1) return {
784
+ success: false,
785
+ consumed: 0,
786
+ error: require_message.message`No input to pass through.`
787
+ };
788
+ const token = context.buffer[0];
789
+ if (format === "greedy") {
790
+ const captured = [...context.buffer];
791
+ return {
792
+ success: true,
793
+ next: {
794
+ ...context,
795
+ buffer: [],
796
+ state: [...context.state, ...captured]
797
+ },
798
+ consumed: captured
799
+ };
800
+ }
801
+ if (context.optionsTerminated) return {
802
+ success: false,
803
+ consumed: 0,
804
+ error: require_message.message`Options terminated; cannot capture pass-through options.`
805
+ };
806
+ if (format === "equalsOnly") {
807
+ if (!equalsOptionPattern.test(token)) return {
808
+ success: false,
809
+ consumed: 0,
810
+ error: require_message.message`Expected --option=value format, but got: ${token}.`
811
+ };
812
+ return {
813
+ success: true,
814
+ next: {
815
+ ...context,
816
+ buffer: context.buffer.slice(1),
817
+ state: [...context.state, token]
818
+ },
819
+ consumed: [token]
820
+ };
821
+ }
822
+ if (format === "nextToken") {
823
+ if (!optionPattern.test(token)) return {
824
+ success: false,
825
+ consumed: 0,
826
+ error: require_message.message`Expected option, but got: ${token}.`
827
+ };
828
+ if (token.includes("=")) return {
829
+ success: true,
830
+ next: {
831
+ ...context,
832
+ buffer: context.buffer.slice(1),
833
+ state: [...context.state, token]
834
+ },
835
+ consumed: [token]
836
+ };
837
+ const nextToken = context.buffer[1];
838
+ if (nextToken !== void 0 && !optionPattern.test(nextToken)) return {
839
+ success: true,
840
+ next: {
841
+ ...context,
842
+ buffer: context.buffer.slice(2),
843
+ state: [
844
+ ...context.state,
845
+ token,
846
+ nextToken
847
+ ]
848
+ },
849
+ consumed: [token, nextToken]
850
+ };
851
+ return {
852
+ success: true,
853
+ next: {
854
+ ...context,
855
+ buffer: context.buffer.slice(1),
856
+ state: [...context.state, token]
857
+ },
858
+ consumed: [token]
859
+ };
860
+ }
861
+ return {
862
+ success: false,
863
+ consumed: 0,
864
+ error: require_message.message`Unknown passThrough format: ${format}.`
865
+ };
866
+ },
867
+ complete(state) {
868
+ return {
869
+ success: true,
870
+ value: state
871
+ };
872
+ },
873
+ suggest(_context, _prefix) {
874
+ return [];
875
+ },
876
+ getDocFragments(_state, _defaultValue) {
877
+ return {
878
+ fragments: [{
879
+ type: "entry",
880
+ term: { type: "passthrough" },
881
+ description: options.description
882
+ }],
883
+ description: options.description
884
+ };
885
+ },
886
+ [Symbol.for("Deno.customInspect")]() {
887
+ return `passThrough(${format})`;
888
+ }
889
+ };
890
+ }
726
891
 
727
892
  //#endregion
728
893
  exports.argument = argument;
729
894
  exports.command = command;
730
895
  exports.constant = constant;
731
896
  exports.flag = flag;
732
- exports.option = option;
897
+ exports.option = option;
898
+ exports.passThrough = passThrough;
@@ -307,5 +307,82 @@ type CommandState<TState> = undefined | ["matched", string] | ["parsing", TState
307
307
  * to the inner parser for the remaining arguments.
308
308
  */
309
309
  declare function command<T, TState>(name: string, parser: Parser<T, TState>, options?: CommandOptions): Parser<T, CommandState<TState>>;
310
+ /**
311
+ * Format options for how {@link passThrough} captures options.
312
+ * @since 0.8.0
313
+ */
314
+ type PassThroughFormat = "equalsOnly" | "nextToken" | "greedy";
315
+ /**
316
+ * Options for the {@link passThrough} parser.
317
+ * @since 0.8.0
318
+ */
319
+ interface PassThroughOptions {
320
+ /**
321
+ * How to capture option values:
322
+ *
323
+ * - `"equalsOnly"`: Only capture `--opt=val` format (default, safest).
324
+ * Values with spaces (`--opt val`) are not captured.
325
+ *
326
+ * - `"nextToken"`: Capture `--opt` and its value as separate tokens
327
+ * (`--opt val`). The next token is captured if it doesn't start with `-`.
328
+ *
329
+ * - `"greedy"`: Capture *all* remaining tokens from first unrecognized token.
330
+ * This is useful for wrapper/proxy tools that pass everything through.
331
+ *
332
+ * @default `"equalsOnly"`
333
+ */
334
+ readonly format?: PassThroughFormat;
335
+ /**
336
+ * A description of what pass-through options are used for.
337
+ */
338
+ readonly description?: Message;
339
+ }
340
+ /**
341
+ * Creates a parser that collects unrecognized options and passes them through.
342
+ * This is useful for building wrapper CLI tools that need to forward unknown
343
+ * options to an underlying tool.
344
+ *
345
+ * **Important**: This parser intentionally weakens Optique's strict parsing
346
+ * philosophy where "all input must be recognized." The benefit is enabling
347
+ * legitimate wrapper/proxy tool patterns, but the trade-off is that typos
348
+ * in pass-through options won't be caught.
349
+ *
350
+ * @param options Configuration for how to capture options.
351
+ * @returns A {@link Parser} that captures unrecognized options as an array
352
+ * of strings.
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * // Default format: only captures --opt=val
357
+ * const parser = object({
358
+ * debug: option("--debug"),
359
+ * extra: passThrough(),
360
+ * });
361
+ *
362
+ * // mycli --debug --foo=bar --baz=qux
363
+ * // → { debug: true, extra: ["--foo=bar", "--baz=qux"] }
364
+ *
365
+ * // nextToken format: captures --opt val pairs
366
+ * const parser = object({
367
+ * debug: option("--debug"),
368
+ * extra: passThrough({ format: "nextToken" }),
369
+ * });
370
+ *
371
+ * // mycli --debug --foo bar
372
+ * // → { debug: true, extra: ["--foo", "bar"] }
373
+ *
374
+ * // greedy format: captures all remaining tokens
375
+ * const parser = command("exec", object({
376
+ * container: argument(string()),
377
+ * args: passThrough({ format: "greedy" }),
378
+ * }));
379
+ *
380
+ * // myproxy exec mycontainer --verbose -it bash
381
+ * // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
382
+ * ```
383
+ *
384
+ * @since 0.8.0
385
+ */
386
+ declare function passThrough(options?: PassThroughOptions): Parser<readonly string[], readonly string[]>;
310
387
  //#endregion
311
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option };
388
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough };
@@ -307,5 +307,82 @@ type CommandState<TState> = undefined | ["matched", string] | ["parsing", TState
307
307
  * to the inner parser for the remaining arguments.
308
308
  */
309
309
  declare function command<T, TState>(name: string, parser: Parser<T, TState>, options?: CommandOptions): Parser<T, CommandState<TState>>;
310
+ /**
311
+ * Format options for how {@link passThrough} captures options.
312
+ * @since 0.8.0
313
+ */
314
+ type PassThroughFormat = "equalsOnly" | "nextToken" | "greedy";
315
+ /**
316
+ * Options for the {@link passThrough} parser.
317
+ * @since 0.8.0
318
+ */
319
+ interface PassThroughOptions {
320
+ /**
321
+ * How to capture option values:
322
+ *
323
+ * - `"equalsOnly"`: Only capture `--opt=val` format (default, safest).
324
+ * Values with spaces (`--opt val`) are not captured.
325
+ *
326
+ * - `"nextToken"`: Capture `--opt` and its value as separate tokens
327
+ * (`--opt val`). The next token is captured if it doesn't start with `-`.
328
+ *
329
+ * - `"greedy"`: Capture *all* remaining tokens from first unrecognized token.
330
+ * This is useful for wrapper/proxy tools that pass everything through.
331
+ *
332
+ * @default `"equalsOnly"`
333
+ */
334
+ readonly format?: PassThroughFormat;
335
+ /**
336
+ * A description of what pass-through options are used for.
337
+ */
338
+ readonly description?: Message;
339
+ }
340
+ /**
341
+ * Creates a parser that collects unrecognized options and passes them through.
342
+ * This is useful for building wrapper CLI tools that need to forward unknown
343
+ * options to an underlying tool.
344
+ *
345
+ * **Important**: This parser intentionally weakens Optique's strict parsing
346
+ * philosophy where "all input must be recognized." The benefit is enabling
347
+ * legitimate wrapper/proxy tool patterns, but the trade-off is that typos
348
+ * in pass-through options won't be caught.
349
+ *
350
+ * @param options Configuration for how to capture options.
351
+ * @returns A {@link Parser} that captures unrecognized options as an array
352
+ * of strings.
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * // Default format: only captures --opt=val
357
+ * const parser = object({
358
+ * debug: option("--debug"),
359
+ * extra: passThrough(),
360
+ * });
361
+ *
362
+ * // mycli --debug --foo=bar --baz=qux
363
+ * // → { debug: true, extra: ["--foo=bar", "--baz=qux"] }
364
+ *
365
+ * // nextToken format: captures --opt val pairs
366
+ * const parser = object({
367
+ * debug: option("--debug"),
368
+ * extra: passThrough({ format: "nextToken" }),
369
+ * });
370
+ *
371
+ * // mycli --debug --foo bar
372
+ * // → { debug: true, extra: ["--foo", "bar"] }
373
+ *
374
+ * // greedy format: captures all remaining tokens
375
+ * const parser = command("exec", object({
376
+ * container: argument(string()),
377
+ * args: passThrough({ format: "greedy" }),
378
+ * }));
379
+ *
380
+ * // myproxy exec mycontainer --verbose -it bash
381
+ * // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
382
+ * ```
383
+ *
384
+ * @since 0.8.0
385
+ */
386
+ declare function passThrough(options?: PassThroughOptions): Parser<readonly string[], readonly string[]>;
310
387
  //#endregion
311
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option };
388
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough };