Skip to main content

Overview

The prompt module provides interactive CLI prompts with support for text input, passwords, confirmations, single/multi-select, and more. All prompts support fallback values for non-interactive environments.

Imports

import {
  text,
  password,
  confirm,
  select,
  multiselect,
  group,
  spinner,
  intro,
  outro,
  note,
  log,
  cancel,
  isCancel,
  CANCEL
} from '@bunli/runtime/prompt'

text

Prompt for text input with optional validation and schema support.
async function text<T = string>(
  message: string,
  options?: PromptOptions
): Promise<T>

Parameters

message
string
required
The prompt message to display
options
PromptOptions

Example

import { text } from '@bunli/runtime/prompt'
import { z } from 'zod'

// Simple text input
const name = await text('What is your name?')

// With default value
const repo = await text('Repository name?', {
  default: 'my-project'
})

// With validation
const email = await text('Email address?', {
  validate: (input) => {
    if (!input.includes('@')) return 'Invalid email'
    return true
  }
})

// With schema validation
const port = await text<number>('Port number?', {
  schema: z.coerce.number().int().min(1).max(65535)
})

// Non-interactive with fallback
const apiKey = await text('API key?', {
  fallbackValue: process.env.API_KEY
})

password

Prompt for password input (hidden or toggleable).
async function password<T = string>(
  message: string,
  options?: PromptOptions
): Promise<T>

Parameters

message
string
required
The prompt message to display
options
PromptOptions
Same as text, but input is masked

Example

import { password } from '@bunli/runtime/prompt'

const secret = await password('Enter password:')

// With validation
const strongPassword = await password('Create password:', {
  validate: (input) => {
    if (input.length < 8) return 'Password must be at least 8 characters'
    return true
  }
})
Press Ctrl+R to toggle password visibility during input

confirm

Prompt for yes/no confirmation.
async function confirm(
  message: string,
  options?: ConfirmOptions
): Promise<boolean>

Parameters

message
string
required
The confirmation message
options
ConfirmOptions

Example

import { confirm } from '@bunli/runtime/prompt'

const shouldContinue = await confirm('Continue?', {
  default: true
})

if (shouldContinue) {
  // proceed
}

select

Prompt to select a single option from a list.
async function select<T = string>(
  message: string,
  options: SelectOptions<T>
): Promise<T>

Parameters

message
string
required
The prompt message
options
SelectOptions<T>
required

Example

import { select } from '@bunli/runtime/prompt'

const framework = await select('Choose a framework:', {
  options: [
    { label: 'React', value: 'react', hint: 'Popular UI library' },
    { label: 'Vue', value: 'vue', hint: 'Progressive framework' },
    { label: 'Svelte', value: 'svelte', hint: 'Compiler-based' },
    { label: 'Angular', value: 'angular', disabled: true }
  ],
  default: 'react'
})

console.log(`Selected: ${framework}`)
Use Up/Down arrows or 1-9 number keys for quick selection

multiselect

Prompt to select multiple options from a list.
async function multiselect<T = string>(
  message: string,
  options: MultiSelectOptions<T>
): Promise<T[]>

Parameters

message
string
required
The prompt message
options
MultiSelectOptions<T>
required

Example

import { multiselect } from '@bunli/runtime/prompt'

const features = await multiselect('Select features:', {
  options: [
    { label: 'TypeScript', value: 'typescript' },
    { label: 'ESLint', value: 'eslint' },
    { label: 'Prettier', value: 'prettier' },
    { label: 'Tests', value: 'tests' }
  ],
  min: 1,
  initialValues: ['typescript']
})

console.log(`Selected: ${features.join(', ')}`)
Press Space to toggle selection, Enter to submit

group

Run multiple prompts in sequence and collect results.
async function group<T extends Record<string, () => Promise<unknown>>>(
  steps: T
): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>> }>

Example

import { group, text, select, confirm } from '@bunli/runtime/prompt'

const result = await group({
  name: () => text('Project name?'),
  framework: () => select('Framework?', {
    options: [
      { label: 'React', value: 'react' },
      { label: 'Vue', value: 'vue' }
    ]
  }),
  install: () => confirm('Install dependencies?')
})

// result is typed as:
// {
//   name: string
//   framework: string
//   install: boolean
// }

spinner

Create an animated spinner for long-running operations.
function spinner(options?: SpinnerOptions | string): Spinner

Parameters

options
SpinnerOptions | string
String shorthand for initial text, or options object

Methods

start
(text?: string) => void
Start the spinner with optional text
stop
(text?: string) => void
Stop the spinner with optional final text
succeed
(text?: string) => void
Stop with success indicator
fail
(text?: string) => void
Stop with error indicator
warn
(text?: string) => void
Stop with warning indicator
info
(text?: string) => void
Stop with info indicator
update
(text: string) => void
Update spinner text while running

Example

import { spinner } from '@bunli/runtime/prompt'

const s = spinner('Installing dependencies...')
s.start()

try {
  await installDeps()
  s.succeed('Dependencies installed!')
} catch (error) {
  s.fail('Installation failed')
}

// With timer
const s2 = spinner({
  text: 'Building project...',
  showTimer: true,
  animation: 'braille'
})
s2.start()
await build()
s2.succeed()

Output Functions

intro

Display an intro message.
function intro(message: string): void

outro

Display an outro message.
function outro(message: string): void

note

Display a note box with optional title.
function note(message: string, title?: string): void

log

Structured logging functions.
const log = {
  info(message: string): void
  success(message: string): void
  warn(message: string): void
  error(message: string): void
}

cancel

Display a cancellation message.
function cancel(message?: string): void

Example

import { intro, outro, note, log } from '@bunli/runtime/prompt'

intro('Project Setup')

log.info('Starting installation...')
log.success('Installation complete')

note(`
Project: my-app
Framework: React
TypeScript: Yes
`, 'Configuration')

outro('Setup complete!')

Cancellation

All prompts can be cancelled with Esc or Ctrl+C, returning the CANCEL symbol.

isCancel

Check if a value is the cancel symbol.
function isCancel(value: unknown): value is Cancel

Example

import { text, isCancel } from '@bunli/runtime/prompt'

const name = await text('Your name?')

if (isCancel(name)) {
  console.log('Cancelled!')
  process.exit(0)
}

console.log(`Hello, ${name}!`)

promptOrExit

Automatically exit if prompt is cancelled.
import { text, promptOrExit } from '@bunli/runtime/prompt'

const name = promptOrExit(
  await text('Your name?'),
  'Setup cancelled'
)

// name is guaranteed to be string, not Cancel

Best Practices

Non-Interactive Environments

Always provide fallbackValue for CI/CD pipelines and non-TTY environments

Validation

Use schema for complex validation and type coercion (Zod, Valibot, etc.)

User Experience

  • Use clear, concise messages
  • Provide sensible default values
  • Add hints for complex options
  • Group related prompts together

Error Handling

  • Check for cancellation with isCancel()
  • Provide clear validation error messages
  • Use spinners for long operations

Build docs developers (and LLMs) love