Seed CLISeed CLI

Prompt

Interactive prompts with full type inference.

bun add @seedcli/prompt

The prompt module provides interactive terminal prompts powered by @inquirer/prompts, with added type inference and a consistent API.

Input

Prompt for text input:

const name = await seed.prompt.input({ message: 'What is your name?' })

// With options
const email = await seed.prompt.input({
  message: 'Email address?',
  default: 'user@example.com',
  validate: (value) => {
    if (!value.includes('@')) return 'Must be a valid email'
    return true
  },
  transformer: (value) => value.toLowerCase().trim(),
})

Input Options

OptionTypeDescription
messagestringPrompt message (required)
defaultstringDefault value
requiredbooleanWhether input is required
validate(value: string) => boolean | stringValidation function
transformer(value: string) => stringTransform before returning

Number

Prompt for a numeric value:

const port = await seed.prompt.number({
  message: 'Port number?',
  default: 3000,
  min: 1,
  max: 65535,
})

Number Options

OptionTypeDescription
messagestringPrompt message (required)
defaultnumberDefault value
minnumberMinimum allowed value
maxnumberMaximum allowed value
stepnumberIncrement step
requiredbooleanWhether input is required
validate(value: number | undefined) => boolean | stringValidation function

Confirm

Yes/no confirmation:

const proceed = await seed.prompt.confirm({ message: 'Deploy to production?' })

const overwrite = await seed.prompt.confirm({
  message: 'File exists. Overwrite?',
  default: false,
})

Password

Masked input for secrets:

const token = await seed.prompt.password({
  message: 'API token?',
  mask: '*',
  validate: (value) => {
    if (value.length < 8) return 'Token must be at least 8 characters'
    return true
  },
})

Select

Single-choice selection:

// Simple string choices
const env = await seed.prompt.select({
  message: 'Target environment?',
  choices: ['development', 'staging', 'production'] as const,
})
// env is typed as 'development' | 'staging' | 'production'

// Labeled choices
const db = await seed.prompt.select({
  message: 'Database?',
  choices: [
    { name: 'PostgreSQL', value: 'postgres' },
    { name: 'MySQL', value: 'mysql' },
    { name: 'SQLite', value: 'sqlite', description: 'File-based, no server needed' },
  ] as const,
})
// db is typed as 'postgres' | 'mysql' | 'sqlite'

Select Options

OptionTypeDescription
messagestringPrompt message (required)
choicesReadonlyArray<Choice<T> | T>Available choices (required)
defaultTPre-selected value
loopbooleanEnable list wrapping

Choice Object

PropertyTypeDescription
namestringDisplay label
valueTValue returned on selection
descriptionstringHelp text shown below
disabledboolean | stringDisable with optional reason

Multiselect

Multiple-choice selection:

const features = await seed.prompt.multiselect({
  message: 'Select features:',
  choices: ['typescript', 'eslint', 'prettier', 'testing'] as const,
  required: true,
})
// features is typed as ReadonlyArray<'typescript' | 'eslint' | 'prettier' | 'testing'>

Multiselect Options

OptionTypeDescription
messagestringPrompt message (required)
choicesReadonlyArray<Choice<T> | T>Available choices (required)
defaultReadonlyArray<T>Pre-selected values
requiredbooleanAt least one required
loopbooleanEnable list wrapping
validate(value: ReadonlyArray<T>) => boolean | stringValidation function

Autocomplete

Search and select with typeahead:

const pkg = await seed.prompt.autocomplete({
  message: 'Search packages:',
  source: async (input) => {
    if (!input) return []
    const res = await fetch(`https://registry.npmjs.org/-/v1/search?text=${input}`)
    const data = await res.json()
    return data.objects.map((o: any) => o.package.name)
  },
})

Autocomplete Options

OptionTypeDescription
messagestringPrompt message (required)
source(input: string | undefined) => Promise<ReadonlyArray<Choice<T> | T>>Async source function (required)
defaultTDefault value

Editor

Open the user's editor for long-form input:

const message = await seed.prompt.editor({
  message: 'Commit message:',
  default: 'feat: ',
  postfix: '.md',
})

Editor Options

OptionTypeDescription
messagestringPrompt message (required)
defaultstringInitial editor content
postfixstringTemp file extension (for syntax highlighting)
validate(value: string) => boolean | stringValidation function
waitForUserInputbooleanWait for editor to close

Form

Combine multiple prompts into a single form:

const answers = await seed.prompt.form<{
  name: string
  version: string
  private: boolean
}>([
  { name: 'name', type: 'input', message: 'Project name?' },
  { name: 'version', type: 'input', message: 'Version?', default: '0.0.1' },
  { name: 'private', type: 'confirm', message: 'Private?', default: true },
  {
    name: 'license',
    type: 'select',
    message: 'License?',
    choices: ['MIT', 'Apache-2.0', 'GPL-3.0'],
  },
])

Form Field

PropertyTypeDescription
namestringField name (key in result)
type'input' | 'number' | 'confirm' | 'password' | 'select'Prompt type
messagestringPrompt message
defaultunknownDefault value
choicesReadonlyArray<Choice | string>Choices (for select type)
validate(value: unknown) => boolean | stringValidation function

Cancellation

All prompts throw PromptCancelledError when the user presses Ctrl+C:

import { PromptCancelledError } from '@seedcli/prompt'

try {
  const name = await seed.prompt.input({ message: 'Name?' })
} catch (error) {
  if (error instanceof PromptCancelledError) {
    seed.print.info('Cancelled.')
    return
  }
  throw error
}

On this page