claude-agent-ledger 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,8 +5,9 @@
5
5
  [![npm](https://img.shields.io/npm/v/claude-agent-ledger.svg)](https://www.npmjs.com/package/claude-agent-ledger)
6
6
  [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
7
 
8
- > *Even I needed `--anonymize` before posting my own data publicly.
9
- > That's why the flag exists. Use it before you screenshot.*
8
+ > Use `--anonymize` before sharing output. It replaces project paths and
9
+ > session IDs, but it does not rename subagent or tool names — review those
10
+ > before you post a screenshot.
10
11
 
11
12
  ---
12
13
 
@@ -26,32 +27,40 @@ If you run **seven** subagents daily (reliability, release, review, iOS factory,
26
27
 
27
28
  ## Install
28
29
 
30
+ Requires [Bun](https://bun.sh/) 1.1 or newer. The CLI is Bun-native even when
31
+ you install it from npm.
32
+
29
33
  ```bash
30
- npm install -g claude-agent-ledger
31
- # or: bun install -g claude-agent-ledger
34
+ bun install -g claude-agent-ledger
35
+ # or, with Bun already available: npm install -g claude-agent-ledger
32
36
 
33
37
  agent-ledger week --summary # the dashboard (start here)
34
- agent-ledger week --tree # subagent fan-out tree (the MOAT view) — new in 0.5
35
- agent-ledger week --by tool # which tool ate the budget (Read/Bash/Grep/MCP) — new in 0.5
38
+ agent-ledger week --tree # parent → child subagent attribution
39
+ agent-ledger week --by tool # which tool ate the budget (Read/Bash/Grep/MCP)
36
40
  agent-ledger week --by model # opus vs sonnet vs haiku
37
41
  agent-ledger week --by project # which project ate your tokens
38
42
  agent-ledger week --by session # which single sessions went sideways
39
43
  agent-ledger week --by day # daily bar chart with peak/avg/variance
40
- agent-ledger today --budget 50 # CI gate: exit 2 if today > $50 — new in 0.5
41
- agent-ledger week --json | jq # JSON output for ANY view incl. summary — new in 0.5
44
+ agent-ledger today --budget 50 # CI gate: exit 2 if today > $50
45
+ agent-ledger week --json | jq # JSON output for any view, including summary
42
46
  agent-ledger month --md > report.md
43
47
 
44
- # Live tail — polling delta meter — new in 0.7
48
+ # Live tail — polling delta meter
45
49
  agent-ledger watch # refresh every 10s
46
50
  agent-ledger watch 4 # refresh every 4s
47
51
 
48
- # Root-cause analysis on a single expensive session — new in 0.6
52
+ # Root-cause analysis on a single expensive session
49
53
  agent-ledger explain <session-id> # heuristic: top-3 turns + tool/model breakdown
50
54
  ANTHROPIC_API_KEY=sk-ant-... agent-ledger explain <session-id>
51
- # LLM mode: Haiku 4.5 ≤4-bullet root cause (~$0.001/run)
55
+ # LLM mode: Haiku 4.5 ≤4-bullet root cause (typically <$0.005/run)
52
56
  ```
53
57
 
54
- No server. No account. No data leaves your machine.
58
+ All aggregate commands run locally, require no account, and make no network
59
+ requests. `explain` is also local by default. If you explicitly set
60
+ `ANTHROPIC_API_KEY`, it sends derived session metrics — subagent/model/tool
61
+ names, timestamps, token counts, and cost totals — to the
62
+ Anthropic API. The session identifier is redacted. It does **not** send prompts,
63
+ responses, tool content, or project paths.
55
64
 
56
65
  ## Why this and not `/cost`?
57
66
 
@@ -70,77 +79,23 @@ Claude Code's built-in `/cost` shows the **current session** cost. That's it.
70
79
 
71
80
  Run `agent-ledger week --summary` once and decide.
72
81
 
73
- ## Example output (real, from the author's machine)
74
-
75
- ### `--summary` (the dashboard)
76
-
77
- ```
78
- agent-ledger week summary 2026-04-15 → 2026-04-22
79
-
80
- Shadow cost $13,450.05
81
- Sessions 219
82
- Projects 9
83
- Plan reference $200/mo (Max)
84
- Multiplier 252× vs Max plan
85
-
86
- Top subagent (main) $12,787.19 (95%)
87
- Top model claude-opus-4-7 $7,278.87 (54%)
88
- Top project ~/clawbot $6,657.93 (50%)
89
- Peak day 2026-04-15 $3,679.16
90
-
91
- Project mix
92
- ~/clawbot $6,657.93 (50%)
93
- ~/clawbot-work $4,433.32 (33%)
94
- ~/ZStack-zstack-workspace $1,890.02 (14%)
95
-
96
- Cache 1h writes 119.65M tokens · $3589.46
97
- Cache reads 5.59B tokens · $8236.15
98
- Cache reuse 38× reads/writes
99
- Server tools none
100
- ```
101
-
102
- ### Default — by subagent
103
-
104
- ```
105
- agent-ledger week · 2026-04-14 → 2026-04-21
106
-
107
- subagent sess tokens(in/out) cache tokens (1h/5m/read) cost
108
- ───────────────────────────────────────────────────────────────────────────────────────────
109
- (main) 28 514K / 15.87M 1h:116.10M 5m: 0 r:5229.48M $12524.97
110
- general-purpose 148 113K / 955K 1h: 0 5m: 20.67M r: 183.94M $599.97
111
- Swift Developer 13 4K / 574K 1h: 0 5m: 1.61M r: 47.55M $28.93
112
- Market Researcher 10 107K / 74K 1h: 0 5m: 863K r: 8.61M $7.26
113
- Explore 13 176K / 77K 1h: 0 5m: 2.60M r: 22.38M $6.47
114
- zstackws 3 232 / 20K 1h: 0 5m: 829K r: 3.52M $4.47
115
- claude-code-guide 1 2K / 2K 1h: 0 5m: 56K r: 223K $0.08
116
- ───────────────────────────────────────────────────────────────────────────────────────────
117
- total 216 916K / 17.57M 1h:116.10M 5m: 26.62M r:5495.71M $13172.14
118
- ```
82
+ ## Example
119
83
 
120
- ### `--by model`
84
+ This is deterministic output from the repository's synthetic fixture, using the
85
+ price snapshot documented below:
121
86
 
122
- ```
123
- subagent sess tokens(in/out) cache tokens (1h/5m/read) cost
124
- ───────────────────────────────────────────────────────────────────────────────────────────
125
- claude-opus-4-7 16 59K / 8.35M 1h: 61.15M 5m: 2.32M r:2974.59M $6967.29
126
- claude-opus-4-6 127 544K / 8.25M 1h: 55.02M 5m: 13.30M r:2422.18M $6160.46
127
- claude-sonnet-4-6 65 135K / 946K 1h: 0 5m: 8.81M r: 98.12M $77.07
128
- claude-haiku-4-5 13 178K / 67K 1h: 0 5m: 2.19M r: 21.57M $4.33
129
- ```
87
+ ```text
88
+ agent-ledger today summary
130
89
 
131
- ### `--by day` (ASCII bar chart)
90
+ Shadow cost $0.08
91
+ Sessions 2
92
+ Projects 1
132
93
 
94
+ Top subagent Reality Checker $0.06 (74%)
95
+ Top model claude-sonnet-4-6 $0.06 (74%)
96
+ Cache reads 56K tokens · $0.02
97
+ Cache reuse 28× reads/writes
133
98
  ```
134
- 2026-04-15 $3749.84 ████████████████████████████████████ (18 sess)
135
- 2026-04-16 $2492.99 ████████████████████████ (175 sess)
136
- 2026-04-17 $2111.63 ████████████████████ (9 sess)
137
- 2026-04-18 $1840.02 ██████████████████ (18 sess)
138
- 2026-04-19 $644.97 ██████ (5 sess)
139
- 2026-04-20 $1095.58 ███████████ (6 sess)
140
- 2026-04-21 $1273.55 ████████████ (9 sess)
141
- ```
142
-
143
- Yes those are real numbers. Yes it's a lot. See [Shadow cost vs actual bill](#shadow-cost-vs-actual-bill).
144
99
 
145
100
  ## Shadow cost vs actual bill
146
101
 
@@ -151,9 +106,13 @@ If you're on a fixed-price subscription (Claude Pro / Max / Claude Code plan), y
151
106
  - Understanding where time-and-token budget goes, regardless of how you pay
152
107
  - Spotting runaway agents (a 10× spike is still a 10× spike, capped or not)
153
108
  - Deciding when to downshift to Haiku or up to Opus per task
154
- - Justifying a Max subscription internally ("our shadow cost is $13K/wk, Max is $200/mo — that's a 65× discount")
109
+ - Comparing usage concentration with a fixed-price plan. Shadow cost is an
110
+ estimate, not a claim that subscription usage is interchangeable with raw API
111
+ capacity.
155
112
 
156
- If you're on pay-as-you-go, the `cost` column is roughly your bill (±5–10% depending on tier / geography / `server_tool_use` calibration).
113
+ If you're on pay-as-you-go, treat the `cost` column as a directional estimate.
114
+ Batch discounts, long-context premiums, provider-specific rates, retries, and
115
+ future price changes can make an invoice differ.
157
116
 
158
117
  ## How it works
159
118
 
@@ -163,10 +122,10 @@ and any `server_tool_use` (web_search / web_fetch) requests. Subagent invocation
163
122
  under `<session>/subagents/agent-*.jsonl`, with a sidecar `.meta.json` carrying the real
164
123
  subagent name (e.g. `Reality Checker`, `ios-factory`).
165
124
 
166
- `claude-agent-ledger` walks the tree, resolves subagent types from the sidecar metadata,
167
- and aggregates by your chosen key (subagent, model, or day). Pricing is applied locally
168
- from `src/pricing.ts`. Nothing is sent anywhere — the tool never touches the network
169
- after install.
125
+ `claude-agent-ledger` walks the tree, resolves subagent types from the sidecar
126
+ metadata, and aggregates by your chosen key. Pricing is applied locally from
127
+ `src/pricing.ts`. Aggregate commands never touch the network after install; the
128
+ optional API-backed `explain` mode is disclosed in [Install](#install).
170
129
 
171
130
  ### Cache accounting
172
131
 
@@ -182,24 +141,31 @@ Naive trackers lump these together as "input" and either overstate (by counting
182
141
 
183
142
  ### Server tool pricing
184
143
 
185
- `web_search_requests` and `web_fetch_requests` are counted at $0.01/request and shown in a footer when present:
144
+ `web_search_requests` cost $0.01/request. `web_fetch_requests` are counted for
145
+ observability but have no per-request charge; fetched content still contributes
146
+ normal token costs. Both appear in the footer:
186
147
 
187
148
  ```
188
149
  server tools: web_search ×7, web_fetch ×0 ($0.07)
189
150
  ```
190
151
 
191
- If Anthropic's actual rate differs (or web_fetch has its own price), open an issue.
152
+ Rates were checked against Anthropic's official pricing page on 2026-07-23.
153
+ If a log contains a model ID without a verified rate, the CLI warns on stderr
154
+ and leaves that model's token cost at `$0.00` instead of guessing.
192
155
 
193
156
  ## Flags
194
157
 
195
158
  | Flag | What it does |
196
159
  |---|---|
197
160
  | `--summary` | One-screen dashboard with all the headline numbers |
198
- | `--by <subagent\|model\|day\|project\|session>` | Group rows. Default: `subagent`. `day` renders as ASCII bar chart. |
161
+ | `--by <subagent\|model\|day\|project\|session\|tool>` | Group rows. Default: `subagent`. `day` renders as ASCII bar chart. |
162
+ | `--tree` | Render parent → child subagent cost attribution |
163
+ | `--budget <USD>` | Exit with code 2 when estimated cost exceeds the threshold |
199
164
  | `--anonymize` | Replace project paths and session ids with `~/repo-A`, `sess-A`, ... so you can share screenshots without doxing your employer / private repos. |
200
165
  | `--md` | Markdown table output (good for committing or sharing) |
201
166
  | `--json` | Raw JSON output (pipe into jq) |
202
- | `--plan pro\|max` | Hide dollar columns; show token utilization only |
167
+ | `--plan pro\|max` | Mask estimated costs in grouped table views; `--summary` and `--tree` keep their cost-oriented output |
168
+ | `--verbose` | Print parse counts and elapsed time to stderr |
203
169
 
204
170
  | Env | What it does |
205
171
  |---|---|
@@ -221,11 +187,16 @@ If Anthropic's actual rate differs (or web_fetch has its own price), open an iss
221
187
 
222
188
  ## Status
223
189
 
224
- `v0.2.0` shipped 2026-04-21. Validated on ~200K turns of real Claude Code logs.
225
- Pricing matches Anthropic published rates as of 2026-04-21. Shadow cost values
226
- may drift ±5-10% from Anthropic's actual invoice in edge cases (retries, tier
227
- bumps, `server_tool_use` calibration). Calibration PRs welcome — see issue
228
- templates.
190
+ The npm badge above reports the current published version. See
191
+ [`CHANGELOG.md`](CHANGELOG.md) for release history.
192
+
193
+ The source price table was checked against Anthropic's published rates on
194
+ 2026-07-23. Sonnet 5 automatically switches from its introductory rate to the
195
+ published standard rate on 2026-09-01. Pricing can change, so shadow costs are
196
+ estimates rather than invoice replicas; check
197
+ [`src/pricing.ts`](src/pricing.ts) and the
198
+ [official pricing page](https://platform.claude.com/docs/en/about-claude/pricing)
199
+ before making billing decisions.
229
200
 
230
201
  - [x] Log parser (JSONL streaming, recursive subagent discovery)
231
202
  - [x] Per-subagent aggregation
@@ -235,7 +206,7 @@ templates.
235
206
  - [x] ANSI color, TTY-aware
236
207
  - [x] Cache 1h/5m TTL split pricing
237
208
  - [x] `--plan pro|max` to mask shadow cost
238
- - [x] `server_tool_use` pricing (web_search, web_fetch)
209
+ - [x] `server_tool_use` accounting (paid web search, no-fee web fetch)
239
210
  - [ ] Cost forecasting (trailing 7-day burn rate)
240
211
  - [ ] Calibration against real Anthropic invoices
241
212
  - [ ] Homebrew tap
@@ -253,14 +224,15 @@ agent-ledger today
253
224
  ## Development
254
225
 
255
226
  ```bash
256
- bun test # 22 pass, 0 fail
227
+ bun test # 35 pass, 0 fail
257
228
  bun run typecheck
229
+ bun run build
258
230
  ```
259
231
 
260
232
  ## Author
261
233
 
262
- Built by [@XJM-free](https://jiexiang.dev) while running seven production Claude Code
263
- subagents for infrastructure work. More context coming on the blog.
234
+ Built by [Jie Xiang](https://jiexiang.dev)
235
+ ([@XJM-free](https://github.com/XJM-free)).
264
236
 
265
237
  ## License
266
238
 
@@ -3,8 +3,10 @@ import { Aggregator, type GroupKey, SubagentGraph } from '../src/aggregator.ts';
3
3
  import { analyzeSession, formatHeuristic, llmExplain } from '../src/explain.ts';
4
4
  import { formatMarkdown, formatSummary, formatTable, formatTrend } from '../src/format.ts';
5
5
  import { parseAll } from '../src/parser.ts';
6
+ import { isKnownModel } from '../src/pricing.ts';
6
7
  import { formatTree, formatTreeMarkdown } from '../src/tree.ts';
7
- import type { LedgerReport } from '../src/types.ts';
8
+ import type { LedgerReport, SubagentTreeNode } from '../src/types.ts';
9
+ import packageJson from '../package.json';
8
10
 
9
11
  type Period = 'today' | 'week' | 'month';
10
12
  type Plan = 'payg' | 'pro' | 'max';
@@ -37,8 +39,9 @@ function periodRange(period: Period): { from: Date; to: Date } {
37
39
  return { from, to };
38
40
  }
39
41
 
40
- function usage(): never {
41
- console.error(`Usage:
42
+ function usage(exitCode = 1): never {
43
+ const write = exitCode === 0 ? console.log : console.error;
44
+ write(`Usage:
42
45
  agent-ledger <today|week|month> [flags] — aggregate views
43
46
  agent-ledger explain <sessionId> — root-cause why one session was expensive (new in 0.6)
44
47
 
@@ -57,7 +60,7 @@ Flags:
57
60
  --budget <USD> Exit non-zero (code 2) if total cost > USD. Pipe-friendly.
58
61
  --md Markdown table output (good for sharing / commits)
59
62
  --json Raw JSON output (also works with --summary now)
60
- --plan pro|max Suppress dollar columns (token utilization only)
63
+ --plan pro|max Mask estimated costs in grouped table views.
61
64
  --anonymize Replace project paths and session ids with safe placeholders.
62
65
  --verbose Print parse stats (files, turns, elapsed) to stderr.
63
66
 
@@ -72,7 +75,7 @@ Examples:
72
75
  agent-ledger week --json | jq '.total.cost.totalCost'
73
76
  agent-ledger today --budget 50 # CI gate: fail if today > $50
74
77
  `);
75
- process.exit(1);
78
+ process.exit(exitCode);
76
79
  }
77
80
 
78
81
  function parseArgs(argv: string[]): Args {
@@ -117,6 +120,16 @@ function parseArgs(argv: string[]): Args {
117
120
  }
118
121
  budget = n;
119
122
  i++;
123
+ } else if (
124
+ flag !== '--md' &&
125
+ flag !== '--json' &&
126
+ flag !== '--summary' &&
127
+ flag !== '--tree' &&
128
+ flag !== '--anonymize' &&
129
+ flag !== '--verbose'
130
+ ) {
131
+ console.error(`agent-ledger: unknown flag '${flag}'`);
132
+ usage();
120
133
  }
121
134
  }
122
135
  return {
@@ -144,6 +157,29 @@ function anonymizeReport(report: LedgerReport, group: GroupKey): LedgerReport {
144
157
  return { ...report, rows: newRows };
145
158
  }
146
159
 
160
+ function anonymizeTrees(roots: SubagentTreeNode[]): SubagentTreeNode[] {
161
+ const labels = new Map<string, string>();
162
+ const labelFor = (sessionId: string): string => {
163
+ const existing = labels.get(sessionId);
164
+ if (existing) return existing;
165
+ const i = labels.size;
166
+ const suffix =
167
+ i < 26
168
+ ? String.fromCharCode(65 + i)
169
+ : String.fromCharCode(65 + Math.floor(i / 26) - 1) +
170
+ String.fromCharCode(65 + (i % 26));
171
+ const label = `sess-${suffix}`;
172
+ labels.set(sessionId, label);
173
+ return label;
174
+ };
175
+ const visit = (node: SubagentTreeNode): SubagentTreeNode => ({
176
+ ...node,
177
+ sessionId: labelFor(node.sessionId),
178
+ children: node.children.map(visit),
179
+ });
180
+ return roots.map(visit);
181
+ }
182
+
147
183
  function planMask(report: LedgerReport, plan: Plan): LedgerReport {
148
184
  if (plan === 'payg') return report;
149
185
  const zero = () => ({
@@ -231,20 +267,32 @@ async function explainCmd(sessionPrefix: string | undefined): Promise<void> {
231
267
  console.log(llm);
232
268
  } else if (!process.env.ANTHROPIC_API_KEY) {
233
269
  console.log('');
234
- console.log('💡 Set ANTHROPIC_API_KEY to enable LLM root-cause analysis (~$0.001/run via Haiku 4.5)');
270
+ console.log(
271
+ '💡 Set ANTHROPIC_API_KEY to enable LLM root-cause analysis (typically <$0.005/run via Haiku 4.5)',
272
+ );
235
273
  }
236
274
  }
237
275
 
238
276
  async function main(): Promise<void> {
277
+ const cliArgs = process.argv.slice(2);
278
+ const command = cliArgs[0];
279
+ if (command === 'help' || cliArgs.includes('--help') || cliArgs.includes('-h')) {
280
+ usage(0);
281
+ }
282
+ if (command === '--version' || command === '-v') {
283
+ console.log(packageJson.version);
284
+ return;
285
+ }
286
+
239
287
  // Subcommand dispatch.
240
- if (process.argv[2] === 'explain') {
288
+ if (command === 'explain') {
241
289
  return explainCmd(process.argv[3]);
242
290
  }
243
- if (process.argv[2] === 'watch') {
291
+ if (command === 'watch') {
244
292
  const interval = Number(process.argv[3]) || 10;
245
293
  return watchCmd(interval);
246
294
  }
247
- const args = parseArgs(process.argv.slice(2));
295
+ const args = parseArgs(cliArgs);
248
296
  const { from, to } = periodRange(args.period);
249
297
  const t0 = Date.now();
250
298
 
@@ -258,8 +306,12 @@ async function main(): Promise<void> {
258
306
  const graph = args.tree ? new SubagentGraph() : undefined;
259
307
 
260
308
  let turnCount = 0;
309
+ const unknownModels = new Set<string>();
261
310
  for await (const turn of parseAll({ from, to })) {
262
311
  turnCount++;
312
+ if (turn.model && !isKnownModel(turn.model)) {
313
+ unknownModels.add(turn.model);
314
+ }
263
315
  aggBySub.add(turn);
264
316
  aggByMod?.add(turn);
265
317
  aggByDay?.add(turn);
@@ -268,6 +320,14 @@ async function main(): Promise<void> {
268
320
  graph?.add(turn);
269
321
  }
270
322
 
323
+ if (unknownModels.size > 0) {
324
+ console.error(
325
+ 'agent-ledger: no verified price for model(s): ' +
326
+ [...unknownModels].sort().join(', ') +
327
+ '; their token cost is shown as $0.00',
328
+ );
329
+ }
330
+
271
331
  if (args.verbose) {
272
332
  const elapsed = Date.now() - t0;
273
333
  console.error(
@@ -278,7 +338,8 @@ async function main(): Promise<void> {
278
338
  let totalCost = aggBySub.finalize().total.cost.totalCost;
279
339
 
280
340
  if (args.tree) {
281
- const roots = graph!.finalize(20);
341
+ const rawRoots = graph!.finalize(20);
342
+ const roots = args.anonymize ? anonymizeTrees(rawRoots) : rawRoots;
282
343
  totalCost = roots.reduce((s, r) => s + r.totalCost, 0);
283
344
  const out =
284
345
  args.json
@@ -308,8 +369,9 @@ async function main(): Promise<void> {
308
369
  } else {
309
370
  let report = aggUser!.finalize();
310
371
  if (args.anonymize) report = anonymizeReport(report, args.by);
311
- report = planMask(report, args.plan);
372
+ // Keep the unmasked estimate for --budget even when --plan hides it.
312
373
  totalCost = report.total.cost.totalCost;
374
+ report = planMask(report, args.plan);
313
375
  const output =
314
376
  args.json
315
377
  ? JSON.stringify(report, null, 2)
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "claude-agent-ledger",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "See exactly where your Claude Code budget goes — per subagent, per task.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "agent-ledger": "./bin/agent-ledger.ts"
7
+ "agent-ledger": "bin/agent-ledger.ts"
8
8
  },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "LICENSE",
13
+ "README.md"
14
+ ],
9
15
  "scripts": {
10
16
  "dev": "bun run bin/agent-ledger.ts",
11
17
  "test": "bun test",
12
- "build": "bun build bin/agent-ledger.ts --target=node --outdir dist",
18
+ "build": "bun build bin/agent-ledger.ts --target=bun --outdir dist",
13
19
  "typecheck": "tsc --noEmit"
14
20
  },
15
21
  "engines": {
@@ -27,7 +33,11 @@
27
33
  "license": "MIT",
28
34
  "repository": {
29
35
  "type": "git",
30
- "url": "https://github.com/XJM-free/claude-agent-ledger.git"
36
+ "url": "git+https://github.com/XJM-free/claude-agent-ledger.git"
37
+ },
38
+ "homepage": "https://github.com/XJM-free/claude-agent-ledger#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/XJM-free/claude-agent-ledger/issues"
31
41
  },
32
42
  "devDependencies": {
33
43
  "@types/bun": "latest",
package/src/explain.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  // tool-call breakdown + cache-reuse stats.
6
6
  // 2. LLM mode (set ANTHROPIC_API_KEY): pipes the same evidence into
7
7
  // Haiku 4.5 for a 4-bullet verdict ("prompt bloat", "Opus mis-selection",
8
- // "no cache reuse", "tool output overflow"). Costs ~$0.001/run.
8
+ // "no cache reuse", "tool output overflow"). Typically costs <$0.005/run.
9
9
 
10
10
  import { homedir } from 'node:os';
11
11
  import { join } from 'node:path';
@@ -166,7 +166,9 @@ interface AnthropicMsgRes {
166
166
  export async function llmExplain(s: SessionAnalysis): Promise<string | undefined> {
167
167
  const apiKey = process.env.ANTHROPIC_API_KEY;
168
168
  if (!apiKey) return undefined;
169
- const evidence = formatHeuristic(s);
169
+ // The session identifier is useful locally but adds no analytical value to
170
+ // the API request, so keep it off the network.
171
+ const evidence = formatHeuristic({ ...s, sessionId: '[redacted]' });
170
172
  const prompt = `You are a Claude Code cost-optimization expert. A user's session burned ${fmtUsd(s.totalCost)} across ${s.turnCount} turns. Below is the per-turn breakdown:
171
173
 
172
174
  ${evidence}
package/src/pricing.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { CostBreakdown, ModelId, TokenUsage } from './types.ts';
2
2
 
3
3
  // Prices are per 1M tokens, USD. Keep in sync with
4
- // https://www.anthropic.com/pricing as of 2026-04-21.
4
+ // https://platform.claude.com/docs/en/about-claude/pricing as of 2026-07-23.
5
5
  //
6
6
  // Prompt caching (Anthropic):
7
7
  // cache_creation 5-min TTL: 1.25× base input rate
@@ -10,15 +10,31 @@ import type { CostBreakdown, ModelId, TokenUsage } from './types.ts';
10
10
  //
11
11
  // We include both current and prior-generation model IDs because real logs span
12
12
  // version bumps (Claude Code does not auto-rewrite historical logs when you upgrade).
13
+ const SONNET_5_STANDARD_PRICE_START = new Date('2026-09-01T00:00:00Z');
14
+ const SONNET_5_INTRO_RATE = { input: 2, output: 10 };
15
+ const SONNET_5_STANDARD_RATE = { input: 3, output: 15 };
16
+
13
17
  const PRICING: Record<string, { input: number; output: number }> = {
14
- // Current generation (2026-04)
15
- 'claude-opus-4-7': { input: 15, output: 75 },
18
+ // Current generation (2026-07)
19
+ 'claude-fable-5': { input: 10, output: 50 },
20
+ 'claude-mythos-5': { input: 10, output: 50 },
21
+ 'claude-opus-4-8': { input: 5, output: 25 },
22
+ 'claude-opus-4-7': { input: 5, output: 25 },
23
+ 'claude-opus-4-6': { input: 5, output: 25 },
24
+ 'claude-opus-4-5': { input: 5, output: 25 },
25
+ // resolveModel applies the introductory rate through 2026-08-31.
26
+ 'claude-sonnet-5': SONNET_5_STANDARD_RATE,
16
27
  'claude-sonnet-4-6': { input: 3, output: 15 },
17
- 'claude-haiku-4-5-20251001': { input: 0.8, output: 4 },
18
- // Prior generation (still appears in logs written before model upgrades)
19
- 'claude-opus-4-6': { input: 15, output: 75 },
20
- 'claude-opus-4-5': { input: 15, output: 75 },
21
28
  'claude-sonnet-4-5': { input: 3, output: 15 },
29
+ 'claude-haiku-4-5-20251001': { input: 1, output: 5 },
30
+ 'claude-haiku-4-5': { input: 1, output: 5 },
31
+ // Prior generation (still appears in logs written before model upgrades)
32
+ 'claude-opus-4-1-20250805': { input: 15, output: 75 },
33
+ 'claude-opus-4-20250514': { input: 15, output: 75 },
34
+ 'claude-sonnet-4-20250514': { input: 3, output: 15 },
35
+ 'claude-haiku-3-5-20241022': { input: 0.8, output: 4 },
36
+ 'claude-3-haiku-20240307': { input: 0.25, output: 1.25 },
37
+ // Kept for the aliases observed in older Claude Code logs.
22
38
  'claude-haiku-4-0': { input: 0.8, output: 4 },
23
39
  };
24
40
 
@@ -30,25 +46,30 @@ const CACHE_5M_MULTIPLIER = 1.25;
30
46
  const CACHE_1H_MULTIPLIER = 2.0;
31
47
  const CACHE_READ_MULTIPLIER = 0.1;
32
48
 
33
- // Anthropic server-tool pricing (https://www.anthropic.com/pricing as of 2026-04):
49
+ // Anthropic server-tool pricing (official pricing docs as of 2026-07-23):
34
50
  // Web search: $10 per 1,000 requests = $0.01 / request
35
- // Web fetch: treated identically here pending Anthropic split (file an issue if wrong)
51
+ // Web fetch: no per-request charge; fetched content still incurs token costs
36
52
  const WEB_SEARCH_PRICE_PER_REQUEST = 0.01;
37
- const WEB_FETCH_PRICE_PER_REQUEST = 0.01;
53
+ const WEB_FETCH_PRICE_PER_REQUEST = 0;
38
54
 
39
- function resolveModel(model: ModelId | undefined): { input: number; output: number } {
55
+ function resolveModel(model: ModelId | undefined, asOf: Date): { input: number; output: number } {
40
56
  if (!model) return { input: 0, output: 0 };
41
57
  if (FREE_MODELS.has(model)) return { input: 0, output: 0 };
58
+ if (model === 'claude-sonnet-5' || model.startsWith('claude-sonnet-5-')) {
59
+ return asOf < SONNET_5_STANDARD_PRICE_START
60
+ ? SONNET_5_INTRO_RATE
61
+ : SONNET_5_STANDARD_RATE;
62
+ }
42
63
  const exact = PRICING[model];
43
64
  if (exact) return exact;
44
- // Fuzzy family prefix match as a safety net for unknown subversions.
45
- // e.g. "claude-opus-4-8-20260301" falls back to Opus pricing until we add it.
65
+ // A provider/date-suffixed ID can inherit a verified base model's rate.
66
+ // Unknown future model families deliberately return zero instead of silently
67
+ // inventing a price; the CLI warns when it sees one.
46
68
  for (const key of Object.keys(PRICING)) {
47
- if (model.startsWith(key)) return PRICING[key] as { input: number; output: number };
69
+ if (model.startsWith(`${key}-`)) {
70
+ return PRICING[key] as { input: number; output: number };
71
+ }
48
72
  }
49
- if (model.startsWith('claude-opus-')) return { input: 15, output: 75 };
50
- if (model.startsWith('claude-sonnet-')) return { input: 3, output: 15 };
51
- if (model.startsWith('claude-haiku-')) return { input: 0.8, output: 4 };
52
73
  return { input: 0, output: 0 };
53
74
  }
54
75
 
@@ -70,8 +91,12 @@ function splitCacheCreation(usage: TokenUsage): CacheBreakdown {
70
91
  };
71
92
  }
72
93
 
73
- export function costFor(usage: TokenUsage, model: ModelId | undefined): CostBreakdown {
74
- const rate = resolveModel(model);
94
+ export function costFor(
95
+ usage: TokenUsage,
96
+ model: ModelId | undefined,
97
+ asOf: Date = new Date(),
98
+ ): CostBreakdown {
99
+ const rate = resolveModel(model, asOf);
75
100
  const cacheRead = usage.cache_read_input_tokens ?? 0;
76
101
  const { fiveMin, oneHour } = splitCacheCreation(usage);
77
102
 
@@ -110,5 +135,5 @@ export function isKnownModel(model: ModelId | undefined): boolean {
110
135
  if (!model) return false;
111
136
  if (FREE_MODELS.has(model)) return true;
112
137
  if (PRICING[model]) return true;
113
- return Object.keys(PRICING).some((key) => model.startsWith(key));
138
+ return Object.keys(PRICING).some((key) => model.startsWith(`${key}-`));
114
139
  }
@@ -1,35 +0,0 @@
1
- ---
2
- name: Bug report
3
- about: Report incorrect output, crashes, or wrong attribution
4
- title: ''
5
- labels: bug
6
- ---
7
-
8
- ## What happened
9
-
10
- <!-- What did agent-ledger do? What did you expect it to do instead? -->
11
-
12
- ## How to reproduce
13
-
14
- ```bash
15
- agent-ledger <your-exact-command>
16
- ```
17
-
18
- ## Output (redact sensitive paths / subagent names)
19
-
20
- ```
21
- <paste the output here>
22
- ```
23
-
24
- ## Your setup
25
-
26
- - OS: <!-- macOS 15.3 / Ubuntu 22.04 / ... -->
27
- - Bun version: <!-- `bun --version` -->
28
- - agent-ledger version: <!-- `agent-ledger --version` (pre-alpha: use commit SHA) -->
29
- - Claude Code version: <!-- `claude --version` -->
30
-
31
- ## If you're on pay-as-you-go, the calibration ask
32
-
33
- If you compare `agent-ledger month` against a real Anthropic invoice and
34
- they differ by > 10%, I want to hear about it. Even one line: "agent-ledger
35
- says $X, invoice says $Y" is incredibly useful. Thanks.