2 <img src="UsageMenubar/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png" width="128" height="128" alt="Usage Menubar app icon">
7A macOS menu bar application that displays your [Hyper (Charm)](https://hyper.charm.land) credit balance — and, if you use the Claude Code CLI, your Claude subscription limits alongside it.
11- ⚡ **Menu bar display** — shows `⚡{balance}` (e.g. `⚡42`) right in your menu bar
12- 🤖 **Claude Code limits** — your Pro/Max 5-hour and 7-day usage windows in the popover, with the percentage used and a countdown to the next reset. No setup: if you have signed in with the Claude Code CLI, it just appears. If you haven't, the section doesn't
13- 🔄 **Auto-refresh** — fetches your balance on a configurable interval (1m / 5m / 15m / 30m), and again on wake from sleep
14- 📊 **Balance sparkline** — a subtle mini-chart in the popover shows your balance trend over recent fetches
15- 📋 **Click-to-copy** — click the balance number in the popover to copy it to the clipboard
16- 🖱️ **Right-click menu** — right-click the menu bar icon for quick access to Refresh, Open hyper.charm.land, and Quit
17- 🔔 **Low-balance notifications** — alerts you when your balance drops below 10 credits, including on the very first fetch if you are already below the threshold
18- 🔒 **Keychain storage** — your API key is stored securely in macOS Keychain
19- 🎨 **Color-coded balance** — green (≥100), yellow (10–99), red (<10)
20- 🔐 **Secure input** — API key field is masked
21- 🚀 **Launch at login** — optional, using `SMAppService`
22- 🔑 **Get key link** — quick link to [hyper.charm.land](https://hyper.charm.land) right next to the API key field
23- 🫥 **Agent app** — no dock icon, no main window; lives entirely in the menu bar
27### Homebrew (recommended)
31brew install --cask usage-menubar
37brew install --cask ilyakooo0/tap/usage-menubar
40### Download the pre-built release
421. Go to the [Releases page](https://github.com/ilyakooo0/usage-menubar/releases)
432. Download the latest `UsageMenubar-*.zip`
443. Unzip and move `UsageMenubar.app` to `/Applications`
454. Launch the app — it will appear in your menu bar as ⚡
47> **Note:** Releases are **ad-hoc signed** — the CI build applies `codesign -s -` to the app, which gives it a stable code identity but is *not* an Apple Developer ID signature. macOS will still warn you on first launch: right-click the app → **Open**, or allow it in **System Settings → Privacy & Security**. Removing that prompt entirely requires a paid Developer ID certificate and notarization.
511. Click the ⚡ icon in your menu bar
522. Enter your Hyper API key (starts with `sk-...`) in the **API Key** field
544. Your balance will appear immediately
56You can get your API key from [hyper.charm.land](https://hyper.charm.land).
58### Claude Code limits (optional, no setup)
60If you use the [Claude Code CLI](https://claude.com/claude-code), the popover also shows how much of your Pro/Max plan you have used: the limit currently in effect as a headline percentage, the 5-hour and 7-day windows underneath, and how long until the next reset.
62There is nothing to configure. The app reads the OAuth credentials Claude Code already stores when you run `claude` and sign in — first from its login-Keychain item, then from `~/.claude/.credentials.json` if the Keychain has nothing. If neither exists, the Claude section simply doesn't appear; it is not treated as an error, and the Hyper balance is unaffected.
64Two things worth knowing:
66- **macOS will ask once.** Those credentials belong to Claude Code, not to this app, so the first read prompts you to allow access to the `Claude Code-credentials` Keychain item. Choose **Always Allow** and you won't be asked again.
67- **The credentials are read-only, with one exception.** When the access token has expired, the app refreshes it against Anthropic's token endpoint and writes the result back to wherever it read it from — exactly as Claude Code itself would. Nothing else about them is ever modified, and the fields the app doesn't model are preserved verbatim, so your CLI session keeps working.
71Everything is configured from the popover — click the ⚡ icon in the menu bar.
73| Setting | Where | Notes |
74| ------------------- | ------------------------------ | ------------------------------------------------------------------------------------ |
75| **API key** | **API Key** field → **Save** | Stored in the macOS Keychain. Clearing the field and saving deletes the stored key. |
76| **Launch at login** | **Launch at Login** toggle | Registers the app with `SMAppService`. |
77| **Notifications** | System permission prompt | macOS asks on first launch (only if not already decided). Required for low-balance alerts. |
78| **Refresh now** | **Refresh** button | Forces an immediate fetch, on top of the automatic refresh. |
79| **Refresh interval**| **Refresh every** picker | Choose 1m, 5m, 15m, or 30m. Defaults to 5m. Stored in `UserDefaults` and persisted across launches. |
81The app also refreshes automatically when your Mac wakes from sleep.
89- [xcodegen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`)
95git clone https://github.com/ilyakooo0/usage-menubar.git
98# Generate the Xcode project
102open UsageMenubar.xcodeproj
104# Or build from the command line
106 -scheme UsageMenubar \
107 -configuration Release \
108 -archivePath build/UsageMenubar.xcarchive \
110 CODE_SIGNING_ALLOWED=NO
117 -scheme UsageMenubar \
118 -configuration Debug \
119 -destination "platform=macOS"
124Versions follow the format `YYYY.MM.DD.HHHH` where `HHHH` is the UTC hour and minute (24-hour).
126| Commit time (UTC) | Version |
127| ------------------------ | ----------------- |
128| 2026-07-09 14:23 | `2026.07.09.1423` |
129| 2026-01-01 00:00 | `2026.01.01.0000` |
130| 2026-12-31 23:59 | `2026.12.31.2359` |
132The version is derived automatically from the commit timestamp in the GitHub Actions release workflow.
138├── project.yml # xcodegen project definition
140│ ├── UsageMenubarApp.swift # @main App, NSStatusBar, timer, sleep/wake
141│ ├── ViewModel.swift # Observable state: balance, Claude usage, refresh, notifications
142│ ├── MenuView.swift # SwiftUI popover view
143│ ├── CreditsChecker.swift # API client (GET /v1/credits)
144│ ├── ClaudeUsageClient.swift # Claude OAuth client (GET /api/oauth/usage) + models
145│ ├── KeychainHelper.swift # Keychain wrapper for API key
146│ ├── VersionFormatter.swift # YYYY.MM.DD.HHHH formatting
147│ ├── Info.plist # LSUIElement=true (agent app)
148│ ├── UsageMenubar.entitlements # Network client; deliberately not sandboxed
149│ └── Assets.xcassets/ # App icon, accent color
150├── UsageMenubarTests/
151│ ├── CreditsCheckerTests.swift # API, retry, fractional balance, keychain tests
152│ ├── ClaudeUsageClientTests.swift # Usage decoding, token refresh, retry, credential store
153│ └── VersionFormatterTests.swift # Version formatting tests
154├── .github/workflows/
155│ └── release.yml # Build + release on push to master
1631. **Menu bar item**: The app creates an `NSStatusItem` with a variable-length button showing `⚡{balance}` or `⚡?`. Left-click toggles the popover; right-click opens a context menu (Refresh, Open hyper.charm.land, Quit).
1642. **Popover**: Clicking the menu bar item shows an `NSPopover` containing a SwiftUI `MenuView` with the balance, sparkline, API key field, and settings.
1653. **API client**: `CreditsChecker` performs `GET https://hyper.charm.land/v1/credits` with `Authorization: Bearer <key>`, retries transient failures (5xx, timeouts) with exponential backoff, and decodes `{"balance": <int>}` (tolerating fractional values by rounding).
1664. **Keychain**: The API key is stored/retrieved via `KeychainHelper` using the macOS Security framework. `save()` returns a `Bool` indicating success.
1675. **Timer**: A `Timer` fires at the user-selected interval (1m / 5m / 15m / 30m, default 5m) to refresh the balance; the app also refreshes on wake from sleep. Changing the interval in the popover restarts the timer immediately.
1686. **State**: `ViewModel` owns the balance, loading/error state, balance history (for the sparkline), refresh task (cancellable to prevent races), and the refresh interval, and is shared between the menu bar item and the popover.
1697. **Notifications**: When the balance crosses below 10 credits — or is already below 10 on the first successful fetch — `ViewModel` posts a local `UNUserNotification`. Permission is requested only once.
1708. **Agent app**: `LSUIElement=true` in `Info.plist` makes the app a background agent — no dock icon, no main window.
1719. **Claude usage**: `ClaudeUsageClient` reads Claude Code's OAuth credentials (its `Claude Code-credentials` Keychain item via `/usr/bin/security`, falling back to `~/.claude/.credentials.json`), refreshes the access token when it has expired, and fetches `GET https://api.anthropic.com/api/oauth/usage` with `Authorization: Bearer …` and `anthropic-beta: oauth-2025-04-20`. It is an actor so that overlapping refreshes (timer, manual click, wake) join a single in-flight token exchange — the endpoint rotates the refresh token, and rotating it twice would sign Claude Code out. Missing credentials are reported as "not configured" rather than as an error, and the popover hides the section.
17210. **Independent fetches**: `ViewModel.refresh()` runs the Hyper and Claude requests concurrently with `async let`. Neither can fail, stall, or be unconfigured in a way that affects the other.
17311. **No sandbox**: Reading credentials that belong to another app means spawning `/usr/bin/security` and reading a file in the user's home directory, neither of which the App Sandbox permits. The app is distributed ad-hoc signed via Homebrew rather than through the App Store, where the sandbox would be mandatory.
177- **Author**: Ilya Koo ([@ilyakooo0](https://github.com/ilyakooo0))
178- **API**: [Hyper by Charm](https://hyper.charm.land)
179- **Built with**: Swift 5.9, SwiftUI, macOS 14+
183MIT — see [LICENSE](LICENSE).
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list