claude-agent-ledger 0.4.1 → 0.7.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,101 @@ 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.7.0] — 2026-04-23
9
+
10
+ ### Added — live tail + property tests
11
+
12
+ - **`agent-ledger watch [intervalSec]`** — live polling mode. Re-aggregates
13
+ today's cost every N seconds (default 10), prints a delta meter:
14
+
15
+ ```
16
+ [14:46:34] today $3310.71 ↑ +$3310.71 · 9560 turns (Δ9560) · top: (main) $3167.95
17
+ [14:46:40] today $3310.71 · +$0.00 · 9560 turns (Δ0) · top: (main) $3167.95
18
+ ```
19
+
20
+ Answers "what's happening right now" without needing to re-run queries.
21
+ Ctrl-C to exit.
22
+
23
+ - **Property tests via `fast-check`** — 7 new tests, 1400+ generated cases
24
+ covering:
25
+ - `costFor()` returns non-negative for any valid input
26
+ - `totalCost === sum(components)` exactly (ε < 1e-9)
27
+ - `cache_read ≤ fresh input` cost (90% discount holds)
28
+ - `cache_1h ≥ cache_5m` cost (1h tier always more expensive)
29
+ - `Aggregator.total === sum(rows)` for any random turn stream
30
+ - Non-assistant turns contribute 0 cost
31
+ - `SubagentGraph.totalCost ≥ selfCost` (children only add)
32
+
33
+ Total test count: 22 → 29 (kept all prior tests passing).
34
+
35
+ ## [0.6.0] — 2026-04-23
36
+
37
+ ### Added — `agent-ledger explain <sessionId>` (the LLM-native move)
38
+
39
+ A new subcommand that answers **"why was this single session expensive?"**
40
+ This is the question Helicone / Langfuse / OpenLLMetry physically cannot
41
+ answer — they observe API calls, not Claude Code's prompts and tool calls.
42
+
43
+ Two modes:
44
+
45
+ - **Heuristic mode** (default, no API key needed) — analyzes the session
46
+ JSONL and prints:
47
+ - Subagent + total cost + turn count
48
+ - Model breakdown (which model dominated)
49
+ - Tool invocation count (Read/Bash/Grep/Edit/MCP — top 8)
50
+ - Top 3 most expensive turns with full token + tool breakdown
51
+ - Cache reuse ratio + verdict (`<5×` warning, `>20×` good)
52
+
53
+ - **LLM mode** (set `ANTHROPIC_API_KEY`) — pipes the heuristic evidence into
54
+ Haiku 4.5 with a tightly-scoped prompt: "identify the SPECIFIC root cause
55
+ + ONE concrete action to cut this session's cost by 50%+." Costs about
56
+ $0.001/run. Cite turn numbers from the breakdown.
57
+
58
+ ```bash
59
+ agent-ledger explain 63063a38
60
+ agent-ledger explain b988bd89-803a-4c78 # full uuid or 8-char prefix both work
61
+ ```
62
+
63
+ ## [0.5.0] — 2026-04-23
64
+
65
+ ### Added — performance + production-readiness foundation
66
+
67
+ - **Streaming JSONL parser** — replaces `await file.text()` (which OOM'd on
68
+ 10GB+ logs). Now reads via `Bun.file().stream()` + `TextDecoder` line-by-line.
69
+ Memory profile is constant regardless of log size.
70
+ - **Concurrent file walk** — `findSessionLogs` parallelizes per-project
71
+ `readdir` (`Promise.all`), and `parseAll` reads up to 8 JSONL files
72
+ concurrently. ~5–10× cold-start speedup on multi-project setups.
73
+ - **Zero materialization** — bin no longer collects every turn into an array
74
+ before aggregating. Instead one parser stream fans out to N `Aggregator`
75
+ instances in parallel. Memory is now O(unique-keys) not O(turns).
76
+ - **`--by tool`** — new GroupKey. Attribute spend per tool call (`Read`,
77
+ `Bash`, `Grep`, `Write`, MCP tool names). Answers "which tool is eating my
78
+ budget" — the most actionable lens for a Claude Code user.
79
+ - **`--tree` (the MOAT)** — subagent-graph cost attribution. Walks the
80
+ parent→child orchestration tree and shows
81
+ `orchestrator → 3×researcher + 6×swift-dev = $312`. Helicone / Langfuse /
82
+ OpenLLMetry can't do this — they observe API calls, not Claude Code's
83
+ multi-agent runtime.
84
+ - **`--budget <USD>`** — exit code `2` if total cost exceeds budget. Wire it
85
+ into your CI / shell startup / `pre-commit` to gate spend.
86
+ - **`--json` works for `--summary`** — previously summary was text-only.
87
+ Now pipe-friendly: `agent-ledger week --summary --json | jq`.
88
+ - **`--verbose`** — print parse stats (turns / elapsed) to stderr.
89
+
90
+ ### Changed
91
+
92
+ - `Aggregator` is now a class (`add()` / `finalize()`) instead of an async
93
+ function. Enables single-pass fan-out and live-tail in v0.6.
94
+ - `parseAll` and `parseFile` no longer block on full file reads.
95
+ - `SessionTurn` adds `parentSessionId` and `toolUses[]` fields.
96
+
97
+ ### Internal
98
+
99
+ - Backwards-compatible: the old `aggregate(turns, from, to, group)` async
100
+ function still exists and wraps the new `Aggregator` class. All v0.4.x
101
+ tests pass without modification (22/22).
102
+
8
103
  ## [0.4.1] — 2026-04-22
9
104
 
10
105
  ### Added
package/README.md CHANGED
@@ -5,6 +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.*
10
+
8
11
  ---
9
12
 
10
13
  ## Why
@@ -28,11 +31,24 @@ npm install -g claude-agent-ledger
28
31
  # or: bun install -g claude-agent-ledger
29
32
 
30
33
  agent-ledger week --summary # the dashboard (start here)
31
- agent-ledger week --by model # which Claude model burned the budget
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
36
+ agent-ledger week --by model # opus vs sonnet vs haiku
32
37
  agent-ledger week --by project # which project ate your tokens
33
38
  agent-ledger week --by session # which single sessions went sideways
34
39
  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
35
42
  agent-ledger month --md > report.md
43
+
44
+ # Live tail — polling delta meter — new in 0.7
45
+ agent-ledger watch # refresh every 10s
46
+ agent-ledger watch 4 # refresh every 4s
47
+
48
+ # Root-cause analysis on a single expensive session — new in 0.6
49
+ agent-ledger explain <session-id> # heuristic: top-3 turns + tool/model breakdown
50
+ ANTHROPIC_API_KEY=sk-ant-... agent-ledger explain <session-id>
51
+ # LLM mode: Haiku 4.5 ≤4-bullet root cause (~$0.001/run)
36
52
  ```
37
53
 
38
54
  No server. No account. No data leaves your machine.
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env bun
2
- import { aggregate, type GroupKey } from '../src/aggregator.ts';
2
+ import { Aggregator, type GroupKey, SubagentGraph } from '../src/aggregator.ts';
3
+ import { analyzeSession, formatHeuristic, llmExplain } from '../src/explain.ts';
3
4
  import { formatMarkdown, formatSummary, formatTable, formatTrend } from '../src/format.ts';
4
5
  import { parseAll } from '../src/parser.ts';
5
- import type { LedgerReport, SessionTurn } from '../src/types.ts';
6
+ import { formatTree, formatTreeMarkdown } from '../src/tree.ts';
7
+ import type { LedgerReport } from '../src/types.ts';
6
8
 
7
9
  type Period = 'today' | 'week' | 'month';
8
10
  type Plan = 'payg' | 'pro' | 'max';
@@ -14,7 +16,10 @@ interface Args {
14
16
  plan: Plan;
15
17
  by: GroupKey;
16
18
  summary: boolean;
19
+ tree: boolean;
17
20
  anonymize: boolean;
21
+ budget?: number; // USD threshold; non-zero exit if exceeded
22
+ verbose: boolean;
18
23
  }
19
24
 
20
25
  function periodRange(period: Period): { from: Date; to: Date } {
@@ -33,35 +38,39 @@ function periodRange(period: Period): { from: Date; to: Date } {
33
38
  }
34
39
 
35
40
  function usage(): never {
36
- console.error(`Usage: agent-ledger <today|week|month> [flags]
41
+ console.error(`Usage:
42
+ agent-ledger <today|week|month> [flags] — aggregate views
43
+ agent-ledger explain <sessionId> — root-cause why one session was expensive (new in 0.6)
37
44
 
38
45
  Flags:
39
- --summary One-screen dashboard: total + top subagent + top
40
- model + peak day + cache reuse + leverage vs $200/mo plan
41
- --by <subagent|model|day|project|session>
46
+ --summary One-screen dashboard (start here).
47
+ --by <subagent|model|day|project|session|tool>
42
48
  Group rows by:
43
49
  subagent (default) — agent attribution
44
50
  model — opus vs sonnet vs haiku
45
51
  day — daily ASCII bar chart
46
52
  project — by Claude Code project (decoded path)
47
53
  session — by session id (top spenders)
54
+ tool — by Read/Bash/Write/Grep — what your turns burn on
55
+ --tree Subagent graph: parent→child cost attribution.
56
+ The MOAT view: orchestrator → 3×researcher + 6×swift-dev = $312.
57
+ --budget <USD> Exit non-zero (code 2) if total cost > USD. Pipe-friendly.
48
58
  --md Markdown table output (good for sharing / commits)
49
- --json Raw JSON output (for piping into jq)
59
+ --json Raw JSON output (also works with --summary now)
50
60
  --plan pro|max Suppress dollar columns (token utilization only)
51
- --anonymize Replace project paths and session ids with safe
52
- placeholders (~/repo-A, sess-A) so the output is
53
- shareable without doxing yourself.
61
+ --anonymize Replace project paths and session ids with safe placeholders.
62
+ --verbose Print parse stats (files, turns, elapsed) to stderr.
54
63
 
55
64
  Environment:
56
65
  NO_COLOR=1 Disable ANSI colors
57
66
  FORCE_COLOR=1 Force colors when piping
58
67
 
59
68
  Examples:
60
- agent-ledger week --summary # the dashboard
61
- agent-ledger week --by day # daily bar chart
62
- agent-ledger week --by model # which Claude model burned the budget
63
- agent-ledger month --md > month.md
69
+ agent-ledger week --summary # the dashboard
70
+ agent-ledger week --tree # subagent fan-out tree
71
+ agent-ledger week --by tool # which Claude tool ate the budget
64
72
  agent-ledger week --json | jq '.total.cost.totalCost'
73
+ agent-ledger today --budget 50 # CI gate: fail if today > $50
65
74
  `);
66
75
  process.exit(1);
67
76
  }
@@ -72,6 +81,7 @@ function parseArgs(argv: string[]): Args {
72
81
 
73
82
  let plan: Plan = 'payg';
74
83
  let by: GroupKey = 'subagent';
84
+ let budget: number | undefined;
75
85
  for (let i = 0; i < rest.length; i++) {
76
86
  const flag = rest[i];
77
87
  if (flag === '--plan') {
@@ -89,13 +99,24 @@ function parseArgs(argv: string[]): Args {
89
99
  next === 'model' ||
90
100
  next === 'day' ||
91
101
  next === 'project' ||
92
- next === 'session'
102
+ next === 'session' ||
103
+ next === 'tool'
93
104
  ) {
94
105
  by = next;
95
106
  i++;
96
107
  } else {
108
+ console.error(`agent-ledger: unknown --by value '${next}'`);
97
109
  usage();
98
110
  }
111
+ } else if (flag === '--budget') {
112
+ const next = rest[i + 1];
113
+ const n = Number(next);
114
+ if (!Number.isFinite(n) || n <= 0) {
115
+ console.error(`agent-ledger: --budget requires a positive number (got '${next}')`);
116
+ usage();
117
+ }
118
+ budget = n;
119
+ i++;
99
120
  }
100
121
  }
101
122
  return {
@@ -105,19 +126,17 @@ function parseArgs(argv: string[]): Args {
105
126
  plan,
106
127
  by,
107
128
  summary: rest.includes('--summary'),
129
+ tree: rest.includes('--tree'),
108
130
  anonymize: rest.includes('--anonymize'),
131
+ budget,
132
+ verbose: rest.includes('--verbose'),
109
133
  };
110
134
  }
111
135
 
112
- // Replace identifying labels with safe placeholders. Run after aggregation,
113
- // before formatting. We anonymize project paths (which leak employer + repo
114
- // names) and session ids; leave subagent / model / day untouched (those are
115
- // either public Anthropic info or generic agent type names).
116
136
  function anonymizeReport(report: LedgerReport, group: GroupKey): LedgerReport {
117
137
  if (group !== 'project' && group !== 'session') return report;
118
138
  const prefix = group === 'project' ? '~/repo-' : 'sess-';
119
139
  const labelFor = (i: number): string => {
120
- // Use A..Z then AA..ZZ for many entries
121
140
  if (i < 26) return prefix + String.fromCharCode(65 + i);
122
141
  return prefix + String.fromCharCode(65 + Math.floor(i / 26) - 1) + String.fromCharCode(65 + (i % 26));
123
142
  };
@@ -125,11 +144,6 @@ function anonymizeReport(report: LedgerReport, group: GroupKey): LedgerReport {
125
144
  return { ...report, rows: newRows };
126
145
  }
127
146
 
128
- // For --summary we may need to anonymize multiple sub-reports.
129
- function anonymizeForSummary(byProject: LedgerReport): LedgerReport {
130
- return anonymizeReport(byProject, 'project');
131
- }
132
-
133
147
  function planMask(report: LedgerReport, plan: Plan): LedgerReport {
134
148
  if (plan === 'payg') return report;
135
149
  const zero = () => ({
@@ -148,49 +162,172 @@ function planMask(report: LedgerReport, plan: Plan): LedgerReport {
148
162
  };
149
163
  }
150
164
 
151
- // Materialize once so --summary can re-aggregate by 3 dimensions cheaply.
152
- async function collectTurns(opts: { from: Date; to: Date }): Promise<SessionTurn[]> {
153
- const out: SessionTurn[] = [];
154
- for await (const t of parseAll(opts)) out.push(t);
155
- return out;
165
+ // `--watch` live-tail mode. Re-aggregates "today" every N seconds and
166
+ // prints a clean delta meter. Polling-based (no inotify) so it works on
167
+ // any platform Bun runs on. Ctrl-C to exit.
168
+ async function watchCmd(intervalSec: number): Promise<void> {
169
+ const interval = Math.max(2, intervalSec);
170
+ let prevCost = 0;
171
+ let prevTurnCount = 0;
172
+ const t0 = Date.now();
173
+ console.log(`agent-ledger watch — refreshing every ${interval}s · Ctrl-C to exit`);
174
+ console.log('');
175
+ const tick = async () => {
176
+ const { from, to } = periodRange('today');
177
+ const agg = new Aggregator('subagent', from, to);
178
+ let turns = 0;
179
+ for await (const turn of parseAll({ from, to })) {
180
+ turns++;
181
+ agg.add(turn);
182
+ }
183
+ const report = agg.finalize();
184
+ const cost = report.total.cost.totalCost;
185
+ const dCost = cost - prevCost;
186
+ const dTurns = turns - prevTurnCount;
187
+ const elapsedMin = ((Date.now() - t0) / 60000).toFixed(1);
188
+ const arrow = dCost > 0 ? '↑' : dCost < 0 ? '↓' : '·';
189
+ const ts = new Date().toISOString().slice(11, 19);
190
+ const top = report.rows[0];
191
+ const topLabel = top ? `top: ${top.subagent} ${fmtUsdInline(top.cost.totalCost)}` : 'no data';
192
+ console.log(
193
+ `[${ts}] today $${cost.toFixed(2)} ${arrow} +$${dCost.toFixed(2)} · ${turns} turns (Δ${dTurns}) · ${topLabel} · live ${elapsedMin}m`,
194
+ );
195
+ prevCost = cost;
196
+ prevTurnCount = turns;
197
+ };
198
+ await tick();
199
+ const id = setInterval(() => {
200
+ tick().catch((e) => console.error('watch tick error:', e));
201
+ }, interval * 1000);
202
+ process.on('SIGINT', () => {
203
+ clearInterval(id);
204
+ console.log('\nagent-ledger watch — exiting');
205
+ process.exit(0);
206
+ });
207
+ // Keep process alive
208
+ await new Promise(() => {});
156
209
  }
157
210
 
158
- async function* iterTurns(turns: SessionTurn[]): AsyncGenerator<SessionTurn> {
159
- for (const t of turns) yield t;
211
+ const fmtUsdInline = (n: number): string => '$' + n.toFixed(n < 0.01 ? 4 : 2);
212
+
213
+ async function explainCmd(sessionPrefix: string | undefined): Promise<void> {
214
+ if (!sessionPrefix) {
215
+ console.error('agent-ledger explain: missing sessionId argument');
216
+ console.error('Usage: agent-ledger explain <sessionId>');
217
+ console.error(' (sessionId can be a full uuid or 8-char prefix)');
218
+ process.exit(1);
219
+ }
220
+ const analysis = await analyzeSession(sessionPrefix);
221
+ if (!analysis) {
222
+ console.error(`agent-ledger explain: no session found matching '${sessionPrefix}'`);
223
+ console.error(' (try the 8-char prefix from `agent-ledger week --by session`)');
224
+ process.exit(1);
225
+ }
226
+ const heuristic = formatHeuristic(analysis);
227
+ console.log(heuristic);
228
+ const llm = await llmExplain(analysis);
229
+ if (llm) {
230
+ console.log('');
231
+ console.log(llm);
232
+ } else if (!process.env.ANTHROPIC_API_KEY) {
233
+ console.log('');
234
+ console.log('💡 Set ANTHROPIC_API_KEY to enable LLM root-cause analysis (~$0.001/run via Haiku 4.5)');
235
+ }
160
236
  }
161
237
 
162
238
  async function main(): Promise<void> {
239
+ // Subcommand dispatch.
240
+ if (process.argv[2] === 'explain') {
241
+ return explainCmd(process.argv[3]);
242
+ }
243
+ if (process.argv[2] === 'watch') {
244
+ const interval = Number(process.argv[3]) || 10;
245
+ return watchCmd(interval);
246
+ }
163
247
  const args = parseArgs(process.argv.slice(2));
164
248
  const { from, to } = periodRange(args.period);
165
- const turns = await collectTurns({ from, to });
166
-
167
- if (args.summary) {
168
- const [bySub, byMod, byDay, byProjRaw] = await Promise.all([
169
- aggregate(iterTurns(turns), from, to, 'subagent'),
170
- aggregate(iterTurns(turns), from, to, 'model'),
171
- aggregate(iterTurns(turns), from, to, 'day'),
172
- aggregate(iterTurns(turns), from, to, 'project'),
173
- ]);
174
- const byProj = args.anonymize ? anonymizeForSummary(byProjRaw) : byProjRaw;
175
- console.log(formatSummary(bySub, byMod, byDay, byProj, args.period));
176
- return;
249
+ const t0 = Date.now();
250
+
251
+ // Determine which aggregators we need based on flags.
252
+ // Single parser pass dispatches to all in parallel — no buffering.
253
+ const aggBySub = new Aggregator('subagent', from, to);
254
+ const aggByMod = args.summary ? new Aggregator('model', from, to) : undefined;
255
+ const aggByDay = args.summary ? new Aggregator('day', from, to) : undefined;
256
+ const aggByProj = args.summary ? new Aggregator('project', from, to) : undefined;
257
+ const aggUser = !args.summary && !args.tree ? new Aggregator(args.by, from, to) : undefined;
258
+ const graph = args.tree ? new SubagentGraph() : undefined;
259
+
260
+ let turnCount = 0;
261
+ for await (const turn of parseAll({ from, to })) {
262
+ turnCount++;
263
+ aggBySub.add(turn);
264
+ aggByMod?.add(turn);
265
+ aggByDay?.add(turn);
266
+ aggByProj?.add(turn);
267
+ aggUser?.add(turn);
268
+ graph?.add(turn);
177
269
  }
178
270
 
179
- let report = await aggregate(iterTurns(turns), from, to, args.by);
180
- if (args.anonymize) report = anonymizeReport(report, args.by);
181
- report = planMask(report, args.plan);
182
-
183
- let output: string;
184
- if (args.json) {
185
- output = JSON.stringify(report, null, 2);
186
- } else if (args.markdown) {
187
- output = formatMarkdown(report, args.period);
188
- } else if (args.by === 'day') {
189
- output = formatTrend(report, args.period);
271
+ if (args.verbose) {
272
+ const elapsed = Date.now() - t0;
273
+ console.error(
274
+ `agent-ledger: parsed ${turnCount} turns from ~/.claude/projects in ${elapsed}ms`,
275
+ );
276
+ }
277
+
278
+ let totalCost = aggBySub.finalize().total.cost.totalCost;
279
+
280
+ if (args.tree) {
281
+ const roots = graph!.finalize(20);
282
+ totalCost = roots.reduce((s, r) => s + r.totalCost, 0);
283
+ const out =
284
+ args.json
285
+ ? JSON.stringify({ roots, totalCost }, null, 2)
286
+ : args.markdown
287
+ ? formatTreeMarkdown(roots, totalCost)
288
+ : formatTree(roots, totalCost);
289
+ console.log(out);
290
+ } else if (args.summary) {
291
+ const bySubR = aggBySub.finalize();
292
+ const byModR = aggByMod!.finalize();
293
+ const byDayR = aggByDay!.finalize();
294
+ let byProjR = aggByProj!.finalize();
295
+ if (args.anonymize) byProjR = anonymizeReport(byProjR, 'project');
296
+ totalCost = bySubR.total.cost.totalCost;
297
+ if (args.json) {
298
+ console.log(
299
+ JSON.stringify(
300
+ { subagent: bySubR, model: byModR, day: byDayR, project: byProjR, totalCost },
301
+ null,
302
+ 2,
303
+ ),
304
+ );
305
+ } else {
306
+ console.log(formatSummary(bySubR, byModR, byDayR, byProjR, args.period));
307
+ }
190
308
  } else {
191
- output = formatTable(report, args.period, args.by);
309
+ let report = aggUser!.finalize();
310
+ if (args.anonymize) report = anonymizeReport(report, args.by);
311
+ report = planMask(report, args.plan);
312
+ totalCost = report.total.cost.totalCost;
313
+ const output =
314
+ args.json
315
+ ? JSON.stringify(report, null, 2)
316
+ : args.markdown
317
+ ? formatMarkdown(report, args.period)
318
+ : args.by === 'day'
319
+ ? formatTrend(report, args.period)
320
+ : formatTable(report, args.period, args.by);
321
+ console.log(output);
322
+ }
323
+
324
+ // Budget gate — non-zero exit code so this can be a CI/shell hook.
325
+ if (args.budget !== undefined && totalCost > args.budget) {
326
+ console.error(
327
+ `agent-ledger: BUDGET EXCEEDED — total $${totalCost.toFixed(2)} > budget $${args.budget.toFixed(2)}`,
328
+ );
329
+ process.exit(2);
192
330
  }
193
- console.log(output);
194
331
  }
195
332
 
196
333
  main().catch((err) => {
package/bun.lock CHANGED
@@ -6,6 +6,7 @@
6
6
  "name": "agent-ledger",
7
7
  "devDependencies": {
8
8
  "@types/bun": "latest",
9
+ "fast-check": "^4.7.0",
9
10
  "typescript": "^5.6.0",
10
11
  },
11
12
  },
@@ -17,6 +18,10 @@
17
18
 
18
19
  "bun-types": ["bun-types@1.3.12", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.12.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
19
20
 
21
+ "fast-check": ["fast-check@4.7.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ=="],
22
+
23
+ "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="],
24
+
20
25
  "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
21
26
 
22
27
  "undici-types": ["undici-types@7.19.2", "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-agent-ledger",
3
- "version": "0.4.1",
3
+ "version": "0.7.0",
4
4
  "description": "See exactly where your Claude Code budget goes — per subagent, per task.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/bun": "latest",
34
+ "fast-check": "^4.7.0",
34
35
  "typescript": "^5.6.0"
35
36
  }
36
37
  }