Release notes
You can now freely switch between various usage panel styles to suit your mood. The default panel, which previously only had one look, has been rebuilt from the ground up using modern web technologies (HTML/CSS). This not only adds subtle press-shrink feedback to buttons and reduces the white flash on opening, but also introduces several new choices: a dark Minimal style, a medical monitor waveform (ECG, where the waveform amplitude scales with usage percentage and higher burn rates produce more intense rhythms), a digital-rain Matrix style, and a localized Taiwan panel featuring its flag. It's like upgrading a basic digital watch that only tells time into a smartwatch where you can download cool animated faces anytime, and it runs smoother than ever.
You can now keep track of your Antigravity usage simultaneously, so you don't have to worry about overspending on a third AI tool. The panel now features a dedicated card for it, clearly splitting your current usage (Session) and weekly cap (Weekly). Even smarter, if you haven't touched your quota at all, it politely hides the reset time instead of endlessly displaying a meaningless "reset in ~24h". It's like your financial manager not only keeping an eye on your original two wallets but now taking care of a third new one as well, and never bothering you when there's no money moving inside it.
You can now gauge how tight your usage is at a mere glance, without pausing to read the exact numbers. The progress bars for each tool are no longer stuck on a single color; they now dynamically shift warning colors based on how much quota you've burned: below 50% stays the original brand color (Claude's orange or Codex's cyan), over half turns an amber yellow to remind you, and reaching 80% or above flashes straight to a warning red. It's just like a car's gas gauge—green normally, yellow when running low, and blaring red when it's hitting empty—allowing you to catch the danger in your peripheral vision instantly.
Your screen will no longer suffer annoying momentary freezes when you click the menu bar. Previously, every usage refresh would cause the interface to stutter slightly; these read operations have now been shoved to the background to ensure it opens instantly. In addition, the system has fixed a mountain of minor bugs that might have startled you: cost estimates no longer get permanently stuck on stale numbers when your network is flaky, the interface stops throwing random errors, and the logic for grabbing usage data (including filtering out old logs and isolating corrupted record files) is now far more precise. The pricing cache (saving fetched prices locally to avoid looking them up again) even expires automatically after seven days so you aren't staring at outdated, expensive numbers after a model price drop. It's like asking your butler to stop sorting the bills in front of you in the living room, and instead quietly balance the books in the back office before handing you the final tally — never interrupting your actual work.
You no longer have to crack open a terminal and type a bunch of commands just to install and run it. The tool has been officially renamed to "usage" and neatly bundled into a standalone macOS application (.app) that you can simply double-click to launch. No matter which folder you drop it in, the built-in hook (a script that grabs usage in the background) automatically recognizes its surroundings and finds the correct paths to install itself. If you've used the older version before (back when it was called "usag"), it politely sweeps away specific v0.1.x leftover paths and startup items, while explicitly leaving your existing configuration paths and binary names untouched so it won't break your current setup. It's like taking a piece of furniture that previously required you to read a manual and screw it together yourself, and turning it into a fully assembled product straight out of the box — it even takes out specific previous-generation trash for you while preserving the layout you're used to, and comes with a much prettier name printed on it.
Original changelog
Original changelogPanel system completely revamped with multiple cool themes to choose from
**Default panel now renders via WKWebView + HTML/CSS**: the classic default panel moved to a shared HTML/CSS layer, paving the way for a future Windows version; macOS still embeds it in `NSPopover` via `WKWebView`.
WKWebView integration adds a JS bridge (refresh / quit / switch), preload support, and a dark backing layer to remove launch-time white flash; panel switching tears down the web view to break retain cycles.
Panel buttons now have pressed-depth and subtle scale feedback on click.
Removed the CoreGraphics `panels/classic.py` implementation in favor of `HTMLPanel`.
**ECG panel**: medical-monitor style panel. `ECGView` drives a dual-channel ECG waveform animation via `NSTimer` at 80 ms — LEAD A for Claude Code, LEAD B for Codex. Waveform amplitude scales with quota usage percent; higher burn rate produces more intense rhythms. Text labels and waveform zones are separated into fixed vertical sections so they never overlap.
**Matrix panel (駭客任務)**: animated digital-rain panel — black background, cascading katakana + digit characters in Matrix green. `MatrixRainView` is driven by an `NSTimer` at 80 ms; each tick draws one bright head glyph and a 10-character fading trail per column. Card areas use a translucent dark-green fill with green borders; all buttons and headers use terminal bracket style (`[ SWITCH ]`, `[ REFRESH ]`, `[ EXIT ]`); rate/status/today labels use uppercase English prefixes.
**Panel switching system**: a `⇄ Switch panel` button in the popover top-right opens an `NSMenu` of all registered panels; the selected panel applies immediately and is persisted via `NSUserDefaults` (key `usage.activePanelId`), so the last choice survives restarts.
**Classic panel**: the original two-card + footer layout, with the switch button embedded in the Claude card's top-right and a new `ClassicSwitchButton` that stays legible in both light and dark mode.
**Taiwan panel**: red-on-white themed panel (a 20-line `ThemeConfig`), with a top header bar containing the TAIWAN flag icon, the "台灣用量監控" title, and the switch button. Popover height grows from 574 → 672 when this panel is active.
New `panels/` module: `base.py` provides the `Panel` Protocol, `ThemeConfig` dataclass, generic `ThemedPanel`, and `NSUserDefaults` helpers; `classic.py` / `taiwan.py` are concrete panels; `__init__.py` provides the panel registry (`get_panel(id)`, `all_panels()`, with classic fallback for unknown ids).
New `assets/taiwan.png`, registered in `setup_app.py`'s `resources` list so it ships inside the `.app` bundle.
`menubar.py` shrunk significantly (1041 → 524 lines): all popover drawing and layout moved into `panels/`; `PopoverViewController` is now a lightweight container that rebuilds its content view from the active `Panel`; `AppDelegate` gains `switchPanel:` / `selectPanel:` and `_set_active_panel_id` to drive panel transitions.
Added `tests/test_panels.py` (11 cases) covering: panel registry contents, each panel's `preferred_size`, `NSUserDefaults` round-trip, unknown-id fallback, `ThemeConfig` application, and `ThemedPanel` height difference with/without a header.
README: replaced mainland Chinese phrasing ("打API", "打網路") with standard Taiwanese usage ("呼叫 API", "連網路").
Popover redesign:
- Claude Code / Codex cards now show a branded icon in the header (`claude.webp` / `codex.webp`).
- Card surfaces and progress fills switched to gradient (`NSGradient`); accent colours brightened (Claude leans warm orange, Codex leans cyan).
- "Refresh now" and "Quit" buttons replaced with a custom `ActionButton` that draws primary / secondary styles (primary uses the accent gradient, secondary uses a translucent bordered fill).
- Rate / status / today-cost line wrapped in its own card so the three sections share one visual language.
- Spacing, weights, tracking, and muted colours re-tuned for stronger contrast in both Light and Dark Mode.
Popover redesigned: Claude / Codex sections now sit in subtle inset cards, with refined spacing, font weights, and muted footer text. Card fill adapts to Dark / Light appearance.
Original changelogAntigravity support added to keep a tight grip on your third AI wallet
**Antigravity quota tracking**: the popover now shows three cards for Claude Code, Codex, and Antigravity; the Antigravity card has two rows for current usage (Session) and weekly cap (Weekly).
Antigravity buckets with `remainingFraction == 1.0` (unused) now hide reset times, avoiding the API's rolling placeholder from appearing as an endless "reset in ~24h".
`antigravity_loader` now splits quota buckets by reset window: shorter windows become Session and longer windows become Weekly. When Google's API exposes a weekly bucket, Weekly fills automatically.
Original changelogProgress bar colors shift with usage so you can spot an empty tank instantly
**Minimal panel**: dark minimal panel inspired by Linear / Raycast. Near-black background (`#0A0A0C`), rounded cards, accent-coloured progress bars (Claude warm-orange / Codex cyan). Each card has a Session row (26pt number) and a Weekly row (24pt), each with a label, percentage text, 2px progress bar, and reset countdown. Footer card presents rate, status, and today's cost as a two-column label-left / value-right layout with horizontal dividers between rows. Three-button bar (Refresh / Quit / Switch panel) uses accent gradient for primary and translucent bordered fill for secondary.
Progress bars now change colour based on usage level: below 50% keeps the brand colour, 50–80% shifts to amber, ≥ 80% turns red.
Progress bars now change colour based on usage level: below 50% keeps the brand colour (Claude orange / Codex cyan), 50–80% shifts to amber, ≥ 80% turns red.
Original changelogRefreshing no longer freezes, with massive under-the-hood bug fixes and performance boosts
New dependencies: `pyobjc-framework-WebKit`, `pyobjc-framework-Quartz`.
Tightened `codex_loader` / `history_loader._as_int` typing with `max(0, int(value))`.
Use Quartz `CGColorCreateGenericRGB` to create the `CGColorRef`, eliminating the launch-time `ObjCPointerWarning`.
`pricing.py`: `_pricing_cache` now records its source (cache / fetched / fallback) and timestamp. Fallback results use a short 10-minute TTL so cost estimates no longer stay stuck on stale fallback values after offline startup when the network recovers.
`README.md` / `README.en.md`: added a sentence to the pricing table section noting that first launch without a cache does a synchronous fetch and may take ~10 seconds on slow networks, so new users don't think the app is hung.
New `tests/test_main.py` (9 cases) covering `parse_args` and `_apply_outcome` behaviour.
New `tests/test_menubar.py` (14 cases) covering pure helpers: `format_human_time`, `_format_percent`, `_bar_color`, `_quota_row`, `_missing_row`, `_today_title(mock=True)`, `_empty_state`, `_error_state`, `_popover_size`.
Added 4 new cases in `tests/test_pricing.py` covering fallback TTL, retry-then-fetched, and no-refetch for fetched / cache sources.
Test suite grew from 63 → 90 passed.
`codex_loader.py`: use last token-event timestamp for `hours_back` filtering; per-file fault-tolerant sort.
`history_loader.py`: composite dedup key when id fields are absent; reject bool and negative token values.
README: corrected three factual inaccuracies (network claim, Codex data source, cost is an estimate).
README: added Quick start table, Download the app section, and Troubleshooting table.
`codex_loader.py`: use the last token-event timestamp for `hours_back` filtering so long sessions no longer drop recent tokens; per-file fault-tolerant sort so a single bad file doesn't break the entire session scan.
`history_loader.py`: fall back to a composite dedup key when `message_id` / `request_id` is absent; reject bool and negative token values.
README now ships 5 badges (CI status, latest release, Python version, platform, license).
Added bilingual `CONTRIBUTING.md` / `CONTRIBUTING.en.md`: spells out what issues / PRs should include, the three checks required before merge, off-limits technical identifiers and UI constants, the bilingual CHANGELOG rule, and commit message style.
All tests use `monkeypatch` to redirect path constants; **real `~/.claude` and `~/.codex` are never touched** (verified by before/after mtime comparison).
Test count: 44 → 60. Runtime: 0.04s → 0.08s.
Bumped `actions/setup-python` from v5 to v6 (v6 runs on Node.js 24). GitHub had been warning that v5 runs on Node.js 20 and the runner will force Node 24 after 2026-09-16; pre-empting the breakage.
Tightened `menubar.py` mypy config from a blanket `# mypy: ignore-errors` to `disable-error-code="import-untyped,misc"`, which only suppresses PyObjC's missing stubs and dynamic base-class errors. Real type errors (the class of bug behind `tracker.sample`'s `AttributeError`) will now be caught.
`docs/popover.png` updated to the new look.
Live data no longer collapses to `--` with `狀態:錯誤 (AttributeError)`. The stale `self.tracker.sample(...)` call in `menubar.py` (left over from 0.1.2's `sample()` removal) raised `AttributeError` on every successful refresh; dropped the call. `tracker.group()` already reads history entries directly.
Applied `ruff format` across the project (formatting only; no logic changes).
`.gitignore` now excludes `*.egg-info/` and `.pytest_cache/`.
English README (`README.en.md`) and a language switcher at the top of both READMEs.
CI runs `pytest -v` after ruff and mypy.
GitHub Actions CI runs `ruff check` and `mypy` on push to main and pull requests (macos-latest runner, uv-managed deps).
Issue templates (bug report, feature request) and pull request template under `.github/`.
`menubar.py`: I/O moved off the AppKit main thread (background `threading.Thread` + `performSelectorOnMainThread_withObject_waitUntilDone_`), eliminating the periodic UI freeze on each refresh tick. A `_refresh_in_flight` flag prevents re-entry.
`menubar.py`: divider lines re-centered between provider blocks (first_y=178, second_y=352). "今日" status line returned to 12pt to match the rest of the footer.
`pricing_cache.json` expires after 7 days based on mtime, so stale prices don't linger after a model price drop.
Seven previously silent `except OSError` sites in `pricing.py`, `codex_loader.py`, and `history_loader.py` now log a warning before swallowing the error.
`blocks.py` — unused dead code.
Original changelogRenamed and bundled: double-click to launch with zero terminal commands and automatic cleanup
`scripts/install-hook.sh`: wrap paths with `shlex.quote()` when generating the statusLine command, matching `setup_hook.py`. Prevents broken hook installs when the user's Python or hook path contains spaces.
`menubar.py` / `codex_loader.py`: silent `except` blocks now emit `logger.warning(exc_info=True)` when `USAGE_DEBUG=1`, otherwise stay quiet. Debug sessions no longer mistake parse failures for "Codex not installed".
Internal app identifiers changed from `usag` to `usage`: bundle id, filenames, launchctl label, and `~/.claude/` paths were renamed.
`setup_hook.py` now detects and clears old v0.1.x `usag` leftovers: hook script, settings statusLine, backup key, and status file.
`install-launchagent.sh` / `uninstall-launchagent.sh` now clean the old LaunchAgent plist and label automatically.
`usage_client.py` now falls back to the old `usag-status.json` path for upgrade compatibility.
Public app naming and internal bundle identifiers are now consistently `usage`.
`setup_app.py` now packages `usag_statusline.py` so the `.app` bundle ships the hook source.
`setup_hook.py` now resolves the hook source in both source-tree mode and `.app` bundle mode.
The popover now shows a one-click "立即安裝 hook" recovery button when the status file is missing.
`usage_client.py`: guard `rate_limits` sub-fields against non-dict values.
`setup_hook.py`: validate settings before writing; safely rebuild backup field if not a dict.
Sync status label changed from `usag-status` to `usage` to match the public-facing project name.
`setup_hook.py`: wrap interpreter and hook paths with `shlex.quote()` so hooks work when the project directory contains spaces (PR #1, thanks @DennisWei9898).
`usag_statusline.py`: replace `datetime.UTC` (Python 3.11+) with `timezone.utc` for compatibility with macOS system Python 3.9 (PR #1, thanks @DennisWei9898).
`usage_client.py`: guard `rate_limits` and its sub-fields against non-dict values.
`setup_hook.py`: validate `settings.json` structure before writing; safely rebuild the backup field if it is not a dict.
`setup_app.py` declares `claude.webp` / `codex.webp` as py2app `resources` so the `.app` bundle ships the icons.
`menubar.py` resolves icon paths via `NSBundle.mainBundle().pathForResource_ofType_`, so both the dev deployment (LaunchAgent runs `main.py` directly) and the `.app` bundle find the assets.
README's "How it gets the data" section now includes a mermaid diagram visualizing the `Claude Code → hook → JSON file → usage` chain, with `Anthropic API` explicitly drawn as **never called** (dashed broken line).
Added three new test files covering the three highest-risk "I/O / parse boundary" modules (previously zero coverage, the same class of code that produced the 0.1.2 → 0.1.3 "change one place, miss another" bug):
- `tests/test_usage_client.py`: `_read_status_file` with both paths missing / `USAG_STATUS` bad JSON / fallback to TT_STATUS; `_build_snapshot` missing fields / percent out-of-range clamp; `ClaudeUsageClient` outcomes in mock and real mode.
- `tests/test_codex_loader.py`: `load_entries` with missing sessions dir / valid JSONL / `hours_back` cutoff filter / bad JSON line / missing fields / `_parse_timestamp` across three ISO 8601 variants; `load_rate_limits` returns None when file missing / parses primary + secondary windows.
- `tests/test_setup_hook.py`: `setup` in a clean env / existing custom statusLine gets backed up / idempotent on repeat; `unsetup` restores backup / behaves cleanly when never installed; `_is_usag_hook` discriminator.
Public-facing name unified from `usag` to `usage`, matching the GitHub repo:
- `pyproject.toml`'s `name` changed from `"usag"` to `"usage"` (so PyPI / `pip list` now show `usage`).
- `README.md` / `README.en.md` headers and prose now say `usage`.
- `.github/ISSUE_TEMPLATE/bug_report.md` updated likewise.
**Intentionally unchanged** (to avoid breaking existing installs): all file paths, settings keys, and binary names keep the `usag` prefix — `~/.claude/usag-status.json`, `~/.claude/usag-statusline.py`, `~/Library/Logs/usag/`, `com.lollapalooza.usag` (LaunchAgent label), `usag.app` (bundle), `USAG_DEBUG` (env var), `settings.usag.previousStatusLine` (JSON key) are all untouched. The technical short name is `usag`; the public name is `usage`.
`pyproject.toml`'s `description` was rewritten from "在 macOS 終端機顯示 Claude Code 用量的繁中小工具" (terminal-only) to "usage — 在 macOS menu bar 顯示 Claude Code 用量的繁中小工具(也提供終端機 TUI)". The old description misrepresented the project as terminal-only; the new one reflects the menu-bar-first reality and aligns the displayed project name with the repo.
Release workflow (`.github/workflows/release.yml`) is now self-healing: after a tag is pushed, if the matching GitHub release does not exist yet, the workflow first creates it via `gh release create` (empty notes, target set to the tag's ref) and then uploads `usag.app.zip`. The "workflow assumes release already exists, upload fails" trap hit during 0.1.3 won't recur.
`pricing.py`: pricing cache moved from the package directory to `~/.claude/pricing_cache.json` so the read-only `.app` bundle can refresh the cache.
`UsageRateTracker.sample()` dead code (was a no-op called from `main._apply_outcome`).
py2app `.app` bundle build config (`setup_app.py`, `build_app.sh`) so users can run usag without a terminal.
GitHub Actions release workflow (`release.yml`) automatically builds `usag.app.zip` and attaches it to each tagged release.
pytest test suite under `tests/` covering `pricing`, `history_loader`, and `usage_rate` (44 tests, 89% line coverage).
`USAG_DEBUG=1` environment variable enables warning-level logger output for the previously silent OSError sites.
`usage_rate.py`: 30-second TTL cache for `group()`; stops re-scanning the last hour of JSONL on every TUI tick.
README: use `python3` instead of `python` (the uv venv only ships the `python3` symlink); documented `USAG_DEBUG`.
`setup_hook.py` and `pricing.py` use atomic writes (`tempfile.mkstemp` + `os.replace`); a crash mid-write no longer corrupts `~/.claude/settings.json` or `pricing_cache.json`.
`install-launchagent.sh` uses `BASH_SOURCE` to resolve the project directory; previously broke when run from anywhere other than the project root.
`uninstall-launchagent.sh` removes logs from `~/Library/Logs/usag/` (the actual location), not from the project directory.