All docs
Overview · Your first CLI

Your first CLI

This page walks through scaffolding a new CLI, prompting the user, running an async task with a spinner, and printing a structured error. About five minutes end-to-end.

Scaffold

sh
npx caret-cli init deploy-cli
cd deploy-cli
npm install

init writes package.json, tsconfig.json, src/index.ts, and a caret.md AI-instruction file. The starter src/index.ts is a working CLI you can run with npm run dev immediately.

Add the components you need

The starter ships with no components yet — pull only what you use.

sh
npx caret-cli add prompt
npx caret-cli add spinner
npx caret-cli add error

Files land under caret/. Each add prints the runtime dependencies the component declares — install them once.

Write the deploy command

Replace src/index.ts with this. Three primitives, no provider, no theme object.

src/index.ts
import { prompt, spinner, error, success } from './caret'

async function main() {
  const project = await prompt.text({
    label: 'Project name',
    validate: (v) => v.length > 0 ? null : 'Required',
  })

  const env = await prompt.select({
    label: 'Environment',
    options: [
      { value: 'staging', label: 'Staging' },
      { value: 'prod',    label: 'Production' },
    ],
    default: 'staging',
  })

  await spinner(`Deploying ${project} to ${env}`, async () => {
    await fakeDeploy(env)
  }, { onSuccess: `${project} is live` })

  success('Done')
}

async function fakeDeploy(env: string) {
  await new Promise((r) => setTimeout(r, 1500))
  if (env === 'prod' && Math.random() < 0.2) {
    throw new Error('Health check failed')
  }
}

main().catch((e) => {
  error('Deploy failed', {
    body: e instanceof Error ? e.message : String(e),
    hint: 'Check the deploy logs and re-run.',
    see: 'https://caret.dev/docs/cli',
  })
  process.exit(1)
})

Run it

sh
npm run dev

You'll see a prompt for the project name, then a select for the environment, then a braille spinner that resolves into either a green or — about one in five times in production — a Rust-style error block with hint and see.

No theme, no provider
The CLI you just wrote has zero configuration. Caret reads the active terminal capabilities, picks the right symbol set, and respects NO_COLOR. You don't enable any of this — it's the manifesto's "Correctness is not opt-in" rule.

Next