An interactive clarify-before-you-code gate for coding agents. The agent computes a branching decision tree; clarinator serves it as a single-page UI on loopback, blocks until you answer (revealing follow-up questions as you pick), then prints the structured result to stdout for the agent to read.
Built to be run on demand — no install step in your project:
bunx est7/clarinator clarity up --input payload.jsonRequires bun on the host (curl -fsSL https://bun.sh/install | bash).
- The agent writes a
ClarityPayload(a decision page, see below) to a JSON file. clarinator clarity up --input payload.jsonvalidates it, serves the prebuilt UI athttp://127.0.0.1:<random>/, and opens your browser.- You answer. Each pick may reveal child questions gated by
show_if; the page resolves the active set client-side — no round-trips. - You hit Send to agent. The server prints the result to stdout and exits.
For multi-page clarity flows, include flow metadata. clarity up starts the
session and waits for page 1. The browser only submits the current page's
answers; the agent reads stdout, then decides whether another page is needed. If
so, it generates the next page and runs:
bunx est7/clarinator clarity continue --input next-page.jsonWhen the flow is complete or should be cancelled, close the session:
bunx est7/clarinator clarity downExit codes: 0 submitted · 2 usage/validation error · 3 cancelled · 4 timeout.
clarinator --version
clarinator clarity up --input <payload.json> [--out <result.json>]
[--timeout-ms 1800000] [--locale zh] [--no-open]
clarinator clarity continue --input <payload.json> [--out <result.json>]
clarinator clarity down
clarinator plan up --input <payload.json> [--out <result.json>]
clarinator plan down
--input defaults to stdin. The result is always printed to stdout; --out
additionally writes it to a file. UI chrome is localized via --locale
(en / zh, falls back to the browser language).
- A decision with
show_ifis active only when the referenced (earlier) decision's answer is one ofin[]. Inactive decisions are hidden and excluded from the result.show_if.decisionmust reference an earlier decision → the tree is acyclic by construction. - Exactly one option per decision must have
recommended: true. allow_custom: trueadds a free-text answer. A custom answer never satisfies an option-idshow_ifguard — if a free-text answer needs follow-ups the agent could not foresee, the agent runs a second clarinator round.
{
"mode": "clarity",
"title": "Login PRD",
"action": "continue",
"sessionId": "login-prd",
"pageId": "entry-path",
"result": [
{ "decisionId": "auth-method", "question": "…", "optionId": "magic-link", "answer": "Magic link only", "custom": false }
]
}bun install
bun run dev # vite dev server (hot reload)
bun run check # typecheck + vitest (logic) + build + bun test (server)src/reducer.ts+src/validate.ts— the pure branching engine (vitest).server/primitive.ts—startBlockingSingleSubmitServer, a domain-free loopback blocking server (reused by futureplanmode).bin/clarinator.ts— the CLI.dist/app.html— the committed prebuilt single-file UI. Rebuild and commit it wheneversrc/changes (bun run build).
Step 2 of the SOP, reusing the same blocking primitive. The agent writes a
PlanPayload ({ title, subtitle?, plan } where plan is Markdown); clarinator
renders it into commentable blocks. You attach inline comments per block, add
overall feedback, and either Approve or Request changes.
bunx est7/clarinator plan up --input plan.jsonResult:
{
"mode": "plan",
"title": "Login flow — implementation plan",
"decision": "revise",
"annotations": [
{ "blockIndex": 4, "quote": "## Risks", "comment": "also rate-limit /auth/request" }
],
"generalFeedback": "tighten the token TTL"
}Because the plan is derived from the agent's reasoning after clarity, Step 1 and Step 2 are necessarily separate invocations: clarity blocks → exits → the agent synthesizes the plan → plan blocks → exits. The agent orchestrates the handoff.
{ "title": "Login PRD", "subtitle": "v1 · B2C", "context": "Email-first, magic-link primary. TTL / session model undecided.", "decisions": [ { "id": "auth-method", "question": "Which auth methods for v1?", "recommendation_reason": "Spec twice says 'low friction' + B2C — magic link hits both.", "allow_custom": true, "options": [ { "id": "magic-link", "label": "Magic link only", "recommended": true, "reason": "Lowest friction." }, { "id": "password", "label": "Password + magic link", "recommended": false } ] }, { "id": "password-strength", "question": "Password policy?", "recommendation_reason": "Only relevant once passwords are in scope.", "show_if": { "decision": "auth-method", "in": ["password"] }, "options": [ { "id": "lenient", "label": "Lenient", "recommended": true }, { "id": "strict", "label": "Strict", "recommended": false } ] } ] }