Prompts
Interactive input, password, confirm, select, multiselect, filter, and multifilter prompts.
@crustjs/prompts exports seven terminal prompts, shared themes, injected terminal IO, and renderer helpers. Prompt UI writes to stderr by default.
Install
npm install @crustjs/promptsQuick example
const name = await input({
message: "Project name?",
validate(value) {
if (!value.trim()) throw new Error("Name is required");
},
});
const runtime = await select({ message: "Runtime?", choices: ["bun", "node", "deno"] });
const features = await multiselect({
message: "Features?",
choices: ["lint", "test", "release"],
required: true,
});
const proceed = await confirm({ message: `Create ${name} for ${runtime}?`, default: true });
console.log({ features, proceed });Built-in prompts return the submitted value. input() and password() also accept a Standard Schema v1 schema or a throw-based validate callback, but not both.
┃ Project name?
crust-app│
✓ Project name? crust-appInjecting IO
declare const io: PromptIO;
await withTerminalIO(io, () => input({ message: "Name?" }));Each prompt accepts an optional second PromptIO argument with optional input and output. Resolution is explicit IO, ambient withTerminalIO() IO, then process.stdin and process.stderr.
withPromptIO() is an output-compatible alias. The ambient scope is shared with @crustjs/progress.
Testing custom prompts
const prompt = renderPrompt(myPrompt, { message: "Name?" });
prompt.type("Ada");
prompt.keys("return");
expect(await prompt.answer).toBe("Ada");Import renderPrompt from @crustjs/prompts/testing. Its screen() method returns the latest ANSI-stripped frame, while answer settles after submission.
The subpath also exports createPromptIO, encodeKey, and their key and harness types. Custom prompt functions passed to renderPrompt accept (options, io?), like built-in prompts.
Building custom prompts
import { runPrompt, submit, handleTextEdit, fuzzyFilter } from "@crustjs/prompts";The root exports the renderer, submission, fuzzy matching, list rendering, line formatting, text editing, and shared glyph helpers used by the built-ins. Use these exports when a custom prompt should follow the same terminal behavior.
Prompts
import {
input,
password,
confirm,
select,
multiselect,
filter,
multifilter,
} from "@crustjs/prompts";All prompt functions accept (options, io?). Choice functions preserve literal choice value types when their choices are not widened. A plain-string choice is its own value, so Choice<T> only accepts plain strings that are themselves a T; non-string values such as numbers must use { label, value } objects.
input(options, io?)
const name = await input({ message: "Name?", default: "anonymous" });Single-line text input supports cursor editing, placeholder text, defaults, and validation.
Prop
Type
password(options, io?)
const token = await password({ message: "Token?", mask: "*" });Password input masks active text and shows a fixed-length mask after submission. Its default mask is *.
Prop
Type
confirm(options, io?)
const proceed = await confirm({ message: "Continue?", default: false });Confirm returns a boolean. Left, Right, and Tab toggle the value; y and h select true, while n and l select false.
Prop
Type
select(options, io?)
const runtime = await select({ message: "Runtime?", choices: ["bun", "node"] });Select returns one choice value. Object-valued defaults match by reference, so pass the same object used in choices.
Prop
Type
multiselect(options, io?)
const tools = await multiselect({ message: "Tools?", choices: ["lint", "test"], max: 2 });Multiselect returns selected values in choice order. required prevents an empty submission, and max limits toggle-all and invert operations.
Prop
Type
filter(options, io?)
const language = await filter({ message: "Language?", choices: ["TypeScript", "Rust"] });Filter fuzzy-matches typed text and returns one highlighted choice.
Prop
Type
multifilter(options, io?)
const features = await multifilter({ message: "Features?", choices: ["lint", "test"] });Multifilter combines fuzzy matching with multi-selection and returns values in original choice order.
Prop
Type
Validation
┃ Project name?
│
Name is requiredA validate callback throws to reject input, and its error message renders inline. A Standard Schema parses the raw string, returns its transformed output type, and renders the first issue or Validation failed.
Async validators and schemas are supported. initial and non-TTY default values also pass through a configured schema before being returned.
Themes
const prompts = createPrompts({ theme: { prefix: magenta, success: cyan } });
await prompts.input({ message: "Name?", initial: "Ada" });A PromptTheme contains prefix, message, placeholder, cursor, selected, unselected, error, success, hint, and filterMatch. Instance overrides from createPrompts() sit between defaultTheme and per-prompt overrides.
Style capability behavior comes from @crustjs/style.
Non-interactive environments
# input({ message: "Name?", default: "ci-user" }) on a non-TTY
ci-user
# input({ message: "Name?" }) on a non-TTY
NonInteractiveError: Prompts require an interactive terminal (TTY).Every prompt returns initial without rendering when it is provided. On a non-TTY, all prompts except password() can return an explicitly supplied default; otherwise they reject with NonInteractiveError.
Ctrl+C rejects with a DOMException whose name is AbortError.
Custom prompts
const result = await runPrompt({
initialState: { count: 0 },
render: (state) => `Count: ${state.count}`,
handleKey: (key, state) => (key.name === "return" ? submit(state.count) : state),
});runPrompt() owns rendering, input, submission, cancellation, and theme merging. Only one prompt may use a given input or output stream at a time. An error thrown by render, handleKey, or renderSubmitted rejects the returned promise after the terminal and stream reservations are restored. Pending key-handler results are ignored after cleanup, so they cannot write into a replacement prompt.
Use handleTextEdit() for shared cursor editing. It returns null for keys it does not handle.
Exports
import { input, runPrompt, createPrompts, defaultTheme } from "@crustjs/prompts";The root includes all seven prompts, renderer and IO helpers, theme APIs, fuzzy and text-editing helpers, render-format helpers, glyphs, errors, and their public types. Testing helpers remain in @crustjs/prompts/testing.
See the source inventory in packages/prompts/src/index.ts.
Design
stdout: application data
stderr: prompt framesThe package depends on @crustjs/style for ANSI styling. It keeps prompt output off stdout by default and shares terminal IO with progress indicators.