5.0 KiB
prompt
(no submodules)
Purpose
prompt provides a collection of pure, injectable interactive prompt functions for CLI applications: text, password, confirm, select, and multiselect. All five functions accept an InputReader as their last argument for testing and embedding — callers supply any object with question(prompt): Promise<string> and close(): void. In production, createDefaultReader() builds an InputReader backed by @suaveplan/readline/interface, which in turn reads from process.stdin.
Prompt functions are asynchronous, loop-safe, and validation-aware. They re-prompt on invalid input rather than throwing, keeping the user experience coherent across all terminal types.
Features
text(opts, reader?)— free-form text input with optional default and validation callbackpassword(opts, reader?)— masked input (structurally identical to text; masking is delegated to the terminal emulator or TTY mode)confirm(opts, reader?)— yes/no boolean with optional default; acceptsy,yes,n,no(case-insensitive)select(opts, reader?)— numbered single-choice list; re-prompts on out-of-range inputmultiselect(opts, reader?)— comma-separated multi-choice with configurablemin/maxand deduplicationcreateDefaultReader()— async factory returning anInputReaderwired toprocess.stdinvia@suaveplan/readline- All functions accept both a plain
stringmessage or a full options object for brevity
Usage: Basic Example
import { confirm, select, text } from "@suaveplan/cli/prompt/prompt";
const name = await text("Your name?");
const confirmed = await confirm({ message: "Continue?", default: true });
const role = await select({ message: "Pick a role:", choices: ["admin", "user", "guest"] });
console.log(name, confirmed, role);
Usage: Advanced Example
import { multiselect, text } from "@suaveplan/cli/prompt/prompt";
import type { InputReader } from "@suaveplan/cli/prompt/prompt";
// Inject a reader for testing
function mockReader(lines: string[]): InputReader {
let i = 0;
return { question: async () => lines[i++] ?? "", close: () => {} };
}
const username = await text(
{ message: "Username?", validate: (v) => v.length >= 3 || "at least 3 chars" },
mockReader(["ab", "alice"]),
);
const features = await multiselect(
{ message: "Enable features:", choices: ["logging", "metrics", "tracing"], min: 1 },
mockReader(["1,2"]),
);
console.log(username, features);
API Reference
text(opts: TextPromptOptions | string, reader?: InputReader): Promise<string>
Prompts for free-form text. When opts is a string it is used as the message. Returns the user's input or opts.default if the input is empty and a default is set. Re-prompts if opts.validate returns an error string.
password(opts: PasswordPromptOptions | string, reader?: InputReader): Promise<string>
Identical interface to text but semantically for secret input. The caller is responsible for disabling echo on the actual terminal if needed.
confirm(opts: ConfirmPromptOptions | string, reader?: InputReader): Promise<boolean>
Prompts for a yes/no answer. Accepts y, yes, n, no (case-insensitive). Returns opts.default when the user submits an empty line and a default is configured. Re-prompts on unrecognized input.
select<T>(opts: SelectPromptOptions<T>, reader?: InputReader): Promise<T>
Displays a numbered list of choices and returns the value of the selected entry. Choices may be plain strings (label = value) or SelectChoice<T> objects. Re-prompts when the entered number is out of range.
multiselect<T>(opts: MultiSelectPromptOptions<T>, reader?: InputReader): Promise<T[]>
Displays a numbered list and accepts comma-separated indices. Deduplicates repeated indices. Enforces opts.min / opts.max bounds and re-prompts when any index is invalid or the count is out of bounds.
createDefaultReader(): Promise<InputReader>
Async factory that lazily imports @suaveplan/readline/interface and wires it to process.stdin / process.stdout. Returns an InputReader. The caller is responsible for calling close() when done.
InputReader
Injectable interface: { question(prompt: string): Promise<string>; close(): void; }. Pass a mock for tests.
TextPromptOptionsSchema, ConfirmPromptOptionsSchema, SelectChoiceSchema
Zod schemas for the corresponding option types — useful for config-file driven prompts.
Implementation Notes
All prompt functions manage the reader lifecycle when no reader is injected: they call createDefaultReader() in a try/finally block that calls close() after the prompt resolves. When a reader is injected, the caller owns the lifecycle. Validation callbacks return either true (pass) or an error string (fail); the error is written to process.stderr before re-prompting. The multiselect function considers any out-of-range index a hard error and re-prompts rather than silently discarding it.