Skip to main content

Claude usage in the menu bar

The same numbers the /usage command and claude.ai β†’ Settings β†’ Usage show β€” your 5-hour session and weekly account limits β€” folded into the sessions menu-bar plugin (no separate icon). The shared icon gets a compact usage suffix; the dropdown gets a "Claude usage" section.

🟒 1 Β· 🟒 60% ← sessions count Β· usage (the worse of session/weekly), one icon
─────────────────
1 running Β· 0 need you Β· 0 idle Β· 0 collision
─────────────────
Claude usage
🟒 Session 60% · Weekly 35%
Session (5h) resets in 29m Β· 18:20
Weekly resets Thu 17:00
Weekly Β· Sonnet 0%
Updated 17:49 (just now)
↻ Refresh usage
─────────────────
🟒 <your sessions…>

It is optional β€” if the usage feed isn't installed, the sessions plugin shows exactly as before (no suffix, no section).

How it works β€” producer / consumer​

Split into two halves so the menu bar never blocks on the API and the API is hit at a sane cadence:

  • Producer β€” scripts/usage-fetch.sh, run every 5 minutes by a launchd LaunchAgent (io.bewith.usage.plist). It reads your Claude Code OAuth token from the macOS Keychain, GETs https://api.anthropic.com/api/oauth/usage (the endpoint /usage itself uses, with anthropic-beta: oauth-2025-04-20), and writes a small cache at ~/.bewith/cache/usage.json β€” atomically (temp + rename) so the reader never sees a half-written file. The token is never written to disk and never placed in a process argument.
  • Consumer β€” the sessions reader scripts/sessions-view.mjs imports scripts/usage-view.mjs and renders the cache into the title suffix + dropdown section. It only reads the cache (every 5s; refreshOnOpen makes a click re-read instantly), so it does no network and touches no credential.

Why folded, not a second icon: the producer/cache split already decouples the 5-min API cadence from the 5-s display refresh, so there's no reason to spend a second menu-bar slot (which macOS readily hides behind the notch). Why a LaunchAgent and not cron: it runs in your GUI session, so it can read the login-Keychain token (a cron job / root daemon generally cannot); and RunAtLoad + StartInterval give a fresh fetch on login/boot and a coalesced catch-up run on wake from sleep for free β€” the "self-reset" behaviour, no sleep-detection code.

Install​

It rides the sessions menu-bar installer β€” one idempotent command installs the sessions plugin and the usage feed (the launchd agent), and kicks one immediate fetch:

pnpm sessions:menubar # = bash scripts/install-sessions-menubar.sh

Re-running is always safe — it bootout→bootstraps the agent, so a deleted plist, an unloaded agent, or a wiped cache all heal on the next run. Deletion does not auto-resurrect; you re-run this on purpose.

bash scripts/install-sessions-menubar.sh --uninstall # removes the plugin + usage LaunchAgent + cache

It can also be installed as an opt-in step during developer onboarding (bewith onboard).

The states it shows​

The menu bar is honest about freshness β€” it never shows a wrong number as if it were current. The title suffix is the single worse-of-the-two number; the dropdown carries the breakdown.

StateTitle suffixMeaning
freshΒ· 🟒/🟠/πŸ”΄ 60%Last fetch succeeded. Dot colour = the worse of session/weekly (green <70%, orange 70–89%, red β‰₯90%).
staleΒ· β—” 60%? + "Claude usage (stale)"Last successful fetch older than ~11 min (producer not running / machine asleep). Shows the last-known numbers, clearly marked.
auth-errorΒ· ⚠Token expired or unauthorized. Open Claude Code (it refreshes the Keychain token) β€” the next fetch picks it up automatically.
initializing(no suffix)No cache yet (first run / just installed). Resolves within ~5 min or on ↻ Refresh usage.

↻ Refresh usage in the dropdown forces a live fetch (use it if you think the number is wrong) and re-renders.

Title vs dropdown. Whether the % appears in the menu-bar title or only inside the dropdown is your choice: βš™οΈ Settings β†’ Claude usage % in menu bar (show in menu bar / dropdown only), or bash scripts/bewith-config.sh set usageInTitle on|off. Default on. The dropdown section always shows when the feed is installed.

Caveats​

  • Undocumented endpoint. /api/oauth/usage is the internal endpoint the app/CLI use; there is no public contract. It may change; if it does, the dropdown shows an error state rather than a wrong number.
  • Token expiry. The OAuth token expires and Claude Code refreshes it in the Keychain. While Claude Code hasn't run and the token is expired, it shows auth-error until the next refresh.

Verify it works​

bash scripts/usage-fetch.sh # producer: writes ~/.bewith/cache/usage.json
cat ~/.bewith/cache/usage.json | python3 -m json.tool # state:"fresh", data.five_hour / seven_day β€” and NO token
node scripts/usage-view.mjs # plain-text render of the cache
node scripts/sessions-view.mjs --swiftbar # the folded menu (title suffix + "Claude usage" section)
launchctl print "gui/$(id -u)/io.bewith.usage" | grep -E "state|runs" # the agent is loaded

See also​

  • Claude status in the menu bar β€” the sibling feed (incidents + whether they hit your model) folded into the same plugin.
  • Session mission control β€” the menu-bar plugin this folds into (every concurrent session) and the shared ~/.bewith settings menu.