Skip to main content

claude-usage-calc

Calculate Claude Code token consumption, message counts, and list-price cost from local JSONL transcript logs. Use when the user asks "how many tokens / prompts / messages did I use", "what's my Claude cost", "usage in last N days", or anything about local Claude Code usage analytics.

Aller à l'installation

Informations de source

Dépôt
mediar-ai/skillhubz
Dernière activité de la source
11 avril 2026 à 21:00
Langue détectée de SKILL.md
anglais
Étoiles
7
Forks
4

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
claude-usage-calc
description
Calculate Claude Code token consumption, message counts, and list-price cost from local JSONL transcript logs. Use when the user asks "how many tokens / prompts / messages did I use", "what's my Claude cost", "usage in last N days", or anything about local Claude Code usage analytics.
allowed-tools
Bash, Read
# Claude Code Usage Calculation Compute token usage, prompt counts, and cost from local Claude Code transcript files. ## Where the data lives - Transcripts: `~/.claude/projects/**/*.jsonl` (one file per session, one JSON object per line) - Each line has `timestamp` (ISO8601 UTC), `type` (`user` | `assistant` | `system`), and for assistant lines a `message.usage` block from the API response - No transcripts older than ~90d typically exist locally (rotated/cleaned) ## CRITICAL: things to get right ### 1. Each `usage` block is per-API-call, NOT per-conversation Verified by inspection: turn 1 has `cache_read=11k`, turn 146 has `cache_read=89k`, growing as the cached prefix grows. Summing across turns is correct for billing because each API call is independently charged. Cache reads dominating (often 90%+ of tokens) is expected and normal, not a counting bug. ### 2. DEDUPE BY `message.id` — THIS IS THE BIGGEST FOOTGUN Claude Code logs the same assistant message multiple times across parent chains and sidechain entries. **Without dedup, totals are inflated ~2.4×.** Always keep a `seen` set of `message.id` and skip duplicates. A real measurement showed 794k raw assistant entries collapsing to 376k unique turns. ### 3. "Real human prompts" ≠ all `type:user` entries The harness logs tool_result continuations as `type:user`. To count actual prompts the human sent, filter `type:user` entries AND exclude any whose `message.content` is a list containing a block with `type=="tool_result"`. In a real run: 564,909 raw user entries → 53,406 actual human prompts. ### 4. Filter by `timestamp`, not file mtime File mtime is unreliable (a session touched today may include messages from weeks ago). Parse `timestamp` per line and bucket by `(now - t).days`. ### 5. List price ≠ what the user actually pays ALWAYS state this explicitly in the report. If the user is on Max / Pro / committed-use / enterprise, real billing is dramatically lower. Tell them the authoritative number is at `console.anthropic.com` or `claude.ai` billing. ### 6. Pricing per million tokens (USD, list price) | Family | input | output | cache_create | cache_read | |---------|-------|--------|--------------|------------| | opus | 15.00 | 75.00 | 18.75 | 1.50 | | sonnet | 3.00 | 15.00 | 3.75 | 0.30 | | haiku | 0.80 | 4.00 | 1.00 | 0.08 | Detect family from `message.model` substring (`opus` / `sonnet` / `haiku`). Default unknown to sonnet. ## What NOT to do - ❌ Do not sum raw assistant entries without deduping by `message.id` - ❌ Do not count all `type:user` entries as "prompts" (most are tool_results) - ❌ Do not filter by file mtime instead of per-line `timestamp` - ❌ Do not present list-price cost as "what you paid" — always caveat it - ❌ Do not exclude cache_read from totals "because it looks too big" — it IS billed (at 0.1× input) - ❌ Do not assume `usage` is cumulative across the conversation — it is per-API-call - ❌ Do not write throwaway scripts in the repo or home dir — use `scripts/` (gitignored) if needed ## What TO do - ✅ Stream all JSONLs through one Python pass via `find ... -print0 | xargs -0 cat | python3 -c '...'` - ✅ Dedupe by `message.id` with a `seen` set - ✅ Bucket by age: `0-30d`, `30-60d`, `60-90d` for trend questions - ✅ Break out by model family (opus / sonnet / haiku) — Opus typically dominates cost - ✅ Report all four token buckets separately: input, output, cache_create, cache_read - ✅ Always show: real human prompts, unique assistant turns, total tokens, list-price cost - ✅ Caveat that list price ≠ real bill - ✅ Point user to console.anthropic.com or claude.ai billing for ground truth ## Reference script Run this to compute last-30d usage. Adjust the day window as needed. ```bash find ~/.claude/projects -name "*.jsonl" -print0 2>/dev/null | xargs -0 cat 2>/dev/null | python3 -c " import sys, json from datetime import datetime, timezone now = datetime.now(timezone.utc) WINDOW_DAYS = 30 PRICES = { 'opus': {'in':15.00,'out':75.00,'cc':18.75,'cr':1.50}, 'sonnet': {'in': 3.00,'out':15.00,'cc': 3.75,'cr':0.30}, 'haiku': {'in': 0.80,'out': 4.00,'cc': 1.00,'cr':0.08}, } def fam(m): if not m: return 'sonnet' m = m.lower() if 'opus' in m: return 'opus' if 'haiku' in m: return 'haiku' return 'sonnet' seen = set() agg = {} real_user_msgs = 0 dup_skipped = 0 for line in sys.stdin: try: d = json.loads(line) ts = d.get('timestamp') if not ts: continue t = datetime.fromisoformat(ts.replace('Z','+00:00')) if (now - t).days >= WINDOW_DAYS: continue if d.get('type') == 'user': msg = d.get('message',{}) c = msg.get('content') if isinstance(msg,dict) else None tool_result = isinstance(c,list) and any(isinstance(x,dict) and x.get('type')=='tool_result' for x in c) if not tool_result: real_user_msgs += 1 continue if d.get('type') != 'assistant': continue msg = d.get('message') if isinstance(d.get('message'),dict) else None if not msg: continue mid = msg.get('id') if mid: if mid in seen: dup_skipped += 1 continue seen.add(mid) u = msg.get('usage') or {} f = fam(msg.get('model')) a = agg.setdefault(f, {'in':0,'out':0,'cc':0,'cr':0,'turns':0}) a['in'] += u.get('input_tokens',0) or 0 a['out'] += u.get('output_tokens',0) or 0 a['cc'] += u.get('cache_creation_input_tokens',0) or 0 a['cr'] += u.get('cache_read_input_tokens',0) or 0 a['turns'] += 1 except: pass grand_cost = 0.0 grand_tok = 0 print(f'{\"model\":<8} {\"turns\":>9} {\"in\":>14} {\"out\":>14} {\"cc\":>16} {\"cr\":>16} {\"cost\":>11}') for f,a in sorted(agg.items()): p = PRICES[f] cost = (a['in']*p['in']+a['out']*p['out']+a['cc']*p['cc']+a['cr']*p['cr'])/1_000_000 grand_cost += cost grand_tok += a['in']+a['out']+a['cc']+a['cr'] print(f\"{f:<8} {a['turns']:>9,} {a['in']:>14,} {a['out']:>14,} {a['cc']:>16,} {a['cr']:>16,} \${cost:>10,.2f}\") print() print(f'real human prompts: {real_user_msgs:,}') print(f'unique assistant turns: {sum(a[\"turns\"] for a in agg.values()):,} (deduped {dup_skipped:,} duplicates)') print(f'TOTAL tokens: {grand_tok:,}') print(f'TOTAL cost (LIST price): \${grand_cost:,.2f}') print() print('NOTE: list price; real bill is lower if on Max/Pro/committed-use.') print('Authoritative usage: console.anthropic.com or claude.ai billing.') " ``` ## Reporting format Always report (in this order): 1. **Real human prompts** sent in window 2. **Unique assistant API calls** (with dedup count noted) 3. Per-model breakdown table (turns / input / output / cache_create / cache_read / cost) 4. **TOTAL tokens** and **TOTAL list-price cost** 5. **Caveat**: list price ≠ real bill, point to console for ground truth 6. If asked about trends, also bucket `30-60d` and `60-90d` and explain growth drivers (cache_read inflation from longer sessions / parallel agents is normal)
Voir sur GitHub