@adfrolic/claude-code-integration
v0.1.1
Published
Claude Code integration: a statusline command showing sponsored messages — see README.md.
Readme
Claude Code integration
Status: implemented (Phase 6 of docs/architecture.md's build order).
A small Node CLI (adfrolic-claude-code) with two commands:
adfrolic-claude-code auth— run once, interactively, to sign in (device-authorization flow — see below). Writes~/.adfrolic/session.json(0600).adfrolic-claude-code statusline— the command Claude Code'sstatusLine.commandsetting invokes on every refresh. Fast, non-interactive, and fails silent (prints nothing, exits 0) on any error, slow network, or missing session — seesrc/statuslineCommand.ts's doc comment.
The bin is named adfrolic-claude-code, not adfrolic, so it never
collides with the universal installer package (adfrolic on npm, see
installer/README.md) if both happen to be installed globally at once —
most developers only need one or the other (see "Which install path should
I use?" below).
Setup
npm install -g @adfrolic/claude-code-integration # once published
adfrolic-claude-code auth # once, interactivelyThen add to Claude Code's settings.json:
{
"statusLine": {
"type": "command",
"command": "adfrolic-claude-code statusline"
}
}Which install path should I use?
There are two independent ways to get the Claude Code statusline working, and they intentionally don't share a runtime — installing (or updating) one never silently touches the other:
npx adfrolic install --host=claude-code(seeinstaller/README.md) — the quickest path: one universal CLI detects Claude Code is present and wires~/.claude/settings.jsonitself, using its own bundled, dependency- free statusline/auth implementation (installer/lib/statusline.js). Good default if you also use other AdFrolic-supported editors and want one tool to set all of them up.- This package (
@adfrolic/claude-code-integration) — a dedicated, separately-versioned package with its own more extensive test suite (authStore.test.ts,statuslineCommand.test.ts) and a couple of extra hardening details (a hard 2-second render deadline, explicit stdin draining). Good default if you only use Claude Code and want the most thoroughly-tested implementation, or if you're scripting/pinning an exact version outside the universal installer's release cadence.
Both talk to the same backend (GET /v1/ads/serve, placement
claude_code.wait) and are functionally equivalent from the backend's
point of view — pick whichever install path suits you and don't install
both globally at once (Claude Code's statusLine.command only runs one).
Why the statusline, not the thinking spinner
Checked early (before implementation started), because the whole
claude_code.wait placement concept originally assumed the spinner text
("Baking…", "Simmering…", "Finagling…", etc.) could be swapped per-request.
It can't:
- No hook fires during the thinking/spinner phase — Claude Code's hook events cover prompts, tool calls, session lifecycle, and message display, but nothing while a turn is in progress.
- No plugin or MCP surface can inject into the spinner at runtime.
- The spinner's text is only customisable statically, once, via settings
(
spinnerVerbs/spinnerTipsOverride-style keys) — fixed at session start, not something a backend can serve dynamically per request. That rules it out for an ad network, which needs a fresh creative per impression.
The actual integration point is the statusline — a separate,
genuinely-scriptable line rendered below the prompt input (not inside the
spinner). It runs a developer-provided command on refresh (event-driven,
plus optional timer refresh), with full freedom over its output — adfrolic
statusline calls GET /v1/ads/serve (through a local cache, so most
refreshes cost no network round trip — see below) and renders the returned
creative. Same "shown at a natural pause" intent as claude_code.wait,
different exact spot on screen.
Device-authorization sign-in
A CLI has no browser-redirect OAuth surface and no secure place to hold a
Firebase client session, so it uses the same device-authorization flow as
the VS Code extension (extensions/vscode/src/deviceAuthFlow.ts) — modelled
on gh auth login: adfrolic-claude-code auth gets a short user code, opens the web
app's confirmation page, and polls until the developer approves it there.
See apps/api/src/routes/deviceAuth.ts for the server side and its
phishing-risk mitigation notes.
Caching and the impression lifecycle
adfrolic-claude-code statusline may be invoked far more often than once per ad — it
caches the current creative (~/.adfrolic/statusline-cache.json) for five
minutes so most invocations render from disk with no network call at all.
On the invocation where the cached ad's minimum display duration has
elapsed, it fires the qualify call and marks the cache so it's never sent
twice. This trades a small chance of losing one qualification (if the
process is killed mid-request) for never blocking/slowing down the
statusline on every single refresh — see src/statuslineCommand.ts.
Known limitation: no click-through
Unlike the VS Code extension's status bar/sidebar, the terminal statusline here is display-only — no keyboard/mouse target within Claude Code's own UI to trigger a click. (OSC 8 terminal hyperlinks were considered and rejected: support is inconsistent enough across terminals that a garbled escape sequence in the statusline was judged a worse failure mode than no click-through at all.) Revisit if Claude Code adds an interactive statusline segment.
Constraints (apply regardless of mechanism chosen)
- No source code, prompts, AI responses, or terminal contents are ever sent
to the backend — see
docs/privacy.md. - All advertising/accounting logic (ad selection, budgeting, fraud,
billing) stays server-side; this client only requests, displays, and
reports on adverts via
@adfrolic/ad-client, the same shared SDK used by the VS Code extension. - A failure anywhere in this integration must never disrupt a Claude Code session (spec section 77) — always fail silently.
