A minimalist Claude usage tracker for the macOS menu bar.
usagi shows your Claude.ai session usage as a small gauge in the menu bar, and the full picture — the rolling 5‑hour session, the weekly limit, and any paid overage spend — in a native dropdown.
"Beware of bugs in the above code; I have only specified it, not written it." – @duggan
brew tap duggan/usagi
brew install --cask usagi
Or grab the DMG from Releases and drag to Applications.
Sign in with your Claude account on first launch — the session token lives in your macOS Keychain and never leaves the machine.
- It only ever talks to
claude.ai— an embeddedWKWebViewpointed atclaude.ai/loginto sign in, thenclaude.ai/api/...to read your usage windows and overage limit. Nothing is sent anywhere else. - No telemetry, no analytics, no crash reporting — usagi doesn't phone home, full stop.
- Your session token stays in the macOS Keychain (service
ie.duggan.usagi.session). "Sign out" deletes that entry and clears the embedded web view's cookies/storage. - It uses an unofficial claude.ai API — the same endpoints the website itself calls. They can change or break without notice; that's the main reason there'd ever be an update.
- "Launch at login" is the standard
SMAppServicemechanism — toggle it here, or in System Settings → General → Login Items.
Requires macOS 14+ to run, and Xcode 16 or newer to build — the SwiftUI code needs the macOS 15 SDK (View.body is @MainActor there). swift test runs the unit tests.
./build.sh # release build → bin/Usagi.app
UNIVERSAL=1 DMG=1 ./build.sh # universal binary + DMG (drag-install)
open bin/Usagi.app
For a Developer ID-signed and notarized build:
SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
NOTARIZE_PROFILE="usagi-notarize" \
DMG=1 UNIVERSAL=1 \
./build.sh
NOTARIZE_PROFILE is a notarytool store-credentials profile in your login keychain. Alternatively, pass NOTARIZE_KEY (path to .p8), NOTARIZE_KEY_ID, and NOTARIZE_ISSUER to use an App Store Connect API key directly.
Tag a commit vX.Y.Z and push. The release GitHub Actions workflow signs, notarizes, and publishes a DMG to the matching GitHub Release. The workflow runs inside a release environment with required-reviewer approval, so a stray tag cannot expose signing secrets.
-
Export the Developer ID Application certificate from Keychain Access (right-click → Export →
.p12with a strong password). Then:base64 -i developer-id.p12 | pbcopy # paste into MACOS_CERTIFICATE -
Create an App Store Connect API key at App Store Connect → Users and Access → Integrations → App Store Connect API. Role: Developer. Apple lets you download the
.p8exactly once. Then:base64 -i AuthKey_XXXXXXXXXX.p8 | pbcopy # paste into APPLE_API_KEY -
Create a
releaseenvironment in repo Settings → Environments → New environment. Add yourself as a required reviewer. -
Add secrets to the
releaseenvironment (not repo-level secrets):Secret Value MACOS_CERTIFICATEbase64 of the exported .p12MACOS_CERTIFICATE_PWDthe .p12passwordKEYCHAIN_PWDany random password — used for the temp keychain SIGN_IDENTITYfull cert common name, e.g. Developer ID Application: Ross Duggan (XXXXXXXXXX)APPLE_API_KEYbase64 of the .p8APPLE_API_KEY_ID10-character Key ID from App Store Connect APPLE_API_ISSUER_IDthe Issuer ID UUID at the top of the Keys page HOMEBREW_TAP_TOKENoptional — fine-grained PAT with Contents: read & write on duggan/homebrew-usagi; enables auto-bumping the cask. Omit it and the release just skips that step.
The temporary keychain is deleted on every job exit (success or failure), so secrets never persist on the runner.
The cask lives in duggan/homebrew-usagi (Casks/usagi.rb). Its source of truth is homebrew/usagi.rb in this repo — the release workflow substitutes the new version + sha256 and pushes the result to the tap, so a release auto-publishes the cask. Edit homebrew/usagi.rb here for anything else (zap paths, deps, …).
- App.swift —
AppDelegateowning theNSStatusItemand itsNSMenu(the first item hosts the SwiftUI usage bars; the rest are standard menu items rebuilt per state). The status-item image is redrawn reactively viawithObservationTrackingwheneverAppStatechanges. - ViewModels/AppState.swift —
@Observableroot state: phase, snapshot, overage, organization, refresh interval. Coordinates load → refresh → sign-out lifecycle. - Services/ClaudeAPIClient.swift —
URLSession-backed actor callingclaude.ai/api/organizations,/usage,/overage_spend_limit. SendsCookie: sessionKey=.... - Services/AuthCoordinator.swift —
WKWebViewwindow pointed atclaude.ai/login; observes the cookie store and persists the capturedsessionKeyto the Keychain. - Services/SessionStore.swift — Keychain CRUD for the session key (service
ie.duggan.usagi.session). - Services/UsageRefresher.swift — Timer that drives
AppState.refresh()on the configured interval.
MIT.