One local dashboard for the usage and remaining limits of Codex, Claude, Gemini/Antigravity, CommandCode, and OpenCode.
Click any screenshot to open it full size.
Every number here comes from a credential that belongs to you: OAuth refresh tokens with cloud-platform scope, billing API keys, provider subscriptions. A hosted version of this dashboard would need all of them on a server, where one breach exposes five accounts at once. That trade is not worth a convenience page, so this tool does the opposite: the collectors run on your machine, and credentials never leave it.
What that costs:
- Your computer has to be on for the dashboard to be current.
- Setup is more than pasting a URL.
What it buys:
- Nothing to sign up for, no server to trust, no credential in someone else's database.
- It reads the same local files your editors already write, at full speed.
Remote access is solved with a private network rather than a public deployment. See Viewing from a phone.
| Provider | Source | Gives |
|---|---|---|
| Codex | ~/.codex/sessions/**/*.jsonl |
Tokens, sessions, plan limits |
| Claude | ~/.claude/projects/**/*.jsonl, ~/.claude.json |
Tokens, requests, plan limits |
| Gemini / Antigravity | Running Antigravity app, loopback API | Per-group 5-hour and weekly limits, Argon weekly limit, plan |
| CommandCode | ~/.codex/codex-router/usage-events.jsonl, local billing API |
Tokens, credits, request latency |
| OpenCode | Local opencode.db, official OpenCode Go usage API |
Local tokens and recorded cost; account plan limits |
Provider support is uneven, and the dashboard says so rather than guessing. A
provider with no readable quota shows unavailable; a value that may no longer
be current shows stale. See Provider notes.
- macOS (the service uses
launchd; the collectors are portable but the service setup is not) - Node.js 22.13 or newer (24.x recommended — OpenCode support needs
node:sqlite, which is not flag-free until 22.13) - The tools you want to measure, installed and signed in at least once
- Tailscale, only if you install with
--tailscaleto keep the dashboard off the local network
You do not need every provider installed. Missing ones render as unavailable.
The fastest path is to hand the repo to a coding agent, because the setup is mostly "find which credentials this machine already has".
From the repository root, paste this into Claude Code or Codex:
Install and run this project. It is local-only; do not deploy anything, and do not expose it beyond this machine and the Tailscale tailnet. Build it, start it, and confirm each provider card shows a real number rather than
unavailable. Then install it as a launchd service so it starts at login, and runtailscale serve --bg 4317so the tailnet can reach it. If a provider reports no data, read that provider's collector inlib/collectors/before changing code.
Then open http://localhost:4317, or the tailnet URL if you set up Serve.
npm install
npm run snapshot # prints what each collector found
npm run build
./scripts/install-launchd.shnpm run snapshot is the useful step to run first. It shows which providers
produced numbers before any UI is involved, which makes a missing credential
obvious.
The service listens on every interface, so a phone on the same Wi-Fi can open
http://<machine-name>:4317 straight away. That also means anyone else on that
network can. If you would rather close the local network and reach it over
Tailscale instead:
./scripts/install-launchd.sh --tailscale # loopback + `tailscale serve`--help lists --bind, --port and --label. Details and the funnel
alternative are in Viewing from a phone.
scripts/install-launchd.sh generates a LaunchAgent at
~/Library/LaunchAgents/com.usage-check.dashboard.plist and loads it. The plist
is generated rather than committed so the Node path matches the machine it runs
on. --port and --label let you run more than one instance without the two
colliding.
# restart after a rebuild (the common case)
npm run build && launchctl kickstart -k gui/$(id -u)/com.usage-check.dashboard
# stop / start
launchctl bootout gui/$(id -u)/com.usage-check.dashboard
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.usage-check.dashboard.plist
# status and logs
launchctl print gui/$(id -u)/com.usage-check.dashboard | head -8
tail -f ~/Library/Logs/usage-check.logIf you have used systemd, the mapping is direct: RunAtLoad is
WantedBy=multi-user.target, KeepAlive is Restart=always, ProgramArguments
is ExecStart, and launchctl print is systemctl status.
Two things worth knowing:
- A LaunchAgent starts at login, not at boot. With FileVault enabled macOS
cannot auto-login, so the service is not up while the machine sits at the
unlock screen. A LaunchDaemon in
/Library/LaunchDaemonswould start earlier, at the cost of running as root. bootoutimmediately followed bybootstrapcan fail with5: Input/output error. The install script retries; by hand, wait a second and run it again.
Idle cost is one Node process at roughly 200 MB RSS and effectively 0% CPU. It only computes when a browser asks, so leaving the page closed makes it dormant.
By default the service listens on every interface, so a phone on the same Wi-Fi
opens http://<machine-name>:4317 with nothing to install. Anyone else on that
network can open it too.
If you want it reachable only from devices you control, install with
--tailscale. That binds the service to 127.0.0.1 and publishes it to your
tailnet through Tailscale Serve:
./scripts/install-launchd.sh --tailscale
tailscale serve status # https://<machine>.<tailnet>.ts.netServe terminates HTTPS on your tailnet and proxies the loopback port, so nothing
on the local network can reach it and the mapping survives reboots. Turning it
off later is tailscale serve reset, or reinstall without the flag.
Note that Tailscale Serve has to be enabled once per tailnet, from the link it prints on first use, which is the part that makes this option more work than the default.
Use tailscale funnel instead if you need access from outside the tailnet. That
publishes the port to the public internet, so put an auth layer in front of it —
a funnel URL is reachable by anyone who has it.
- Card order — drag any card by its grip. The Summary block moves as one piece, so it can sit anywhere among the provider cards.
- Summary — collapse it to a single line with the chevron.
- Range —
1d/7d/30d/60dcontrols the per-day chart. - Layout is stored per machine, not per browser — preferences are written to
~/.usage-check/prefs.json, so the desktop and the phone show the same arrangement.localStorageis only a cache for instant first paint.
lib/collectors/ one module per provider, plain ESM, no framework imports
lib/snapshot.mjs runs the collectors, merges router-sourced quota
lib/prefs.mjs layout preferences on disk
app/ Next.js App Router; /api/snapshot and /api/prefs
components/ cards, quota bars, chart, table, drag grid
scripts/ install-launchd.sh, snapshot.mjs
The collectors are the part worth keeping if you rewrite the UI. They have no Next.js dependency and can be run directly:
node scripts/snapshot.mjs --json | jq '.providers[].name'Codex is the reason the collectors read files at all carefully: its session directory grows past 20 GB, so each file is read with a 64 KB head and a 512 KB tail instead of a full scan. Adding a naive full pass would turn the dashboard into a multi-second page.
These are the things that will look like bugs if you do not know them.
Codex rollouts are append-only and get resumed, so a session that started days ago can still be the file producing today's tokens. Rows are dated by the last record in the file rather than the session start, and the limits shown are whatever the newest rollout recorded — your last request, not right now. If you stop using Codex, that card stops updating.
Claude limits are read live from api.anthropic.com/api/oauth/usage, the
endpoint behind Claude Code's /usage. The token comes from the login keychain
entry Claude Code maintains — ~/.claude/.credentials.json is a leftover from
older installs and is only a fallback. The refresh token is deliberately left
alone, because refreshing it can rotate the credential out from under a running
Claude Code session; if the access token has expired the card says to run Claude
Code once instead of trading your login for a number on a dashboard.
Gemini / Antigravity reads the running desktop app's local
RetrieveUserQuotaSummary API. Keep Antigravity open for current limits. The
collector discovers its loopback port and reads the same quota pools as Models
& Usage, including the separate Gemini 4 Argon weekly pool. When the app is
closed, the last successful reading is shown as stale. Direct Google OAuth
queries were removed after returning different limits for the same account and
omitting Argon; combining those responses did not make them accurate.
CommandCode exposes 5-hour and weekly windows only. The monthly bar the official app shows is derived here from the credit pool and is labeled as derived in the UI. Its billing endpoint is undocumented.
OpenCode reads local messages from its database, opened read-only while its
WAL file is live. These token and recorded-cost totals cover this machine's CLI
history, not all clients on the account, and recorded model cost is not your
subscription bill. Go's 5-hour, weekly, and monthly limits are read separately
from https://opencode.ai/zen/go/v1/usage, using the CLI key in
~/.local/share/opencode/auth.json, under opencode or the older opencode-go
entry (or OPENCODE_API_KEY). XDG data paths are
respected. An expired key shows Limits unavailable with a recovery message;
it never becomes a fabricated 0% reading. Connect a current Console key using
opencode auth login to restore live limits.
Each collector in lib/collectors/ carries the reasoning inline, including the
endpoints that were tried and rejected, so the same dead ends are not
rediscovered.
- No analytics, no telemetry, no outbound calls except the provider quota APIs needed to read your own numbers.
- Credentials are read from where your existing tools already store them and are never copied elsewhere.
- Conversations, prompts, and file contents are not read. Only usage metadata.
- Nothing is written outside
~/.usage-check/and the launchd log files.
If you find code that does not match this section, report it privately rather than in a public issue — see SECURITY.md.
Antigravity is queried over loopback using the running app's local CSRF token. That token is never sent to a remote host or stored in the quota cache.
MIT