npx tallyhook
What your AI coding agents actually cost — per repo, per team, and per client you bill.
One dependency-free Node.js file. It reads the session logs Claude Code and Codex CLI already write on your machine and prices them. No account, no signup, nothing uploaded.
npx tallyhook
AI coding-agent usage on this machine, last 30 days (public API list prices)
REPO SESSIONS OUTPUT LIST COST
github.com/acme/storefront 118 10.8M $1,450.59
github.com/acme/patient-portal 100 7.2M $867.84
github.com/globex/intranet 44 3.1M $402.17
local:scratch 4 96.2k $20.36
TOTAL 266 21.2M $2,740.96
Previous 30 days: $1,602.11 across 186 sessions — up 71%.
Group the repos the way your business works
A per-repo table answers "what is this project costing me." The next question forks on who is asking: a lead wants these repos added up per team or service, and a shop wants them added up per client, with a rate on top. It is one map either way, so use the word that fits.
npx tallyhook teams add "Platform" acme/storefront globex/intranet
npx tallyhook teams add "Clinical" acme/patient-portal
npx tallyhook --by team
TEAM SESSIONS OUTPUT LIST COST
Platform 162 13.9M $1,852.76
Clinical 100 7.2M $867.84
TOTAL 262 21.1M $2,720.60
Not counted: $20.36 in repos no team claims. `tallyhook teams` lists them.
Add a rate and the same rows carry what to invoice — the number you need is not "what did this repo cost" but "what do I put on Acme's invoice this month":
npx tallyhook clients add "Acme Corp" acme/storefront acme/patient-portal --rate 20
npx tallyhook clients add "Globex" globex/ --rate 35
npx tallyhook --by client
CLIENT SESSIONS OUTPUT LIST COST BILLABLE
Acme Corp 218 18.0M $2,318.43 $2,782.12
Globex 44 3.1M $402.17 $542.93
TOTAL 262 21.1M $2,720.60 $3,325.05
Not counted: $20.36 in repos no client claims. `tallyhook clients` lists them.
teams, groups and clients are one command under three names, over one map stored in
~/.tallyhook/clients.json — on your machine, still free, still uploading nothing. The word you type
changes the heading and the wording around it and never the data, so a map set up as clients reads
back as teams with nothing to migrate, and --json keeps its clients key whichever word you use.
The BILLABLE column appears only once something actually has a rate.
tallyhook teams on its own prints the map plus every repo nothing claims yet, most expensive
first — which is the list of work standing between you and the whole machine accounted for.
Billable totals are summed from rows rounded to the cent, deliberately: a client who adds up the lines you showed them has to land on the total you billed.
Commands
npx tallyhook # spend per repo, last 30 days
npx tallyhook --by team # roll those repos up per team or service
npx tallyhook --by client # the same roll-up, with a rate on top to bill
npx tallyhook --by model # the same spend per model
npx tallyhook teams # the map, and which repos nothing claims yet
npx tallyhook mcp # MCP server, so an agent can ask what its own work cost
| flag | what it does |
|---|---|
--days 90 |
a longer window. The trend line always compares against the equally long window immediately before it, so --days 7 compares this week against last week. |
--by team|client|group|model|repo |
how to group. team, client and group are one grouping under three names. A session that spanned two models has its cost split between them rather than attributed whole. |
--markup 20 |
try a rate without saving it. Beats any stored rate for that one run, so it never silently no-ops. |
--json |
the same numbers with no prose, for a script. Nothing unpriceable is guessed: if the price table is unreachable every cost is null and priced is false. With --by team or --by client it also carries counted and not_counted_cost_usd, which always add up to total_cost_usd, so a script can tell how much of the machine the grouped rows actually cover. |
Everything above runs entirely on your machine. The only network call is a GET for the public price
table (https://tallyhook.dev/api/prices). Prefer to read it before you run it? It is one file,
tallyhook.js, in this repo — download it, read it, then node tallyhook.js.
What it gets right
Most of the work in this file is in not being wrong:
- Streamed rows: Claude Code writes one JSONL row per content block, each carrying the same
usageobject. Rows are deduplicated bymessage.id, or the same tokens get counted many times. - Subagents: forked subagent transcripts copy the parent's assistant rows. Deduplication spans
every file of a session, including
subagents/*.jsonl. - Cache pricing: cache reads, 5-minute cache writes (1.25× input) and 1-hour cache writes (2× input) are each priced separately. Fast-mode messages are priced at the fast-mode rate.
- Codex: reads
~/.codex/sessions/**/*.jsonl, and the.jsonl.zstfiles Codex compresses after seven days (needs Node 22.15+; older Node skips them rather than guessing). - Repos, not folders: sessions group by git remote (
host/org/repo), so the same repo in two checkouts is one line. No remote meanslocal:<folder>. - Files, not just cwd: a session that edited files in three repos is attributed to the repos the files belong to, not to whichever directory the agent happened to start in.
- Honest numbers: costs are list-price equivalents from the vendors' public API pricing. On a subscription that is the value of what you used, not a bill. A model with no known price is reported as unpriced rather than guessed at.
Ask your agent what it just cost you
claude mcp add tallyhook -- npx -y tallyhook mcp
An MCP server over stdio, so Claude Code (or any MCP client) can
answer cost questions about its own work. No account, no token, nothing uploaded — it reads the
same local logs the report does. Or by hand, in .mcp.json:
{ "mcpServers": { "tallyhook": { "command": "npx", "args": ["-y", "tallyhook", "mcp"] } } }
Then ask it "what has this repo cost me this month?", "which sessions were the expensive ones?" or "am I spending more than last month?"
| tool | answers |
|---|---|
usage_by_repo |
spend grouped by git repository over the last N days |
usage_by_model |
the same spend grouped by model — a session spanning two models is split between them, so the totals agree |
expensive_sessions |
the individual sessions that cost the most, for finding a runaway agent loop |
usage_summary |
total and session count, against the equally long window immediately before |
Parsing every local log is the slow part, so it is cached for a minute per process: the first
question takes a few seconds on a busy machine, the rest are instant. Set "privacy": true in
~/.tallyhook/config.json and expensive_sessions omits the prompt snippet too.
Install it as an agent skill
npx skills add tallyhook/tallyhook-collector
Installs agent-cost-report for Claude Code, Codex, Cline, Amp and twenty-odd other agents at once,
so the agent itself can answer "what has this project cost", "what is the Platform team spending"
and "what do I bill Acme this month" without being told how. The skill carries the rules that stop
the answer being wrong: that these are list-price equivalents rather than a bill, that a grouped
report covers only mapped repositories and says in money what it excluded, and that an unreachable price table means report tokens rather than
guess at dollars.
For Claude Code specifically there is also a plugin, with /tallyhook:cost and /tallyhook:groups
plus the MCP server above.
When one machine is not enough
Everything above reads only what is on this disk, which is also its limit: it cannot see your colleagues' sessions, it forgets history once Claude Code rotates its logs, and it cannot hand anybody else a link. Tallyhook is the hosted layer for those three: every developer's sessions in one total, history that outlives the logs, a budget and alert per repo, team or client, and a read-only report link that needs no login — for a lead, for finance, or for the client you are billing. There is a live demo that needs no signup.
npx tallyhook install <token> # registers Claude Code hooks (SessionEnd, and Stop at most every 10 min), uploads history
npx tallyhook sync [--dry-run] # upload anything new now; --dry-run prints a summary and never touches the network
npx tallyhook status # what is configured
npx tallyhook uninstall # remove the hooks and ~/.tallyhook
The local commands stay local and account-free either way; installing a token does not change what they read or send anything extra.
Uploaded per session (install/sync only): token counts by model, start and end time, tool and
version, git remote and branch, which repositories the edited files belong to, developer identity
from git config, machine hostname, turn and tool-call counts, paths of edited files, and the first
160 characters of the first prompt (turn that off with "privacy": true).
Never uploaded: transcripts, code, diffs, tool output, environment variables.
Node.js 18+. macOS and Linux; Windows is newly allowed as of 0.4.2 and not yet verified. Licence: MIT.
