What did that agent actually cost you?
agentledger-cli is a local-first ledger for AI coding agents. It reads the
session logs that thirteen agents already write to your machine — Claude
Code, Codex, OpenCode, Gemini CLI, Qwen Code, Kilo Code, DSH, pi-agent,
OpenClaw, Cherry Studio, CodeBuddy CN, Lingma and WorkBuddy — and turns them
into tokens and dollars. Five of those thirteen no other tool has an adapter
for.
$ agentledger
agentledger — 302 session files scanned
Metric Tokens Est. cost
───────────── ─────── ─────────
Input tokens 335.96M $826
Cache read 2.73B $344
Cache write 366.4k $0.0842
Output tokens 11.31M $64.66
TOTAL 3.08B $1235
Agent Tokens Est. cost Records
─────────── ─────── ───────── ───────
dsh 2.25B $961 6994
codex 219.45M $193 3059
opencode 304.17M $25.42 4298
codebuddy 24.10M $13.62 362
lingma 2.76M $7.77 43
workbuddy 41.94M $28.81 487
claude-code 205.05M $2.24 1462
cherry-studio 765.8k $1.63 29
openclaw 31.39M $1.28 959
kilo 3.32M $0.3186 103
pi 47.7k $0.0000 5
! 15 model(s) have no built-in price and used the
$3/$15 per-Mtok default ($999 of the total):
step-5-preview (1818 records)
hy4-preview (1741 records)
...
Fix in one step: `agentledger prices --template` prints a
fill-in override file for exactly these models.
(That is a real run on the machine it was written on — including the warning, which is the point of it. Most of that total is placeholder-priced, and the tool says so rather than letting you quote it.)
If you pay per token for your agents, your bill is a lagging indicator. By the
time you see it you have already spent it. agentledger-cli gives you the number
while you are still working:
-
Local only. It reads
~/.claude/projects,~/.codex/sessions,~/.local/share/opencode,~/.dsh/sessions,~/.openclaw/agents,~/.pi/agent/sessions,~/Library/Application Support/CherryStudio/Data, and~/.gemini/tmp. Nothing is uploaded, no account, no API key, no network call of any kind. -
Cache buckets are priced separately. Cache reads and cache writes bill at different rates from fresh input, and they are the majority of real traffic — 2.00B of the 2.32B tokens in my own logs are cache reads.
-
Fast. A full scan of ~2.3B tokens of logs takes about a second.
-
Boring dependencies. Node 22.5+ and the standard library. That's it.
-
Cost attribution. Codex records the working directory and branch in
session_meta; Claude Code recordscwdandgitBranch; OpenCode's store joins messages to sessions.by-projectandby-branchprice each of those separately, so you can see what a feature branch actually cost rather than only what a model cost. Grouping is lossless — per-project costs sum back to the grand total, and a test asserts it. -
Waste you can act on. Cache reads dominate real agent traffic — 1.93B of 2.23B tokens on the machine this was written on.
wasteranks sessions by cache-read volume and states what share the top few hold, which points at context churn (a long session re-sending its prefix, or a tool returning large output every turn) rather than at "usage went up".
The field, first. There are four tools worth knowing about, and this one is the smallest:
| agents | network calls | stars | |
|---|---|---|---|
| ccusage | 18 | fetches prices (has --offline) |
19,019 |
| CodeBurn | 37 claimed | fetches LiteLLM daily | 11,541 |
| AgentsView | many | — | 6,224 |
| agentledger | 12 | none, ever | 0 |
So agentledger covers fewer agents than ccusage or CodeBurn, and has none of the stars. What it holds is narrower and, I think, real:
- It makes no network call of any kind — no telemetry, no update check,
no pricing fetch. ccusage has an
--offlineflag, which is a flag you only need if you make network calls by default, and CodeBurn's own documentation says "Prices are fetched from LiteLLM and cached locally for 24 hours." agentledger's numbers cannot change under you, and it works on a plane. - Zero dependencies, no build step, ~5600 lines of JavaScript. CodeBurn is a TypeScript project; ccusage is a Rust binary.
- Five agents no other tool reads — DSH, Cherry Studio, CodeBuddy CN, Lingma and WorkBuddy.
- It tells you when it does not know. A model with no verified price is named in the output with the placeholder cost it was given, rather than quietly priced at zero.
What it does not have: a dashboard, a wider agent list, a hundred stars, or a name people already search for. If you want those, use the other one.
ccusage is the incumbent and it covers
more agents than this. The honest count, rather than a flattering one:
ccusage has 18 agent adapters (19 directories under rust/adapters, one
of which is common, a shared helper rather than an agent), and 7 of those
18 overlap with the ones below. agentledger has 13 sources; 5 of them
ccusage has no adapter for at all.
It reports reasoning token counts but does not bill them in the OpenCode or Kilo lanes
— on the kilo store sum(reasoning) is 133 tokens and ccusage's output total
is sum(output) alone, 39,597 against agentledger's 39,730 — and its
statusline shows 5-hour billing blocks and burn rate. Its pricing also comes from LiteLLM's database, embedded at build
time from a locked revision. If you want the most complete tool, use that
one. What we share now: TTL-split cache writes, a statusline that prefers
the host's own session cost over any estimate, and per-model overrides.
Where this differs, as far as I can tell: agentledger's table is a checked-in
static file that a CI job reconciles against live LiteLLM weekly — a stale
entry fails a build on a schedule instead of waiting for a manual
update-litellm-pricing; savings prices the counterfactual (what the same
ledger would have cost with no prompt caching); and the whole tool is ~3,100
lines of zero-dependency JavaScript with no build step, no runtime fetch, and
no network call of any kind.
Five sources ccusage cannot read at all. ccusage has 18 adapters
and no dsh adapter — ccusage dsh returns Unknown command — and no
Cherry Studio adapter either; there is no cherry command in it at all.
crosscheck reports the first automatically rather than trusting prose:
ONLY DSH (DeepSeek Harness) — 1471043240 tokens, $856.72 (ccusage has no adapter)
DSH
(DeepSeek Harness) keeps multi-frame zstd sessions at
~/.dsh/sessions/*/session.v4.jsonl.zstd; on this machine that is 58 sessions
and ~1.5B tokens, more than every other agent combined. Caveat stated plainly:
most DSH models reach it through gateways with no canonical public price, so a
large share of that lane is placeholder-priced and the report says so. The
token counts are real; the dollars in that lane are estimates until you set
real rates with --prices.
One lane where ccusage reports $0.00 for every model. On this machine its
OpenClaw lane reads 31.39M tokens and prices all of it at zero — neither
glm-4.7:cloud nor stepfun/step-3.5-flash:free is in its embedded table.
agentledger prices glm-4.7:cloud from six LiteLLM vendor rows that agree
exactly on $0.60/$2.20 (zai/glm-4.7, novita/zai-org/glm-4.7,
novita/zai-org/glm-4.7-h, baseten/zai-org/GLM-4.7,
vertex_ai/zai-org/glm-4.7-maas, zai.glm-4.7), which is $1.28 of real spend
on this machine's logs. crosscheck names that case instead of folding it
into a percentage, because "they say $0" and "we disagree" are different facts.
Reasoning tokens are billed as output. OpenCode records them in a separate
tokens.reasoning field; verified on this machine that total = input + output + reasoning + cache.read + cache.write holds for all 2,176 rows that
have reasoning, so they are additive and bill at the output rate. (Codex is
the opposite: its output_tokens already includes reasoning, and adding the
field again double-counted 244,601 tokens. Both cases are pinned by tests.
ccusage excludes reasoning from OpenCode's output bucket, so it reports lower
output there — 1,220,552 against agentledger's 1,695,212 for the same model.)
pi and OpenClaw keep the same field, and the same rule applies: pi's 800
reasoning tokens bill as output where ccusage drops them.
Measured against ccusage on the same machine (ccusage 20.0.34, darwin
arm64, run against the same ~/.claude/projects, ~/.codex/sessions,
opencode.db, ~/.pi/agent/sessions and ~/.openclaw): token counts agree
exactly for codex (219,388,883), opencode (240,912,255), pi (19,254 /
21,191 / 25,472) and openclaw (28,319,456 / 614,261 / 2,457,344), and for
Claude Code after the fix below
(in=2,373,420 / out=511,342 / cache read=202,163,520 for the largest model).
That head-to-head is also how a real bug surfaced: Claude Code re-emits the
same assistant message (resumed sessions, sidechain replays, retries) and this
tool was summing every row — 2,861 rows for 1,260 distinct message ids, a 2.2x
over-count that reported $15.67 for a lane costing ~$2.21. It now deduplicates
on message.id with ccusage's rule (prefer non-sidechain, then more tokens).
Where the totals still differ it is pricing, not counting: mimo-v2.5-pro has
no canonical LiteLLM entry (only reseller rows that disagree with each other),
so this tool's rate for it is an estimate and ccusage's is a different one.
agentledger exists because I wanted something I could read end to end. Whether that trade is worth it is your call, not mine.
It still makes no network call of any kind — no telemetry, no update check, no
pricing fetch. The one thing it does ask of the runtime is node:sqlite, which
is part of Node itself.
With Homebrew, if you have it:
brew install garyleelty/agentledger/agentledgerA tap is just a GitHub repository, so this path needs no registry account and
no token from anyone — not even from the project. Verified end to end: tapped,
installed, and agentledger --version reports 0.6.0 with every source
listed.
From a release tarball (no npm account, no registry, no build step):
npm install -g https://github.com/garyleelty/agentledger/releases/download/v0.6.0/agentledger-cli-0.6.0.tgzThe tarball is attached to each release by the release workflow using the
repo's own token, so this path works whether or not the package is on the
registry. Verified: installed from that URL and ran agentledger --version.
Or with npm, straight from the repository:
npm install -g git+https://github.com/garyleelty/agentledger.git # install
npx --package=git+https://github.com/garyleelty/agentledger.git agentledger # run onceAll of these give you agentledger and aledger.
Or one line, with no npm involvement at all:
curl -fsSL https://raw.githubusercontent.com/garyleelty/agentledger/main/install.sh | shThat clones into ~/.agentledger and symlinks agentledger (and the shorter
aledger) into ~/.local/bin, printing the PATH line to add if
~/.local/bin is not already on yours. Upgrade with
git -C ~/.agentledger pull, or just re-run the installer.
Or run it straight from a clone, no install step at all:
git clone https://github.com/garyleelty/agentledger.git && cd agentledger
node bin/agentledger.jsThe registry package is not published yet.
npx agentledger-clifails with a 404 becauseagentledger-cliis not on npm; publishing needs a maintainer token with 2FA bypass, which this project's CI does not have. Thegit+httpsform above is the way to install through npm until that lands, and the Homebrew tap needs no npm at all. This README will lead withnpx agentledger-clionce the package is actually there.Want it on npm under your own name? The package is plain JavaScript with no build step, so from a clone:
npm install -g . # installs `agentledger` and `aledger`or publish your own fork:
npm publish --access public
Requires Node 22.5+ — the OpenCode lane reads a SQLite database and
node:sqlite's DatabaseSync landed in 22.5.
agentledger-cli summary # totals + per-agent breakdown (also the default with no args)
agentledger-cli sources # which sources have data on this machine
agentledger-cli by-project # cost per repository / working directory
agentledger-cli by-branch # cost per git branch
agentledger-cli waste # where the cache-read spend is concentrated
agentledger-cli savings # what prompt caching saved, priced against no cache
agentledger-cli by-model # which model is eating your budget
agentledger-cli by-day -d 30 # daily spend curve
agentledger-cli by-week # the same, rolled up by week
agentledger-cli by-month # the same, rolled up by month
agentledger-cli blocks # billing blocks: the windows a quota resets in
agentledger-cli by-day --until 2026-06-30 # one month, not all of it
agentledger-cli sessions -n 20 # most expensive sessions
agentledger-cli budget -b 100 # spend against a limit
agentledger-cli statusline # one line for Claude Code's status bar
agentledger-cli watch # live totals, refreshes every 5s
agentledger-cli prices # the built-in pricing table
agentledger-cli aggregate # shareable totals + groupings, no session ids
agentledger-cli share -o page.html # the same, as a self-contained HTML page
agentledger-cli json # machine-readable dumpEvery other command reports what already happened. budget is the one that
answers the question you actually have while working — am I about to go over:
$ agentledger budget -b 100
budget $100
spent $1090 ████████████████████████ 1089.5%
remaining $0.0000 OVER by $990
run rate $4.13/day over 264.0 days of activity
already $990 over.
The run rate is measured over the span of data you actually have, not a
calendar period: a ledger that covers three months but was only used for two
days should not report a three-month run rate. --json emits the same numbers
plus the window, which is the shape a statusline or a CI gate wants:
agentledger budget -b 100 --json | jq '{spent: .spentUSD, over: .over, perDay: .runRateUSDPerDay}'An overrun is reported as over: true with daysUntilRunOut: null, rather
than a misleading projection.
statusline is for Claude Code's status bar. Add to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "agentledger statusline",
"padding": 0
}
}Claude Code pipes it a JSON payload on every render. When the payload carries the session's own cost (current versions do), agentledger echoes that number — the host's own measurement beats any estimate, and no files are read, so the bar never stutters. On older payloads it prices just the session's transcript file, not the whole ledger. It always exits 0 and prints exactly one line: a statusline that can break your prompt bar is a statusline you delete, so the degradation paths are tested as hard as the happy one (131 tests, 6 of them this contract).
budget tells you what you spent. This tells you what prompt caching saved,
by pricing the same records as though every cache-read token had been billed
at its model's fresh-input rate:
$ agentledger savings
What prompt caching saved
paid $1087
without cache $3975
saved $2888 72.7% cheaper
[██████████████████████░░░░░░░░] 72.7% saved
1.93B of 2.24B tokens came from cache
(86.5% of the total).
That is the theorem behind the whole tool: cache reads are the overwhelming
majority of agent traffic, they bill at a fraction of the input rate, and a
tool that does not price them separately is wrong by an order of magnitude.
savings is the same arithmetic run backwards — and the reason a cache-aware
budget matters more than an invoice.
Counted per model at that model's own rates, with no provider-level averaging.
Two JSON shapes, deliberately:
| Command | Use it for | Contents |
|---|---|---|
aggregate |
sharing, CI, a digest | totals + per-agent and per-model groupings |
json |
debugging, spreadsheets | one row per usage record, including session ids |
json is the debugging shape. On Claude Code and Codex the session id is a
UUID that names a real log file under the agent's own directory, so a report
built from it publishes a map of your projects. Use aggregate for anything
that leaves your machine:
agentledger-cli aggregate > spend.json # ~3 kB
agentledger-cli json > records.json # one row per record, much largerThe ledger is local, so there is nothing to link to — the only shareable
artifact is a snapshot. share renders one as a single HTML file: inline CSS,
no scripts, no external requests, opens straight from file:// and survives a
screenshot.
agentledger-cli share -o spend.html # writes the file
agentledger-cli share # or prints to stdoutPrivacy rules are the digest's: aggregates only, never a session id, never a
path. tests/share.test.js pins that, plus the offline guarantee (no <script>,
no inline handler, no url(), no stylesheet link).
Useful flags:
| Flag | Meaning |
|---|---|
-a, --agent <name> |
only one agent (claude-code, codex, opencode, kilo, dsh, gemini-cli, pi, openclaw, cherry-studio, codebuddy, lingma, workbuddy, qwen) |
-p, --path <dir> |
also scan a directory or single file |
--since <date> |
only records at/after a date |
--until <date> |
only records before a date, inclusive of that day |
--timezone <tz> |
the zone whose days the reports use (default: this machine's) |
-n, --top <n> |
rows in ranked reports |
--prices <file> |
override prices from a JSON map (see below) |
--json |
JSON instead of a table — for a grouped report, the breakdown it names, not a raw record dump |
Examples:
# What did today's Claude Code session cost?
agentledger-cli -a claude-code --since "$(date +%F)" by-day
# Export everything for a spreadsheet
agentledger-cli json > ledger.json
# Pipe into a status line / CI check
agentledger-cli json | jq '.totals.costUSD'Every number this tool prints is a list price. If you pay something else — a negotiated rate, a proxy lane, a regional price — bring your own:
# my-prices.json — partial entries merge over the built-in row,
# so overriding `input` never resets output or the cache multipliers.
{
"gpt-5.5": { "input": 4.0, "output": 24.0 },
"minimax-cn/MiniMax-M2.7": { "input": 0.25, "output": 1.0 },
"my-gateway-model": { "input": 0.5, "output": 1.5 }
}
agentledger --prices my-prices.json # any report
agentledger prices --prices my-prices.json # the table, overrides marked *Overrides win over everything, including the -free/:free zero-lane rule:
if you name a model explicitly, you meant it. A bad file fails the run with
a clear message — a half-applied price file would still print an
authoritative-looking table, which is the one outcome this feature must
never produce.
Don't want to write the file by hand? Ask for it:
agentledger prices --template > prices.json # only the unpriced models, blank
$EDITOR prices.json # fill in your rates
agentledger --prices prices.jsonThe template names exactly the models in your ledger that fell back to the default, each with its record count and token volume, so you can see what a blank line is worth before you go looking up rates.
agentledger blocks groups your usage into the rolling windows a
subscription quota resets in — the thing ccusage calls session blocks, and
the feature most Claude Code Max subscribers actually live in:
agentledger blocks # every window, oldest first
agentledger blocks --block-hours 3 # a different window length
agentledger blocks --json # the same, as JSONThe window starts with activity: the first record, floored to the hour, and runs for five hours from there. That is ccusage's rule, and it is not the obvious one — "local midnight, then five-hour chunks" is a natural guess and it is wrong, which is why the rule was measured against the binary rather than assumed. On this machine the two agree exactly: same window starts, same entry counts, same token totals, block for block.
Two things here are agentledger's own:
- It works for every agent, not just Claude Code. A quota window is a
property of the subscription, not of the log format. ccusage's
blocksis Claude Code only; this computes it across all nine lanes. - The active window comes with a burn rate and a projection — how fast the window is being spent and where it lands if the pace holds. That is the number you want while a session is running, and it is the one ccusage shows on its statusline and nowhere else.
<synthetic> records are excluded: Claude Code writes placeholder turns
under that model name with every bucket zero, and they are not usage. Kept
in, they open windows that contain nothing.
| Agent | Logs read | Status |
|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl |
✅ |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl |
✅ |
| OpenCode | ~/.local/share/opencode/opencode.db |
✅ |
| Kilo | ~/.local/share/kilo/kilo.db |
✅ |
| DSH (DeepSeek Harness) | ~/.dsh/sessions/*/session.v4.jsonl.zstd |
✅ |
| OpenClaw | ~/.openclaw/agents/*/sessions/*.jsonl* |
✅ |
| pi-agent | ~/.pi/agent/sessions/**/*.jsonl |
✅ |
| Gemini CLI | ~/.gemini/tmp/**/logs.json |
✅ |
| Cherry Studio | ~/Library/Application Support/CherryStudio/Data/cherrystudio.sqlite |
✅ |
| CodeBuddy CN | ~/Library/Application Support/CodeBuddy CN/logs/**/腾讯云代码助手*.log |
✅ |
| Lingma (通义灵码) | ~/Library/Application Support/Lingma/SharedClientCache/cache/db/local.db |
✅ |
| WorkBuddy | ~/.workbuddy/projects/**/*.jsonl |
✅ |
| Qwen Code | ~/.qwen/projects/*/chats/*.jsonl |
✅ |
| Anything else | --path + format sniffing |
partial |
agentledger sources tells you which of those are actually present on your
machine, so an empty lane is never mistaken for a wrong path:
$ agentledger sources
Agent Log location Here Docs
───────────── ─────────────────────────────────────────────── ─────────── ─────────────────────────────────────────────────
claude-code ~/.claude/projects 62 file(s) https://docs.claude.com/en/docs/claude-code
codex ~/.codex/sessions 11 file(s) https://github.com/openai/codex
opencode ~/.local/share/opencode 1 file(s) https://opencode.ai/docs
kilo ~/.local/share/kilo 1 file(s) https://kilo.ai
dsh ~/.dsh/sessions 49 file(s) https://github.com/deepseek-ai/deepseek-harness
gemini-cli ~/.gemini/tmp not found https://github.com/google-gemini/gemini-cli
pi ~/.pi/agent/sessions 5 file(s) https://github.com/earendil-works/pi-coding-agent
openclaw ~/.openclaw/agents 101 file(s) https://github.com/clawdbot/clawdbot
cherry-studio ~/Library/Application Support/CherryStudio/Data 1 file(s) https://www.cherry-ai.com/
codebuddy ~/Library/Application Support/CodeBuddy CN/logs 37 file(s) https://copilot.tencent.com/
lingma ~/Library/Application Support/Lingma/SharedClientCache/cache/db 1 file(s) https://lingma.aliyun.com/
workbuddy ~/.workbuddy/projects 10 file(s) https://copilot.tencent.com/
qwen ~/.qwen/projects empty https://github.com/QwenLM/qwen-code
11 of 13 sources have data on this machine.
agentledger-cli sources prints this table with links.
CodeBuddy CN hides its usage across three stores, and two of the things that are easiest to get wrong there are invisible in a diff. Its extension log carries the token buckets, its history index carries the clock and the request list, its message files carry the model, and its sessions database carries the working directory. Any one of them alone reads zero records.
inputTokensis the whole prompt, not the fresh input. The log also carriescachedMissTokensandcacheTokens, andinputTokens === cacheTokens + cachedMissTokens(80395 === 8192 + 72203). UsinginputTokensas the fresh input would have inflated this lane's cost about thirtyfold.- The log's timestamps are local, and the offset has to be recovered. The history index gives the same request's start as epoch milliseconds, so the difference is the machine's offset. Read as UTC+8 the median gap is 14.1 seconds — an API call's round trip; read as UTC it is 28,814 seconds. The offset is taken as the median of those candidates, because one real request's step is 4.5 hours from its start and would drag a mean.
- Each step is a separate API call. A request's prompt grows step by
step and a sub-agent request resets it, so the records are per step, not
per request. Reading the
AgentReportersummary line as well would double-count every request. creditis a quota, not money. It does not scale with the token counts or with the list price — the ratio between credit and cost ranged 4× to 126× for the same model. It is not used as a cost anywhere.
Lingma's cache split is only partly known, and its lane says so. Its
SQLite store records the whole prompt and the completion for every model
call, but the cached part is written to a companion CLI log for only 5 of
the 43 calls on this machine; the rest record a cached_tokens of 0,
which is the cache-write count, not the read count. So the whole prompt
is priced as fresh for those 38 calls, and the lane's dollars are an
upper bound: $7.77 as reported, against $1.04 if every unknown call
were entirely cached. The token counts are exact either way. The model is
not recorded at all, so the lane is priced at the stated default and named
in the unpriced list.
OpenCode and Cherry Studio are the two supported agents whose store is a SQLite database rather than append-only text. Both are opened read-only — a running agent is never blocked — and a locked or foreign database is skipped exactly the way a truncated log line is.
Cherry Studio needs a dedup rule the others do not. It is a GUI that hosts other agents, so it writes a usage row for requests the hosted agent also logs. On this machine 3 of its 33 rows are the same request as a pi-agent row with identical token counts (17,858 tokens that would otherwise be counted twice). The agent's own log wins; the host's copy is dropped. The match is on the whole-prompt token total plus the model's family, within a 2-second window. Each half was forced by a measurement:
- Whole-prompt, not input and not output. The two stores write the same request 20-60ms apart, so an exact-timestamp match finds nothing — a silent no-op that looks like success. And they bucket differently: pi reports input as fresh-only plus a separate cache read, the host reports the whole prompt, and the host keeps reasoning out of output while pi folds it in (158 vs 204 for the same request). So matching on output misses every row that had reasoning.
- The model's family, because whole-prompt totals collide. 402 of the
14,959 distinct whole-prompt values on this machine are shared by two or
more records, and 739 of those pairs fall inside the 2-second window. A
prompt-size-only key would therefore be one collision away from deleting
a legitimate row. The family is the model id with its provider prefix
removed —
new-api:deepseek-v4.1-flash-freeanddeepseek-v4.1-flash-freeare the same model behind two naming conventions — so a same-sized prompt from a different model can never match.
Cherry Studio's other 30 rows are its own — June and July sessions no other source covers.
Two of the newer adapters share a shape and are worth naming, because both differences are invisible in a diff and each one silently loses real spend:
- OpenClaw keeps archives. Alongside the live
<uuid>.jsonlfiles it writes<uuid>.jsonl.reset.<ts>and<uuid>.jsonl.deleted.<ts>siblings — on this machine 127 of 959 usage rows and 2.03M input tokens, from sessions that were reset mid-run. Matching only bare.jsonlwould drop them. The siblingopenclaw-agent.sqlite(transcript_events) holds the same events, so reading both would double-count; only the JSONL is read. - pi-agent puts the model on the message. DSH's usage record has no model
field at all and needs the preceding
request/headercarried forward; pi and OpenClaw carry it on the same record as the usage, and amodel_changeevent can switch models mid-session, so the per-message value is read directly.
Each parser extracts raw token counts, then
src/pricing.js prices them:
cost = input × in-price
+ cacheRead × in-price × cacheReadMultiplier
+ cacheWrite × in-price × cacheWriteMultiplier
+ output × out-price
Prices are USD per 1M tokens, snapshotting the public pricing pages. They are
baked into the package so the tool works offline and stays deterministic.
Override a model by editing PRICING in src/pricing.js — it is a plain
object.
Costs are estimates. They do not include subscription plans (Claude Pro / Max, ChatGPT Plus), discounts, negotiated rates, or taxes. For API-billed usage they are normally within a few percent.
Two tools exist to check the tool against the world rather than against
itself. Both are in tools/ and both run offline.
python3 tools/audit_pricing.py /path/to/litellm.json # prices vs LiteLLM
node tools/crosscheck.mjs --ccusage /path/to/ccusage # totals vs ccusageaudit_pricing.py diffs every built-in price against the authoritative
LiteLLM model database and exits non-zero on disagreement. It runs weekly in
CI. Its first run caught 7 wrong entries in this table, including an absolute
price pasted into a multiplier field (gpt-5: cached reads overstated 25%).
crosscheck.mjs runs agentledger and ccusage against the same logs and
diffs token buckets and cost per lane. This is what found four real bugs that
no amount of internal testing surfaced:
| bug | what it did |
|---|---|
| duplicate Claude messages | 2,861 rows for 1,260 message ids — 2.2x over-count |
| MiMo cache multiplier | 0.1x where LiteLLM says ~0.008x — 12x overcharge |
| codex reasoning | output_tokens already includes reasoning; adding it double-counted 244,601 tokens |
| codex baseline | first token_count is real usage, not zero — 108,711 tokens dropped |
Token buckets must agree exactly (they are counted facts); cost is allowed a tolerance because two tools estimating models with no canonical price will legitimately differ.
Neither tool is a substitute for thinking, but "the suite is green" only proves a program is consistent with itself. Comparing against an independent implementation is what actually finds bugs.
agentledger-cli opens your log files in read mode and writes nothing. There is
no telemetry, no update check, and no code path that can reach the network. You
can verify this in a few minutes — the whole tool is ~5600 lines with zero
dependencies.
Issues and PRs welcome, especially:
- parsers for other agents (Cursor, Aider, Continue, Amp, …)
- more accurate pricing tables
- a statusline snippet for your shell of choice
agentledger is MIT-licensed and free forever. If it saved you a surprise bill, you can send something directly — no account, no signup, no intermediary:
ETH 0xB30a208B84814D3F7091Bb779f2b0225288349aE
BTC 1EKTrfpKc7fQwNYUv4oHLa2cna6NqDq3sM
Check whether anything has arrived (queries public endpoints, read-only):
python3 tools/tip.pyThere is no paid tier, no license key and no ads. Tipping is the only way this project makes money, and it is entirely optional.
These addresses are not just pasted here — python3 tools/prove_control.py
re-derives both from the private key and produces an ECDSA signature that
verifies against the derived public key, so you can confirm they really are
the project's before sending anything.
MIT.
