Skip to content

About

Yet Another Usage Tracker — local, zero-dependency weekly Claude Code usage pacing dashboard built from your own transcripts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

17 Commits

Folders and files

Repository files navigation

Yet Another Usage Tracker

A local, zero-dependency dashboard that paces your weekly Claude Code usage so you don't burn the allowance by Wednesday. It reads the transcripts Claude Code already writes to ~/.claude/projects/, computes what share of your weekly allowance you've spent, and tells you how much today gets.

Nothing leaves the machine. No API keys, no credentials, no network calls — the server binds to localhost and only reads files you already have.

Not affiliated with, endorsed by, or supported by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic. This tool estimates consumption from local transcript data; it is not an official usage meter, and the numbers are an approximation (see Known limits).

macOS and Windows both have always-on installers — a LaunchAgent on macOS, a built launcher exe plus a Startup-folder shortcut on Windows. The server and dashboard are plain Node and should run anywhere Claude Code does.

Status

  • scan — incremental reader for ~/.claude/projects/**/*.jsonl
  • weighting — relative consumption model incl. cache write/read multipliers
  • budget — weekly window, limit estimation, buffer+debt allocation
  • server — localhost HTTP + JSON API, zero dependencies
  • web — dashboard with drag/scroll availability calendar
  • percentages — all display in % of weekly allowance (no API dollars)
  • calibration — enter the % /usage shows, tool solves for the real limit
  • 5-hour block — start inferred from transcript activity
  • launchagent — always-on install script (macOS)
  • windows launcher — built exe + Start Menu/Desktop/Startup shortcuts
  • durable plan — atomic writes + serialized saves; settings survive concurrency
  • cross-file dedup — warm and cold scans agree (was inflating toward 2x)
  • setup gate — blocking dialog collects the reset window + both /usage readings first
  • live readings — status-line hook forwards the real 5h / weekly % and reset times
  • advisor usage — advisor calls are counted, priced at the advisor's own model
  • Full Disk Access — grant it to node so the LaunchAgent can read ~/Desktop
  • limit pinning — auto-pins on first real rate-limit hit; unverified until one occurs

Install

Requires Node 20+. No npm install — there are no dependencies.

git clone https://github.com/andoodle/yet-another-usage-tracker.git
cd yet-another-usage-tracker
node src/server.mjs

Then open http://localhost:4478. Set BUDGET_PORT to use a different port.

Run

Always-on (recommended, macOS) — runs at login, restarts if it dies:

scripts/install-launchagent.sh

Then just open http://localhost:4478.

Desktop shortcut (no Terminal window; opens the dashboard, and starts the server first if it isn't answering):

scripts/make-desktop-shortcut.sh              # -> ~/Desktop
scripts/make-desktop-shortcut.sh /Applications
scripts/make-desktop-shortcut.sh --uninstall

Always-on (recommended, Windows) — builds a small launcher exe, then adds Start Menu, Desktop, and Startup-folder shortcuts:

.\scripts\install-windows.ps1
.\scripts\install-windows.ps1 -NoAutostart     # shortcuts only, no login start
.\scripts\install-windows.ps1 -Uninstall

Nothing is installed to build it. csc.exe (the C# compiler) ships inside Windows as part of the .NET Framework, and the icon is rendered by this repo's own scripts/make-icon.mjs. Node stays the only prerequisite.

The exe is a ~10KB launcher, not a self-contained binary: it checks whether the port is already answering, spawns node src/server.mjs hidden if not (logging to claude-budget.log), waits for the port, then opens your browser. --serve skips the browser, which is what the Startup shortcut uses so login doesn't hand you a tab you didn't ask for. Launching it twice won't start a second server.

A Node SEA build would produce a true standalone exe, but at ~110MB, plus a bundler dependency and a rewrite of the static-file serving to read from SEA assets. Node is already required to run the server, so the thin launcher buys the same double-clickability for 0.01% of the size and no source changes.

Autostart is a Startup-folder shortcut rather than a Scheduled Task or a service, deliberately: this is a personal dashboard, not infrastructure. If you're not logged in, you're not coding, and there's nothing to pace.

Manual, or as a fallback: node src/server.mjs, or double-click Open Claude Budget.command (macOS).

Live readings from the status line (recommended)

Claude Code hands its status-line command your real /usage percentages. scripts/statusline.mjs forwards them to the dashboard, which then calibrates the weekly and 5-hour limits and both reset times on its own. Point statusLine in ~/.claude/settings.json at it:

"statusLine": {
  "type": "command",
  "command": "node /path/to/yet-another-usage-tracker/scripts/statusline.mjs"
}

On its own it prints 5h 23% · week 41%. To keep a status line you already have, put its command after --; it receives the same input and its output is shown unchanged:

"command": "node /path/to/yet-another-usage-tracker/scripts/statusline.mjs -- npx -y ccstatusline@latest"

The reading is posted to localhost and never waited on, so a stopped dashboard costs the status line nothing beyond a 300ms connect timeout. Set BUDGET_PORT in the command's environment if the server runs on another port.

Why it works the way it does

The real percentages reach only the status line. There is no usage API to call, and /usage can't be automated — claude -p "/usage" returns only the sentence "You are currently using your subscription to power your Claude Code usage". What Claude Code (v2.1.80+) does do is pass its status-line command a rate_limits object: five_hour and seven_day, each with used_percentage and resets_at, for Pro and Max, once a session has had its first API response (docs). The status-line hook above turns those into calibration.

They are two numbers, though, not a ledger: no per-day shape, and no Fable percentage. So consumption is still computed from your own transcripts as a relative weight, and the live readings set its scale:

weight = input + output×5 + cacheWrite×1.25 + cacheRead×r   (×model tier, ×2 fast mode)

r is the model's cache-read ratio — 0.1 for most models, 0.05 for Opus 5.5, 0.025 for Fable 5.1. Advisor calls are recorded inside the executor's message (usage.iterations, type advisor_message) and are weighted at the advisor's own model, in its own pool.

Those coefficients come from API price ratios, but nothing is displayed in dollars — on a subscription there is no dollar meter, and showing API prices would be a number you never pay. Everything on screen is a share of your weekly allowance.

That's also why the estimate being imperfect doesn't matter much: pacing is scale-invariant. If the total is 20% off, every day's slice is off by the same factor, and the ratio between days — the thing that tells you whether to keep going — is unchanged.

The Fable sub-limit, and why the weight is calibrated

Fable is capped at 50% of the weekly limit (documented by Anthropic), and its usage also counts toward the weekly total. Two meters, one budget.

That leaves two unknowns: the weekly limit W, and how heavily Fable is metered. API list prices have no necessary relationship to subscription metering, so the documented 50% is treated as fact and the weight k is solved instead, from two readings taken at the same moment:

fable×k          = fablePct × 0.5 × W
fable×k + other  = weekPct × W

other is measured directly and unaffected by k, so W falls out, then k. Solve it on your own account; one solve is one data point.

With the status-line hook, the week % is always current, so typing the Fable % alone solves against it. A live week reading on its own re-solves W at the current k and never touches k: pairing a Fable % read earlier with a later week % would solve the weight against usage the Fable reading never saw.

k is one number for every Fable model, but Fable 5 and 5.1 bill cache reads at different ratios (0.1 vs 0.025), so a k solved in a week heavy on one fits the other less well. Recalibrate after switching.

Estimating the weekly limit

  1. Inferred (default): the heaviest rolling 7-day stretch you've actually sustained, plus 15%. Rolling, not calendar-aligned — a calendar-week max ignores the in-progress week, which lets a heavy current week exceed its own inferred limit and pin remaining budget to zero. This number paces allocation, but it is not shown as a percentage: every "% of limit" readout stays — until a real reading is entered. Pacing only needs the week's relative shape; reporting "72% of your limit" off a guess would be fiction stated to the pixel. Because that leaves nothing on screen, an uncalibrated dashboard opens behind a blocking setup dialog that asks for the reset window and both percentages before anything renders. It has no dismiss button until a solve actually fails, so an unsolvable week can't lock you out.
  2. Live: with the status-line hook, every changed reading solves limit = spentThisWeek ÷ pct with no typing. The 5-hour limit is solved the same way against the current block.
  3. Calibrated: run /usage, type the weekly percentage into the field. Same solve, one reading. A live reading replaces it on the next update.
  4. Pinned: when you hit a real limit, Claude Code writes an error into the transcript. The scanner detects it and pins the limit to your exact week-to-date consumption at that instant. Free ground truth, no typing.

Allocation: buffer + debt hybrid

Each policy fixes the other's weakness:

  • Baseline — every day's share is computed once from the week's weights. It does not move when you overspend, which is what makes the overspend visible instead of silently repricing later days.
  • Debt — cumulative (used − baseline) through yesterday, shown as "running balance". Reported, not absorbed.
  • Reserve — 15% held back all week, released when 2 days remain. Its job is to pay the debt down late, so a heavy Tuesday doesn't throttle Friday.

Remaining days get baseline + (releasedReserve − debt) × weight, with a hard cap so the week can't exceed the limit regardless. Tunable via reserveFraction and reserveReleaseDays in ~/.claude/budget-data/plan.json.

Where the reset times come from

With the status-line hook Without it
5-hour block start Observed — five_hour.resets_at Inferred — first activity after a ≥5h gap, rolling every 5h, or typed in
Weekly reset anchor Observed — seven_day.resets_at Configured — nothing in the transcripts records it

Files

Path Role
src/scan.mjs Incremental transcript reader. Caches per-file byte offsets + per-message records in ~/.claude/budget-data/scan-cache.json, de-duplicated across files at merge time. A cold scan re-reads every transcript and can take minutes on a large history; incremental scans read only appended bytes.
src/pricing.mjs Per-model tiers and the cache multipliers that set relative weight.
src/budget.mjs Week windowing, limit inference, buffer+debt allocation, block detection.
src/server.mjs GET /api/state, POST /api/plan, POST /api/observe (status-line readings), static files.
scripts/statusline.mjs Status-line command: forwards rate_limits to the server, optionally wraps another status line.
web/ Dashboard. Build-once / patch-in-place rendering so drags survive updates.

Plan and cache live in ~/.claude/budget-data/ — deleting either is safe. BUDGET_DATA_DIR points them elsewhere, e.g. to try a change against a copy.

Known limits

  • Messages appearing in several transcripts (a resumed or forked session copies its parent's history — ~50% of usage-bearing messages here) are counted once, de-duplicated at merge time over a sorted file walk, so warm and cold scans agree. A message with no id can't be identified across files and is always counted.
  • Only this machine's transcripts are counted. The allowance is per account, but ~/.claude/projects/ is per machine. Without the status-line hook, a laptop and a desktop each see only their own share and both understate the account-wide total. Live readings are account-wide, so with the hook the headline % matches /usage at each reading; the per-day split between readings still comes from this machine only.
  • The status line carries no Fable percentage, so the Fable weight still needs a typed Fable % from /usage. Live readings arrive only while a Claude Code session with the hook is running, and only on Pro and Max.
  • Cache-write TTL is assumed 5m unless the transcript carries the newer usage.cache_creation breakdown, so 1h-TTL writes are under-weighted.
  • Without the status-line hook, the weekly reset day/hour is a guess until you set it to match /usage.
  • ~/Desktop is TCC-protected, so the LaunchAgent needs Full Disk Access granted to your node binary (or move the project elsewhere).
  • Re-grant Full Disk Access after brew upgrade node. macOS attaches TCC grants to the resolved binary, and Homebrew's node is a symlink into a version-stamped path (/opt/homebrew/Cellar/node/<version>/bin/node). A node upgrade moves the real binary, the grant stops matching, and the agent fails silently — the dashboard just stops updating with no error or prompt. Symptom: ~/.claude/budget-data/agent.err shows EPERM. Fix: re-add /opt/homebrew/bin/node under System Settings → Privacy & Security → Full Disk Access. Moving the project off ~/Desktop avoids this permanently.

Contributing

Issues and PRs welcome. It's a small, dependency-free codebase — keep it that way: no runtime dependencies, no build step, plain ES modules.

License

MIT — see LICENSE.

About

Yet Another Usage Tracker — local, zero-dependency weekly Claude Code usage pacing dashboard built from your own transcripts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages