Skip to content

Instantly share code, notes, and snippets.

@s3w47m88
Created September 17, 2026 17:25
Show Gist options
  • Select an option

  • Save s3w47m88/7e38086deaef054adcb48aa3851a68ac to your computer and use it in GitHub Desktop.

Select an option

Save s3w47m88/7e38086deaef054adcb48aa3851a68ac to your computer and use it in GitHub Desktop.
Claude Code token audit: prompt + script

Claude Code token audit — prompt

Paste this into Claude Code on your machine. It is self-contained. (Or skip it and run the included token-audit.mjs directly: node token-audit.mjs 2026-08-18.)

Audit my Claude Code token usage and price it at Anthropic API list rates.

Data: every *.jsonl under ~/.claude/projects/ (recursive). Each line is JSON.
Only count lines where type == "assistant" and message.usage exists.
Dedup by message.id + ":" + requestId (skip repeats).
Filter by timestamp to the window I give you.

Per request, from message.usage:
- read  = cache_read_input_tokens
- w1h   = cache_creation.ephemeral_1h_input_tokens (fallback: cache_creation_input_tokens)
- w5m   = cache_creation.ephemeral_5m_input_tokens
- out   = output_tokens
- inp   = input_tokens

Prices ($ per million tokens), matched by model prefix:
  claude-fable-5-1 / claude-fable-5: input 10, output 50, cache read 0.25
  claude-opus-5 / claude-opus-4-8 / -4-7 / -4-6: input 5, output 25, cache read 0.5
  claude-sonnet-5: input 2, output 10, cache read 0.2
  claude-sonnet-4-6: input 3, output 15, cache read 0.3
  claude-haiku-4-5: input 1, output 5, cache read 0.1
  unknown model -> use opus-5 prices
Cache write cost = (w1h * 2 + w5m * 1.25) * input price.

Also compute:
- human prompts = lines with type == "user" whose message.content is a string, or an array with no tool_result block
- calls per prompt = requests / human prompts
- median request size = median of (read + w1h + w5m + inp); % of requests over 200k
- cold resumes = per file, consecutive assistant requests more than 1 hour apart (count them and their cache-write cost)
- subagent share = cost from files under a /subagents/ folder or whose name contains "agent-"
- cost per model

Output JSON: window, requests, sessions, humanPrompts, callsPerPrompt, costUSD, perDayUSD, per30dUSD,
share % of cost for {cacheReads, cacheWrites, output, typedInput}, medianRequestTokens,
pctRequestsOver200k, coldResumes, coldResumeWriteUSD, subagentShare, byModel.

Write it as ~/.claude/scripts/token-audit.mjs taking args [SINCE] [UNTIL] (YYYY-MM-DD),
run it for the last 30 days, save output to ~/.claude/reports/, and show me a short table:
cost/30d, the four cost shares, median request size, % over 200k, calls per prompt, cold resumes.

Reference numbers (Spencer's Mac, same pricing)

Metric Before hygiene rules After
Cost / 30d (API-rate equivalent) $8,631 $2,390
Cache reads / writes / output 64 / 26 / 10% 34 / 45 / 21%
Median request 175k tokens 110k
Requests over 200k 44% 4%
Calls per prompt 7.8 6.4
// Claude Code token audit. Usage: node token-audit.mjs [since YYYY-MM-DD] [until YYYY-MM-DD]
// Prices: API list, $/MTok. cache write 1h = 2x input, 5m = 1.25x input.
import fs from "fs"; import path from "path";
const P = {
"claude-fable-5-1":{i:10,o:50,r:0.25}, "claude-fable-5":{i:10,o:50,r:0.25},
"claude-opus-5":{i:5,o:25,r:0.5}, "claude-opus-4-8":{i:5,o:25,r:0.5}, "claude-opus-4-7":{i:5,o:25,r:0.5}, "claude-opus-4-6":{i:5,o:25,r:0.5},
"claude-sonnet-5":{i:2,o:10,r:0.2}, "claude-sonnet-4-6":{i:3,o:15,r:0.3}, "claude-haiku-4-5":{i:1,o:5,r:0.1},
};
const price=m=>P[m]||P[Object.keys(P).find(k=>(m||"").startsWith(k))]||P["claude-opus-5"];
const since=new Date(process.argv[2]||"2000-01-01"), until=new Date(process.argv[3]||"2100-01-01");
const root=path.join(process.env.HOME,".claude/projects");
const files=[];(function walk(d){for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".jsonl")&&files.push(p);}})(root);
const seen=new Set(); const T={read:0,w1h:0,w5m:0,out:0,inp:0}; const C={read:0,write:0,out:0,inp:0};
let reqs=0,prompts=0,cold=0,coldCost=0; const sizes=[],sess=new Set(),byModel={},sub={cost:0,reqs:0};
for(const f of files){
const lines=fs.readFileSync(f,"utf8").split("\n"); let lastTs=null; const isSub=f.includes("/subagents/")||/agent-/.test(path.basename(f));
for(const l of lines){ if(!l)continue; let j; try{j=JSON.parse(l)}catch{continue}
const ts=new Date(j.timestamp); if(!(ts>=since&&ts<until))continue;
if(j.type==="user"&&j.message){const c=j.message.content; if(typeof c==="string"||(Array.isArray(c)&&!c.some(b=>b.type==="tool_result")))prompts++;}
const u=j.message?.usage; if(!u||j.type!=="assistant")continue;
const key=(j.message.id||"")+":"+(j.requestId||""); if(seen.has(key))continue; seen.add(key);
reqs++; sess.add(j.sessionId||f); const pr=price(j.message.model);
const w1=u.cache_creation?.ephemeral_1h_input_tokens??u.cache_creation_input_tokens??0, w5=u.cache_creation?.ephemeral_5m_input_tokens??0;
const r=u.cache_read_input_tokens||0,o=u.output_tokens||0,i=u.input_tokens||0;
const c={read:r*pr.r/1e6,write:(w1*2+w5*1.25)*pr.i/1e6,out:o*pr.o/1e6,inp:i*pr.i/1e6};
T.read+=r;T.w1h+=w1;T.w5m+=w5;T.out+=o;T.inp+=i; for(const k in c)C[k]+=c[k];
const tot=r+w1+w5+i; sizes.push(tot); const cs=c.read+c.write+c.out+c.inp;
const m=j.message.model; byModel[m]=byModel[m]||{cost:0,reqs:0}; byModel[m].cost+=cs; byModel[m].reqs++;
if(isSub){sub.cost+=cs;sub.reqs++;}
if(lastTs&&ts-lastTs>3600e3){cold++;coldCost+=c.write;} lastTs=ts;
}
}
const total=C.read+C.write+C.out+C.inp; sizes.sort((a,b)=>a-b); const med=sizes[Math.floor(sizes.length/2)]||0;
const over=sizes.filter(s=>s>200e3).length; const days=Math.max(1,(Math.min(until,new Date())-since)/864e5);
const pct=x=>(100*x/total).toFixed(1)+"%";
const out={window:{since:since.toISOString().slice(0,10),until:until.toISOString().slice(0,10),days:+days.toFixed(1)},
requests:reqs,sessions:sess.size,humanPrompts:prompts,callsPerPrompt:+(reqs/Math.max(1,prompts)).toFixed(1),
costUSD:+total.toFixed(0),perDayUSD:+(total/days).toFixed(0),per30dUSD:+(total/days*30).toFixed(0),
share:{cacheReads:pct(C.read),cacheWrites:pct(C.write),output:pct(C.out),typedInput:pct(C.inp)},
tokens:T,medianRequestTokens:med,pctRequestsOver200k:(100*over/Math.max(1,sizes.length)).toFixed(1)+"%",
coldResumes:cold,coldResumeWriteUSD:+coldCost.toFixed(0),subagentShare:pct(sub.cost),
byModel:Object.fromEntries(Object.entries(byModel).map(([k,v])=>[k,{reqs:v.reqs,usd:+v.cost.toFixed(0)}]))};
console.log(JSON.stringify(out,null,1));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment