claude-agent-ledger 0.2.0 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to `claude-agent-ledger` will be documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] — 2026-04-22
9
+
10
+ ### Added
11
+ - `--summary` flag — single-screen dashboard with shadow cost, top subagent /
12
+ model / day, cache reuse ratio, and **leverage vs $200/mo Max plan**.
13
+ This is the killer view for "why install over /cost?"
14
+ - Auto-derived insights footer on the default table:
15
+ - Top contributor with %
16
+ - 1h cache spend call-out (when ≥5% of total)
17
+ - Cache reuse ratio (read/write) with verdict
18
+ - Monthly $ equivalent + leverage vs Max plan
19
+ - Daily-mode insights: peak day, avg/day, peak/trough variance
20
+
21
+ ### Changed
22
+ - `--by day` polish:
23
+ - Bar fixed to 40-char width so session counts align in their own column
24
+ - Brighter cyan (`\x1b[96m`) bar character
25
+ - Total row separated by horizontal rule, properly aligned
26
+ - Header line now shows period + total + sessions + group count at a glance:
27
+ `agent-ledger week 2026-04-15 → 2026-04-22 · $13,356.27 shadow · 217 sessions · 7 agents`
28
+
8
29
  ## [0.2.0] — 2026-04-21
9
30
 
10
31
  ### Added
package/README.md CHANGED
@@ -27,15 +27,49 @@ If you run **seven** subagents daily (reliability, release, review, iOS factory,
27
27
  npm install -g claude-agent-ledger
28
28
  # or: bun install -g claude-agent-ledger
29
29
 
30
- agent-ledger today # today's spend, by subagent
31
- agent-ledger week --by model # last 7 days, by Claude model
32
- agent-ledger week --by day # daily bar chart
30
+ agent-ledger week --summary # the dashboard (start here)
31
+ agent-ledger week --by model # which Claude model burned the budget
32
+ agent-ledger week --by day # daily bar chart with peak/avg/variance
33
33
  agent-ledger month --md > report.md
34
34
  ```
35
35
 
36
36
  No server. No account. No data leaves your machine.
37
37
 
38
- ## Example output (real)
38
+ ## Why this and not `/cost`?
39
+
40
+ Claude Code's built-in `/cost` shows the **current session** cost. That's it.
41
+
42
+ `claude-agent-ledger` answers questions `/cost` can't:
43
+
44
+ - Which of my **10+ subagents** burned the budget? (`--by model` / default)
45
+ - Which **day** of last week was the expensive one? (`--by day`)
46
+ - What would my Claude Max usage have cost on **pay-as-you-go**? (the "shadow cost" framing)
47
+ - Is my **cache reuse ratio** healthy? (auto-derived insight)
48
+ - What's the **leverage** I'm getting from my $200/mo subscription? (e.g. 250×)
49
+
50
+ Run `agent-ledger week --summary` once and decide.
51
+
52
+ ## Example output (real, from the author's machine)
53
+
54
+ ### `--summary` (the dashboard)
55
+
56
+ ```
57
+ agent-ledger week summary 2026-04-15 → 2026-04-22
58
+
59
+ Shadow cost $13,356.27
60
+ Sessions 217
61
+ Plan reference $200/mo (Max)
62
+ Multiplier 250× vs Max plan
63
+
64
+ Top subagent (main) $12,701.19 (95%)
65
+ Top model claude-opus-4-7 $7,184.45 (54%)
66
+ Peak day 2026-04-15 $3,679.80
67
+
68
+ Cache 1h writes 119.45M tokens · $3583.42
69
+ Cache reads 5.54B tokens · $8162.93
70
+ Cache reuse 38× reads/writes
71
+ Server tools none
72
+ ```
39
73
 
40
74
  ### Default — by subagent
41
75
 
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import { aggregate, type GroupKey } from '../src/aggregator.ts';
3
- import { formatMarkdown, formatTable, formatTrend } from '../src/format.ts';
3
+ import { formatMarkdown, formatSummary, formatTable, formatTrend } from '../src/format.ts';
4
4
  import { parseAll } from '../src/parser.ts';
5
- import type { LedgerReport } from '../src/types.ts';
5
+ import type { LedgerReport, SessionTurn } from '../src/types.ts';
6
6
 
7
7
  type Period = 'today' | 'week' | 'month';
8
8
  type Plan = 'payg' | 'pro' | 'max';
@@ -13,6 +13,7 @@ interface Args {
13
13
  json: boolean;
14
14
  plan: Plan;
15
15
  by: GroupKey;
16
+ summary: boolean;
16
17
  }
17
18
 
18
19
  function periodRange(period: Period): { from: Date; to: Date } {
@@ -34,24 +35,24 @@ function usage(): never {
34
35
  console.error(`Usage: agent-ledger <today|week|month> [flags]
35
36
 
36
37
  Flags:
37
- --by <subagent|model|day> Group rows by subagent (default), model, or day.
38
- "day" renders as a daily trend bar chart.
39
- --md Emit Markdown table (good for sharing / commits)
40
- --json Emit raw JSON (for piping into other tools)
38
+ --summary One-screen dashboard: total + top subagent + top
39
+ model + peak day + cache reuse + leverage vs $200/mo plan
40
+ --by <subagent|model|day> Group rows by subagent (default), model, or day
41
+ "day" renders as a daily ASCII bar chart
42
+ --md Markdown table output (good for sharing / commits)
43
+ --json Raw JSON output (for piping into jq)
41
44
  --plan pro|max Suppress dollar columns (token utilization only)
42
- Default is pay-as-you-go: shows shadow cost in $.
43
45
 
44
46
  Environment:
45
47
  NO_COLOR=1 Disable ANSI colors
46
- FORCE_COLOR=1 Force colors even when piping
48
+ FORCE_COLOR=1 Force colors when piping
47
49
 
48
50
  Examples:
49
- agent-ledger today
50
- agent-ledger week --by model
51
+ agent-ledger week --summary # the dashboard
51
52
  agent-ledger week --by day # daily bar chart
52
- agent-ledger week --md > week.md
53
- agent-ledger month --json | jq '.total.cost.totalCost'
54
- agent-ledger week --plan max
53
+ agent-ledger week --by model # which Claude model burned the budget
54
+ agent-ledger month --md > month.md
55
+ agent-ledger week --json | jq '.total.cost.totalCost'
55
56
  `);
56
57
  process.exit(1);
57
58
  }
@@ -88,6 +89,7 @@ function parseArgs(argv: string[]): Args {
88
89
  json: rest.includes('--json'),
89
90
  plan,
90
91
  by,
92
+ summary: rest.includes('--summary'),
91
93
  };
92
94
  }
93
95
 
@@ -109,10 +111,33 @@ function planMask(report: LedgerReport, plan: Plan): LedgerReport {
109
111
  };
110
112
  }
111
113
 
114
+ // Materialize once so --summary can re-aggregate by 3 dimensions cheaply.
115
+ async function collectTurns(opts: { from: Date; to: Date }): Promise<SessionTurn[]> {
116
+ const out: SessionTurn[] = [];
117
+ for await (const t of parseAll(opts)) out.push(t);
118
+ return out;
119
+ }
120
+
121
+ async function* iterTurns(turns: SessionTurn[]): AsyncGenerator<SessionTurn> {
122
+ for (const t of turns) yield t;
123
+ }
124
+
112
125
  async function main(): Promise<void> {
113
126
  const args = parseArgs(process.argv.slice(2));
114
127
  const { from, to } = periodRange(args.period);
115
- let report = await aggregate(parseAll({ from, to }), from, to, args.by);
128
+ const turns = await collectTurns({ from, to });
129
+
130
+ if (args.summary) {
131
+ const [bySub, byMod, byDay] = await Promise.all([
132
+ aggregate(iterTurns(turns), from, to, 'subagent'),
133
+ aggregate(iterTurns(turns), from, to, 'model'),
134
+ aggregate(iterTurns(turns), from, to, 'day'),
135
+ ]);
136
+ console.log(formatSummary(bySub, byMod, byDay, args.period));
137
+ return;
138
+ }
139
+
140
+ let report = await aggregate(iterTurns(turns), from, to, args.by);
116
141
  report = planMask(report, args.plan);
117
142
 
118
143
  let output: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-agent-ledger",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "See exactly where your Claude Code budget goes — per subagent, per task.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/format.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { AggregatedRow, LedgerReport } from './types.ts';
2
2
 
3
3
  const PAD_LABEL = 22;
4
+ const BAR_WIDTH = 40;
5
+ const PLAN_MAX_PRICE = 200; // monthly USD reference for shadow-cost leverage
4
6
 
5
7
  // Color helpers — TTY-aware, NO_COLOR-respecting (https://no-color.org/).
6
8
  const USE_COLOR = (() => {
@@ -14,6 +16,7 @@ const c = {
14
16
  bold: USE_COLOR ? '\x1b[1m' : '',
15
17
  green: USE_COLOR ? '\x1b[32m' : '',
16
18
  cyan: USE_COLOR ? '\x1b[36m' : '',
19
+ cyanBright: USE_COLOR ? '\x1b[96m' : '',
17
20
  yellow: USE_COLOR ? '\x1b[33m' : '',
18
21
  red: USE_COLOR ? '\x1b[31m' : '',
19
22
  magenta: USE_COLOR ? '\x1b[35m' : '',
@@ -22,6 +25,7 @@ const c = {
22
25
  };
23
26
 
24
27
  function fmtTokens(n: number): string {
28
+ if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(2)}B`;
25
29
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
26
30
  if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`;
27
31
  return String(n);
@@ -31,6 +35,10 @@ function fmtUsd(n: number): string {
31
35
  return `$${n.toFixed(2)}`;
32
36
  }
33
37
 
38
+ function fmtUsdComma(n: number): string {
39
+ return `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
40
+ }
41
+
34
42
  function colorCost(n: number, padded: string): string {
35
43
  if (!USE_COLOR) return padded;
36
44
  if (n >= 1000) return `${c.bold}${c.red}${padded}${c.reset}`;
@@ -39,6 +47,11 @@ function colorCost(n: number, padded: string): string {
39
47
  return `${c.dim}${padded}${c.reset}`;
40
48
  }
41
49
 
50
+ function pct(part: number, whole: number): string {
51
+ if (whole === 0) return '0%';
52
+ return `${((part / whole) * 100).toFixed(0)}%`;
53
+ }
54
+
42
55
  function formatRow(row: AggregatedRow, isTotal = false): string {
43
56
  const labelText = row.subagent.padEnd(PAD_LABEL);
44
57
  const label = isTotal
@@ -54,10 +67,74 @@ function formatRow(row: AggregatedRow, isTotal = false): string {
54
67
  return ` ${label}${sessions} ${io} ${c.dim}1h:${c.reset}${cached1h} ${c.dim}5m:${c.reset}${cached5m} ${c.dim}r:${c.reset}${cacheRead} ${cost}`;
55
68
  }
56
69
 
70
+ // Auto-derived insights. The whole point of installing the tool over /cost.
71
+ function deriveInsights(report: LedgerReport): string[] {
72
+ const lines: string[] = [];
73
+ const total = report.total.cost.totalCost;
74
+ if (total === 0) return lines;
75
+
76
+ // Top contributor with %
77
+ const top = report.rows[0];
78
+ if (top && top.cost.totalCost > 0) {
79
+ lines.push(
80
+ ` ${c.cyanBright}⚡${c.reset} top: ${c.cyan}${top.subagent}${c.reset} drives ${c.bold}${pct(top.cost.totalCost, total)}${c.reset} of spend ${c.dim}(${fmtUsd(top.cost.totalCost)} of ${fmtUsd(total)})${c.reset}`,
81
+ );
82
+ }
83
+
84
+ // 1h cache call-out (if non-trivial)
85
+ const oneHourCost =
86
+ report.total.cost.cacheCreation1hCost ?? 0;
87
+ if (oneHourCost >= total * 0.05) {
88
+ lines.push(
89
+ ` ${c.yellow}💾${c.reset} 1h cache writes: ${fmtTokens(report.total.cacheCreation1hTokens)} tokens · ${fmtUsd(oneHourCost)} ${c.dim}(${pct(oneHourCost, total)} of total)${c.reset}`,
90
+ );
91
+ }
92
+
93
+ // Cache reuse ratio — read vs write
94
+ const writeTokens =
95
+ report.total.cacheCreation1hTokens + report.total.cacheCreation5mTokens;
96
+ if (writeTokens > 0) {
97
+ const ratio = report.total.cacheReadTokens / writeTokens;
98
+ const verdict =
99
+ ratio >= 20
100
+ ? `${c.green}excellent reuse${c.reset}`
101
+ : ratio >= 5
102
+ ? `${c.yellow}OK reuse${c.reset}`
103
+ : `${c.red}low reuse${c.reset}`;
104
+ lines.push(
105
+ ` ${c.cyanBright}📥${c.reset} cache reuse: ${fmtTokens(report.total.cacheReadTokens)} reads / ${fmtTokens(writeTokens)} writes = ${c.bold}${ratio.toFixed(0)}×${c.reset} ${c.dim}(${verdict}${c.dim})${c.reset}`,
106
+ );
107
+ }
108
+
109
+ // Plan leverage
110
+ const dayCount =
111
+ Math.max(
112
+ 1,
113
+ Math.round(
114
+ (report.period.to.getTime() - report.period.from.getTime()) /
115
+ (1000 * 60 * 60 * 24),
116
+ ),
117
+ );
118
+ const monthlyEquivalent = (total / dayCount) * 30;
119
+ const leverage = monthlyEquivalent / PLAN_MAX_PRICE;
120
+ if (leverage >= 1) {
121
+ lines.push(
122
+ ` ${c.magenta}💸${c.reset} ${fmtUsdComma(monthlyEquivalent)}/mo equivalent ${c.dim}vs $${PLAN_MAX_PRICE}/mo plan = ${c.reset}${c.bold}${leverage.toFixed(0)}×${c.reset} ${c.dim}leverage${c.reset}`,
123
+ );
124
+ }
125
+
126
+ return lines;
127
+ }
128
+
57
129
  export function formatTable(report: LedgerReport, label: string): string {
58
- const header =
59
- `${c.bold}agent-ledger ${label}${c.reset} ${c.dim}·${c.reset} ` +
60
- `${c.gray}${report.period.from.toISOString().slice(0, 10)} → ${report.period.to.toISOString().slice(0, 10)}${c.reset}`;
130
+ const summaryLine =
131
+ `${c.gray}${report.period.from.toISOString().slice(0, 10)} → ${report.period.to.toISOString().slice(0, 10)}${c.reset}` +
132
+ ` ${c.dim}·${c.reset} ` +
133
+ `${c.bold}${fmtUsdComma(report.total.cost.totalCost)}${c.reset} ${c.dim}shadow${c.reset}` +
134
+ ` ${c.dim}·${c.reset} ${report.total.sessionCount} sessions` +
135
+ ` ${c.dim}·${c.reset} ${report.rows.length} ${label.includes('day') ? 'days' : label.includes('model') ? 'models' : 'agents'}`;
136
+
137
+ const header = `${c.bold}agent-ledger ${label}${c.reset} ${summaryLine}`;
61
138
 
62
139
  const colsRaw =
63
140
  ' ' +
@@ -95,35 +172,137 @@ export function formatTable(report: LedgerReport, label: string): string {
95
172
  );
96
173
  }
97
174
 
175
+ // Insights
176
+ const insights = deriveInsights(report);
177
+ if (insights.length) {
178
+ lines.push('');
179
+ lines.push(...insights);
180
+ }
181
+
98
182
  return lines.join('\n');
99
183
  }
100
184
 
101
- // Daily trend chart: vertical-ish ASCII bars per day.
185
+ // Daily trend chart with fixed-width bars.
102
186
  export function formatTrend(report: LedgerReport, label: string): string {
103
187
  const max = report.rows.reduce((m, r) => Math.max(m, r.cost.totalCost), 0);
104
- const barWidth = 36;
105
188
 
106
- const header =
107
- `${c.bold}agent-ledger ${label} (daily)${c.reset} ${c.dim}·${c.reset} ` +
108
- `${c.gray}${report.period.from.toISOString().slice(0, 10)} → ${report.period.to.toISOString().slice(0, 10)}${c.reset}`;
189
+ const summaryLine =
190
+ `${c.gray}${report.period.from.toISOString().slice(0, 10)} → ${report.period.to.toISOString().slice(0, 10)}${c.reset}` +
191
+ ` ${c.dim}·${c.reset} ` +
192
+ `${c.bold}${fmtUsdComma(report.total.cost.totalCost)}${c.reset} ${c.dim}shadow${c.reset}` +
193
+ ` ${c.dim}·${c.reset} ${report.total.sessionCount} sessions across ${report.rows.length} days`;
194
+
195
+ const header = `${c.bold}agent-ledger ${label} (daily)${c.reset} ${summaryLine}`;
109
196
 
110
197
  const lines = [header, ''];
111
198
  for (const row of report.rows) {
112
- const bars = Math.round((row.cost.totalCost / Math.max(max, 0.01)) * barWidth);
113
- const bar =
114
- (USE_COLOR ? c.cyan : '') + '█'.repeat(bars) + (USE_COLOR ? c.reset : '');
199
+ const bars = Math.round((row.cost.totalCost / Math.max(max, 0.01)) * BAR_WIDTH);
200
+ const barFilled = '█'.repeat(bars);
201
+ const barPadding = ' '.repeat(BAR_WIDTH - bars);
202
+ const bar = `${USE_COLOR ? c.cyanBright : ''}${barFilled}${USE_COLOR ? c.reset : ''}${barPadding}`;
115
203
  const dateLabel = `${c.gray}${row.subagent}${c.reset}`;
116
- const cost = colorCost(row.cost.totalCost, fmtUsd(row.cost.totalCost).padStart(9));
117
- const sessions = `${c.gray}(${row.sessionCount} sess)${c.reset}`;
118
- lines.push(` ${dateLabel} ${cost} ${bar} ${sessions}`);
204
+ const cost = colorCost(row.cost.totalCost, fmtUsd(row.cost.totalCost).padStart(10));
205
+ const sessionsRaw = `${row.sessionCount} sess`;
206
+ const sessions = `${c.dim}· ${sessionsRaw}${c.reset}`;
207
+ lines.push(` ${dateLabel} ${cost} ${bar} ${sessions}`);
208
+ }
209
+ const sepWidth = 12 + 2 + 10 + 2 + BAR_WIDTH + 2 + 10;
210
+ lines.push(` ${c.gray}${'─'.repeat(sepWidth)}${c.reset}`);
211
+ const totalLabel = `${c.bold}total${c.reset}`.padEnd(12 + (USE_COLOR ? c.bold.length + c.reset.length : 0));
212
+ const totalCost = colorCost(
213
+ report.total.cost.totalCost,
214
+ fmtUsd(report.total.cost.totalCost).padStart(10),
215
+ );
216
+ lines.push(` ${totalLabel} ${totalCost}`);
217
+
218
+ // Daily-mode insights
219
+ const insights = deriveDailyInsights(report);
220
+ if (insights.length) {
221
+ lines.push('');
222
+ lines.push(...insights);
119
223
  }
120
- lines.push('');
224
+
225
+ return lines.join('\n');
226
+ }
227
+
228
+ function deriveDailyInsights(report: LedgerReport): string[] {
229
+ if (report.rows.length === 0) return [];
230
+ const lines: string[] = [];
231
+ const peak = [...report.rows].sort((a, b) => b.cost.totalCost - a.cost.totalCost)[0]!;
232
+ const trough = [...report.rows]
233
+ .filter((r) => r.cost.totalCost > 0)
234
+ .sort((a, b) => a.cost.totalCost - b.cost.totalCost)[0];
235
+ const avg = report.total.cost.totalCost / report.rows.length;
236
+
237
+ lines.push(
238
+ ` ${c.cyanBright}⚡${c.reset} peak: ${c.cyan}${peak.subagent}${c.reset} ${c.bold}${fmtUsd(peak.cost.totalCost)}${c.reset} ${c.dim}(${peak.sessionCount} sess)${c.reset}`,
239
+ );
121
240
  lines.push(
122
- ` ${c.bold}total${c.reset.padEnd(PAD_LABEL - 5)}${' '.repeat(2)}${colorCost(
123
- report.total.cost.totalCost,
124
- fmtUsd(report.total.cost.totalCost).padStart(9),
125
- )}`,
241
+ ` ${c.gray}📊${c.reset} avg/day: ${fmtUsd(avg)}`,
126
242
  );
243
+ if (trough && trough.cost.totalCost > 0) {
244
+ const variance = peak.cost.totalCost / trough.cost.totalCost;
245
+ if (variance >= 3) {
246
+ lines.push(
247
+ ` ${c.yellow}🌪${c.reset} variance: ${c.bold}${variance.toFixed(0)}×${c.reset} ${c.dim}between peak (${peak.subagent}) and trough (${trough.subagent})${c.reset}`,
248
+ );
249
+ }
250
+ }
251
+ return lines;
252
+ }
253
+
254
+ // Single-screen dashboard. Aggregates the same period across 3 dimensions.
255
+ export function formatSummary(
256
+ bySubagent: LedgerReport,
257
+ byModel: LedgerReport,
258
+ byDay: LedgerReport,
259
+ label: string,
260
+ ): string {
261
+ const total = bySubagent.total.cost.totalCost;
262
+ const dayCount = Math.max(1, byDay.rows.length);
263
+ const monthlyEquivalent = (total / dayCount) * 30;
264
+ const leverage = monthlyEquivalent / PLAN_MAX_PRICE;
265
+
266
+ const top = (rows: AggregatedRow[]) => rows[0];
267
+ const tSub = top(bySubagent.rows);
268
+ const tMod = top(byModel.rows);
269
+ const tDay = [...byDay.rows].sort((a, b) => b.cost.totalCost - a.cost.totalCost)[0];
270
+
271
+ const k = (label: string, value: string) =>
272
+ ` ${c.gray}${label.padEnd(20)}${c.reset} ${value}`;
273
+ const dollar = (n: number) => `${c.bold}${fmtUsdComma(n)}${c.reset}`;
274
+
275
+ const writeTokens =
276
+ bySubagent.total.cacheCreation1hTokens + bySubagent.total.cacheCreation5mTokens;
277
+ const reuseRatio = writeTokens > 0 ? bySubagent.total.cacheReadTokens / writeTokens : 0;
278
+
279
+ const lines = [
280
+ `${c.bold}agent-ledger ${label} summary${c.reset} ${c.gray}${bySubagent.period.from.toISOString().slice(0, 10)} → ${bySubagent.period.to.toISOString().slice(0, 10)}${c.reset}`,
281
+ '',
282
+ k('Shadow cost', dollar(total)),
283
+ k('Sessions', `${bySubagent.total.sessionCount}`),
284
+ k('Plan reference', `$${PLAN_MAX_PRICE}/mo (Max)`),
285
+ k('Multiplier', `${c.bold}${leverage.toFixed(0)}×${c.reset} ${c.dim}vs Max plan${c.reset}`),
286
+ '',
287
+ k('Top subagent', tSub ? `${c.cyan}${tSub.subagent}${c.reset} ${dollar(tSub.cost.totalCost)} ${c.dim}(${pct(tSub.cost.totalCost, total)})${c.reset}` : '—'),
288
+ k('Top model', tMod ? `${c.cyan}${tMod.subagent}${c.reset} ${dollar(tMod.cost.totalCost)} ${c.dim}(${pct(tMod.cost.totalCost, total)})${c.reset}` : '—'),
289
+ k('Peak day', tDay ? `${c.cyan}${tDay.subagent}${c.reset} ${dollar(tDay.cost.totalCost)}` : '—'),
290
+ '',
291
+ k('Cache 1h writes', `${fmtTokens(bySubagent.total.cacheCreation1hTokens)} tokens · ${fmtUsd(bySubagent.total.cost.cacheCreation1hCost)}`),
292
+ k('Cache reads', `${fmtTokens(bySubagent.total.cacheReadTokens)} tokens · ${fmtUsd(bySubagent.total.cost.cacheReadCost)}`),
293
+ k(
294
+ 'Cache reuse',
295
+ writeTokens > 0
296
+ ? `${c.bold}${reuseRatio.toFixed(0)}×${c.reset} ${c.dim}reads/writes${c.reset}`
297
+ : 'n/a',
298
+ ),
299
+ k(
300
+ 'Server tools',
301
+ bySubagent.total.webSearchRequests + bySubagent.total.webFetchRequests > 0
302
+ ? `web_search ×${bySubagent.total.webSearchRequests}, web_fetch ×${bySubagent.total.webFetchRequests} (${fmtUsd(bySubagent.total.cost.serverToolUseCost)})`
303
+ : `${c.dim}none${c.reset}`,
304
+ ),
305
+ ];
127
306
  return lines.join('\n');
128
307
  }
129
308