0Z4C0
// navigation
// @ozaco/cli

Prompt

Interactive prompts — text, password, number, confirm, select, multiselect and more.

import from@ozaco/cli/prompt

@ozaco/cli/prompt is the default implementation of the Prompt protocol: a family of interactive questions that render over a raw-input Terminal session and resolve to a typed value. It exposes one plugin, DefaultPrompt, whose actions cover free-text entry (text, password, number, path) and choosing from options (confirm, select, multiselect, autocomplete).

Every prompt shares a common frame: a message line, optional description, a live-editing body, and — on submit or cancel — a committed summary line. Validators run on submit and return a message to reject; Ctrl-C cancels with a CoreErrors.Cancelled failure. List prompts paginate, skip disabled choices during navigation, and show per-choice hints.

When the terminal is not interactive (piped input, CI), each prompt takes a non-interactive fallback instead of blocking: text-like prompts return their initial (or empty/zero), confirm returns its initial, and choice prompts return the initial/first choice — or fail with CoreErrors.NotInteractive when none is available. Requires Terminal and Palette to be installed. Option types live in PromptDef in @ozaco/cli/core.

01

Implementation

const

DefaultPrompt

The prompt implementation providing all eight prompt actions.

signature
const DefaultPrompt: PromptDef

Install it alongside Terminal and Palette, then call Prompt.actions.*. Each action opens its own terminal session, so prompts can be issued one after another.

example
import { install } from '@ozaco/std/plugin'
import { Prompt } from '@ozaco/cli/core'
import { DefaultPrompt } from '@ozaco/cli/prompt'

yield* install(DefaultPrompt)
const name = yield* Prompt.actions.text({ message: 'Your name?' })
02

Free-text prompts

Single-line editors that resolve to a string or number.

fn

text

Ask for a line of text, with an optional initial value, placeholder and validator.

signature
text(options: PromptDef.TextOptions): Future<string>

initial pre-fills the input, placeholder shows dim guide text while empty, and validate(value) runs on Enter — returning a message keeps the prompt open with that error. Falls back to initial ?? "" when non-interactive.

example
const email = yield* Prompt.actions.text({
  message: 'Email',
  placeholder: 'you@example.com',
  validate: v => (v.includes('@') ? undefined : 'Enter a valid email'),
})
fn

password

Ask for a secret, echoing a mask character instead of the typed text.

signature
password(options: PromptDef.PasswordOptions): Future<string>

The typed value is hidden behind mask (default ); an empty mask hides input entirely. validate runs on Enter. Falls back to an empty string when non-interactive.

example
const token = yield* Prompt.actions.password({
  message: 'API token',
  validate: v => (v.length >= 20 ? undefined : 'Too short'),
})
fn

number

Ask for a number, restricting keystrokes and enforcing range/integer rules.

signature
number(options: PromptDef.NumberOptions): Future<number>

Only digits (and -, plus . when float is set) are accepted. On Enter the value is parsed and checked against min/max, integer-ness (unless float), then validate. Falls back to initial ?? 0 when non-interactive.

example
const port = yield* Prompt.actions.number({
  message: 'Port',
  initial: 3000,
  min: 1,
  max: 65535,
})
fn

path

Ask for a filesystem path with Tab completion and a live directory listing.

signature
path(options: PromptDef.PathOptions): Future<string>

Resolves relative input against root (default process.cwd()). Tab completes to the longest common prefix of matching entries (appending / for a single directory). Matching entries are listed live; set directory to offer only directories. Falls back to initial ?? "" when non-interactive.

example
const dir = yield* Prompt.actions.path({
  message: 'Output directory',
  directory: true,
})
03

Choice prompts

Pick one or more values from a list of `PromptDef.Choice`s.

fn

confirm

Ask a yes/no question; returns a boolean.

signature
confirm(options: PromptDef.ConfirmOptions): Future<boolean>

Starts at initial (default false), shown as (y/N) or (Y/n). Arrow/Tab/Space toggle the choice; pressing y or n submits immediately. Falls back to initial when non-interactive.

example
const ok = yield* Prompt.actions.confirm({
  message: 'Overwrite existing file?',
  initial: false,
})
fn

select

Pick one value from a list; returns the chosen `value`.

signature
select<T>(options: PromptDef.SelectOptions<T>): Future<T>

Up/Down move the highlight (skipping disabled choices), Enter selects. initial may be an index or a value. The list paginates at 10 rows and shows each choice’s hint. Falls back to the initial choice, or fails with NotInteractive if none is available.

example
const env = yield* Prompt.actions.select({
  message: 'Environment',
  choices: [
    { value: 'dev', hint: 'local' },
    { value: 'prod', label: 'Production' },
  ],
})
fn

multiselect

Pick several values with checkboxes; returns the chosen values.

signature
multiselect<T>(options: PromptDef.MultiSelectOptions<T>): Future<T[]>

Space toggles the highlighted checkbox, Enter submits. initial pre-selects indices or values; required, min and max are enforced on submit with an inline error. Falls back to the resolved initial selection when non-interactive.

example
const features = yield* Prompt.actions.multiselect({
  message: 'Enable features',
  min: 1,
  choices: [
    { value: 'ssr' },
    { value: 'pwa' },
    { value: 'i18n' },
  ],
})
fn

autocomplete

Filter a list as you type, then pick a match; returns the chosen `value`.

signature
autocomplete<T>(options: PromptDef.AutocompleteOptions<T>): Future<T>

Typing filters the choices (default: case-insensitive substring on the label); Up/Down cycle through matches (wrapping), Enter selects. Provide a custom suggest(input, choices) to control matching. Matches paginate at 8 rows. Falls back to the first choice, or fails with NotInteractive if there are none.

example
const pkg = yield* Prompt.actions.autocomplete({
  message: 'Package',
  placeholder: 'type to search',
  choices: packages.map(name => ({ value: name })),
})