{"slug": "show-hn-tallyhook-price-claude-code-and-codex-logs-per-client-you-bill", "title": "Show HN: Tallyhook – price Claude Code and Codex logs per client you bill", "summary": "Tallyhook, a dependency-free Node.js tool, prices Claude Code and Codex CLI session logs per repository and per billable client using public API list prices, running entirely on the user's machine with no account or upload. The tool reports a sample 30-day total of $2,740.96 across 266 sessions, with the previous 30 days at $1,602.11 across 186 sessions, a 71% increase. Tallyhook deduplicates streamed JSONL rows by message.id and prices cache reads, 5-minute cache writes at 1.25x input, and 1-hour cache writes at 2x input.", "body_md": "What your AI coding agents actually cost — **per repo, and per client you bill.**\n\nOne dependency-free Node.js file. It reads the session logs **Claude Code** and **Codex CLI** already\nwrite on your machine and prices them. No account, no signup, nothing uploaded.\n\n```\nnpx tallyhook\nAI coding-agent usage on this machine, last 30 days (public API list prices)\n\nREPO                              SESSIONS    OUTPUT    LIST COST\ngithub.com/acme/storefront             118     10.8M    $1,450.59\ngithub.com/acme/patient-portal         100      7.2M      $867.84\ngithub.com/globex/intranet              44      3.1M      $402.17\nlocal:scratch                            4     96.2k       $20.36\nTOTAL                                  266     21.2M    $2,740.96\n\nPrevious 30 days: $1,602.11 across 186 sessions — up 71%.\n```\n\nIf you run agents on work you bill someone for, the number you need is not \"what did this repo cost\" — it's \"what do I put on Acme's invoice this month.\" So group the repos and set a rate:\n\n```\nnpx tallyhook clients add \"Acme Corp\" acme/storefront acme/patient-portal --rate 20\nnpx tallyhook clients add \"Globex\" globex/ --rate 35\nnpx tallyhook --by client\nCLIENT          SESSIONS    OUTPUT    LIST COST     BILLABLE\nAcme Corp            218     18.0M    $2,318.43    $2,782.12\nGlobex                44      3.1M      $402.17      $542.93\nTOTAL                262     21.1M    $2,720.60    $3,325.05\n\nNot counted: $20.36 in repos no client claims. `tallyhook clients` lists them.\n```\n\nThat mapping lives in `~/.tallyhook/clients.json`, on your machine, and it is still free and still\nuploads nothing. `tallyhook clients` on its own prints the map **plus every repo no client claims\nyet, most expensive first** — which is the list of work standing between you and a complete invoice.\n\nBillable 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.\n\n```\nnpx tallyhook                       # spend per repo, last 30 days\nnpx tallyhook --by client           # roll repos up into the clients you bill\nnpx tallyhook --by model            # the same spend per model\nnpx tallyhook clients               # the billing map, and which repos nobody claims\nnpx tallyhook mcp                   # MCP server, so an agent can ask what its own work cost\n```\n\n| flag | what it does | \n|---|---|\n| `--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. | \n| `--by client\\|model\\|repo` | how to group. A session that spanned two models has its cost split between them rather than attributed whole. | \n| `--markup 20` | try a rate without saving it. Beats any stored rate for that one run, so it never silently no-ops. | \n| `--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 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 client rows actually cover. | \n\nEverything above runs entirely on your machine. The only network call is a `GET` for the public price\ntable (`https://tallyhook.dev/api/prices`). Prefer to read it before you run it? It is one file,\n`tallyhook.js`, in this repo — download it, read it, then `node tallyhook.js`.\n\nMost of the work in this file is in not being wrong:\n\n- **Streamed rows:** Claude Code writes one JSONL row per content block, each carrying the same`usage` object. Rows are deduplicated by`message.id` , or the same tokens get counted many times.\n- **Subagents:** forked subagent transcripts copy the parent's assistant rows. Deduplication spans\nevery file of a session, including`subagents/*.jsonl` .\n- **Cache pricing:** cache reads, 5-minute cache writes (1.25× input) and 1-hour cache writes\n(2× input) are each priced separately. Fast-mode messages are priced at the fast-mode rate.\n- **Codex:** reads`~/.codex/sessions/**/*.jsonl` , and the`.jsonl.zst` files Codex compresses after\nseven days (needs Node 22.15+; older Node skips them rather than guessing).\n- **Repos, not folders:** sessions group by git remote (`host/org/repo` ), so the same repo in two\ncheckouts is one line. No remote means`local:<folder>` .\n- **Files, not just cwd:** a session that edited files in three repos is attributed to the repos the\nfiles belong to, not to whichever directory the agent happened to start in.\n- **Honest numbers:** costs are list-price equivalents from the vendors' public API pricing. On a\nsubscription that is the value of what you used, not a bill. A model with no known price is\nreported as unpriced rather than guessed at.\n\n```\nclaude mcp add tallyhook -- npx -y tallyhook mcp\n```\n\nAn [MCP](https://modelcontextprotocol.io) server over stdio, so Claude Code (or any MCP client) can\nanswer cost questions about its own work. **No account, no token, nothing uploaded** — it reads the\nsame local logs the report does. Or by hand, in `.mcp.json`:\n\n```\n{ \"mcpServers\": { \"tallyhook\": { \"command\": \"npx\", \"args\": [\"-y\", \"tallyhook\", \"mcp\"] } } }\n```\n\nThen ask it *\"what has this repo cost me this month?\"*, *\"which sessions were the expensive ones?\"*\nor *\"am I spending more than last month?\"*\n\n| tool | answers | \n|---|---|\n| `usage_by_repo` | spend grouped by git repository over the last N days | \n| `usage_by_model` | the same spend grouped by model — a session spanning two models is split between them, so the totals agree | \n| `expensive_sessions` | the individual sessions that cost the most, for finding a runaway agent loop | \n| `usage_summary` | total and session count, against the equally long window immediately before | \n\nParsing every local log is the slow part, so it is cached for a minute per process: the first\nquestion takes a few seconds on a busy machine, the rest are instant. Set `\"privacy\": true` in\n`~/.tallyhook/config.json` and `expensive_sessions` omits the prompt snippet too.\n\n```\nnpx skills add tallyhook/tallyhook-collector\n```\n\nInstalls `agent-cost-report` for Claude Code, Codex, Cline, Amp and twenty-odd other agents at once,\nso the agent itself can answer \"what has this project cost\" and \"what do I bill Acme this month\"\nwithout being told how. The skill carries the rules that stop the answer being wrong: that these are\nlist-price equivalents rather than a bill, that a client report covers only mapped repositories and\nsays in money what it excluded, and that an unreachable price table means report tokens rather than\nguess at dollars.\n\nFor Claude Code specifically there is also a plugin, with `/tallyhook:cost` and `/tallyhook:clients`\nplus the MCP server above.\n\nEverything above reads only what is on this disk, which is also its limit: it cannot see your\ncolleagues' sessions, it forgets history once Claude Code rotates its logs, and it cannot hand a\nclient anything. [Tallyhook](https://tallyhook.dev) is the hosted layer for those three:\nevery developer's sessions in one total, history that outlives the logs, a budget and alert per\nclient, and a report link you can send a client with no login. There is a\n[live demo](https://tallyhook.dev/demo) that needs no signup.\n\n```\nnpx tallyhook install <token>       # registers Claude Code hooks (SessionEnd, and Stop at most every 10 min), uploads history\nnpx tallyhook sync [--dry-run]      # upload anything new now; --dry-run prints a summary and never touches the network\nnpx tallyhook status                # what is configured\nnpx tallyhook uninstall             # remove the hooks and ~/.tallyhook\n```\n\nThe local commands stay local and account-free either way; installing a token does not change what they read or send anything extra.\n\n**Uploaded per session (install/sync only):** token counts by model, start and end time, tool and\nversion, git remote and branch, which repositories the edited files belong to, developer identity\nfrom git config, machine hostname, turn and tool-call counts, paths of edited files, and the first\n160 characters of the first prompt (turn that off with `\"privacy\": true`).\n\n**Never uploaded:** transcripts, code, diffs, tool output, environment variables.\n\nNode.js 18+. macOS and Linux; Windows is newly allowed as of 0.4.2 and not yet verified. Licence: MIT.", "url": "https://wpnews.pro/news/show-hn-tallyhook-price-claude-code-and-codex-logs-per-client-you-bill", "canonical_source": "https://github.com/tallyhook/tallyhook-collector", "published_at": "2026-10-08 13:30:02+00:00", "updated_at": "2026-10-08 13:47:24.262353+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "developer-tools", "ai-products"], "entities": ["Tallyhook", "Claude Code", "Codex CLI", "Node.js", "Acme Corp", "Globex"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-tallyhook-price-claude-code-and-codex-logs-per-client-you-bill", "markdown": "https://wpnews.pro/news/show-hn-tallyhook-price-claude-code-and-codex-logs-per-client-you-bill.md", "text": "https://wpnews.pro/news/show-hn-tallyhook-price-claude-code-and-codex-logs-per-client-you-bill.txt", "jsonld": "https://wpnews.pro/news/show-hn-tallyhook-price-claude-code-and-codex-logs-per-client-you-bill.jsonld"}}