idleads
v0.4.5
Published
Monetize the "thinking…" moment in Claude Code. Installs a tasteful sponsor line via official statusLine/spinnerVerbs/hooks only — never patches your editor. 70% revenue share, provably-real impressions.
Maintainers
Readme
idleads
The IdleAds terminal client. It turns the "thinking…" moment in Claude Code into a single, tasteful, clickable sponsored line — and makes those impressions provably measurable — using official extension points only. It never patches Claude Code, never weakens its security, and leaves zero trace on uninstall.
Full specs: idleads.dev
Install
idleads login # device-code Google sign-in → ~/.idleads/auth.json (0600)
idleads install # write runtime files + wire Claude Code settings/hooks
# restart Claude Code to pick up the new settingsYou can install before login — it runs in demo mode (ads serve, but you
earn nothing) until you sign in. Re-running install is safe (idempotent).
Flags / env:
idleads install --sponsor-marker— prefix spinner verbs withAd ·for devs who stream or screen-share. Default off.IDLEADS_BASE_URL=…— override the gateway base URL (defaulthttps://idleads.dev).
Uninstall (zero-trace)
idleads uninstallRemoves only our entries: the statusLine (only if it points at our
script), our spinnerVerbs, the hook groups whose command path contains
/.idleads/, and our Codex notify line (only if it's ours). It then deletes
the entire ~/.idleads/ directory. Your own statusLine, spinnerVerbs, hooks,
and every other key in ~/.claude/settings.json are left exactly as they were.
The hooks key is removed only if it becomes empty.
Other commands
idleads status— show whether you're signed in, the installed state, the currently cached ad, and how many hook records are pending upload.idleads wrap <cmd>— v2 stub. A future PTY row-reservation wrapper for rendering ads in any terminal CLI (e.g. Codex) with real focus-based viewability (TERMINAL_ADS_SPEC §6). Not yet implemented.
What gets written
~/.idleads/
config.json backend URL, poll interval, debug flag
auth.json bearer token + stable device id (chmod 0600)
statusline.mjs render-only status line + detached-flush scheduler
hook-append.sh hot-path hook spooler (POSIX sh, ~1ms, no Node, no network)
hook-append.ps1 Windows twin of the spooler (PowerShell; wired on win32)
flush.mjs uploader: spool → /v1/events, then /v1/serve → cli-ad.json
cli-ad.json cached current ad the status line renders
cli-spinner-ad.json cached current ad the spinner renders (own serve token)
events.ndjson hook spool (truncated by flush; 1MB cap, drop-oldest)
flush.lock single-flusher guard
~/.claude/settings.json ONLY: statusLine, spinnerVerbs, and the 7 hooks
(UserPromptSubmit, Stop, PreToolUse, PostToolUse,
Notification, SessionStart, SessionEnd). Key-scoped.
~/.codex/config.toml ONLY the `notify` key, and only if it was unset.
(POSIX only — skipped on Windows, where the hooks
carry measurement alone.)How measurement works (no daemon)
Claude Code re-runs the statusLine command on conversation-state changes — that
cadence is our scheduler. statusline.mjs only renders (reads cli-ad.json,
prints one OSC 8-hyperlinked line) and, when the spool is stale or the ad is past
TTL, spawns flush.mjs detached (lockfile-guarded). flush.mjs reconstructs
busy intervals from the hook event stream, posts impression_rendered +
view_tick signals to /v1/events, then fetches the next ad from /v1/serve.
The server recomputes eligibility and bills — the client never declares an
impression valid (SPEC §5).
Privacy — what a "tick" contains
This is the one that matters for trust. The hook spool is plaintext on your own
disk (~/.idleads/events.ndjson) — you can read it yourself.
A spooled hook record contains only:
- a wall-clock millisecond timestamp,
- the hook event name (e.g.
PostToolUse), - a tool tag (
claude-code/codex), - the session id (used as the session nonce).
hook-append.sh whitelists each payload down to just session_id and
hook_event_name before it's written (preferring jq when present, with a
dependency-free sed fallback). This is a whitelist, not a blacklist — prompts,
tool inputs/outputs, file paths, cwd, and transcript_path are all discarded,
so a new payload field can never leak by default. On Windows, hook-append.ps1
does the same whitelist (and degrades an unparseable payload to {} rather than
spool it unfiltered).
It never records your prompts, your code, file paths, repository names, or
transcript content — at any version. The only thing uploaded to the gateway are
these timing signals plus the server-issued serve token; the server derives geo
from the request IP (stored only as a salted hash), never from anything the client
sends.
Limitations & notes
- JSONC comments in
~/.claude/settings.jsonare not preserved through an edit. We parse JSONC tolerantly (comments + trailing commas stripped) and re-emit standard 2-space JSON. All data keys are preserved; only comments are lost, and only on a file we actually modify (install/uninstall, and the spinner's live ad-line rotation — see below, write-only-on-change). This is a deliberate trade to keep the CLI dependency-free (no comment-aware JSON writer). If you keep comments in your settings, expect them to be dropped. - Hook stdin schema: we assume Claude Code's hook payload is JSON with at
least
session_idand (optionally)hook_event_name; these are the only two fields we keep — anything else in the payload is dropped before write. The spooler tolerates non-single-line and empty payloads. Event names are tagged by the wrapper; if absent we fall back to the payload'shook_event_name. - Spinner verbs: install seeds a calm, neutral verb set tagged as ours
(
spinnerVerbs.mode = "replace",_idleads: true). The flusher then rotates the live ad line in via Claude Code's settings hot-reload (no restart) — this meansidleadslive-edits~/.claude/settings.jsonon its background flush loop (~60s), write-only-on-change, not just at install/uninstall — and reverts to neutral on any no-fill / killswitch / signed-out window — it never leaves a stale ad line on screen. We only ever touch the slot while it's free or already ours; configure your ownspinnerVerbsand we leave it untouched (and emit no spinner impressions). The spinner bills CPM-only — it's a second surface over the same busy intervals the status line measures. Pass--sponsor-markerto prefix the line withAd ·when you stream/screen-share. - No binary patching, ever. Everything here is an official setting or a standard terminal escape sequence (OSC 8 for the clickable link).
