claude-agent-ledger 0.4.0 → 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,110 @@ 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
+
103
+ ## [0.4.1] — 2026-04-22
104
+
105
+ ### Added
106
+ - `--anonymize` flag — replaces project paths with `~/repo-A`, `~/repo-B`,
107
+ ... and session ids with `sess-A`, `sess-B`, ... ordered by spend descending.
108
+ Use this when you want to share your output publicly (HN / Bluesky /
109
+ presentation) without leaking employer names or internal repo names.
110
+ Subagent / model / day labels stay untouched (those are public info).
111
+
8
112
  ## [0.4.0] — 2026-04-22
9
113
 
10
114
  ### 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.
@@ -180,6 +196,7 @@ If Anthropic's actual rate differs (or web_fetch has its own price), open an iss
180
196
  |---|---|
181
197
  | `--summary` | One-screen dashboard with all the headline numbers |
182
198
  | `--by <subagent\|model\|day\|project\|session>` | Group rows. Default: `subagent`. `day` renders as ASCII bar chart. |
199
+ | `--anonymize` | Replace project paths and session ids with `~/repo-A`, `sess-A`, ... so you can share screenshots without doxing your employer / private repos. |
183
200
  | `--md` | Markdown table output (good for committing or sharing) |
184
201
  | `--json` | Raw JSON output (pipe into jq) |
185
202
  | `--plan pro\|max` | Hide dollar columns; show token utilization only |
@@ -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,6 +16,10 @@ interface Args {
14
16
  plan: Plan;
15
17
  by: GroupKey;
16
18
  summary: boolean;
19
+ tree: boolean;
20
+ anonymize: boolean;
21
+ budget?: number; // USD threshold; non-zero exit if exceeded
22
+ verbose: boolean;
17
23
  }
18
24
 
19
25
  function periodRange(period: Period): { from: Date; to: Date } {
@@ -32,32 +38,39 @@ function periodRange(period: Period): { from: Date; to: Date } {
32
38
  }
33
39
 
34
40
  function usage(): never {
35
- 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)
36
44
 
37
45
  Flags:
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|project|session>
46
+ --summary One-screen dashboard (start here).
47
+ --by <subagent|model|day|project|session|tool>
41
48
  Group rows by:
42
49
  subagent (default) — agent attribution
43
50
  model — opus vs sonnet vs haiku
44
51
  day — daily ASCII bar chart
45
52
  project — by Claude Code project (decoded path)
46
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.
47
58
  --md Markdown table output (good for sharing / commits)
48
- --json Raw JSON output (for piping into jq)
59
+ --json Raw JSON output (also works with --summary now)
49
60
  --plan pro|max Suppress dollar columns (token utilization only)
61
+ --anonymize Replace project paths and session ids with safe placeholders.
62
+ --verbose Print parse stats (files, turns, elapsed) to stderr.
50
63
 
51
64
  Environment:
52
65
  NO_COLOR=1 Disable ANSI colors
53
66
  FORCE_COLOR=1 Force colors when piping
54
67
 
55
68
  Examples:
56
- agent-ledger week --summary # the dashboard
57
- agent-ledger week --by day # daily bar chart
58
- agent-ledger week --by model # which Claude model burned the budget
59
- 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
60
72
  agent-ledger week --json | jq '.total.cost.totalCost'
73
+ agent-ledger today --budget 50 # CI gate: fail if today > $50
61
74
  `);
62
75
  process.exit(1);
63
76
  }
@@ -68,6 +81,7 @@ function parseArgs(argv: string[]): Args {
68
81
 
69
82
  let plan: Plan = 'payg';
70
83
  let by: GroupKey = 'subagent';
84
+ let budget: number | undefined;
71
85
  for (let i = 0; i < rest.length; i++) {
72
86
  const flag = rest[i];
73
87
  if (flag === '--plan') {
@@ -85,13 +99,24 @@ function parseArgs(argv: string[]): Args {
85
99
  next === 'model' ||
86
100
  next === 'day' ||
87
101
  next === 'project' ||
88
- next === 'session'
102
+ next === 'session' ||
103
+ next === 'tool'
89
104
  ) {
90
105
  by = next;
91
106
  i++;
92
107
  } else {
108
+ console.error(`agent-ledger: unknown --by value '${next}'`);
93
109
  usage();
94
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++;
95
120
  }
96
121
  }
97
122
  return {
@@ -101,7 +126,22 @@ function parseArgs(argv: string[]): Args {
101
126
  plan,
102
127
  by,
103
128
  summary: rest.includes('--summary'),
129
+ tree: rest.includes('--tree'),
130
+ anonymize: rest.includes('--anonymize'),
131
+ budget,
132
+ verbose: rest.includes('--verbose'),
133
+ };
134
+ }
135
+
136
+ function anonymizeReport(report: LedgerReport, group: GroupKey): LedgerReport {
137
+ if (group !== 'project' && group !== 'session') return report;
138
+ const prefix = group === 'project' ? '~/repo-' : 'sess-';
139
+ const labelFor = (i: number): string => {
140
+ if (i < 26) return prefix + String.fromCharCode(65 + i);
141
+ return prefix + String.fromCharCode(65 + Math.floor(i / 26) - 1) + String.fromCharCode(65 + (i % 26));
104
142
  };
143
+ const newRows = report.rows.map((r, i) => ({ ...r, subagent: labelFor(i) }));
144
+ return { ...report, rows: newRows };
105
145
  }
106
146
 
107
147
  function planMask(report: LedgerReport, plan: Plan): LedgerReport {
@@ -122,47 +162,172 @@ function planMask(report: LedgerReport, plan: Plan): LedgerReport {
122
162
  };
123
163
  }
124
164
 
125
- // Materialize once so --summary can re-aggregate by 3 dimensions cheaply.
126
- async function collectTurns(opts: { from: Date; to: Date }): Promise<SessionTurn[]> {
127
- const out: SessionTurn[] = [];
128
- for await (const t of parseAll(opts)) out.push(t);
129
- 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(() => {});
130
209
  }
131
210
 
132
- async function* iterTurns(turns: SessionTurn[]): AsyncGenerator<SessionTurn> {
133
- 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
+ }
134
236
  }
135
237
 
136
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
+ }
137
247
  const args = parseArgs(process.argv.slice(2));
138
248
  const { from, to } = periodRange(args.period);
139
- const turns = await collectTurns({ from, to });
140
-
141
- if (args.summary) {
142
- const [bySub, byMod, byDay, byProj] = await Promise.all([
143
- aggregate(iterTurns(turns), from, to, 'subagent'),
144
- aggregate(iterTurns(turns), from, to, 'model'),
145
- aggregate(iterTurns(turns), from, to, 'day'),
146
- aggregate(iterTurns(turns), from, to, 'project'),
147
- ]);
148
- console.log(formatSummary(bySub, byMod, byDay, byProj, args.period));
149
- 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);
150
269
  }
151
270
 
152
- let report = await aggregate(iterTurns(turns), from, to, args.by);
153
- report = planMask(report, args.plan);
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;
154
279
 
155
- let output: string;
156
- if (args.json) {
157
- output = JSON.stringify(report, null, 2);
158
- } else if (args.markdown) {
159
- output = formatMarkdown(report, args.period);
160
- } else if (args.by === 'day') {
161
- output = formatTrend(report, args.period);
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
+ }
162
308
  } else {
163
- 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);
164
330
  }
165
- console.log(output);
166
331
  }
167
332
 
168
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.0",
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
  }