@@ -4,24 +4,33 @@
44
55# UsageMenubar
66
7−A 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.
7+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.
88
99## Features
1010
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
11+- ⚡ **Hyper credits** — balance from `hyper.charm.land`, with sparkline trend, click-to-copy, and low-balance notifications
12+- 🤖 **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
13+- 🤖 **z.ai Coding Plan** — 5-hour and weekly token quota percentages, with reset countdowns. Enter your API key in settings
14+- 🔄 **Auto-refresh** — configurable interval (1m / 5m / 15m / 30m), plus refresh on wake from sleep
15+- 🖱️ **Right-click menu** — quick access to Refresh and Quit
16+- 🔒 **Keychain storage** — API keys stored securely in macOS Keychain
2117- 🚀 **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
2318- 🫥 **Agent app** — no dock icon, no main window; lives entirely in the menu bar
2419
20+## Menu bar
21+
22+Each service shows an emoji-prefixed segment when configured and above 0%:
23+
24+| Service | Emoji | Example |
25+|---------|-------|---------|
26+| Hyper balance | ⚡ | `⚡42` |
27+| Claude 5-hour | 🕐 | `🕐62%` |
28+| Claude 7-day | 📅 | `📅8%` |
29+| z.ai 5-hour | 🤖 | `🤖12%` |
30+| z.ai weekly | 📆 | `📆3%` |
31+
32+Unconfigured services don't appear at all — no placeholder, no icon.
33+
2534## Installation
2635
2736### Homebrew (recommended)
@@ -42,37 +51,47 @@ brew install --cask ilyakooo0/tap/usage-menubar
42511. Go to the [Releases page](https://github.com/ilyakooo0/usage-menubar/releases)
43522. Download the latest `UsageMenubar-*.zip`
44533. Unzip and move `UsageMenubar.app` to `/Applications`
45−4. Launch the app — it will appear in your menu bar as ⚡
54+4. Launch the app — it will appear in your menu bar
4655
4756> **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.
4857
49−### Set your API key
58+## Setup
5059
51−1. Click the ⚡ icon in your menu bar
52−2. Enter your Hyper API key (starts with `sk-...`) in the **API Key** field
53−3. Click **Save**
54−4. Your balance will appear immediately
60+Click the menu bar icon to open the popover. Each service is configured independently:
5561
56−You can get your API key from [hyper.charm.land](https://hyper.charm.land).
62+### Hyper credits
5763
58−### Claude Code limits (optional, no setup)
64+1. Enter your Hyper API key (starts with `sk-...`) in the **Hyper API Key** field
65+2. Click **Save**
66+3. Your balance appears immediately
5967
60−If 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.
68+Get your key from [hyper.charm.land](https://hyper.charm.land).
6169
62−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 simply doesn't appear; it is not treated as an error, and the Hyper balance is unaffected.
70+### Claude Code limits (no setup)
6371
64−Two things worth knowing:
72+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.
73+
74+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.
6575
6676- **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 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. (Refreshing rotates the refresh token, so an app doing it behind the CLI's back would race it and sign you out of your own terminal.) If the token has expired, the Claude section says so and asks you to run `claude`; once the CLI has refreshed on its own, the app picks the new token up on its next fetch.
77+- **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`.
78+
79+### z.ai Coding Plan
80+
81+1. Enter your z.ai API key in the **z.ai API Key** field
82+2. Click **Save**
83+3. Your 5-hour and weekly quota percentages appear immediately
84+
85+Get your key from [z.ai/manage-apikey](https://z.ai/manage-apikey/coding-plan/personal/my-plan).
6886
6987## Settings
7088
71−Everything is configured from the popover — click the ⚡ icon in the menu bar.
89+Everything is configured from the popover — click the menu bar icon.
7290
7391| Setting | Where | Notes |
7492| ------------------- | ------------------------------ | ------------------------------------------------------------------------------------ |
75−| **API key** | **API Key** field → **Save** | Stored in the macOS Keychain. Clearing the field and saving deletes the stored key. |
93+| **Hyper API key** | **Hyper API Key** field → **Save** | Stored in the macOS Keychain. Clearing the field and saving deletes the stored key. |
94+| **z.ai API key** | **z.ai API Key** field → **Save** | Stored separately from the Hyper key. |
7695| **Launch at login** | **Launch at Login** toggle | Registers the app with `SMAppService`. |
7796| **Notifications** | System permission prompt | macOS asks on first launch (only if not already decided). Required for low-balance alerts. |
7897| **Refresh now** | **Refresh** button | Forces an immediate fetch, on top of the automatic refresh. |
@@ -138,18 +157,21 @@ usage-menubar/
138157├── project.yml # xcodegen project definition
139158├── UsageMenubar/
140159│ ├── UsageMenubarApp.swift # @main App, NSStatusBar, timer, sleep/wake
141−│ ├── ViewModel.swift # Observable state: balance, Claude usage, refresh, notifications
160+│ ├── ViewModel.swift # Observable state: balance, Claude usage, z.ai usage, refresh
142161│ ├── MenuView.swift # SwiftUI popover view
143−│ ├── CreditsChecker.swift # API client (GET /v1/credits)
162+│ ├── CreditsChecker.swift # Hyper API client (GET /v1/credits)
144163│ ├── ClaudeUsageClient.swift # Claude OAuth client (GET /api/oauth/usage) + models
145−│ ├── KeychainHelper.swift # Keychain wrapper for API key
164+│ ├── ZaiUsageClient.swift # z.ai API client (GET /monitor/usage/quota/limit)
165+│ ├── KeychainHelper.swift # Keychain wrapper (Hyper + z.ai keys)
146166│ ├── VersionFormatter.swift # YYYY.MM.DD.HHHH formatting
147167│ ├── Info.plist # LSUIElement=true (agent app)
148−│ ├── UsageMenubar.entitlements # Network client; deliberately not sandboxed
168+│ ├── UsageMenubar.entitlements # Network client; deliberately not sandboxed
149169│ └── Assets.xcassets/ # App icon, accent color
150170├── UsageMenubarTests/
151−│ ├── CreditsCheckerTests.swift # API, retry, fractional balance, keychain tests
171+│ ├── CreditsCheckerTests.swift # Hyper API, retry, keychain, StubURLProtocol
152172│ ├── ClaudeUsageClientTests.swift # Usage decoding, read-only credential store, retry
173+│ ├── ZaiUsageClientTests.swift # z.ai API, retry, decoding
174+│ ├── StatusBarTextTests.swift # Menu bar title formatting
153175│ └── VersionFormatterTests.swift # Version formatting tests
154176├── .github/workflows/
155177│ └── release.yml # Build + release on push to master
@@ -160,18 +182,18 @@ usage-menubar/
160182
161183## How it works
162184
163−1. **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).
164−2. **Popover**: Clicking the menu bar item shows an `NSPopover` containing a SwiftUI `MenuView` with the balance, sparkline, API key field, and settings.
165−3. **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).
166−4. **Keychain**: The API key is stored/retrieved via `KeychainHelper` using the macOS Security framework. `save()` returns a `Bool` indicating success.
167−5. **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.
168−6. **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.
169−7. **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.
170−8. **Agent app**: `LSUIElement=true` in `Info.plist` makes the app a background agent — no dock icon, no main window.
171−9. **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` with `Authorization: Bearer` and `anthropic-beta: oauth-2025-04-20`. It is strictly read-only — it never refreshes the token and never writes credentials back. The token endpoint rotates the refresh token, so an app that also refreshed would race the CLI and sign the user out of their own terminal. When the token is spent (401/403), the app says so and points at `claude`; the next fetch (every 1–5 min, credentials re-read each time) picks up whatever the CLI refreshed on its own. Missing credentials are reported as "not configured" rather than as an error, and the popover hides the section.
172−10. **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.
185+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).
186+2. **Popover**: Clicking the menu bar item shows an `NSPopover` containing a SwiftUI `MenuView` with each configured service's data and the settings.
187+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.
188+4. **Hyper API client**: `CreditsChecker` performs `GET https://hyper.charm.land/v1/credits` with `Authorization: Bearer <key>`, retries transient failures with exponential backoff, and decodes `{"balance": <int>}`.
189+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.
190+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.
191+7. **Keychain**: API keys are stored/retrieved via `KeychainHelper` using the macOS Security framework. Hyper and z.ai keys are stored separately.
192+8. **Timer**: A `Timer` fires at the user-selected interval (1m / 5m / 15m / 30m, default 5m); the app also refreshes on wake from sleep.
193+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.
194+10. **Agent app**: `LSUIElement=true` in `Info.plist` makes the app a background agent — no dock icon, no main window.
17319511. **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.
174196
175197## License
176198
177−MIT — see [LICENSE](LICENSE).
199+MIT — see [LICENSE](LICENSE).