Interactive prompts
This API is available since Optique 1.0.0.
The @optique/inquirer package wraps any Optique parser with an interactive Inquirer.js prompt. When the user provides a value via CLI, that value is used directly. When the argument is absent, an interactive prompt is shown instead of failing.
The fallback priority is:
- CLI argument
- Interactive prompt
Because interactive prompts are inherently asynchronous, the returned parser always has $mode: "async".
deno add jsr:@optique/inquirernpm add @optique/inquirerpnpm add @optique/inquireryarn add @optique/inquirerbun add @optique/inquirerBasic usage
Wrap any parser with prompt() and provide a prompt configuration object:
import { object } from "@optique/core/constructs";
import { option } from "@optique/core/primitives";
import { integer, string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
import { run } from "@optique/run";
const parser = object({
name: prompt(option("--name", string()), {
type: "input",
message: "Enter your name:",
}),
port: prompt(option("--port", integer()), {
type: "number",
message: "Enter the port number:",
default: 3000,
}),
});
await run(parser);When --name and --port are provided on the command line, the prompts are skipped. When they are absent, the user sees interactive prompts.
Prompt types
input — free-text string
Prompts the user for an arbitrary string value:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const name = prompt(option("--name", string()), {
type: "input",
message: "Enter your name:",
default: "World",
validate: (value) => value.length > 0 || "Name cannot be empty.",
});input properties
message- (required) The question to display.
default- Pre-filled text shown in the input field.
validate- Function called when the user submits. Return
trueto accept or a string error message to reject and re-prompt.
confirm — Boolean yes/no
Prompts the user with a yes/no question:
import { flag } from "@optique/core/primitives";
import { prompt } from "@optique/inquirer";
const verbose = prompt(flag("--verbose"), {
type: "confirm",
message: "Enable verbose output?",
default: false,
});confirm properties
message- (required) The question to display.
default- Default answer when the user presses Enter without typing.
number — numeric input
Prompts the user for a number:
import { option } from "@optique/core/primitives";
import { integer } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const port = prompt(option("--port", integer()), {
type: "number",
message: "Enter the port:",
default: 8080,
min: 1,
max: 65535,
});number properties
message- (required) The question to display.
default- Default number shown to the user.
min,max- Accepted value range.
step- Granularity of valid values. Use
"any"for arbitrary decimals.
NOTE
If the user submits the prompt without entering a number (leaving it blank), the result is a parse failure rather than undefined.
password — masked input
Prompts for a secret value without displaying the characters:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const apiKey = prompt(option("--api-key", string()), {
type: "password",
message: "Enter your API key:",
mask: true,
});password properties
message- (required) The question to display.
mask- When
true, show*for each keystroke. Whenfalseor omitted, input is completely hidden. validate- Same as
input.
editor — multi-line text
Opens the user's $VISUAL or $EDITOR for multi-line input:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const message = prompt(option("--message", string()), {
type: "editor",
message: "Write your commit message:",
default: "",
validate: (value) => value.trim().length > 0 || "Message cannot be empty.",
});editor properties
message- (required) The question to display.
default- Content pre-filled in the editor buffer.
validate- Same as
input.
select — arrow-key single-select
Shows a scrollable list where the user selects one option using arrow keys:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const env = prompt(option("--env", string()), {
type: "select",
message: "Choose the deployment environment:",
choices: ["development", "staging", "production"],
default: "development",
});Choices can also be objects with display names and descriptions:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt, Separator } from "@optique/inquirer";
const color = prompt(option("--color", string()), {
type: "select",
message: "Choose a color:",
choices: [
{ value: "red", name: "Red", description: "A warm primary color" },
{ value: "green", name: "Green", description: "A cool secondary color" },
new Separator("──────────"),
{ value: "custom", name: "Custom…", disabled: "Coming soon" },
],
});select properties
message- (required) The question to display.
choices- (required) Array of strings,
Choiceobjects, orSeparatorinstances. default- Initially highlighted choice value.
rawlist — numbered list
Shows a numbered list and prompts the user to type a number:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const format = prompt(option("--format", string()), {
type: "rawlist",
message: "Choose the output format:",
choices: ["json", "yaml", "toml"],
});rawlist properties
message- (required) The question to display.
choices- (required) Array of strings or
Choiceobjects. default- Pre-selected choice value.
expand — keyboard shortcut single-select
Prompts the user to press a single key to select an option:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const action = prompt(option("--action", string()), {
type: "expand",
message: "What do you want to do?",
choices: [
{ value: "overwrite", name: "Overwrite", key: "o" },
{ value: "skip", name: "Skip", key: "s" },
{ value: "abort", name: "Abort", key: "a" },
],
});expand properties
message- (required) The question to display.
choices- (required) Array of
ExpandChoiceobjects, each with a single lowercase alphanumerickey. default- Default choice key.
checkbox — multi-select
Shows a scrollable list where the user toggles multiple options with Space:
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { multiple } from "@optique/core/modifiers";
import { prompt } from "@optique/inquirer";
const tags = prompt(multiple(option("--tag", string())), {
type: "checkbox",
message: "Select tags:",
choices: ["typescript", "deno", "node", "bun"],
});The inner parser must produce readonly string[], so use multiple() around an option or argument parser.
checkbox properties
message- (required) The question to display.
choices- (required) Array of strings,
Choiceobjects, orSeparatorinstances.
Prompt-only values
When a value should only come from a prompt (no CLI flag at all), pair prompt() with fail<T>():
import { object } from "@optique/core/constructs";
import { fail } from "@optique/core/primitives";
import { prompt } from "@optique/inquirer";
const parser = object({
name: prompt(fail<string>(), {
type: "input",
message: "Enter your name:",
}),
confirm: prompt(fail<boolean>(), {
type: "confirm",
message: "Are you sure?",
default: false,
}),
});fail() always fails the CLI parse, so the prompt runs unconditionally.
Optional prompts
Wrap the inner parser with optional() to allow the user to skip the prompt via CLI while still showing a prompt when the flag is absent. This is equivalent to any other prompt() usage—optional() is handled transparently:
import { option } from "@optique/core/primitives";
import { optional } from "@optique/core/modifiers";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const description = prompt(optional(option("--description", string())), {
type: "input",
message: "Enter a description (or press Enter to skip):",
});NOTE
In this case, if the user just presses Enter at the prompt, the returned value is an empty string "", not undefined. To get undefined when the user leaves the field blank, use validate to reject empty input or handle the empty string in your application.
Composing with other integrations
prompt() composes naturally with bindEnv() and bindConfig(). The innermost wrapper is evaluated first, so nesting order determines fallback priority. This works the same inside object(), tuple(), merge(), and concat(), including dependency-aware suggest*() flows.
For example, to fall back to an environment variable before prompting:
import { object } from "@optique/core/constructs";
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { bindEnv, createEnvContext } from "@optique/env";
import { prompt } from "@optique/inquirer";
import { run } from "@optique/run";
const envContext = createEnvContext({ prefix: "MYAPP_" });
const parser = object({
apiKey: prompt(
bindEnv(option("--api-key", string()), {
context: envContext,
key: "API_KEY",
parser: string(),
}),
{
type: "password",
message: "Enter your API key:",
mask: true,
},
),
});
await run(parser, { contexts: [envContext] });This gives the priority:
CLI argument > Environment variable > Interactive prompt
Testing
All prompt configuration types accept an optional prompter property for testing. When provided, the function is called instead of launching an interactive Inquirer.js prompt:
import { parseAsync } from "@optique/core/parser";
import { option } from "@optique/core/primitives";
import { string } from "@optique/core/valueparser";
import { prompt } from "@optique/inquirer";
const parser = prompt(option("--name", string()), {
type: "input",
message: "Enter your name:",
prompter: () => Promise.resolve("Alice"), // used in tests
});
const result = await parseAsync(parser, []);
// result.value === "Alice"API reference
prompt(parser, config)
Wraps a parser with an interactive prompt fallback.
- Parameters
parser: The inner parser. CLI tokens consumed by this parser suppress the prompt.config: APromptConfig<T>object specifying the prompt type and its options.
- Returns
- A new parser with
$mode: "async"and interactive prompt fallback. Theusageis wrapped in anoptionalterm since the prompt handles the missing-value case.
PromptConfig<T>
A conditional type that maps a parser's value type T to the appropriate prompt configuration union:
| Value type | Accepted config type |
|---|---|
boolean | ConfirmConfig |
number | NumberPromptConfig |
string | InputConfig | PasswordConfig | EditorConfig | SelectConfig | RawlistConfig | ExpandConfig |
readonly string[] | CheckboxConfig |
Optional variants (boolean | undefined, string | undefined, etc.) map to the same config types as their non-optional counterparts.
Choice
An object with value, optional name, description, short, and disabled fields. Used in select, rawlist, and checkbox prompts.
ExpandChoice
Like Choice but requires a key field (single lowercase alphanumeric character). Used in expand prompts.
Separator
Re-exported from Inquirer.js. Use new Separator(text?) to add visual dividers in select and checkbox choice lists.
Prompt and inner parser independence
The CLI path and the prompt path are independent value sources. When a value comes from the CLI, the inner parser's full constraint pipeline (value parsing, choice() domain checks, integer({ min, max }), etc.) is applied. When a value comes from a prompt, it is used as-is—the inner parser's constraints are not re-applied.
This design is intentional: combinators like map() can transform the value domain, making the prompted value incompatible with the inner parser's input path. Treating the two paths independently avoids false rejections and keeps the architecture sound.
As a consequence, any runtime validation you need on prompted values must be configured in the prompt config itself. Some prompt types provide a validate option for this purpose.
Matching constraints between CLI and prompt
When the inner parser carries constraints, you should mirror them in the prompt config.
numberprompt withinteger()semanticsUse
step: 1to restrict the prompt to integers, andmin/maxto match the inner parser's range.import {option} from "@optique/core/primitives"; import {integer} from "@optique/core/valueparser"; import {prompt} from "@optique/inquirer"; constport=prompt(option("--port",integer({min: 1024,max: 65535 })), {type: "number",message: "Enter the port:",min: 1024,max: 65535,step: 1, });inputprompt withstring({ pattern })semanticsUse
validateto enforce the same pattern.import {option} from "@optique/core/primitives"; import {string} from "@optique/core/valueparser"; import {prompt} from "@optique/inquirer"; constid=prompt(option("--id",string({pattern: /^[A-Z]{3}-\d+$/ })), {type: "input",message: "Enter the ID:",validate: (value) => /^[A-Z]{3}-\d+$/.test(value) || "Must match AAA-123 format.", });select/rawlist/expandwithchoice()valuesKeep the prompt
choicesarray consistent with the inner parser'schoice()domain. Ensuring this consistency is the caller's responsibility.import {option} from "@optique/core/primitives"; import {choice} from "@optique/core/valueparser"; import {prompt} from "@optique/inquirer"; constenv=prompt(option("--env",choice(["dev", "staging", "prod"])), {type: "select",message: "Choose environment:",choices: ["dev", "staging", "prod"], // must match choice() values });checkboxwithmultiple()cardinalityThe
checkboxprompt type does not currently support avalidatecallback, so cardinality constraints frommultiple(..., { min, max })cannot be enforced at the prompt level. This is a known limitation; prompted checkbox values may violate the inner parser's cardinality bounds.
IMPORTANT
select, rawlist, expand, and checkbox prompt types do not expose a validate callback. For these types, there is currently no way to add custom runtime validation on prompted values. This is a known limitation that may be addressed in a future release.
Limitations
- Always async —
prompt()always returns an async parser because Inquirer.js prompts are inherently asynchronous. This means anyobject()or other combinator containing aprompt()parser also becomes async. - No shell completion — Interactive prompts do not contribute to shell tab-completion suggestions. Only the wrapped inner parser's suggestions are used.
- Single prompt per field — Each
prompt()call runs the prompter exactly once per parse, even when used insideobject(). - TTY required — Inquirer.js requires an interactive terminal (TTY). In non-interactive environments (CI pipelines, piped input), prompts will error. Use the
prompteroverride for non-interactive testing.