Files
skills/docs/devtools/tooling/cli/src/prompt/prompt.md
2026-05-29 08:20:56 +01:00

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 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

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.