Getting started
Install caique, bind two prompts to flags, and see the same program ask a person on a terminal and refuse — naming the flag — when nobody is there.
caique asks questions a CLI needs answered, with one rule first: a prompt is a flag. A value that arrived any other way — a flag, an environment variable, a config file — is never asked for, and when there is nobody at the terminal to ask, a missing value is an error that names the flag to pass, never a prompt waiting for a person who is not there.
Install
npm install caiqueIt is ESM with a default condition, so require('caique') also works from CommonJS on Node
20.19+ and 22.13+. It depends on closeout, linegauge and paratext, all from this repository,
and on nothing else.
A first program
resolvePrompts() takes the command's options — each may carry a prompt — and the values every
other source already resolved, and asks only what is still missing:
import process from 'node:process';
import { processRuntime } from 'caique';
import { resolvePrompts } from 'caique/binding';
import { createIo } from 'caique/terminal';
const argv = process.argv.slice(2);
const supplied = {};
for (const arg of argv) {
const [, name, value] = /^--([a-z-]+)=(.*)$/u.exec(arg) ?? [];
if (name !== undefined) supplied[name] = value;
}
const io = createIo();
const { values, failure } = await resolvePrompts({
options: {
env: { required: true, prompt: { kind: 'select', message: 'Deploy to?', choices: [{ value: 'staging' }, { value: 'production' }] } },
tag: { required: true, prompt: { kind: 'text', message: 'Which tag?' } },
},
values: supplied,
runtime: processRuntime(),
flags: { json: argv.includes('--json'), yes: argv.includes('--yes') },
io,
});
io.close();
if (failure) {
console.error(`${failure.message}\n fix: ${failure.fix}`);
process.exitCode = 2;
} else {
console.log(`deploying ${values.tag} to ${values.env}`);
}(A real CLI gets supplied from its argument parser; a CLI on
burgee gets this whole pass from its preAction hook.)
In a terminal, it asks both questions. With no terminal on stdin — an agent, a CI job, a pipe — it asks nothing, and the first missing value is an error naming the flag:
--env is required when there is no terminal
fix: pass --env; it would have been asked as "Deploy to?"--tag is required when there is no terminal
fix: pass --tag; it would have been asked as "Which tag?"deploying v1.2.0 to stagingEvery output block on this site is checked: tests/examples.test.ts writes each titled file,
runs the command in the block's title with nothing on stdin, and compares.
The rule
decide() rules on every prompt before anything is drawn. First match wins:
| situation | verdict |
|---|---|
| a value came from any source | not asked |
--json | an error naming the flag: a machine is reading |
--yes, and the prompt is a confirm | answered true, even with no terminal |
no terminal on stdin, CI set, or an agent (CLAUDECODE and the like) | an error naming the flag |
required, or the run passed --interactive | asked |
| anything else | not asked |
Never hangs has the rule in full, and why each row is where it is.
Where next
- Guides: the rule, the six kinds of question, binding prompts to a CLI, the terminal, and widget plugins.
- Why caique: what it does that inquirer and clack do not, cell by cell, with the evidence.
- Coming from inquirer and Coming from clack.
- API reference: every export of every entry point.
caique
The parrot that always answers back, and the boat that goes between ship and shore. Prompts that are flags first, so agents answer before they are asked and non-TTY callers get an error naming the flag, never a hang. Drop-in path for inquirer and clack.
Never hangs
decide() is the rule: a supplied value is never asked for, --json, CI and agents never prompt, --yes answers a confirm, and with nobody on stdin a missing value is an error naming the flag.