Coming from clack
A clack (@clack/prompts) alternative with two ways across: caique/clack, clack's prompts under clack's names on caique's own engine, graded by clack's own suite; or caique's flag-first prompts, where a non-TTY caller gets an error naming the flag instead of a hang.
caique is a clack alternative built on one rule: a prompt is a flag first. There are two ways across, and you can use both in one program:
- Change the import.
caique/clackexports@clack/prompts' prompts, writers, glyphs and settings under clack's own names and call signatures, built on caique's keypress loop with no dependency outside the burgee family. Your prompts keep working. - Move a prompt to a flag. caique's own API takes a prompt as a spec attached to a flag, so an agent answers it on the command line and a non-TTY run fails naming the flag instead of waiting. That is a rewrite, one prompt at a time, and it is what the rest of caique is for.
Change the import
// before
import { cancel, confirm, group, intro, isCancel, outro, select, spinner, text } from '@clack/prompts';
// after
import { cancel, confirm, group, intro, isCancel, outro, select, spinner, text } from 'caique/clack';clack (@clack/prompts) | caique/clack |
|---|---|
text, password, confirm, multiline, date, path | the same, same options and answers |
select, selectKey, multiselect, groupMultiselect | the same |
autocomplete, autocompleteMultiselect | the same |
intro, outro, cancel, note, log, stream | the same |
spinner, tasks, group | the same — the spinner animates only on a terminal outside CI, and prints each message once anywhere else |
isCancel, CANCEL_SYMBOL, limitOptions | the same |
settings, updateSettings | caique's own: they govern caique/clack's prompts, not @clack/core's |
S_*, symbol, symbolBar, unicode, unicodeOr, isTTY, isCI, formatInstructionFooter, SELECT_INSTRUCTIONS, MULTISELECT_INSTRUCTIONS | the same |
box, progress, taskLog | not built — no graded case reaches them |
Two things differ, on purpose. The frames are clack's layout — the guide, the state
glyph, the dimmed answer — and not its exact bytes, so a test of yours that snapshots
clack's output will see different escape sequences. And updateSettings must be imported
from caique/clack: one imported from @clack/core changes a package caique does not
depend on, and our prompts cannot see it.
Is caique compatible with clack?
caique/clack is graded against @clack/prompts' own suite, vendored at 1.8.1 and unmodified
apart from the import specifier. 17 of its 19 files — 289 of its 444 assertions — are
snapshots of clack's exact frames and are subtracted by name; what is left grades behaviour:
| passing | rate | |
|---|---|---|
caique/clack | 16 / 16 | 100.0% |
@clack/prompts itself (control) | 16 / 16 | 100.0% |
From Compatibility, which npm run compat:page generates from the
oracle's last run; that page is the authority. The 16 are all of limit-options.test.ts and
two of guide.test.ts, which render all twelve prompts and check the guide with and without
withGuide. A third guide.test.ts case is subtracted by its exact title. It sets
updateSettings({ withGuide: false }) from @clack/core and asserts our prompts obey it.
That is the case above, and it grades @clack/core's state, not @clack/prompts. The
row is level, so burgee migrate rewrites @clack/prompts to caique/clack. It leaves on
clack a file that imports box, progress or taskLog. It never rewrites @clack/core, and
it refuses a clack file that also imports @clack/core (reason sibling-state) rather than
move its prompts away from the settings that file sets. The refusal's fix reads:
import { updateSettings } from 'caique/clack' instead of '@clack/core', then re-run burgee migrate.
Move a prompt to a flag
clack (@clack/prompts) | caique |
|---|---|
text, password, path | ask({ kind: 'text' | 'password' | 'path', message, initial, validate }, io) from caique/ask. password is read without echo by createIo() from caique/terminal |
confirm | ask({ kind: 'confirm', message, initial }, io). With --yes passed to decide(), it is answered true without asking |
select, multiselect | ask({ kind: 'select' | 'multiselect', message, choices: [{ value, label, hint }] }, io) asks by number. askList(spec, io, multi) from caique/raw adds arrow keys where there is a terminal |
group | resolvePrompts({ options, values, runtime, flags, io }) from caique/binding, which asks in declaration order and stops at the first refusal |
isCancel | check the result: ask() returns { ok: false, reason: 'cancelled' }, and resolvePrompts() returns a failure with code CANCELLED |
A text and a confirm, before and after
// clack
import { cancel, confirm, isCancel, text } from '@clack/prompts';
const name = await text({ message: 'Project name?' });
if (isCancel(name)) { cancel('Cancelled.'); process.exit(0); }
const git = await confirm({ message: 'Initialise git?' });
if (isCancel(git)) { cancel('Cancelled.'); process.exit(0); }// caique
import { processRuntime } from 'caique';
import { resolvePrompts } from 'caique/binding';
import { createIo } from 'caique/terminal';
const io = createIo();
const { values, failure } = await resolvePrompts({
options: {
name: { required: true, prompt: { kind: 'text', message: 'Project name?' } },
git: { required: true, prompt: { kind: 'confirm', message: 'Initialise git?', initial: true } },
},
values: supplied, // what --name, --git, env and config already answered
runtime: processRuntime(),
flags: { json, yes }, // the run's --json and --yes
io,
});
io.close();
if (failure) {
console.error(`${failure.message}\n${failure.fix ?? ''}`);
process.exit(failure.code === 'CANCELLED' ? 0 : 2);
}The two questions are the same, but the second program can also be answered without a
person. An option supplied already answers is never asked. --yes answers the confirm.
With no terminal, or under CI, the first unanswered option comes back as a failure,
--name is required when there is no terminal, rather than a wait. One difference to carry
over deliberately: caique asks only for options marked required, unless the run passed
--interactive.
What you gain over clack
A prompt that waits for a human who is not there is a hang, and a hung agent is a failed
task. caique's decide() rules on every prompt before anything is drawn:
- An agent answers before it is asked. A value from any source — flag, env, config — means the prompt is skipped.
--jsonnever prompts. A missing value is an error,--name is required under --json, with a fix naming the flag.- No terminal is never a prompt. Without a terminal on stdin, or with
CIset, the verdict is an error naming the flag and the fix, never a wait on stdin.--interactivecannot override that; it can only ask for more where a terminal exists. - Checked exhaustively. All 256 combinations of value, kind, TTY, CI,
--json,--yes,--interactiveand required are generated and checked indecide.test.ts. - Line mode is the floor. No raw mode, no cursor movement, no redraw, so the accessible rendering and the terminal rendering are the same bytes. A closed stdin is a cancellation, not an empty answer, and invalid input is re-asked five times, then given up.
caique/clack is 62,238 B against @clack/prompts' 101,684 B across six packages, and a
program that uses caique's own API never loads a byte of it; caique reaches no package
outside the burgee family.
When to switch from clack
- Your CLI runs in CI, in a container or under an agent, and a prompt there must fail fast with the flag that fixes it — move those prompts to flags.
- You want off clack's dependency tree today without touching your prompts — change the
import to
caique/clack. - Stay on clack if you snapshot its exact frames, or need
box,progressortaskLog.
The rule, the widgets and the inquirer path are on caique.
Coming from inquirer
An inquirer alternative for prompts that never hang: caique/inquirer carries @inquirer/core's surface, graded 46 / 46 by its own suite — and caique's prompts are flags first, so a non-TTY caller gets an error naming the flag instead of a hang.
Prompts an agent can answer
Declare each prompt on the option it fills, so an agent answers on the command line, --json refuses instead of asking, and the refusal tells the agent exactly what to pass.