Real AI usage gauges — from a ccusage estimate to the OAuth endpoint Claude Code itself uses
An always-on top-right strip shows current usage/limits for Claude, Codex, and Antigravity, with a click-to-open detail popup. Codex is read straight from the local rollout JSONL (real, zero-network). Claude started as a ccusage-style local estimate — but the estimate didn't match claude.ai's real 5%/9%, so we found the actual endpoint Claude Code uses (api/oauth/usage, authed by the OAuth token in the macOS Keychain) and switched to real numbers with the estimate as fallback. Every path is a usage *query* — zero tokens, no inference, BYO-clean.
The goal
One always-visible strip (top-right) with a tiny color gauge per provider — Claude · Codex · Antigravity — and a click-to-open popup with 5-hour + weekly bars, reset countdowns, and the plan label. The hard part was never the UI; it was: can we read each provider's real remaining limit locally, without spending a token?
What's actually readable, per provider (verified on the machine)
- Codex — real, zero-network. Every Codex turn writes a
rate_limitsobject into~/.codex/sessions/**/rollout-*.jsonl(primary= 5h,secondary= weekly, each withused_percent+resets_at). Read the newest file's last such line → done. No token, no HTTP. - Claude — the twist. The on-disk
rateLimitsin transcripts is alwaysnull;stats-cache.jsonis consumption history, not remaining quota. So we first built a ccusage-style estimate: sum tokens from the last 7 days of transcripts, bucket into a rolling 5-hour window, and gauge against the user's own busiest 5h (no fabricated Anthropic cap). Honest — but it didn't match the real numbers. - Antigravity — deferred. No clean local file; quota lives behind a local language-server RPC (needs the IDE running, which it wasn't) or an undocumented Google internal endpoint. Left as a "later, best-effort" slot rather than shipping something fragile.
The estimate ≠ reality, so we went looking for the real endpoint
The gauge showed one thing; claude.ai → Settings → Usage showed 5% session / 9% weekly, Max (20x). The relative "vs your own peak" estimate is honest but it isn't the real quota. That screen exists, so something serves the real data.
It does: GET https://api.anthropic.com/api/oauth/usage, authed with the OAuth access token Claude Code stores in the macOS Keychain (security find-generic-password -s "Claude Code-credentials" → JSON with accessToken, subscriptionType: "max", rateLimitTier: "default_claude_max_20x"). The Keychain item also gives the plan label with zero network.
Before writing a parser against a guessed shape, we made one live call and read the actual response:
{ "five_hour": { "utilization": 8.0, "resets_at": "2026-06-07T23:00:00Z" },
"seven_day": { "utilization": 10.0, "resets_at": "2026-06-14T11:59:59Z" },
"seven_day_sonnet": { "utilization": 0.0, "resets_at": "..." } }Near 1:1 with the Settings UI. utilization is 0–100, resets_at is ISO. Bearer alone works.
What shipped
- Rust
usage.rs:read_codex_usage()(local file),read_claude_real()(Keychain →api/oauth/usage, fail-soft) withread_claude_usage_estimate()as the fallback if the endpoint errors / token expires / shape changes. The gauge tag flips green "실측" vs amber "추정" so you always know which you're looking at. - The call is a usage query — zero tokens, no inference. It reuses the user's own credential (the macOS Keychain consent prompt is the user granting access — BYO). We never log the token.
- Perf: the heavy transcript scan only runs as a fallback now; the whole snapshot is cached (60s) and computed off-thread (
spawn_blocking), with a 40 MB scan cap, so the popup is instant.
The lesson (Pattern)
Twice in this thread the cheap-but-wrong move was to assume — first "Claude limits aren't readable" (false: they are, via the OAuth endpoint), then the JSON shape. The fix both times was the same: make one real call and read the actual bytes before writing the code. Verify the endpoint, then implement.