Why caique
caique against inquirer and clack, one capability per row, every cell linked to the test, grade or source that proves it.
inquirer and clack draw good prompts for a person at a terminal. Neither asks whether a person
is there. A CLI run by an agent, in CI or with its input piped either waits on a stream nobody
will write to, or fails without saying what to pass instead. caique decides that first: a
prompt is a flag, a value from anywhere is never asked for, and with nobody to ask, a missing
value is an error that names the flag. Its two drop-in paths, caique/inquirer and
caique/clack, are graded by each incumbent's own suite.
The table below is the whole comparison. Every mark links to its evidence: a test in this
repository for ours, and for theirs the source of the exact version compat-oracle grades, or
that package's own test suite. scripts/capabilities-lock.test.ts fails the build when a cited
test no longer contains the title it is cited for.
✓ yes · ◐ partial, with what is missing · ✗ no · — does not apply. Every mark links to its evidence: our test or grade, or the incumbent’s source at the version compat-oracle grades.
| Capability | caique | inquirer-core | clack |
|---|---|---|---|
| Never hangs | |||
No terminal and no value is a refusal, not a waitdecide() rules on every prompt before anything is drawn, and with nobody on stdin a missing value is a usage error rather than a prompt waiting for a person who is not there. | caique: yes | inquirer-core: noreads whatever input stream it is given; nothing checks whether a person is on it | clack: noa prompt reads its input stream whether or not it is a terminal; only raw mode is conditional |
The refusal names the flag to passA prompt is bound to a flag, so the error says --name is required when there is no terminal and how to answer it, which an agent can act on. | caique: yes | inquirer-core: noa prompt has no flag behind it to name | clack: noa prompt has no flag behind it to name |
An agent on a terminal is refused, not promptedUnder CLAUDECODE, CURSOR_AGENT, CODEX_THREAD_ID, GEMINI_CLI or AI_AGENT there is a terminal and no person, so decide() asks roundel's interactive() and refuses, naming the flag, where asking only whether stdin is a terminal would wait forever. | caique: yes | inquirer-core: noreads no agent variable; a prompt on a terminal waits for keys | clack: noreads no agent variable; a prompt on a terminal waits for keys |
--json and CI never promptUnder --json, or with CI set, a missing value is refused even on a terminal, because a machine is reading and nobody is there to type. | caique: yes | inquirer-core: noreads no CI variable and knows no --json | clack: noreads CI to stop its spinner and progress animating, not to stop a prompt |
| The terminal as it was found | |||
| Raw mode is turned off only if the prompt turned it onA program that already had its terminal in raw mode keeps it when a prompt ends, and a signal mid-prompt still turns off the raw mode the prompt did turn on, through closeout. | caique: yes | inquirer-core: noruns on node:readline with terminal: true, whose close() turns raw mode off whatever it was before | clack: noclose() calls setRawMode(input, false) unconditionally |
| Extending it | |||
| Custom prompt kinds through a validated plugin registryA widget for a kind outside the six built-ins is registered by name, and one without a static projection is refused with the code flagstaff and paratext use. | caique: yes | inquirer-core: partialpartiala custom prompt is a function built with createPrompt; there is no registry, and nothing asks it for a text form | clack: partialpartiala custom prompt subclasses Prompt; there is no registry, and nothing asks it for a text form |
A checker for widget plugins before they shipnpx caique check ./rating.mjs validates a plugin against the family schema and exits 1 with a code and a fix when it is refused. | caique: yes | inquirer-core: does not applytakes no plugins, so has nothing to check | clack: does not applytakes no plugins, so has nothing to check |
| Weight | |||
| Every runtime dependency from the same repositoryInstalling it adds closeout, linegauge and paratext, released from this repository through one pipeline, and nothing else. | caique: yes | inquirer-core: noseven dependencies, among them signal-exit, mute-stream and cli-width | clack: no@clack/core, fast-string-width, fast-wrap-ansi and sisteransi |
| Compatibility | |||
Passes @inquirer/core's own test suitecaique/inquirer — createPrompt, the hooks and the keypress helpers — is graded by @inquirer/core 12.0.3's own tests, unedited, rendered through a headless terminal. | caique: yes41 / 41 of its own tests | inquirer-core: yesits own suite, the control run | clack: does not applya different API |
Passes clack's own test suitecaique/clack is graded by the behavioural cases of @clack/prompts 1.8.1's own suite, with the snapshots of clack's exact frames subtracted by name. | caique: partialpartial16 / 17 of its own tests | inquirer-core: does not applya different API | clack: yesits own suite, the control run |
Never hangs
No terminal and no value is a refusal, not a wait
decide()rules on every prompt before anything is drawn, and with nobody on stdin a missing value is a usage error rather than a prompt waiting for a person who is not there.The refusal names the flag to pass
A prompt is bound to a flag, so the error says
--name is required when there is no terminaland how to answer it, which an agent can act on.caique- caique: yes
An agent on a terminal is refused, not prompted
Under
CLAUDECODE,CURSOR_AGENT,CODEX_THREAD_ID,GEMINI_CLIorAI_AGENTthere is a terminal and no person, sodecide()asks roundel'sinteractive()and refuses, naming the flag, where asking only whether stdin is a terminal would wait forever.caique- caique: yes
--jsonandCInever promptUnder
--json, or withCIset, a missing value is refused even on a terminal, because a machine is reading and nobody is there to type.caique- caique: yes
The terminal as it was found
Raw mode is turned off only if the prompt turned it on
A program that already had its terminal in raw mode keeps it when a prompt ends, and a signal mid-prompt still turns off the raw mode the prompt did turn on, through closeout.
Extending it
Custom prompt kinds through a validated plugin registry
A widget for a kind outside the six built-ins is registered by name, and one without a static projection is refused with the code flagstaff and paratext use.
caique- caique: yes
A checker for widget plugins before they ship
npx caique check ./rating.mjsvalidates a plugin against the family schema and exits 1 with a code and a fix when it is refused.caique- caique: yes
Weight
Every runtime dependency from the same repository
Installing it adds closeout, linegauge and paratext, released from this repository through one pipeline, and nothing else.
caique- caique: yes
Compatibility
Passes @inquirer/core's own test suite
caique/inquirer—createPrompt, the hooks and the keypress helpers — is graded by @inquirer/core 12.0.3's own tests, unedited, rendered through a headless terminal.inquirer-core- inquirer-core: yesits own suite, the control run
Passes clack's own test suite
caique/clackis graded by the behavioural cases of @clack/prompts 1.8.1's own suite, with the snapshots of clack's exact frames subtracted by name.inquirer-core- inquirer-core: does not applya different API
Reading it
- The inquirer column is
inquirer-core,@inquirer/core12.0.3: the loop every inquirer prompt runs on, and what compat-oracle grades for inquirer. The legacyinquirer.prompt([...])API is out of scope, and its tarball ships no tests to grade. - The clack column is
@clack/prompts1.8.1 and the@clack/core1.5.1 it depends on. - Neither is installed in this repository, so their cells link to the graded version's
source on jsDelivr (and, for inquirer's raw mode, to Node's
readline, which it runs on). The lock does not re-read a URL cell; each was checked against that file by hand. The compatibility rows cite each one's vendored suite, which the lock reads every run. - clack's compatibility is partial on purpose. One case sets
updateSettings({ withGuide: false })imported from@clack/coreand asserts caique's prompts obey it — module state in a package caique does not depend on (Compatibility).
What is not in the table
A row goes in only when every cell of it can be proved. These were left out:
- A cursor restored when the process is signalled mid-prompt. caique's
raw.test.tsproves it forSIGINTandSIGTERM. inquirer registers a signal-exit handler whose cleanup runs in a promise'sfinally; whether that runs before the process exits was not established, so there is no row rather than a guessed cell. - An accessible rendering. caique's line mode is its accessible mode; clack 1.8 also has an
accessibleoption and readsACCESSIBLE. No test here compares them. - Weight in bytes. The per-subpath figures are asserted by
weight.test.tsand published on Benchmarks.
Widget plugins
A plugin adds a prompt kind beyond the six as a widget with a required static projection; register() validates it, projectionOf() draws any kind, and npx caique check validates a plugin before it ships.
Compatibility
How caique's two drop-ins are graded — @inquirer/core's own suite at 41 / 41, and @clack/prompts' behavioural cases at 16 / 17 — and the differences that remain.