Usage Menubar app icon

# UsageMenubar A macOS menu bar application that displays usage limits and credit balances for multiple LLM services: [Hyper (Charm)](https://hyper.charm.land), [Claude Code](https://claude.com/claude-code), and [z.ai](https://z.ai) Coding Plans. Each service is independent — configure the ones you use, ignore the ones you don't. ## Features - ⚡ **Hyper credits** — balance from `hyper.charm.land`, with sparkline trend, click-to-copy, and low-balance notifications - 🤖 **Claude Code limits** — Pro/Max 5-hour and 7-day usage windows, with countdown to next reset. No setup: if you've signed in with the Claude Code CLI, it just appears - 🤖 **z.ai Coding Plan** — 5-hour and weekly token quota percentages, with reset countdowns. Enter your API key in settings - 🔄 **Auto-refresh** — configurable interval (1m / 5m / 15m / 30m), plus refresh on wake from sleep - 🖱️ **Right-click menu** — quick access to Refresh and Quit - 🔒 **Keychain storage** — API keys stored securely in macOS Keychain - 🚀 **Launch at login** — optional, using `SMAppService` - 🫥 **Agent app** — no dock icon, no main window; lives entirely in the menu bar ## Menu bar Each service shows an emoji-prefixed segment when configured and above 0%: | Service | Emoji | Example | |---------|-------|---------| | Hyper balance | ⚡ | `⚡42` | | Claude 5-hour | 🕐 | `🕐62%` | | Claude 7-day | 📅 | `📅8%` | | z.ai 5-hour | 🤖 | `🤖12%` | | z.ai weekly | 📆 | `📆3%` | Unconfigured services don't appear at all — no placeholder, no icon. ## Installation ### Homebrew (recommended) ```bash brew tap ilyakooo0/tap brew install --cask usage-menubar ``` Or in one command: ```bash brew install --cask ilyakooo0/tap/usage-menubar ``` ### Download the pre-built release 1. Go to the [Releases page](https://github.com/ilyakooo0/usage-menubar/releases) 2. Download the latest `UsageMenubar-*.zip` 3. Unzip and move `UsageMenubar.app` to `/Applications` 4. Launch the app — it will appear in your menu bar > **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. ## Setup Click the menu bar icon to open the popover. Each service is configured independently: ### Hyper credits 1. Enter your Hyper API key (starts with `sk-...`) in the **Hyper API Key** field 2. Click **Save** 3. Your balance appears immediately Get your key from [hyper.charm.land](https://hyper.charm.land). ### Claude Code limits (no setup) If you use the [Claude Code CLI](https://claude.com/claude-code), the popover shows how much of your Pro/Max plan you have used: the 5-hour and 7-day windows, and how long until the next reset. There 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 doesn't appear. - **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. - **The credentials are strictly read-only.** The app reads the access token Claude Code stored and spends it as-is. It never refreshes it, and never writes those credentials back — that is the CLI's job alone. If the token has expired, the Claude section says so and asks you to run `claude`. ### z.ai Coding Plan 1. Enter your z.ai API key in the **z.ai API Key** field 2. Click **Save** 3. Your 5-hour and weekly quota percentages appear immediately Get your key from [z.ai/manage-apikey](https://z.ai/manage-apikey/coding-plan/personal/my-plan). ## Settings Everything is configured from the popover — click the menu bar icon. | Setting | Where | Notes | | ------------------- | ------------------------------ | ------------------------------------------------------------------------------------ | | **Hyper API key** | **Hyper API Key** field → **Save** | Stored in the macOS Keychain. Clearing the field and saving deletes the stored key. | | **z.ai API key** | **z.ai API Key** field → **Save** | Stored separately from the Hyper key. | | **Launch at login** | **Launch at Login** toggle | Registers the app with `SMAppService`. | | **Notifications** | System permission prompt | macOS asks on first launch (only if not already decided). Required for low-balance alerts. | | **Refresh now** | **Refresh** button | Forces an immediate fetch, on top of the automatic refresh. | | **Refresh interval**| **Refresh every** picker | Choose 1m, 5m, 15m, or 30m. Defaults to 5m. Stored in `UserDefaults` and persisted across launches. | The app also refreshes automatically when your Mac wakes from sleep. ## Build from source ### Prerequisites - macOS 14.0+ (Sonoma) - Xcode 15+ - [xcodegen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`) ### Steps ```bash # Clone the repository git clone https://github.com/ilyakooo0/usage-menubar.git cd usage-menubar # Generate the Xcode project xcodegen generate # Open in Xcode open UsageMenubar.xcodeproj # Or build from the command line xcodebuild \ -scheme UsageMenubar \ -configuration Release \ -archivePath build/UsageMenubar.xcarchive \ archive \ CODE_SIGNING_ALLOWED=NO ``` ### Run tests ```bash xcodebuild test \ -scheme UsageMenubar \ -configuration Debug \ -destination "platform=macOS" ``` ## Versioning Versions follow the format `YYYY.MM.DD.HHHH` where `HHHH` is the UTC hour and minute (24-hour). | Commit time (UTC) | Version | | ------------------------ | ----------------- | | 2026-07-09 14:23 | `2026.07.09.1423` | | 2026-01-01 00:00 | `2026.01.01.0000` | | 2026-12-31 23:59 | `2026.12.31.2359` | The version is derived automatically from the commit timestamp in the GitHub Actions release workflow. ## Project structure ``` usage-menubar/ ├── project.yml # xcodegen project definition ├── UsageMenubar/ │ ├── UsageMenubarApp.swift # @main App, NSStatusBar, timer, sleep/wake │ ├── ViewModel.swift # Observable state: balance, Claude usage, z.ai usage, refresh │ ├── MenuView.swift # SwiftUI popover view │ ├── CreditsChecker.swift # Hyper API client (GET /v1/credits) │ ├── ClaudeUsageClient.swift # Claude OAuth client (GET /api/oauth/usage) + models │ ├── ZaiUsageClient.swift # z.ai API client (GET /monitor/usage/quota/limit) │ ├── KeychainHelper.swift # Keychain wrapper (Hyper + z.ai keys) │ ├── VersionFormatter.swift # YYYY.MM.DD.HHHH formatting │ ├── Info.plist # LSUIElement=true (agent app) │ ├── UsageMenubar.entitlements # Network client; deliberately not sandboxed │ └── Assets.xcassets/ # App icon, accent color ├── UsageMenubarTests/ │ ├── CreditsCheckerTests.swift # Hyper API, retry, keychain, StubURLProtocol │ ├── ClaudeUsageClientTests.swift # Usage decoding, read-only credential store, retry │ ├── ZaiUsageClientTests.swift # z.ai API, retry, decoding │ ├── StatusBarTextTests.swift # Menu bar title formatting │ └── VersionFormatterTests.swift # Version formatting tests ├── .github/workflows/ │ └── release.yml # Build + release on push to master ├── README.md ├── LICENSE └── .gitignore ``` ## How it works 1. **Menu bar item**: The app creates an `NSStatusItem` with a variable-length button. Left-click toggles the popover; right-click opens a context menu (Refresh, Quit). 2. **Popover**: Clicking the menu bar item shows an `NSPopover` containing a SwiftUI `MenuView` with each configured service's data and the settings. 3. **Independent fetches**: `ViewModel.refresh()` runs the Hyper, Claude, and z.ai requests concurrently with `async let`. Any can fail, stall, or be unconfigured without affecting the others. 4. **Hyper API client**: `CreditsChecker` performs `GET https://hyper.charm.land/v1/credits` with `Authorization: Bearer `, retries transient failures with exponential backoff, and decodes `{"balance": }`. 5. **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`) and fetches `GET https://api.anthropic.com/api/oauth/usage`. It is strictly read-only — it never refreshes the token and never writes credentials back. Missing credentials are reported as "not configured" rather than as an error, and the popover hides the section. 6. **z.ai usage**: `ZaiUsageClient` fetches `GET https://api.z.ai/api/monitor/usage/quota/limit` and `GET https://api.z.ai/api/biz/subscription/list` concurrently with an API key the user enters in settings. Same retry pattern as the other clients. 7. **Keychain**: API keys are stored/retrieved via `KeychainHelper` using the macOS Security framework. Hyper and z.ai keys are stored separately. 8. **Timer**: A `Timer` fires at the user-selected interval (1m / 5m / 15m / 30m, default 5m); the app also refreshes on wake from sleep. 9. **Notifications**: When the Hyper 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. 10. **Agent app**: `LSUIElement=true` in `Info.plist` makes the app a background agent — no dock icon, no main window. 11. **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. ## License MIT — see [LICENSE](LICENSE).