Skip to content

Releases: domanski-ai/headroom

v0.4.0 — the menu-bar app

Choose a tag to compare

@domanski-ai domanski-ai released this 14 Jul 22:12

headroom v0.4.0 — the menu-bar app

The fleet now lives in your menu bar: a little head that fills like a battery.

Native menu-bar app (macOS, Tauri)

integrations/menubar — a real click-down panel, not a text menu:

  • The tray icon is the reading. A minifig-style head that fills like a
    battery with your fleet's average 5-hour battery (a current window
    contributes its level, a capped one an honest zero). Drawn at runtime,
    redrawn every minute. A dash means no live reading — and the icon applies
    the same fail-closed freshness contract as every other surface before it
    trusts a single number: exact schema, current freshness, sane clocks,
    live accounts only.
  • It drops down like a system panel. Right edge under the icon, native
    rounded corners, dark panel, no chrome. The liquid-glass widget page
    renders straight onto it.
  • It never trades your data for an error page. Once the widget has
    loaded, a reachability blip keeps the last known view (which greys itself
    out honestly); the "is the tunnel up?" screen only appears on a cold start
    with the server genuinely unreachable.
  • Viewer, not a data path. Loopback-only navigation with a strict
    allowlist, no IPC granted to the page, no telemetry. The one network read
    beyond the page itself is the public /widget.json projection, over the
    same numeric-loopback-only socket as the reachability probe — bounded,
    single-flight, size-capped.

Build it with cargo tauri build (Rust 1.88+); see the
menubar README.

Codex conversation handoff

headroom handoff now moves a Codex conversation between codex homes,
with the same transactional guarantees as the Claude path: global lock,
no-clobber publication, crash-recoverable staging, source never modified.
--headless BATON runs codex exec resume on the target for automation.

The averages headline

The glanceable number everywhere — popover, SwiftBar, Übersicht cards — is
now the fleet's average battery (5h big, 7d compact), not the fullest
single tank. Limited windows count as an honest 0; held/stale readings never
move an average. Model-scoped weekly windows (Opus, Fable, …) now ride along
as extra per-account rows, without ever driving the account state — a scoped
cap doesn't block your other models.

Display integrity (three real-world bugs)

All three were found by actually running the widgets against a live fleet:

  • verified_local slots rendered as held. The display layer held any
    account not verified via the provider — while the router happily routed
    it. Display now accepts exactly the trust states routing accepts.
  • A failed refresh attempt demoted everything. An inline refresh racing
    another collector flashed every surface to "held, never promoted to live"
    with a seconds-old snapshot on disk. Demotion now happens only when the
    snapshot is genuinely outside the freshness window — and usage.json
    carries a non-demoting refresh_attempt_failed diagnostic so a dying
    collector is visible early.
  • 34 milliseconds of clock skew held the entire fleet. The freshness
    guard treated any future evaluated_at as untrustworthy — including
    sub-second NTP drift between server and viewer. It now tolerates 10
    seconds of skew; genuinely future-dated feeds still hold.

A rate-limited rate-limit check no longer strands you

Reported by a user on X: when the usage endpoint itself returns 429,
headroom held the account and refused to launch — "no account has proven
headroom" because the meter was busy. collect now carries the last
verified reading through the throttle window: age-bounded by the same
observation window as every reading, identity re-verified at routing time,
and only when the slot's current credential binding still matches the old
row. Expired or rejected cached tokens also hold with actionable codes
(claude_usage_token_expired / _rejected) instead of an opaque collector
error.

For scripts and wrappers

  • "reserved": true on an account keeps it tracked and on the dashboard
    but excluded from all routing — never picked, never a rotation or handoff
    target. For a login that belongs to another workflow.
  • An exported CLAUDE_CONFIG_DIR / CODEX_HOME is honoured as the
    initial slot when it names a registered account — your wrapper's routing
    decision is consumed, not overridden.
  • HEADROOM_LAUNCH_MARKER — a launch-commit handshake written
    immediately before the CLI spawns, so wrappers can distinguish "the CLI
    never started" from a real exit without racing anything. Strict no-clobber
    install: the marker path must be a fresh file, and an env-controlled path
    can never overwrite anything that exists.

Community

  • @alexknowshtml fixed identity binding for personal Max accounts with a
    null orgId (#1).
  • @hanamizuki aligned the dashboard with the widget's semantics for current
    windows on limited accounts (#6) — including a same-day review turnaround.
  • The 429 report above came from X. Keep them coming — real-fleet bug
    reports have driven most of this release.

439 tests. The batch went through two cross-model adversarial review rounds
before release; every P1 was fixed in-tree. Update: git pull — the menubar
app is the only component that needs a local build.

v0.3.0 — widgets

Choose a tag to compare

@domanski-ai domanski-ai released this 13 Jul 19:19

headroom v0.3.0 — widgets

Requested on X ("kinda wish it had a dashboard widget, perhaps even a taskbar
widget"). Here they are. One data plane, glanceable everywhere, never lying about
freshness.

Menu-bar widget (macOS, SwiftBar)

A one-line fleet readout in your menu bar: hr 3/4 · 97% (accounts with proven
headroom / total, and the fullest current 5-hour tank). The dropdown lists every
account with both windows and reset times. Drop
integrations/swiftbar/headroom.1m.sh into SwiftBar and point it at a local
headroom serve or an SSH-forwarded one.

The client is deliberately dumb: it fetches server-rendered text, validates a
version sentinel, strictly allowlists every line (fixed colours, one refresh, one
loopback dashboard link), and prints. It never executes fetched content.

Compact dashboard

http://127.0.0.1:8377/widget (or ?compact=1) is the full themed dashboard sized
as a pinnable mini app — headline fleet bars plus slim per-account rows. Pin it with
Safari's Add to Dock, Edge's install-as-app, or Übersicht.

Windows tray (experimental)

experimental/windows/headroom-tray.ps1 — a PowerShell 5.1 system-tray applet
(nothing to install) that polls a forwarded headroom serve and shows a four-state
battery icon. Marked experimental until a real Windows end-to-end run confirms it;
reports welcome.

Honesty guarantees

Widgets share one projection with the dashboard: stale or held data always renders
gray with no live number, freshness thresholds match everywhere, and the public feed
carries no routing or eligibility claims. Everything is loopback-only; remote access
is SSH forwarding, documented, and the only supported path.

Serving three feeds (/usage.json, /widget.json, /widget.txt) shares one
single-flight refresh gate, so a 60-second widget poll never amplifies provider
reads.

v0.2.1 — automatic handoff, on by default

Choose a tag to compare

@domanski-ai domanski-ai released this 13 Jul 14:34

headroom v0.2.1 — automatic handoff, on by default

The headline: headroom claude can now continue a capped conversation on a fresh
account by itself, in the same terminal, with no commands to type.
This is the thing
headroom was built for — never babysit a capped session again.

Automatic handoff (on by default)

Launch your session with headroom claude as usual — that's it. When that exact session hits a subscription
cap, headroom:

  1. Confirms the cap with three independent proofs — a StopFailure hook matched as
    rate_limit, a narrow session/weekly-limit message, and a fresh identity-bound
    usage read showing the account is actually capped. Missing or ambiguous evidence
    leaves your session running, untouched.
  2. Derives the model you were actually on (from the transcript, so an in-session
    /model switch is respected), and picks a different account with proven headroom
    for that same model family.
  3. Sends Claude one SIGTERM, waits for its own clean shutdown, copies the conversation
    into the target account (byte-for-byte; the source is never modified), and relaunches
    claude --resume … --fork-session in the same terminal.

Your session pauses for a few seconds and continues on the fresh account. What carries:
the conversation, model-family routing, and the working directory. What does not:
background tasks, live MCP connections, pending permission approvals, and permission
mode. Three automatic handoffs in any ten minutes trips a loop guard.

It is on by default, because uninterrupted continuation is the point. Every step
is fail-closed — any doubt and headroom leaves your session alone — and
routing.auto_handoff: false (or --headroom-no-auto-handoff per run) turns it off.

Also in this release

  • headroom handoff is now fully transactional: a global lock, no-clobber publication,
    and crash-recoverable staging. New flags: --yes (confirmed non-interactive handoff)
    and --model FAMILY (when the session model can't be inferred). Manual handoff of an
    interrupted tool call requires --force.
  • Model-family–aware routing everywhere: a session capped on one model is never handed
    to an account whose scoped weekly cap for that model is exhausted.

Platform note

The automatic-handoff path is proven end-to-end on Linux (Claude Code 2.1.x): a real
cap was detected, the session was stopped, moved, and resumed on another account with
full history intact. The one-SIGTERM stop contract has not yet been validated on
macOS
; see docs/KNOWN-LIMITS.md. Usage tracking, the dashboard, manual handoff, and
rotation are unaffected and work on both platforms.

v0.1.2 — session handoff + multi-account macOS

Choose a tag to compare

@domanski-ai domanski-ai released this 13 Jul 10:57

v0.1.2 — hand off a capped session; multiple Claude accounts on one Mac

Two headline features. Update: git pull — no other steps.

🖐️ Session handoff (EXPERIMENTAL)

Hit your 5-hour cap mid-conversation? /exit, then:

headroom handoff

Your conversation continues on your fullest other account, from the same directory, with full history — the capped account cools until its window resets. Under the hood: the transcript is verified line-by-line, copied hash-checked into the target account's home, and resumed with claude --resume <id> --fork-session (a fresh session id, so histories never collide). Every handoff prints a baton — what moved, what doesn't carry (background tasks, MCP connections, per-session permission approvals), and the exact resume command — and is recorded in a ledger that refuses accidental double-handoffs. The source transcript is never modified. Use --print to stage without launching, --to <slot> to pick the target. The status line tells you when: capped -> /exit, then: headroom handoff.

Nine fail-closed guards (still-writing detection, mid-tool-call refusal, router-validated target, atomic hash-verified copy, no overwrites, symlink rejection, cross-domain data-boundary warning, and more). Proven end-to-end on real accounts before release. Same machine, Claude, file-isolated accounts for v1.

🍎 Multiple Claude accounts on one Mac

Current Claude Code builds keep a separate macOS Keychain item per config directory (verified against the official 2.1.207 binary) — so multiple isolated Claude accounts on one Mac work natively. headroom now reads the per-account namespaced item, and headroom connect probes for the capability: with a current CLI, extra Claude accounts connect normally; on older builds that still share one Keychain item, the connect is refused up front (that shared item is how a second login used to silently break the first). If you were told "one Claude account per Mac" in v0.1.1 — update Claude Code, pull this, and connect away. Real-world confirmations welcome.

Also

  • Routing now honours reserve_percent on the model-scoped weekly caps as well.
  • Statusline persists a small session journal (id, transcript path, cwd — throttled, local, 0600) so handoff can identify your session without guessing.

81 tests green. Feedback on the handoff experiment very welcome — it's the feature the rest of this project existed to make possible.

v0.1.1 — macOS fixed

Choose a tag to compare

@domanski-ai domanski-ai released this 13 Jul 09:54

v0.1.1 — macOS fixed, smarter rotation

If v0.1.0 was broken for you on macOS, this release is the fix. Update: git pull (or re-clone) — no other steps.

macOS Keychain support (the big one)

  • Recent Claude Code on macOS stores its OAuth token in the login Keychain, not ~/.claude/.credentials.json — v0.1.0 couldn't read it, so every Mac Claude account was held as credentials missing. headroom now reads the Keychain item directly (approve the security prompt once — Always Allow). One normal claude login on your Mac just works.
  • Connect guard: on macOS all Claude logins share ONE Keychain item — a second claude login overwrites the first (this is what caused the REFUSED … already connected loop some users hit). headroom connect now refuses that second login up front, before it can break your existing slot, and explains the platform limit: one Claude account per Mac. Extra Claude accounts belong on a Linux host; Codex accounts are fully isolated everywhere, including macOS.

Codex tracking robustness

  • The Codex app-server reports rate-limit windows by their real duration and omits windows that aren't a current constraint; v0.1.0 assumed a fixed primary=5h/secondary=weekly shape and dropped the whole reading when it didn't hold. Windows are now bucketed by actual duration — Codex readings stay live through resets.

Reserve routing

  • New routing.reserve_percent (the setup wizard asks): skip any account with less than N% headroom left so sessions start fresh instead of hitting a wall mid-task. Default 0 keeps use-to-the-limit behaviour.

Docs

  • README: multi-machine FAQ (usage is read per-account provider-side; reads are token-safe and never log out your other sessions), reserve section, corrected macOS quickstart note.
  • KNOWN-LIMITS: accurate macOS Keychain section (CLAUDE_CONFIG_DIR does not relocate credentials on macOS — only Linux/Windows).

59 tests green. Thanks to @hanamizuki and the DM bug reporter for the macOS reports. 🙏

v0.1.0 — first public release

Choose a tag to compare

@domanski-ai domanski-ai released this 12 Jul 13:15

headroom tracks the remaining capacity of every Claude and ChatGPT/Codex subscription you own — read live, without spending a single token — puts it on a dashboard you'll actually want to look at, and rotates your tools to the next account with headroom the moment one hits a limit.

Highlights

  • Live usage, both providers. Claude via the OAuth usage API; Codex via the Codex app-server (account/rateLimits/read). Real-time and identity-bound — never a stale guess.
  • A dashboard worth looking at. Five switchable themes (Midnight, Minimal, Chrome, Paper, Terminal) with an instrument bar of meters, gauges and per-account bars. Try it before connecting anything: headroom serve --demo.
  • Fail-closed rotation. headroom claude / headroom codex launch on an account with proven headroom; on a limit-hit, headroom rotate cools it down until its window resets and hands you the next one.
  • Claude Code integration. A color-coded status line and a /rotator skill.
  • Safe by construction. stdlib-only Python (3.9+), no network beyond the two read endpoints, emails redacted by default, credentials never moved. Adversarially reviewed cross-model before release.

Quickstart

git clone https://github.com/domanski-ai/headroom
cd headroom
./install.sh
headroom serve --demo   # preview with sample data, no setup
headroom setup          # connect your accounts

See the README for the full command list and docs/HOW-IT-WORKS.md for the architecture. MIT licensed.