# Why caique

> caique against inquirer and clack, one capability per row, every cell linked to the test, grade or source that proves it.

Source: https://caique.interlace.tools/docs/why-caique

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 (what is missing is said) · ✗ no · — does not apply. Every cell links to its evidence: our test or grade, or the incumbent’s source at the version compat-oracle grades.

### Never hangs

| Capability | **caique** | inquirer-core | clack |
| :-- | :-- | :-- | :-- |
| **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. | [✓](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/decide.test.ts) | [✗ reads whatever input stream it is given; nothing checks whether a person is on it](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/dist/lib/create-prompt.js) | [✗ a prompt reads its input stream whether or not it is a terminal; only raw mode is conditional](https://cdn.jsdelivr.net/npm/@clack/core@1.5.1/dist/index.mjs) |
| **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 terminal` and how to answer it, which an agent can act on. | [✓](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/shape.test.ts) | [✗ a prompt has no flag behind it to name](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/dist/lib/create-prompt.js) | [✗ a prompt has no flag behind it to name](https://cdn.jsdelivr.net/npm/@clack/core@1.5.1/dist/index.mjs) |
| **An agent on a terminal is refused, not prompted** — Under `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. | [✓](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/decide.test.ts) | [✗ reads no agent variable; a prompt on a terminal waits for keys](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/dist/lib/create-prompt.js) | [✗ reads no agent variable; a prompt on a terminal waits for keys](https://cdn.jsdelivr.net/npm/@clack/core@1.5.1/dist/index.mjs) |
| **`--json` and `CI` never prompt** — Under `--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. | [✓](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/decide.test.ts) | [✗ reads no CI variable and knows no --json](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/dist/lib/create-prompt.js) | [✗ reads CI to stop its spinner and progress animating, not to stop a prompt](https://cdn.jsdelivr.net/npm/@clack/prompts@1.8.1/dist/index.mjs) |

### The terminal as it was found

| Capability | **caique** | inquirer-core | clack |
| :-- | :-- | :-- | :-- |
| **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. | [✓](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/raw.test.ts) | [✗ runs on node:readline with terminal: true, whose close() turns raw mode off whatever it was before](https://github.com/nodejs/node/blob/v24.12.0/lib/internal/readline/interface.js) | [✗ close() calls setRawMode(input, false) unconditionally](https://cdn.jsdelivr.net/npm/@clack/core@1.5.1/dist/index.mjs) |

### Extending it

| Capability | **caique** | inquirer-core | clack |
| :-- | :-- | :-- | :-- |
| **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. | [✓](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/plugin.test.ts) | [◐ a custom prompt is a function built with createPrompt; there is no registry, and nothing asks it for a text form](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/dist/lib/create-prompt.js) | [◐ a custom prompt subclasses Prompt; there is no registry, and nothing asks it for a text form](https://cdn.jsdelivr.net/npm/@clack/core@1.5.1/dist/index.mjs) |
| **A checker for widget plugins before they ship** — `npx caique check ./rating.mjs` validates a plugin against the family schema and exits 1 with a code and a fix when it is refused. | [✓](https://github.com/ofri-peretz/burgee/blob/main/scripts/plugin-check-lock.test.ts) | [— takes no plugins, so has nothing to check](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/package.json) | [— takes no plugins, so has nothing to check](https://cdn.jsdelivr.net/npm/@clack/prompts@1.8.1/package.json) |

### Weight

| Capability | **caique** | inquirer-core | clack |
| :-- | :-- | :-- | :-- |
| **Every runtime dependency from the same repository** — Installing it adds closeout, linegauge and paratext, released from this repository through one pipeline, and nothing else. | [✓](https://github.com/ofri-peretz/burgee/blob/main/scripts/package-shape-lock.test.ts) | [✗ seven dependencies, among them signal-exit, mute-stream and cli-width](https://cdn.jsdelivr.net/npm/@inquirer/core@12.0.3/package.json) | [✗ @clack/core, fast-string-width, fast-wrap-ansi and sisteransi](https://cdn.jsdelivr.net/npm/@clack/prompts@1.8.1/package.json) |

### Compatibility

| Capability | **caique** | inquirer-core | clack |
| :-- | :-- | :-- | :-- |
| **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. | [✓ 41 / 41 of its own tests](https://github.com/ofri-peretz/burgee/blob/main/packages/compat-oracle/baseline/inquirer-core.json) | [✓ its own suite, the control run](https://github.com/ofri-peretz/burgee/blob/main/packages/compat-oracle/vendor/inquirer-core/packages/core/core.test.ts) | [— a different API](https://github.com/ofri-peretz/burgee/blob/main/packages/compat-oracle/vendor/clack/packages/prompts/test/limit-options.test.ts) |
| **Passes clack's own test suite** — `caique/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. | [◐ 16 / 17 of its own tests](https://github.com/ofri-peretz/burgee/blob/main/packages/compat-oracle/baseline/clack.json) | [— a different API](https://github.com/ofri-peretz/burgee/blob/main/packages/compat-oracle/vendor/inquirer-core/packages/core/core.test.ts) | [✓ its own suite, the control run](https://github.com/ofri-peretz/burgee/blob/main/packages/compat-oracle/vendor/clack/packages/prompts/test/limit-options.test.ts) |

## Reading it

- **The inquirer column is `inquirer-core`, `@inquirer/core` 12.0.3**: the loop every inquirer
  prompt runs on, and what compat-oracle grades for inquirer. The legacy `inquirer.prompt([...])` API
  is out of scope, and its tarball ships no tests to grade.
- **The clack column is `@clack/prompts` 1.8.1 and the `@clack/core` 1.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/core` and asserts caique's prompts obey it — module state in a
  package caique does not depend on ([Compatibility](/docs/drop-ins)).

## 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.ts` proves
  it for `SIGINT` and `SIGTERM`. inquirer registers a signal-exit handler whose cleanup runs in a
  promise's `finally`; 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
  `accessible` option and reads `ACCESSIBLE`. No test here compares them.
- **Weight in bytes.** The per-subpath figures are asserted by
  [`weight.test.ts`](https://github.com/ofri-peretz/burgee/blob/main/packages/caique/src/weight.test.ts)
  and published on [Benchmarks](https://burgee.interlace.tools/docs/benchmarks).
