@optique/inquirer 1.0.0-dev.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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2025–2026 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,272 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ const __inquirer_prompts = __toESM(require("@inquirer/prompts"));
25
+ const __optique_core_annotations = __toESM(require("@optique/core/annotations"));
26
+ const __optique_core_message = __toESM(require("@optique/core/message"));
27
+
28
+ //#region src/index.ts
29
+ /**
30
+ * Wraps a parser with an interactive Inquirer.js prompt fallback.
31
+ *
32
+ * When the inner parser finds a value in the CLI arguments (consumed tokens),
33
+ * that value is used directly. When no CLI value is found, an interactive
34
+ * prompt is shown to the user.
35
+ *
36
+ * The returned parser always has `$mode: "async"` because Inquirer.js prompts
37
+ * are inherently asynchronous.
38
+ *
39
+ * Example:
40
+ *
41
+ * ```typescript
42
+ * import { option } from "@optique/core/primitives";
43
+ * import { string } from "@optique/core/valueparser";
44
+ * import { prompt } from "@optique/inquirer";
45
+ *
46
+ * const nameParser = prompt(option("--name", string()), {
47
+ * type: "input",
48
+ * message: "Enter your name:",
49
+ * });
50
+ * ```
51
+ *
52
+ * @param parser Inner parser that reads CLI values.
53
+ * @param config Type-safe Inquirer.js prompt configuration.
54
+ * @returns A parser with interactive prompt fallback, always in async mode.
55
+ * @since 1.0.0
56
+ */
57
+ function prompt(parser, config) {
58
+ const promptBindStateKey = Symbol("@optique/inquirer/promptState");
59
+ function isPromptBindState(value) {
60
+ return value != null && typeof value === "object" && promptBindStateKey in value;
61
+ }
62
+ const cfg = config;
63
+ const PromptBindInitialStateClass = class {
64
+ [promptBindStateKey] = true;
65
+ hasCliValue = false;
66
+ };
67
+ const promptBindInitialState = new PromptBindInitialStateClass();
68
+ let promptCache = null;
69
+ async function executePrompt() {
70
+ if ("prompter" in cfg && cfg.prompter != null) {
71
+ const value = await cfg.prompter();
72
+ if (cfg.type === "number" && value === void 0) return {
73
+ success: false,
74
+ error: __optique_core_message.message`No number provided.`
75
+ };
76
+ return {
77
+ success: true,
78
+ value
79
+ };
80
+ }
81
+ switch (cfg.type) {
82
+ case "confirm": return {
83
+ success: true,
84
+ value: await (0, __inquirer_prompts.confirm)({
85
+ message: cfg.message,
86
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
87
+ })
88
+ };
89
+ case "number": {
90
+ const numResult = await (0, __inquirer_prompts.number)({
91
+ message: cfg.message,
92
+ ...cfg.default !== void 0 ? { default: cfg.default } : {},
93
+ ...cfg.min !== void 0 ? { min: cfg.min } : {},
94
+ ...cfg.max !== void 0 ? { max: cfg.max } : {},
95
+ ...cfg.step !== void 0 ? { step: cfg.step } : {}
96
+ });
97
+ if (numResult === void 0) return {
98
+ success: false,
99
+ error: __optique_core_message.message`No number provided.`
100
+ };
101
+ return {
102
+ success: true,
103
+ value: numResult
104
+ };
105
+ }
106
+ case "input": return {
107
+ success: true,
108
+ value: await (0, __inquirer_prompts.input)({
109
+ message: cfg.message,
110
+ ...cfg.default !== void 0 ? { default: cfg.default } : {},
111
+ ...cfg.validate !== void 0 ? { validate: cfg.validate } : {}
112
+ })
113
+ };
114
+ case "password": return {
115
+ success: true,
116
+ value: await (0, __inquirer_prompts.password)({
117
+ message: cfg.message,
118
+ ...cfg.mask !== void 0 ? { mask: cfg.mask } : {},
119
+ ...cfg.validate !== void 0 ? { validate: cfg.validate } : {}
120
+ })
121
+ };
122
+ case "editor": return {
123
+ success: true,
124
+ value: await (0, __inquirer_prompts.editor)({
125
+ message: cfg.message,
126
+ ...cfg.default !== void 0 ? { default: cfg.default } : {},
127
+ ...cfg.validate !== void 0 ? { validate: cfg.validate } : {}
128
+ })
129
+ };
130
+ case "select": return {
131
+ success: true,
132
+ value: await (0, __inquirer_prompts.select)({
133
+ message: cfg.message,
134
+ choices: normalizeChoices(cfg.choices),
135
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
136
+ })
137
+ };
138
+ case "rawlist": return {
139
+ success: true,
140
+ value: await (0, __inquirer_prompts.rawlist)({
141
+ message: cfg.message,
142
+ choices: normalizeChoices(cfg.choices),
143
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
144
+ })
145
+ };
146
+ case "expand": return {
147
+ success: true,
148
+ value: await (0, __inquirer_prompts.expand)({
149
+ message: cfg.message,
150
+ choices: cfg.choices,
151
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
152
+ })
153
+ };
154
+ case "checkbox": return {
155
+ success: true,
156
+ value: await (0, __inquirer_prompts.checkbox)({
157
+ message: cfg.message,
158
+ choices: normalizeChoices(cfg.choices)
159
+ })
160
+ };
161
+ }
162
+ }
163
+ return {
164
+ $mode: "async",
165
+ $valueType: parser.$valueType,
166
+ $stateType: parser.$stateType,
167
+ priority: parser.priority,
168
+ usage: [{
169
+ type: "optional",
170
+ terms: parser.usage
171
+ }],
172
+ initialState: promptBindInitialState,
173
+ parse: (context) => {
174
+ const annotations = (0, __optique_core_annotations.getAnnotations)(context.state);
175
+ const innerState = isPromptBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
176
+ const innerContext = innerState !== context.state ? {
177
+ ...context,
178
+ state: innerState
179
+ } : context;
180
+ const processResult = (result$1) => {
181
+ if (result$1.success) {
182
+ const cliConsumed = result$1.consumed.length > 0;
183
+ const nextState$1 = {
184
+ [promptBindStateKey]: true,
185
+ hasCliValue: cliConsumed,
186
+ cliState: result$1.next.state,
187
+ ...annotations != null ? { [__optique_core_annotations.annotationKey]: annotations } : {}
188
+ };
189
+ return {
190
+ success: true,
191
+ next: {
192
+ ...result$1.next,
193
+ state: nextState$1
194
+ },
195
+ consumed: result$1.consumed
196
+ };
197
+ }
198
+ if (result$1.consumed > 0) return result$1;
199
+ const nextState = {
200
+ [promptBindStateKey]: true,
201
+ hasCliValue: false,
202
+ ...annotations != null ? { [__optique_core_annotations.annotationKey]: annotations } : {}
203
+ };
204
+ return {
205
+ success: true,
206
+ next: {
207
+ ...innerContext,
208
+ state: nextState
209
+ },
210
+ consumed: []
211
+ };
212
+ };
213
+ const result = parser.parse(innerContext);
214
+ if (result instanceof Promise) return result.then(processResult);
215
+ return Promise.resolve(processResult(result));
216
+ },
217
+ complete: (state) => {
218
+ if (isPromptBindState(state) && state.hasCliValue) {
219
+ const r = parser.complete(state.cliState);
220
+ if (r instanceof Promise) return r;
221
+ return Promise.resolve(r);
222
+ }
223
+ if (state === promptBindInitialState) {
224
+ if (promptCache !== null) {
225
+ const cached = promptCache;
226
+ promptCache = null;
227
+ return cached;
228
+ }
229
+ promptCache = executePrompt();
230
+ return promptCache;
231
+ }
232
+ return executePrompt();
233
+ },
234
+ suggest: (context, prefix) => {
235
+ const innerState = isPromptBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
236
+ const innerContext = innerState !== context.state ? {
237
+ ...context,
238
+ state: innerState
239
+ } : context;
240
+ const innerResult = parser.suggest(innerContext, prefix);
241
+ return async function* () {
242
+ yield* innerResult;
243
+ }();
244
+ },
245
+ getDocFragments(state, upperDefaultValue) {
246
+ const configDefault = "default" in cfg ? cfg.default : void 0;
247
+ const defaultValue = upperDefaultValue ?? configDefault;
248
+ return parser.getDocFragments(state, defaultValue);
249
+ }
250
+ };
251
+ }
252
+ /** Normalize choices to the format Inquirer.js expects. */
253
+ function normalizeChoices(choices) {
254
+ return choices.map((c) => {
255
+ if (typeof c === "string") return {
256
+ value: c,
257
+ name: c
258
+ };
259
+ if (c instanceof __inquirer_prompts.Separator) return c;
260
+ return {
261
+ value: c.value,
262
+ ...c.name !== void 0 ? { name: c.name } : {},
263
+ ..."description" in c && c.description !== void 0 ? { description: c.description } : {},
264
+ ..."short" in c && c.short !== void 0 ? { short: c.short } : {},
265
+ ..."disabled" in c && c.disabled !== void 0 ? { disabled: c.disabled } : {}
266
+ };
267
+ });
268
+ }
269
+
270
+ //#endregion
271
+ exports.Separator = __inquirer_prompts.Separator;
272
+ exports.prompt = prompt;
@@ -0,0 +1,354 @@
1
+ import { Separator } from "@inquirer/prompts";
2
+ import { Mode, Parser } from "@optique/core/parser";
3
+
4
+ //#region src/index.d.ts
5
+
6
+ /**
7
+ * A choice item for selection-type prompts (`select`, `rawlist`, `expand`,
8
+ * `checkbox`).
9
+ *
10
+ * @since 1.0.0
11
+ */
12
+ interface Choice {
13
+ /**
14
+ * The value returned when this choice is selected.
15
+ */
16
+ readonly value: string;
17
+ /**
18
+ * Display name shown in the prompt. Defaults to `value`.
19
+ */
20
+ readonly name?: string;
21
+ /**
22
+ * Additional description shown when this choice is highlighted.
23
+ */
24
+ readonly description?: string;
25
+ /**
26
+ * Short text shown after selection. Defaults to `name`.
27
+ */
28
+ readonly short?: string;
29
+ /**
30
+ * If truthy, the choice cannot be selected. A string explains why.
31
+ */
32
+ readonly disabled?: boolean | string;
33
+ }
34
+ /**
35
+ * A choice item for the `expand` prompt type.
36
+ *
37
+ * Unlike {@link Choice}, this requires a `key` field (a single lowercase
38
+ * alphanumeric character) that the user presses to select the item.
39
+ *
40
+ * @since 1.0.0
41
+ */
42
+ interface ExpandChoice {
43
+ /**
44
+ * The value returned when this choice is selected.
45
+ */
46
+ readonly value: string;
47
+ /**
48
+ * Display name shown in the prompt. Defaults to `value`.
49
+ */
50
+ readonly name?: string;
51
+ /**
52
+ * Single lowercase alphanumeric character used as the keyboard shortcut.
53
+ */
54
+ readonly key: string;
55
+ }
56
+ /**
57
+ * Configuration for a `confirm` prompt (boolean value).
58
+ *
59
+ * @since 1.0.0
60
+ */
61
+ interface ConfirmConfig {
62
+ readonly type: "confirm";
63
+ /**
64
+ * The question to display to the user.
65
+ */
66
+ readonly message: string;
67
+ /**
68
+ * Default answer when the user just presses Enter.
69
+ */
70
+ readonly default?: boolean;
71
+ /**
72
+ * Override the prompt execution. When provided, this function is called
73
+ * instead of launching an interactive Inquirer.js prompt. Useful for
74
+ * testing.
75
+ */
76
+ readonly prompter?: () => Promise<boolean>;
77
+ }
78
+ /**
79
+ * Configuration for a `number` prompt.
80
+ *
81
+ * @since 1.0.0
82
+ */
83
+ interface NumberPromptConfig {
84
+ readonly type: "number";
85
+ /**
86
+ * The question to display to the user.
87
+ */
88
+ readonly message: string;
89
+ /**
90
+ * Default number shown to the user.
91
+ */
92
+ readonly default?: number;
93
+ /**
94
+ * Minimum accepted value.
95
+ */
96
+ readonly min?: number;
97
+ /**
98
+ * Maximum accepted value.
99
+ */
100
+ readonly max?: number;
101
+ /**
102
+ * Granularity of valid values. Use `"any"` for arbitrary decimals.
103
+ */
104
+ readonly step?: number | "any";
105
+ /**
106
+ * Override the prompt execution. When provided, this function is called
107
+ * instead of launching an interactive Inquirer.js prompt. Useful for
108
+ * testing.
109
+ *
110
+ * Return `undefined` to simulate the user leaving the field empty (which
111
+ * results in a parse failure).
112
+ */
113
+ readonly prompter?: () => Promise<number | undefined>;
114
+ }
115
+ /**
116
+ * Configuration for an `input` prompt (free-text string).
117
+ *
118
+ * @since 1.0.0
119
+ */
120
+ interface InputConfig {
121
+ readonly type: "input";
122
+ /**
123
+ * The question to display to the user.
124
+ */
125
+ readonly message: string;
126
+ /**
127
+ * Default text pre-filled in the prompt.
128
+ */
129
+ readonly default?: string;
130
+ /**
131
+ * Validation function called when the user submits.
132
+ * Return `true` or a string error message.
133
+ */
134
+ readonly validate?: (value: string) => boolean | string | Promise<boolean | string>;
135
+ /**
136
+ * Override the prompt execution. When provided, this function is called
137
+ * instead of launching an interactive Inquirer.js prompt. Useful for
138
+ * testing.
139
+ */
140
+ readonly prompter?: () => Promise<string>;
141
+ }
142
+ /**
143
+ * Configuration for a `password` prompt (masked input).
144
+ *
145
+ * @since 1.0.0
146
+ */
147
+ interface PasswordConfig {
148
+ readonly type: "password";
149
+ /**
150
+ * The question to display to the user.
151
+ */
152
+ readonly message: string;
153
+ /**
154
+ * If `true`, show `*` characters for each keystroke.
155
+ * If `false` or omitted, input is invisible.
156
+ */
157
+ readonly mask?: boolean;
158
+ /**
159
+ * Validation function called when the user submits.
160
+ * Return `true` or a string error message.
161
+ */
162
+ readonly validate?: (value: string) => boolean | string | Promise<boolean | string>;
163
+ /**
164
+ * Override the prompt execution. When provided, this function is called
165
+ * instead of launching an interactive Inquirer.js prompt. Useful for
166
+ * testing.
167
+ */
168
+ readonly prompter?: () => Promise<string>;
169
+ }
170
+ /**
171
+ * Configuration for an `editor` prompt (external editor).
172
+ *
173
+ * Opens the user's `$VISUAL` or `$EDITOR` for multi-line text input.
174
+ *
175
+ * @since 1.0.0
176
+ */
177
+ interface EditorConfig {
178
+ readonly type: "editor";
179
+ /**
180
+ * The question to display to the user.
181
+ */
182
+ readonly message: string;
183
+ /**
184
+ * Default content pre-filled in the editor.
185
+ */
186
+ readonly default?: string;
187
+ /**
188
+ * Validation function called when the editor is closed.
189
+ * Return `true` or a string error message.
190
+ */
191
+ readonly validate?: (value: string) => boolean | string | Promise<boolean | string>;
192
+ /**
193
+ * Override the prompt execution. When provided, this function is called
194
+ * instead of launching an interactive Inquirer.js prompt. Useful for
195
+ * testing.
196
+ */
197
+ readonly prompter?: () => Promise<string>;
198
+ }
199
+ /**
200
+ * Configuration for a `select` prompt (arrow-key single-select).
201
+ *
202
+ * @since 1.0.0
203
+ */
204
+ interface SelectConfig {
205
+ readonly type: "select";
206
+ /**
207
+ * The question to display to the user.
208
+ */
209
+ readonly message: string;
210
+ /**
211
+ * Available choices. Plain strings and {@link Choice} objects can be mixed.
212
+ * Use `new Separator(...)` for visual dividers.
213
+ */
214
+ readonly choices: readonly (string | Choice | Separator)[];
215
+ /**
216
+ * Initially highlighted choice value.
217
+ */
218
+ readonly default?: string;
219
+ /**
220
+ * Override the prompt execution. When provided, this function is called
221
+ * instead of launching an interactive Inquirer.js prompt. Useful for
222
+ * testing.
223
+ */
224
+ readonly prompter?: () => Promise<string>;
225
+ }
226
+ /**
227
+ * Configuration for a `rawlist` prompt (numbered list).
228
+ *
229
+ * @since 1.0.0
230
+ */
231
+ interface RawlistConfig {
232
+ readonly type: "rawlist";
233
+ /**
234
+ * The question to display to the user.
235
+ */
236
+ readonly message: string;
237
+ /**
238
+ * Available choices. Plain strings and {@link Choice} objects can be mixed.
239
+ */
240
+ readonly choices: readonly (string | Choice)[];
241
+ /**
242
+ * Pre-selected choice value.
243
+ */
244
+ readonly default?: string;
245
+ /**
246
+ * Override the prompt execution. When provided, this function is called
247
+ * instead of launching an interactive Inquirer.js prompt. Useful for
248
+ * testing.
249
+ */
250
+ readonly prompter?: () => Promise<string>;
251
+ }
252
+ /**
253
+ * Configuration for an `expand` prompt (keyboard shortcut single-select).
254
+ *
255
+ * @since 1.0.0
256
+ */
257
+ interface ExpandConfig {
258
+ readonly type: "expand";
259
+ /**
260
+ * The question to display to the user.
261
+ */
262
+ readonly message: string;
263
+ /**
264
+ * Available choices. Each choice requires a `key` field.
265
+ */
266
+ readonly choices: readonly ExpandChoice[];
267
+ /**
268
+ * Default choice key.
269
+ */
270
+ readonly default?: string;
271
+ /**
272
+ * Override the prompt execution. When provided, this function is called
273
+ * instead of launching an interactive Inquirer.js prompt. Useful for
274
+ * testing.
275
+ */
276
+ readonly prompter?: () => Promise<string>;
277
+ }
278
+ /**
279
+ * Configuration for a `checkbox` prompt (multi-select).
280
+ *
281
+ * Use with parsers that return `string[]`, typically via
282
+ * `multiple(option(...))`.
283
+ *
284
+ * @since 1.0.0
285
+ */
286
+ interface CheckboxConfig {
287
+ readonly type: "checkbox";
288
+ /**
289
+ * The question to display to the user.
290
+ */
291
+ readonly message: string;
292
+ /**
293
+ * Available choices. Plain strings and {@link Choice} objects can be mixed.
294
+ * Use `new Separator(...)` for visual dividers.
295
+ */
296
+ readonly choices: readonly (string | Choice | Separator)[];
297
+ /**
298
+ * Override the prompt execution. When provided, this function is called
299
+ * instead of launching an interactive Inquirer.js prompt. Useful for
300
+ * testing.
301
+ */
302
+ readonly prompter?: () => Promise<readonly string[]>;
303
+ }
304
+ /**
305
+ * A union of all string-input prompt configurations.
306
+ *
307
+ * @since 1.0.0
308
+ */
309
+ type StringPromptConfig = InputConfig | PasswordConfig | EditorConfig | SelectConfig | RawlistConfig | ExpandConfig;
310
+ /**
311
+ * Type-safe prompt configuration for a given parser value type `T`.
312
+ *
313
+ * The available prompt types are constrained by the expected value type:
314
+ *
315
+ * - `boolean` or `boolean | undefined` → {@link ConfirmConfig}
316
+ * - `number` or `number | undefined` → {@link NumberPromptConfig}
317
+ * - `string` or `string | undefined` → {@link StringPromptConfig}
318
+ * - `readonly string[]` → {@link CheckboxConfig}
319
+ *
320
+ * @since 1.0.0
321
+ */
322
+ type PromptConfig<T> = BasePromptConfig<Exclude<T, null | undefined>>;
323
+ type BasePromptConfig<T> = T extends boolean ? ConfirmConfig : T extends number ? NumberPromptConfig : T extends string ? StringPromptConfig : T extends readonly string[] ? CheckboxConfig : never;
324
+ /**
325
+ * Wraps a parser with an interactive Inquirer.js prompt fallback.
326
+ *
327
+ * When the inner parser finds a value in the CLI arguments (consumed tokens),
328
+ * that value is used directly. When no CLI value is found, an interactive
329
+ * prompt is shown to the user.
330
+ *
331
+ * The returned parser always has `$mode: "async"` because Inquirer.js prompts
332
+ * are inherently asynchronous.
333
+ *
334
+ * Example:
335
+ *
336
+ * ```typescript
337
+ * import { option } from "@optique/core/primitives";
338
+ * import { string } from "@optique/core/valueparser";
339
+ * import { prompt } from "@optique/inquirer";
340
+ *
341
+ * const nameParser = prompt(option("--name", string()), {
342
+ * type: "input",
343
+ * message: "Enter your name:",
344
+ * });
345
+ * ```
346
+ *
347
+ * @param parser Inner parser that reads CLI values.
348
+ * @param config Type-safe Inquirer.js prompt configuration.
349
+ * @returns A parser with interactive prompt fallback, always in async mode.
350
+ * @since 1.0.0
351
+ */
352
+ declare function prompt<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: PromptConfig<TValue>): Parser<"async", TValue, TState>;
353
+ //#endregion
354
+ export { CheckboxConfig, Choice, ConfirmConfig, EditorConfig, ExpandChoice, ExpandConfig, InputConfig, NumberPromptConfig, PasswordConfig, PromptConfig, RawlistConfig, SelectConfig, Separator, StringPromptConfig, prompt };
@@ -0,0 +1,354 @@
1
+ import { Separator } from "@inquirer/prompts";
2
+ import { Mode, Parser } from "@optique/core/parser";
3
+
4
+ //#region src/index.d.ts
5
+
6
+ /**
7
+ * A choice item for selection-type prompts (`select`, `rawlist`, `expand`,
8
+ * `checkbox`).
9
+ *
10
+ * @since 1.0.0
11
+ */
12
+ interface Choice {
13
+ /**
14
+ * The value returned when this choice is selected.
15
+ */
16
+ readonly value: string;
17
+ /**
18
+ * Display name shown in the prompt. Defaults to `value`.
19
+ */
20
+ readonly name?: string;
21
+ /**
22
+ * Additional description shown when this choice is highlighted.
23
+ */
24
+ readonly description?: string;
25
+ /**
26
+ * Short text shown after selection. Defaults to `name`.
27
+ */
28
+ readonly short?: string;
29
+ /**
30
+ * If truthy, the choice cannot be selected. A string explains why.
31
+ */
32
+ readonly disabled?: boolean | string;
33
+ }
34
+ /**
35
+ * A choice item for the `expand` prompt type.
36
+ *
37
+ * Unlike {@link Choice}, this requires a `key` field (a single lowercase
38
+ * alphanumeric character) that the user presses to select the item.
39
+ *
40
+ * @since 1.0.0
41
+ */
42
+ interface ExpandChoice {
43
+ /**
44
+ * The value returned when this choice is selected.
45
+ */
46
+ readonly value: string;
47
+ /**
48
+ * Display name shown in the prompt. Defaults to `value`.
49
+ */
50
+ readonly name?: string;
51
+ /**
52
+ * Single lowercase alphanumeric character used as the keyboard shortcut.
53
+ */
54
+ readonly key: string;
55
+ }
56
+ /**
57
+ * Configuration for a `confirm` prompt (boolean value).
58
+ *
59
+ * @since 1.0.0
60
+ */
61
+ interface ConfirmConfig {
62
+ readonly type: "confirm";
63
+ /**
64
+ * The question to display to the user.
65
+ */
66
+ readonly message: string;
67
+ /**
68
+ * Default answer when the user just presses Enter.
69
+ */
70
+ readonly default?: boolean;
71
+ /**
72
+ * Override the prompt execution. When provided, this function is called
73
+ * instead of launching an interactive Inquirer.js prompt. Useful for
74
+ * testing.
75
+ */
76
+ readonly prompter?: () => Promise<boolean>;
77
+ }
78
+ /**
79
+ * Configuration for a `number` prompt.
80
+ *
81
+ * @since 1.0.0
82
+ */
83
+ interface NumberPromptConfig {
84
+ readonly type: "number";
85
+ /**
86
+ * The question to display to the user.
87
+ */
88
+ readonly message: string;
89
+ /**
90
+ * Default number shown to the user.
91
+ */
92
+ readonly default?: number;
93
+ /**
94
+ * Minimum accepted value.
95
+ */
96
+ readonly min?: number;
97
+ /**
98
+ * Maximum accepted value.
99
+ */
100
+ readonly max?: number;
101
+ /**
102
+ * Granularity of valid values. Use `"any"` for arbitrary decimals.
103
+ */
104
+ readonly step?: number | "any";
105
+ /**
106
+ * Override the prompt execution. When provided, this function is called
107
+ * instead of launching an interactive Inquirer.js prompt. Useful for
108
+ * testing.
109
+ *
110
+ * Return `undefined` to simulate the user leaving the field empty (which
111
+ * results in a parse failure).
112
+ */
113
+ readonly prompter?: () => Promise<number | undefined>;
114
+ }
115
+ /**
116
+ * Configuration for an `input` prompt (free-text string).
117
+ *
118
+ * @since 1.0.0
119
+ */
120
+ interface InputConfig {
121
+ readonly type: "input";
122
+ /**
123
+ * The question to display to the user.
124
+ */
125
+ readonly message: string;
126
+ /**
127
+ * Default text pre-filled in the prompt.
128
+ */
129
+ readonly default?: string;
130
+ /**
131
+ * Validation function called when the user submits.
132
+ * Return `true` or a string error message.
133
+ */
134
+ readonly validate?: (value: string) => boolean | string | Promise<boolean | string>;
135
+ /**
136
+ * Override the prompt execution. When provided, this function is called
137
+ * instead of launching an interactive Inquirer.js prompt. Useful for
138
+ * testing.
139
+ */
140
+ readonly prompter?: () => Promise<string>;
141
+ }
142
+ /**
143
+ * Configuration for a `password` prompt (masked input).
144
+ *
145
+ * @since 1.0.0
146
+ */
147
+ interface PasswordConfig {
148
+ readonly type: "password";
149
+ /**
150
+ * The question to display to the user.
151
+ */
152
+ readonly message: string;
153
+ /**
154
+ * If `true`, show `*` characters for each keystroke.
155
+ * If `false` or omitted, input is invisible.
156
+ */
157
+ readonly mask?: boolean;
158
+ /**
159
+ * Validation function called when the user submits.
160
+ * Return `true` or a string error message.
161
+ */
162
+ readonly validate?: (value: string) => boolean | string | Promise<boolean | string>;
163
+ /**
164
+ * Override the prompt execution. When provided, this function is called
165
+ * instead of launching an interactive Inquirer.js prompt. Useful for
166
+ * testing.
167
+ */
168
+ readonly prompter?: () => Promise<string>;
169
+ }
170
+ /**
171
+ * Configuration for an `editor` prompt (external editor).
172
+ *
173
+ * Opens the user's `$VISUAL` or `$EDITOR` for multi-line text input.
174
+ *
175
+ * @since 1.0.0
176
+ */
177
+ interface EditorConfig {
178
+ readonly type: "editor";
179
+ /**
180
+ * The question to display to the user.
181
+ */
182
+ readonly message: string;
183
+ /**
184
+ * Default content pre-filled in the editor.
185
+ */
186
+ readonly default?: string;
187
+ /**
188
+ * Validation function called when the editor is closed.
189
+ * Return `true` or a string error message.
190
+ */
191
+ readonly validate?: (value: string) => boolean | string | Promise<boolean | string>;
192
+ /**
193
+ * Override the prompt execution. When provided, this function is called
194
+ * instead of launching an interactive Inquirer.js prompt. Useful for
195
+ * testing.
196
+ */
197
+ readonly prompter?: () => Promise<string>;
198
+ }
199
+ /**
200
+ * Configuration for a `select` prompt (arrow-key single-select).
201
+ *
202
+ * @since 1.0.0
203
+ */
204
+ interface SelectConfig {
205
+ readonly type: "select";
206
+ /**
207
+ * The question to display to the user.
208
+ */
209
+ readonly message: string;
210
+ /**
211
+ * Available choices. Plain strings and {@link Choice} objects can be mixed.
212
+ * Use `new Separator(...)` for visual dividers.
213
+ */
214
+ readonly choices: readonly (string | Choice | Separator)[];
215
+ /**
216
+ * Initially highlighted choice value.
217
+ */
218
+ readonly default?: string;
219
+ /**
220
+ * Override the prompt execution. When provided, this function is called
221
+ * instead of launching an interactive Inquirer.js prompt. Useful for
222
+ * testing.
223
+ */
224
+ readonly prompter?: () => Promise<string>;
225
+ }
226
+ /**
227
+ * Configuration for a `rawlist` prompt (numbered list).
228
+ *
229
+ * @since 1.0.0
230
+ */
231
+ interface RawlistConfig {
232
+ readonly type: "rawlist";
233
+ /**
234
+ * The question to display to the user.
235
+ */
236
+ readonly message: string;
237
+ /**
238
+ * Available choices. Plain strings and {@link Choice} objects can be mixed.
239
+ */
240
+ readonly choices: readonly (string | Choice)[];
241
+ /**
242
+ * Pre-selected choice value.
243
+ */
244
+ readonly default?: string;
245
+ /**
246
+ * Override the prompt execution. When provided, this function is called
247
+ * instead of launching an interactive Inquirer.js prompt. Useful for
248
+ * testing.
249
+ */
250
+ readonly prompter?: () => Promise<string>;
251
+ }
252
+ /**
253
+ * Configuration for an `expand` prompt (keyboard shortcut single-select).
254
+ *
255
+ * @since 1.0.0
256
+ */
257
+ interface ExpandConfig {
258
+ readonly type: "expand";
259
+ /**
260
+ * The question to display to the user.
261
+ */
262
+ readonly message: string;
263
+ /**
264
+ * Available choices. Each choice requires a `key` field.
265
+ */
266
+ readonly choices: readonly ExpandChoice[];
267
+ /**
268
+ * Default choice key.
269
+ */
270
+ readonly default?: string;
271
+ /**
272
+ * Override the prompt execution. When provided, this function is called
273
+ * instead of launching an interactive Inquirer.js prompt. Useful for
274
+ * testing.
275
+ */
276
+ readonly prompter?: () => Promise<string>;
277
+ }
278
+ /**
279
+ * Configuration for a `checkbox` prompt (multi-select).
280
+ *
281
+ * Use with parsers that return `string[]`, typically via
282
+ * `multiple(option(...))`.
283
+ *
284
+ * @since 1.0.0
285
+ */
286
+ interface CheckboxConfig {
287
+ readonly type: "checkbox";
288
+ /**
289
+ * The question to display to the user.
290
+ */
291
+ readonly message: string;
292
+ /**
293
+ * Available choices. Plain strings and {@link Choice} objects can be mixed.
294
+ * Use `new Separator(...)` for visual dividers.
295
+ */
296
+ readonly choices: readonly (string | Choice | Separator)[];
297
+ /**
298
+ * Override the prompt execution. When provided, this function is called
299
+ * instead of launching an interactive Inquirer.js prompt. Useful for
300
+ * testing.
301
+ */
302
+ readonly prompter?: () => Promise<readonly string[]>;
303
+ }
304
+ /**
305
+ * A union of all string-input prompt configurations.
306
+ *
307
+ * @since 1.0.0
308
+ */
309
+ type StringPromptConfig = InputConfig | PasswordConfig | EditorConfig | SelectConfig | RawlistConfig | ExpandConfig;
310
+ /**
311
+ * Type-safe prompt configuration for a given parser value type `T`.
312
+ *
313
+ * The available prompt types are constrained by the expected value type:
314
+ *
315
+ * - `boolean` or `boolean | undefined` → {@link ConfirmConfig}
316
+ * - `number` or `number | undefined` → {@link NumberPromptConfig}
317
+ * - `string` or `string | undefined` → {@link StringPromptConfig}
318
+ * - `readonly string[]` → {@link CheckboxConfig}
319
+ *
320
+ * @since 1.0.0
321
+ */
322
+ type PromptConfig<T> = BasePromptConfig<Exclude<T, null | undefined>>;
323
+ type BasePromptConfig<T> = T extends boolean ? ConfirmConfig : T extends number ? NumberPromptConfig : T extends string ? StringPromptConfig : T extends readonly string[] ? CheckboxConfig : never;
324
+ /**
325
+ * Wraps a parser with an interactive Inquirer.js prompt fallback.
326
+ *
327
+ * When the inner parser finds a value in the CLI arguments (consumed tokens),
328
+ * that value is used directly. When no CLI value is found, an interactive
329
+ * prompt is shown to the user.
330
+ *
331
+ * The returned parser always has `$mode: "async"` because Inquirer.js prompts
332
+ * are inherently asynchronous.
333
+ *
334
+ * Example:
335
+ *
336
+ * ```typescript
337
+ * import { option } from "@optique/core/primitives";
338
+ * import { string } from "@optique/core/valueparser";
339
+ * import { prompt } from "@optique/inquirer";
340
+ *
341
+ * const nameParser = prompt(option("--name", string()), {
342
+ * type: "input",
343
+ * message: "Enter your name:",
344
+ * });
345
+ * ```
346
+ *
347
+ * @param parser Inner parser that reads CLI values.
348
+ * @param config Type-safe Inquirer.js prompt configuration.
349
+ * @returns A parser with interactive prompt fallback, always in async mode.
350
+ * @since 1.0.0
351
+ */
352
+ declare function prompt<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: PromptConfig<TValue>): Parser<"async", TValue, TState>;
353
+ //#endregion
354
+ export { CheckboxConfig, Choice, ConfirmConfig, EditorConfig, ExpandChoice, ExpandConfig, InputConfig, NumberPromptConfig, PasswordConfig, PromptConfig, RawlistConfig, SelectConfig, Separator, StringPromptConfig, prompt };
package/dist/index.js ADDED
@@ -0,0 +1,248 @@
1
+ import { Separator, checkbox, confirm, editor, expand, input, number, password, rawlist, select } from "@inquirer/prompts";
2
+ import { annotationKey, getAnnotations } from "@optique/core/annotations";
3
+ import { message } from "@optique/core/message";
4
+
5
+ //#region src/index.ts
6
+ /**
7
+ * Wraps a parser with an interactive Inquirer.js prompt fallback.
8
+ *
9
+ * When the inner parser finds a value in the CLI arguments (consumed tokens),
10
+ * that value is used directly. When no CLI value is found, an interactive
11
+ * prompt is shown to the user.
12
+ *
13
+ * The returned parser always has `$mode: "async"` because Inquirer.js prompts
14
+ * are inherently asynchronous.
15
+ *
16
+ * Example:
17
+ *
18
+ * ```typescript
19
+ * import { option } from "@optique/core/primitives";
20
+ * import { string } from "@optique/core/valueparser";
21
+ * import { prompt } from "@optique/inquirer";
22
+ *
23
+ * const nameParser = prompt(option("--name", string()), {
24
+ * type: "input",
25
+ * message: "Enter your name:",
26
+ * });
27
+ * ```
28
+ *
29
+ * @param parser Inner parser that reads CLI values.
30
+ * @param config Type-safe Inquirer.js prompt configuration.
31
+ * @returns A parser with interactive prompt fallback, always in async mode.
32
+ * @since 1.0.0
33
+ */
34
+ function prompt(parser, config) {
35
+ const promptBindStateKey = Symbol("@optique/inquirer/promptState");
36
+ function isPromptBindState(value) {
37
+ return value != null && typeof value === "object" && promptBindStateKey in value;
38
+ }
39
+ const cfg = config;
40
+ const PromptBindInitialStateClass = class {
41
+ [promptBindStateKey] = true;
42
+ hasCliValue = false;
43
+ };
44
+ const promptBindInitialState = new PromptBindInitialStateClass();
45
+ let promptCache = null;
46
+ async function executePrompt() {
47
+ if ("prompter" in cfg && cfg.prompter != null) {
48
+ const value = await cfg.prompter();
49
+ if (cfg.type === "number" && value === void 0) return {
50
+ success: false,
51
+ error: message`No number provided.`
52
+ };
53
+ return {
54
+ success: true,
55
+ value
56
+ };
57
+ }
58
+ switch (cfg.type) {
59
+ case "confirm": return {
60
+ success: true,
61
+ value: await confirm({
62
+ message: cfg.message,
63
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
64
+ })
65
+ };
66
+ case "number": {
67
+ const numResult = await number({
68
+ message: cfg.message,
69
+ ...cfg.default !== void 0 ? { default: cfg.default } : {},
70
+ ...cfg.min !== void 0 ? { min: cfg.min } : {},
71
+ ...cfg.max !== void 0 ? { max: cfg.max } : {},
72
+ ...cfg.step !== void 0 ? { step: cfg.step } : {}
73
+ });
74
+ if (numResult === void 0) return {
75
+ success: false,
76
+ error: message`No number provided.`
77
+ };
78
+ return {
79
+ success: true,
80
+ value: numResult
81
+ };
82
+ }
83
+ case "input": return {
84
+ success: true,
85
+ value: await input({
86
+ message: cfg.message,
87
+ ...cfg.default !== void 0 ? { default: cfg.default } : {},
88
+ ...cfg.validate !== void 0 ? { validate: cfg.validate } : {}
89
+ })
90
+ };
91
+ case "password": return {
92
+ success: true,
93
+ value: await password({
94
+ message: cfg.message,
95
+ ...cfg.mask !== void 0 ? { mask: cfg.mask } : {},
96
+ ...cfg.validate !== void 0 ? { validate: cfg.validate } : {}
97
+ })
98
+ };
99
+ case "editor": return {
100
+ success: true,
101
+ value: await editor({
102
+ message: cfg.message,
103
+ ...cfg.default !== void 0 ? { default: cfg.default } : {},
104
+ ...cfg.validate !== void 0 ? { validate: cfg.validate } : {}
105
+ })
106
+ };
107
+ case "select": return {
108
+ success: true,
109
+ value: await select({
110
+ message: cfg.message,
111
+ choices: normalizeChoices(cfg.choices),
112
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
113
+ })
114
+ };
115
+ case "rawlist": return {
116
+ success: true,
117
+ value: await rawlist({
118
+ message: cfg.message,
119
+ choices: normalizeChoices(cfg.choices),
120
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
121
+ })
122
+ };
123
+ case "expand": return {
124
+ success: true,
125
+ value: await expand({
126
+ message: cfg.message,
127
+ choices: cfg.choices,
128
+ ...cfg.default !== void 0 ? { default: cfg.default } : {}
129
+ })
130
+ };
131
+ case "checkbox": return {
132
+ success: true,
133
+ value: await checkbox({
134
+ message: cfg.message,
135
+ choices: normalizeChoices(cfg.choices)
136
+ })
137
+ };
138
+ }
139
+ }
140
+ return {
141
+ $mode: "async",
142
+ $valueType: parser.$valueType,
143
+ $stateType: parser.$stateType,
144
+ priority: parser.priority,
145
+ usage: [{
146
+ type: "optional",
147
+ terms: parser.usage
148
+ }],
149
+ initialState: promptBindInitialState,
150
+ parse: (context) => {
151
+ const annotations = getAnnotations(context.state);
152
+ const innerState = isPromptBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
153
+ const innerContext = innerState !== context.state ? {
154
+ ...context,
155
+ state: innerState
156
+ } : context;
157
+ const processResult = (result$1) => {
158
+ if (result$1.success) {
159
+ const cliConsumed = result$1.consumed.length > 0;
160
+ const nextState$1 = {
161
+ [promptBindStateKey]: true,
162
+ hasCliValue: cliConsumed,
163
+ cliState: result$1.next.state,
164
+ ...annotations != null ? { [annotationKey]: annotations } : {}
165
+ };
166
+ return {
167
+ success: true,
168
+ next: {
169
+ ...result$1.next,
170
+ state: nextState$1
171
+ },
172
+ consumed: result$1.consumed
173
+ };
174
+ }
175
+ if (result$1.consumed > 0) return result$1;
176
+ const nextState = {
177
+ [promptBindStateKey]: true,
178
+ hasCliValue: false,
179
+ ...annotations != null ? { [annotationKey]: annotations } : {}
180
+ };
181
+ return {
182
+ success: true,
183
+ next: {
184
+ ...innerContext,
185
+ state: nextState
186
+ },
187
+ consumed: []
188
+ };
189
+ };
190
+ const result = parser.parse(innerContext);
191
+ if (result instanceof Promise) return result.then(processResult);
192
+ return Promise.resolve(processResult(result));
193
+ },
194
+ complete: (state) => {
195
+ if (isPromptBindState(state) && state.hasCliValue) {
196
+ const r = parser.complete(state.cliState);
197
+ if (r instanceof Promise) return r;
198
+ return Promise.resolve(r);
199
+ }
200
+ if (state === promptBindInitialState) {
201
+ if (promptCache !== null) {
202
+ const cached = promptCache;
203
+ promptCache = null;
204
+ return cached;
205
+ }
206
+ promptCache = executePrompt();
207
+ return promptCache;
208
+ }
209
+ return executePrompt();
210
+ },
211
+ suggest: (context, prefix) => {
212
+ const innerState = isPromptBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
213
+ const innerContext = innerState !== context.state ? {
214
+ ...context,
215
+ state: innerState
216
+ } : context;
217
+ const innerResult = parser.suggest(innerContext, prefix);
218
+ return async function* () {
219
+ yield* innerResult;
220
+ }();
221
+ },
222
+ getDocFragments(state, upperDefaultValue) {
223
+ const configDefault = "default" in cfg ? cfg.default : void 0;
224
+ const defaultValue = upperDefaultValue ?? configDefault;
225
+ return parser.getDocFragments(state, defaultValue);
226
+ }
227
+ };
228
+ }
229
+ /** Normalize choices to the format Inquirer.js expects. */
230
+ function normalizeChoices(choices) {
231
+ return choices.map((c) => {
232
+ if (typeof c === "string") return {
233
+ value: c,
234
+ name: c
235
+ };
236
+ if (c instanceof Separator) return c;
237
+ return {
238
+ value: c.value,
239
+ ...c.name !== void 0 ? { name: c.name } : {},
240
+ ..."description" in c && c.description !== void 0 ? { description: c.description } : {},
241
+ ..."short" in c && c.short !== void 0 ? { short: c.short } : {},
242
+ ..."disabled" in c && c.disabled !== void 0 ? { disabled: c.disabled } : {}
243
+ };
244
+ });
245
+ }
246
+
247
+ //#endregion
248
+ export { Separator, prompt };
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@optique/inquirer",
3
+ "version": "1.0.0-dev.0",
4
+ "description": "Interactive prompt support for Optique via Inquirer.js",
5
+ "keywords": [
6
+ "CLI",
7
+ "command-line",
8
+ "commandline",
9
+ "parser",
10
+ "prompt",
11
+ "interactive",
12
+ "inquirer"
13
+ ],
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Hong Minhee",
17
+ "email": "hong@minhee.org",
18
+ "url": "https://hongminhee.org/"
19
+ },
20
+ "homepage": "https://optique.dev/",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/dahlia/optique.git",
24
+ "directory": "packages/inquirer/"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/dahlia/optique/issues"
28
+ },
29
+ "funding": [
30
+ "https://github.com/sponsors/dahlia"
31
+ ],
32
+ "engines": {
33
+ "node": ">=20.0.0",
34
+ "bun": ">=1.2.0",
35
+ "deno": ">=2.3.0"
36
+ },
37
+ "files": [
38
+ "dist/",
39
+ "package.json",
40
+ "README.md"
41
+ ],
42
+ "type": "module",
43
+ "module": "./dist/index.js",
44
+ "main": "./dist/index.cjs",
45
+ "types": "./dist/index.d.ts",
46
+ "exports": {
47
+ ".": {
48
+ "types": {
49
+ "import": "./dist/index.d.ts",
50
+ "require": "./dist/index.d.cts"
51
+ },
52
+ "import": "./dist/index.js",
53
+ "require": "./dist/index.cjs"
54
+ }
55
+ },
56
+ "sideEffects": false,
57
+ "dependencies": {
58
+ "@inquirer/prompts": "^8.3.0",
59
+ "@optique/core": "1.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^20.19.9",
63
+ "tsdown": "^0.13.0",
64
+ "typescript": "^5.8.3"
65
+ },
66
+ "scripts": {
67
+ "build": "tsdown",
68
+ "prepublish": "tsdown",
69
+ "test": "node --experimental-transform-types --test",
70
+ "test:bun": "bun test",
71
+ "test:deno": "deno test",
72
+ "test-all": "tsdown && node --experimental-transform-types --test && bun test && deno test"
73
+ }
74
+ }