Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ as you cut a tag.

## [Unreleased]

## [0.2.0] - 2026-07-16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the 0.2.0 release link definitions.

[0.2.0] has no matching reference, and [Unreleased] still starts at v0.1.5. Add links for 0.2.0 and 0.1.6, then move the Unreleased baseline to v0.2.0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 14, Update the changelog reference definitions to add
links for releases 0.2.0 and 0.1.6, and change the Unreleased comparison
baseline from v0.1.5 to v0.2.0 while preserving the existing link format.


### Changed
- **Claude live limits now use their own sign-in — no more Keychain prompts.** The app used to
read the token Claude Code stored in your Keychain, but Claude Code re-creates that item on every
token refresh, which wiped the app's access and made macOS re-prompt at random. Now you **Sign in
with Claude** (Settings → Claude → **Add account**, or the banner in the Claude tab): the app runs
its own OAuth in your browser, mints its **own** token, and stores it in its **own** Keychain item
that it refreshes in place. A signed build reads it back with **zero prompts, ever** — and it
persists across restarts (the token refreshes silently; you sign in once).
- **Multiple accounts.** Add more than one Claude account (e.g. Personal + Work); each shows its own
live 5H / 7D limits. Remove an account any time in Settings → Claude.

### Removed
- The opt-in **Connect** toggle that read Claude Code's Keychain item (superseded by Add account).

## [0.1.6] - 2026-07-14

### Added
Expand Down
23 changes: 17 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,23 @@ Scripts/install-hooks.sh # activate the pre-commit personal-data guard
event timestamp** (not file mtime/position). Classify windows by **`window_minutes`**
(300 = 5h, 10080 = weekly), never by primary/secondary position; either can be null. Tail-read
(files reach tens of MB); never scan `logs_2.sqlite`.
- **Claude** — `GET api.anthropic.com/api/oauth/usage` (+ `/api/oauth/profile` for identity).
Token from Keychain `Claude Code-credentials-<hash>` — **enumerate, don't compute** the suffix.
Headers are mandatory: `Authorization: Bearer`, `anthropic-beta: oauth-2025-04-20`,
`User-Agent: claude-code/<ver>` (wrong UA → permanent 429s). Cache ≥ 180s. Multiple profiles
are auto-discovered by parsing `CLAUDE_CONFIG_DIR=…` from shell rc files; tokens are matched to
profiles by account email/UUID.
- **Claude** — `GET api.anthropic.com/api/oauth/usage` (+ `/api/oauth/profile` for identity), using
the app's **own** OAuth token (`Claude/ClaudeOAuth*`, `ClaudeTokenStore`, `ClaudeTokenProvider`),
**not** Claude Code's Keychain item (that item's ACL is wiped on Claude Code's every refresh →
recurring prompt; unfixable from the reader side). We run Claude Code's OAuth client
(`9d1c250a-…`, PKCE S256, loopback `http://localhost:<ephemeral>/callback`, authorize
`claude.ai/oauth/authorize`, token `platform.claude.com/v1/oauth/token`) and store the token in our
own generic-password item (`com.aiusagebar.AIUsageBar.claude-oauth`), keyed by account UUID,
updated **in place** (`SecItemUpdate`, never delete+add) so a signed build reads it with **no
prompt**. **Two different, endpoint-specific User-Agents** (the non-obvious gotcha): the *usage*
endpoint REQUIRES `User-Agent: claude-code/<ver>` (wrong UA → 429), but the *token* endpoint's edge
hard-429s `claude-code/*` and `curl/*` and only accepts the CLI's real transport UA — send
`axios/<ver>` there. Refresh tokens **rotate** (persist the new one; a `ClaudeTokenProvider` actor
serializes refreshes); `invalid_grant` on refresh is terminal → re-auth. Keychain gotcha:
`kSecReturnData` + `kSecMatchLimitAll` in one query is `errSecParam` (-50) — list attributes, then
read each item. Headers otherwise: `Authorization: Bearer`, `anthropic-beta: oauth-2025-04-20`.
Cache ≥ 180s. Config-dir profiles are still auto-discovered; live tokens are matched to them by
account email/UUID, and a signed-in account with no local profile gets its own live-only card.
- **Cost** — subscriptions have no per-token bill, so cost is an **equivalent API cost**:
local JSONL token counts × per-model pricing (`Pricing.swift`).
- **Gemini** — detection only; no local live quota exists.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Everything is local — no proxy, no telemetry, no account of ours.
| Provider | Source | Fidelity |
|---|---|---|
| **Codex** | Newest `token_count` event under the configured Codex root (defaults to `$CODEX_HOME`, then `~/.codex`) — real 5h/weekly %, resets, plan, credits, tokens. Zero-auth. | ✅ Full |
| **Claude** | `GET api.anthropic.com/api/oauth/usage` per profile (OAuth token from the Keychain **after you Connect**, account matched via `/api/oauth/profile`). Before connecting (or if the endpoint is unavailable) shows **local 5H / 7D** token activity + equivalent cost from `~/.claude` logs. | ✅ Full % |
| **Claude** | `GET api.anthropic.com/api/oauth/usage` per account, using a token the app mints via its **own** OAuth (**Add account**) and keeps in its **own** Keychain item — no prompt, refreshed in place. Before signing in (or if the endpoint is unavailable) shows **local 5H / 7D** token activity + equivalent cost from `~/.claude` logs. | ✅ Full % |
| **Gemini** | Detects `gemini-cli` and reads the selected configuration root (defaults to `~/.gemini`); shows the plan cap or "not detected". gemini-cli persists no live quota. | ⚠️ Best-effort |
| **Custom** | Any folder of `.jsonl` logs + the dot-paths you configure (e.g. `rate_limit.used_percent`, `rate_limit.resets_at`). Newest file, last matching line. | ✅ Whatever the tool writes |

Expand Down Expand Up @@ -116,11 +116,13 @@ Scripts/build-app.sh --install # build → /Applications → launch
- `Scripts/build-app.sh --install` — copy to `/Applications` (recommended; needed for launch-at-login)

Nothing prompts for credentials at launch. To see **live** Claude limits, open the Claude tab and
click **Connect** (or Settings → Claude) — macOS then asks once to **Allow** Keychain access for
the Claude token; choose **Always Allow**. Your account, cost, and **last 5H / 7D local usage**
(derived from your `~/.claude` logs) show without connecting.
click **Add account** (or Settings → Claude) — this signs in with Claude in your browser and mints
the app's **own** token, stored in its **own** Keychain item. Because the app created that item, it
reads it back with **no macOS prompt**, and refreshes it in place, so you sign in once and it sticks
across restarts. Add more than one account (Personal + Work) to track each separately. Your account,
cost, and **last 5H / 7D local usage** (from your `~/.claude` logs) show without signing in.
(Building locally auto-signs with an installed Developer ID for a stable signature; released builds
are notarized, so that one-time grant sticks.)
are notarized, so the Keychain item stays readable across launches.)

## Settings

Expand Down
98 changes: 0 additions & 98 deletions Sources/AIUsageBarCore/Claude/ClaudeKeychain.swift

This file was deleted.

Loading
Loading