feat(desktop,host-service): Usage tab — per-account quota meters and token-cost analytics

Merged
KI
3mo ago
+4435-2

Links

  • Research & decisions: plans/20260815-token-spend-twitter-feedback.md (Twitter-thread sentiment, tool landscape, ToS analysis, accuracy verification)
  • UI references: plans/20260816-quota-ui-mobbin-research.md · Marketing research: plans/20260816-quota-tools-marketing-research.md

Summary

This PR bundles two features behind a new top-level Usage dashboard tab:

  1. Official quota meters — per-account session/weekly % used with reset times, read from the providers' own quota endpoints (the number the CLI enforces, not a log-file estimate)
  2. Token/cost analytics — cost & token history from the providers' transcript logs, priced at API list rates, with a per-provider chart, model breakdown, and per-project attribution

Both handle multiple accounts: default homes, CLAUDE_CONFIG_DIR (comma-list), auto-discovered profile dirs (runway's model), per-profile Keychain items, and multiple ~/.codex* homes.


Part 1: host-service usage router

Why

Top user ask from the Twitter thread + Slack feedback: quota-limit % with reset countdowns beats raw token counts, and multi-account users pick the subscription with the most headroom when starting a task. Prior attempts existed (#5798, alchemistchaos/feat/minimal-ai-usage-meter) but placed the data layer in Electron main, which can only ever see the local machine — this router runs per-host.

What / How

  • usage.quota — fetches api.anthropic.com/api/oauth/usage and chatgpt.com/backend-api/wham/usage with the CLIs' own tokens. Tokens are strictly read-only: expired → token_expired status ("run `claude` to refresh"), never refreshed by us — a second client refreshing trips provider token-reuse protection and signs the user's CLI out. 60s promise-cache with rejection eviction.
  • usage.history — walks every profile's projects/ / sessions/ transcripts and prices them at API list rates. Runs as a worker-pool task (workers/tasks/usage.ts): the trees reach multiple GB (~4.6s scan on a heavy user), so parsing must stay off the host-service event loop. Deduped per window via dedupeKey.
  • profiles.ts — multi-account discovery: dot-dirs at ~ + dirs under ~/.config, accepted only when the dir's own .claude.json names an OAuth account (also yields the email with no API call); credentials from the dir's .credentials.json or its Keychain item Claude Code-credentials-<sha256(literal path)[0:8]> probed across path spellings. Codex: ~/.codex* dirs with a token-bearing auth.json, deduped by account email.

Key decisions

DecisionChoiceRationale
Quota sourceOfficial OAuth endpointsThe loudest criticism of T3 Code's usage launch was accuracy; log-derived estimates double-count. We show the number the CLI enforces
Claude dedupemessage.id + requestId, keep last occurrenceClaude Code writes one line per content block with a growing usage snapshot; first-wins undercounts output ~10% (found during verification)
Codex parsinglast_token_usage deltas + consecutive-duplicate suppressionCodex re-emits identical token_count events; after suppression, delta sums land within ~1% of Codex's own cumulative counters
Cache-write pricingPer 5m/1h tier from cache_creationFlat 1.25x (what #5798 did) misprices 1h writes, which bill 2x
Placementhost-service, not Electron mainRemote hosts have their own logins/transcripts; the desktop is a pure consumer

Accuracy verification

Per-model comparison against ccusage v20 for a full day matches token-exact and cent-exact (all fields, all three Claude models). Codex self-check across 136 sessions: within ~1% of Codex's own cumulative counters. Details in the plans doc.


Part 2: desktop Usage tab

What / How

  • New nav entry (gauge icon) in both sidebar variants → /usage route.
  • Quota cards per account: email + plan badge + source dir, Gemini-style meters (label, "N% used", thin bar, "Resets in 5d 13h · Aug 21" caption), amber ≥70% / red ≥90%, distinct expired-token and org-plan states.
  • History dashboard (layout follows the strongest patterns from the Mobbin/T3/ccusage research): API-rate cost headline with the honesty footnote ("* if billed at full API rate"), provider share rows, layered (not stacked) monotone area chart with Cost|Tokens + 7d/30d/90d toggles and crosshair tooltip, five metric tiles (incl. cache savings in dollars), per-model table, per-project bars — the attribution no menu-bar tracker has.
  • Chart palette (#d06a48 Claude / #1596d6 Codex) validated for CVD separation (ΔE ≥ 20), lightness band, and ≥3:1 contrast on both light and dark surfaces.

New components

  • usage/page.tsx + UsageView — route shell, quota cards
  • UsageHistorySection (+ UsageAreaChart, UsageMetricTiles, UsageModelTable, UsageProjectBars) — analytics
  • useHostUsageQuota / useHostUsageHistory — per-host queries via getHostServiceClientByUrl; history has no interval poll (expensive) and keeps previous data across range switches

Manual QA Checklist

Quota

  • Nav entry renders in expanded sidebar and collapsed icon rail, with active state
  • Real Codex account renders weekly windows, reset dates, credits balance
  • Expired Claude token shows the token_expired card with refresh hint
  • Multi-account: synthetic ~/.claude-testprofile (identity + expired credential) discovered and rendered as a second card with its email
  • A live second account with a valid token shows its own quota meters (needs a second real login)

History

  • 30d scan of 5GB / ~1,300 files completes in ~4.6s in the worker pool; UI stays responsive
  • Cost/Tokens toggle drives headline, share rows, and chart together
  • 7d/30d/90d range switch refetches; previous chart holds while loading
  • Synthetic transcript priced exactly ($1.026 hand-computed = $1.03 shown)
  • Verified against ccusage per-model to the cent (see plans doc)
  • Demo film + screenshots captured via CDP

General

  • App launches clean; no renderer console errors on the route
  • Windows/Linux host: Keychain probes correctly no-op (darwin-gated; not exercised on a real machine)

Testing

  • bunx turbo typecheck — host-service + desktop green
  • bun test — usage utils (15 tests) + host-service ratchets (no-main-loop-blocking, no-electron-coupling, worker pool) green
  • Biome clean on all touched paths
  • Live CDP verification against real accounts and real transcript data

Known Limitations

  • Cost is an API-list-rate estimate and labeled as such everywhere; subscription users don't spend this money.
  • Per-account history attribution is only possible per profile-directory — transcripts don't record which account made the call (same limit as every tool in this space).
  • Quota endpoints are undocumented and can change; failures degrade to unavailable cards, never crash the page.
  • History covers Claude + Codex only; other agents' transcripts are follow-ups.

Follow-ups (deferred to keep this reviewable)

  • Compact quota meter in the model/agent dropdown ("start on the account with most headroom") and sidebar-footer ambient meter — mount + policy logic already prototyped on alchemistchaos/feat/minimal-ai-usage-meter
  • Pace projection ("lasts until reset") on quota meters — pure 47-line formula extractable from #5798
  • Near-limit notifications, tray badge

Risks / Rollout

  • Risk: low — read-only feature, new routes/routers only; no schema or existing-behavior changes. Heaviest operation is sandboxed in the worker pool with a 110s task timeout.
  • Rollback: revert the PR; no persisted state to clean up.

Summary by cubic

Adds a Usage tab with per-account quota meters (official endpoints) and token/cost analytics from local transcripts, so users can see reset times and where usage happens by model and workspace. Also fixes manual quota refresh handling and replaces a forbidden project-name snapshot read with a basename(repoPath) fallback.

  • Host-service usage router: reads Anthropic api/oauth/usage and ChatGPT backend-api/wham/usage with existing CLI tokens (read-only; expired → token_expired), caches for 5 minutes, and discovers multiple accounts via profile dirs, CLAUDE_CONFIG_DIR (comma-list), macOS Keychain, and ~/.codex*.
  • History: worker task scans transcript logs, prices at API list rates, and returns daily series with provider/model/workspace breakdowns and per-session IDs; joins transcript cwd to workspace worktrees/projects; hardens scans (no symlink traversal, path dedupe, timestamp fallbacks, negative-token clamping) and merges default-slot credentials to avoid phantom expired accounts.
  • Desktop /usage: compact quota cards (reset captions, expired/unavailable states) and a history dashboard with Cost|Tokens and 7/30/90d, a layered per‑provider area chart with day inspection and provider toggles, metric tiles (incl. cache savings), per‑model table, and top workspaces/projects; drilldowns at /usage/workspace/:name, /usage/model/:providerModelKey, and /usage/workspaces with a Workspaces|Projects pivot and filters; clicking a session row copies its session ID.
  • Polling and refresh: quota polls every 5 minutes; manual refresh bypasses the host cache and now catches rejected updates.
  • Adds recharts.

Summary by CodeRabbit

  • New Features
    • Added a Usage dashboard accessible from the sidebar for Claude and Codex quota information, including usage meters, credits, account status, and reset times.
    • Added usage history with selectable 7-, 30-, and 90-day ranges and cost or token views.
    • Added charts and breakdowns by provider, model, and project, plus token, cache, savings, and estimated cost metrics.
    • Added refresh controls and clear loading, error, and unavailable states.