Recover deleted Claude Code and Codex usage history from a public leaderboard profile, back into the cc.json shape ccusage emits.
npx @intframe/usage-recover <username> -o cc.jsonClaude Code deletes session transcripts after cleanupPeriodDays, which defaults to 30. Once they are gone the usage they represent is gone with them: ccusage reads raw session files, so it cannot report a month it can no longer see, and neither can anything built on top of it.
We lost four months this way across six accounts, on a machine that was decommissioned before anyone thought about it. What made the history recoverable was that aggregates had been submitted to viberank months earlier. This tool is that recovery, packaged.
| per-day cost | exact |
| per-model cost | exact |
| token totals, all four buckets | exact |
| per-day token split | derived from cost |
A profile publishes cost per day but only aggregate token counts, so the daily token split does not exist in the source and cannot be recovered. It is apportioned by cost, which keeps every total truthful and every day's relative weight intact. The output states this in a fidelity field rather than leaving a consumer to assume the whole file is measured.
The interesting part is that the totals come out exact rather than rounded.
A profile prints each token bucket twice. Once as a label, 36.2B, which at that scale covers a 50-million-wide interval and is useless on its own. Once as a bar width, 95.05060355502121%, which carries full float precision but is a ratio with no absolute anchor.
Together they pin the integers. Every percentage is bucket / total computed from whole numbers, so scanning candidates for the smallest bucket and testing whether the other three land on integers isolates one consistent solution. On the profile above it converges with zero residual:
label 36.2B 1.0B 202.6M 657.2M
bar 95.0506035% 2.6933182% 0.5315882% 1.7244898%
solved 36,223,082,857 1,026,403,707 202,584,370 657,190,346
total 38,109,261,280
Standard cc.json, so anything that reads ccusage daily --json reads this: leaderboard submissions, importers, your own scripts.
Every conversion is checked before it is written. Token buckets must sum to their totals at both day and profile level, model costs must sum to the day, dates must be ISO, and cost per token must land inside the band leaderboards accept. A file that fails any of these is not emitted, because the errors it would produce downstream point at the payload rather than at the conversion that made it.
import { fetchProfile, buildCcJson, verify } from "@intframe/usage-recover";
const profile = await fetchProfile("aron-intframe");
const cc = buildCcJson(profile, new Date().toISOString());
const problems = verify(cc);
if (problems.length) throw new Error(problems.join("\n"));recoverTotals is exported separately if you only want the integer-recovery step.
Only public viberank profiles are supported. It reads the rendered page, so a markup change upstream can break parsing; the verification step is there so that surfaces as a clear failure rather than as plausible wrong numbers.
Recovered aggregates are not a substitute for measured sessions. If your transcripts still exist, run ccusage against them.
MIT