Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0ee01b7
feat: limits widget, session health, efficiency grader, live session …
changsunglim May 22, 2026
7b1a186
docs: README fork notice + document new features
changsunglim May 22, 2026
7474b04
docs: add DISCLAIMER (unaffiliated, plan budgets approximate, no tele…
changsunglim May 22, 2026
ae561da
feat: anchored weekly reset window with manual sync + auto-advance
changsunglim May 27, 2026
e85c3f3
feat: session 5h anchor + manual edit form (datetime + percent)
changsunglim May 27, 2026
29d4624
fix(dashboard): edit form UX — reset anchor to now, Enter/Esc keys
changsunglim May 27, 2026
1344c4d
feat(session): rename label, bidirectional time-remaining input, fix …
changsunglim May 27, 2026
978d193
feat(weekly): remove session 5h widget; configurable window length (3…
changsunglim May 27, 2026
8187c7e
feat(filter): add Custom range to global dashboard filter
changsunglim May 27, 2026
016b6b3
Delete docs/screenshot.png
changsunglim May 28, 2026
fe5d1a9
Merge feat/anchored-weekly-reset into main
changsunglim May 29, 2026
30ecc63
fix(weekly): move save_at default to API handler so tests with fake c…
changsunglim May 29, 2026
ba7130b
fix(weekly): per-model cost-weighted billable tokens
changsunglim May 29, 2026
4a7b0a3
docs: add HANDOFF.md setup guide for fresh-Mac install
changsunglim May 29, 2026
5349fc1
fix(limits): add model column to session-health query
changsunglim Jun 5, 2026
be7d436
feat(cli): add --no-browser flag to dashboard for background/launchd use
changsunglim Jun 10, 2026
90277c8
feat(limits): read Anthropic's real usage API instead of estimating
changsunglim Jun 29, 2026
760675c
fix(db): WAL + busy_timeout + serialized scans to stop "database is l…
changsunglim Jun 29, 2026
c0b916a
fix(dashboard): patch raw-string JS syntax error + warm DB on startup
changsunglim Jun 29, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
__pycache__/
server.log
.DS_Store
*.pyc
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 2026-05-29

- Fix weekly model usage miscount: `_billable` now applies per-model cost weights (Opus ~5×, Sonnet 1×, Haiku ~0.25×) anchored to Sonnet-equivalent plan caps. Previously summed all model tokens 1:1, skewing the all-models bar and per-model breakdown on Opus-heavy weeks (cause of recurring manual edits)
- `cache_creation` now weighted 1.25× input; `cache_read` stays 0.1×
- Add accuracy disclaimer under the weekly card: estimate may drift ±1–2pp from Claude Settings → Usage; use Sync/Edit to recalibrate

## 2026-05-27

- Fix weekly all-models bar not reflecting Anthropic's per-user reset: the previous rolling 7d sum drifted after Anthropic's actual reset fired
- Anchor weekly window per install (persisted at `~/.claude/usage-weekly-anchor.json`), auto-advance after expiry by the first turn observed in the new window
- Add `Sync reset to now` button on the weekly card for manual override (use when Claude Settings → Usage shows a fresh 0%)
- Replace "7d rolling" footer text with a live countdown to the next reset, plus anchor source ("manual" / "auto")
- New POST `/api/weekly/sync-reset` endpoint

## 2026-04-09

- Fix token counts inflated ~2x by deduplicating streaming events that share the same message ID
Expand Down
35 changes: 35 additions & 0 deletions DISCLAIMER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Disclaimer

This is an **unofficial, community-maintained fork** of [phuryn/claude-usage](https://github.com/phuryn/claude-usage). It is **not affiliated with, endorsed by, or sponsored by Anthropic, PBC**.

## Trademarks

"Claude" and "Anthropic" are trademarks of Anthropic, PBC. Their use in this project is purely descriptive — to identify the product whose local usage logs this tool reads. No endorsement is implied.

## Plan budgets are approximations

Anthropic does not publish exact token caps for Pro, Max 5×, or Max 20× plans. The values in `limits.py` (`PLAN_BUDGETS`) are best-effort estimates calibrated against observed usage. They may be wrong. Do not rely on them for billing, capacity planning, or any decision with real financial impact.

If your real session is throttled before the bar shows full — or runs longer than the bar predicts — that is expected. The bar is a directional indicator, not a contract.

## API endpoint probing

`limits.py` attempts to read plan metadata from undocumented Anthropic endpoints (`/api/oauth/profile`, `/api/account`) using your locally-stored OAuth token. These calls:

- Are wrapped in try/except and fail silently if the endpoints change or refuse the request
- Make at most one request per 24 hours (cached at `~/.claude/usage-plan-cache.json`)
- Are skipped entirely if `CLAUDE_USAGE_PLAN` is set in your environment

If you prefer zero network calls to Anthropic, set `CLAUDE_USAGE_PLAN=pro` (or your plan) in your shell profile or launchd plist.

## No data leaves your machine

This tool reads only local files (`~/.claude/projects/*.jsonl`) and writes only to a local SQLite database (`~/.claude/usage.db`). The HTTP server binds to `localhost` by default. No telemetry. No analytics. No remote logging.

## Cost estimates

Costs shown are calculated using public Anthropic API list pricing. If you use Claude Code via a Pro or Max subscription, your actual cost is the flat subscription fee — the displayed "API equivalent cost" is informational only.

## No warranty

Provided as-is under the MIT License. See [LICENSE](LICENSE).
121 changes: 121 additions & 0 deletions HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Claude Usage Dashboard — Setup Handoff

Self-contained guide to run this tracker on a fresh Mac. No build step, no
third-party packages — Python standard library only.

---

## 1. Prerequisites

- **macOS** with Claude Code installed and used at least once (so local JSONL
logs exist under `~/.claude/`).
- **Python 3.8+** — check with:
```
python3 --version
```
Ships with macOS. If missing: `brew install python`.

That's it. No `pip install`, no virtualenv.

---

## 2. Get the code

### Option A — clone from GitHub (recommended, stays updatable)
```
git clone https://github.com/changsunglim/claude-usage.git
cd claude-usage
```

### Option B — from the handoff archive
Copy `claude-usage-handoff.tar.gz` to the new Mac, then:
```
tar -xzf claude-usage-handoff.tar.gz
cd claude-usage
```

---

## 3. Run it

```
python3 cli.py dashboard
```

- Scans `~/.claude/` logs → builds local SQLite DB → opens browser at
`http://localhost:8080`.
- Custom port: `python3 cli.py dashboard --port 9000`
- Custom projects dir: `python3 cli.py dashboard --projects-dir /path/to/.claude/projects`

CLI-only views (no browser):
```
python3 cli.py week # last 7 days, per-day + by-model
python3 cli.py today # today's usage
```

---

## 4. Weekly limit accuracy (read this)

The **Weekly · All Models** card estimates your Anthropic plan usage from
local logs. It is a **heuristic**, not Anthropic's official meter:

- Token cost-weights are approximate: **Opus ~5×, Sonnet 1×, Haiku ~0.25×**
(anchored to Sonnet-equivalent plan caps in `limits.py` → `MODEL_WEIGHTS`).
- `cache_creation` weighted 1.25× input; `cache_read` 0.1×.
- Expect **±1–2 percentage points** of drift vs **Claude Settings → Usage**.

### Recalibrate when it drifts
On the weekly card:
- **Sync reset to now** — click right after Claude Settings shows a fresh 0%.
Sets the 7-day anchor to this moment.
- **Edit…** — manually enter the anchor time + the % Claude Settings shows
right now, if you missed the reset moment.
- **Clear** — drop the manual override, return to auto-detection.

Anchor persists at `~/.claude/usage-weekly-anchor.json`.

### Tuning the weights (optional)
If your plan consistently reads high/low on Opus-heavy or Haiku-heavy weeks,
edit `MODEL_WEIGHTS` at the top of `limits.py`. Compare the dashboard % to
`claude /usage` over a known-model week and nudge the `"in"`/`"out"` factors.

---

## 5. Plan detection

Auto-detects Pro / Max 5× / Max 20× via macOS keychain OAuth token
(`Claude Code-credentials`). If detection fails it defaults to **Pro**
(23M weekly Sonnet-equivalent tokens). Caps live in `PLAN_BUDGETS` in
`limits.py`.

---

## 6. Updating later

```
cd claude-usage
git pull origin main
```
Then re-run `python3 cli.py dashboard` (auto-rescans). Use the **Rescan**
button in the UI to force a full DB rebuild.

---

## 7. Files

| File | Purpose |
|------|---------|
| `cli.py` | Entry point: `dashboard`, `week`, `today` commands |
| `dashboard.py` | HTTP server + HTML/JS dashboard + `/api/*` endpoints |
| `scanner.py` | Reads `~/.claude/` JSONL → SQLite DB |
| `limits.py` | Weekly window math, plan detection, cost weights |
| `tests/` | `python3 -m unittest discover tests` |

DB and anchor live under `~/.claude/` — **not** in this repo, so they carry
per-machine state and are safe to delete to reset.

---

*Unofficial fork of [phuryn/claude-usage](https://github.com/phuryn/claude-usage).
Not affiliated with Anthropic. See DISCLAIMER.md.*
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)
[![claude-code](https://img.shields.io/badge/claude--code-black?style=flat-square)](https://claude.ai/code)

> **Fork notice:** This is an unofficial fork of [phuryn/claude-usage](https://github.com/phuryn/claude-usage). Not affiliated with Anthropic. See [DISCLAIMER.md](DISCLAIMER.md). Added features:
> - Live limits widget (weekly stacked bar, per-model breakdown, plan auto-detect)
> - Session Health card with new-session recommendations
> - Multi-metric Token Efficiency grader (Cache Hit + Reuse Ratio + Output Discipline + Cost Efficiency)
> - Per-message live session view (click row → see each prompt with token totals)
> - Session titles extracted from first user prompt (replaces opaque project IDs)
> - Local timezone auto-detect (was UTC-only)
> - Noise filter: skill invocations / system reminders excluded from live view
> - Bug fix: `cutoff` → `start`/`end` ReferenceError that killed charts

**Pro and Max subscribers get a progress bar. This gives you the full picture.**

Claude Code writes detailed usage logs locally — token counts, models, sessions, projects — regardless of your plan. This dashboard reads those logs and turns them into charts and cost estimates. Works on API, Pro, and Max plans.
Expand Down Expand Up @@ -119,10 +129,62 @@ Costs are calculated using **Anthropic API pricing as of April 2026** ([claude.c

---

## Fork features

### Live limits widget
Top banner shows a **weekly all-models stacked bar** (Opus orange, Sonnet blue, Haiku green) with per-model breakdown beneath: `opus X% · 5.5M sonnet Y% · 550k haiku Z%`.

Plan auto-detection order:
1. macOS keychain → OAuth token → `api.anthropic.com/api/oauth/profile` (undocumented, may return None)
2. `CLAUDE_USAGE_PLAN` env var
3. Default `pro`

Plan budgets (approximate — Anthropic does not publish exact caps):

| Plan | Session 5h tokens | Weekly all-models |
|------|-------------------|-------------------|
| `pro` | 613k | 23M |
| `max_5x` | 3.07M | 115M |
| `max_20x` | 12.26M | 460M |

Override via `CLAUDE_USAGE_PLAN=max_5x python cli.py dashboard` or `/api/limits?plan=max_5x`.

Cache-read tokens weighted `0.1×` in billable totals to match Anthropic's cost-equivalent counting (~25% deviation in calibration).

### Session Health card
Heuristic warnings — "start new session" surfaces when any of:
- Context > 150k tokens
- Cache hit rate < 40%
- Avg > 60k tokens/turn
- Session age > 3h

States: `ok` / `info` / `warn` with colored border.

### Token Efficiency grader
A/B/C/D grade from weighted formula:
- Cache Hit 40%
- Reuse Ratio 25%
- Cost Efficiency 20%
- Output Discipline 15%

`?` info button in chart header reveals the formula. Recalcs every 30s with `/api/data`, respects date-range filter.

### Per-message live view
Click any session row → polls `/api/session/<id>` every 5s. Each user prompt shows token totals (input / output / cache_read / cache_creation), model, turn count, and tools used. Skill invocations and system reminders are filtered out so the list shows real human turns only.

### New endpoints
- `GET /api/limits?plan=<pro|max_5x|max_20x>` — weekly + session 5h budgets, plan, used/remaining
- `GET /api/session/<id>` — per-message breakdown for a session

Server uses `ThreadingHTTPServer` so concurrent `/api/data` + `/api/limits` calls don't block.

---

## Files

| File | Purpose |
|------|---------|
| `scanner.py` | Parses JSONL transcripts, writes to `~/.claude/usage.db` |
| `dashboard.py` | HTTP server + single-page HTML/JS dashboard |
| `limits.py` | Plan detection, session 5h window, rolling weekly budgets |
| `cli.py` | `scan`, `today`, `stats`, `dashboard` commands |
17 changes: 10 additions & 7 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ def cmd_stats():
conn.close()


def cmd_dashboard(projects_dir=None, host=None, port=None):
def cmd_dashboard(projects_dir=None, host=None, port=None, open_browser=True):
import webbrowser
import threading
import time
Expand All @@ -371,12 +371,13 @@ def cmd_dashboard(projects_dir=None, host=None, port=None):
host = host or os.environ.get("HOST", "localhost")
port = int(port or os.environ.get("PORT", "8080"))

def open_browser():
time.sleep(1.0)
webbrowser.open(f"http://{host}:{port}")
if open_browser:
def _open():
time.sleep(1.0)
webbrowser.open(f"http://{host}:{port}")

t = threading.Thread(target=open_browser, daemon=True)
t.start()
t = threading.Thread(target=_open, daemon=True)
t.start()
serve(host=host, port=port)


Expand All @@ -390,8 +391,9 @@ def open_browser():
python cli.py today Show today's usage summary
python cli.py week Show last 7 days (per-day + by-model)
python cli.py stats Show all-time statistics
python cli.py dashboard [--projects-dir PATH] [--host HOST] [--port PORT]
python cli.py dashboard [--projects-dir PATH] [--host HOST] [--port PORT] [--no-browser]
Scan + start dashboard
(--no-browser: don't auto-open a tab; for launchd/background)
"""

COMMANDS = {
Expand Down Expand Up @@ -423,6 +425,7 @@ def parse_named_arg(args, flag):
projects_dir=projects_dir,
host=parse_named_arg(rest, "--host"),
port=parse_named_arg(rest, "--port"),
open_browser="--no-browser" not in rest,
)
elif command == "scan" and projects_dir:
cmd_scan(projects_dir=projects_dir)
Expand Down
Loading