# 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` 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 callback - `password(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; accepts `y`, `yes`, `n`, `no` (case-insensitive) - `select(opts, reader?)` — numbered single-choice list; re-prompts on out-of-range input - `multiselect(opts, reader?)` — comma-separated multi-choice with configurable `min`/`max` and deduplication - `createDefaultReader()` — async factory returning an `InputReader` wired to `process.stdin` via `@suaveplan/readline` - All functions accept both a plain `string` message or a full options object for brevity ## Usage: Basic Example ```ts 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 ```ts 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` 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` 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` 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(opts: SelectPromptOptions, reader?: InputReader): Promise` Displays a numbered list of choices and returns the `value` of the selected entry. Choices may be plain strings (label = value) or `SelectChoice` objects. Re-prompts when the entered number is out of range. ### `multiselect(opts: MultiSelectPromptOptions, reader?: InputReader): Promise` 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` 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; 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.