claude-agent-ledger 0.4.0 → 0.4.1

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,15 @@ 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.4.1] — 2026-04-22
9
+
10
+ ### Added
11
+ - `--anonymize` flag — replaces project paths with `~/repo-A`, `~/repo-B`,
12
+ ... and session ids with `sess-A`, `sess-B`, ... ordered by spend descending.
13
+ Use this when you want to share your output publicly (HN / Bluesky /
14
+ presentation) without leaking employer names or internal repo names.
15
+ Subagent / model / day labels stay untouched (those are public info).
16
+
8
17
  ## [0.4.0] — 2026-04-22
9
18
 
10
19
  ### Added
package/README.md CHANGED
@@ -180,6 +180,7 @@ If Anthropic's actual rate differs (or web_fetch has its own price), open an iss
180
180
  |---|---|
181
181
  | `--summary` | One-screen dashboard with all the headline numbers |
182
182
  | `--by <subagent\|model\|day\|project\|session>` | Group rows. Default: `subagent`. `day` renders as ASCII bar chart. |
183
+ | `--anonymize` | Replace project paths and session ids with `~/repo-A`, `sess-A`, ... so you can share screenshots without doxing your employer / private repos. |
183
184
  | `--md` | Markdown table output (good for committing or sharing) |
184
185
  | `--json` | Raw JSON output (pipe into jq) |
185
186
  | `--plan pro\|max` | Hide dollar columns; show token utilization only |
@@ -14,6 +14,7 @@ interface Args {
14
14
  plan: Plan;
15
15
  by: GroupKey;
16
16
  summary: boolean;
17
+ anonymize: boolean;
17
18
  }
18
19
 
19
20
  function periodRange(period: Period): { from: Date; to: Date } {
@@ -47,6 +48,9 @@ Flags:
47
48
  --md Markdown table output (good for sharing / commits)
48
49
  --json Raw JSON output (for piping into jq)
49
50
  --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.
50
54
 
51
55
  Environment:
52
56
  NO_COLOR=1 Disable ANSI colors
@@ -101,9 +105,31 @@ function parseArgs(argv: string[]): Args {
101
105
  plan,
102
106
  by,
103
107
  summary: rest.includes('--summary'),
108
+ anonymize: rest.includes('--anonymize'),
104
109
  };
105
110
  }
106
111
 
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
+ function anonymizeReport(report: LedgerReport, group: GroupKey): LedgerReport {
117
+ if (group !== 'project' && group !== 'session') return report;
118
+ const prefix = group === 'project' ? '~/repo-' : 'sess-';
119
+ const labelFor = (i: number): string => {
120
+ // Use A..Z then AA..ZZ for many entries
121
+ if (i < 26) return prefix + String.fromCharCode(65 + i);
122
+ return prefix + String.fromCharCode(65 + Math.floor(i / 26) - 1) + String.fromCharCode(65 + (i % 26));
123
+ };
124
+ const newRows = report.rows.map((r, i) => ({ ...r, subagent: labelFor(i) }));
125
+ return { ...report, rows: newRows };
126
+ }
127
+
128
+ // For --summary we may need to anonymize multiple sub-reports.
129
+ function anonymizeForSummary(byProject: LedgerReport): LedgerReport {
130
+ return anonymizeReport(byProject, 'project');
131
+ }
132
+
107
133
  function planMask(report: LedgerReport, plan: Plan): LedgerReport {
108
134
  if (plan === 'payg') return report;
109
135
  const zero = () => ({
@@ -139,17 +165,19 @@ async function main(): Promise<void> {
139
165
  const turns = await collectTurns({ from, to });
140
166
 
141
167
  if (args.summary) {
142
- const [bySub, byMod, byDay, byProj] = await Promise.all([
168
+ const [bySub, byMod, byDay, byProjRaw] = await Promise.all([
143
169
  aggregate(iterTurns(turns), from, to, 'subagent'),
144
170
  aggregate(iterTurns(turns), from, to, 'model'),
145
171
  aggregate(iterTurns(turns), from, to, 'day'),
146
172
  aggregate(iterTurns(turns), from, to, 'project'),
147
173
  ]);
174
+ const byProj = args.anonymize ? anonymizeForSummary(byProjRaw) : byProjRaw;
148
175
  console.log(formatSummary(bySub, byMod, byDay, byProj, args.period));
149
176
  return;
150
177
  }
151
178
 
152
179
  let report = await aggregate(iterTurns(turns), from, to, args.by);
180
+ if (args.anonymize) report = anonymizeReport(report, args.by);
153
181
  report = planMask(report, args.plan);
154
182
 
155
183
  let output: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-agent-ledger",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "See exactly where your Claude Code budget goes — per subagent, per task.",
5
5
  "type": "module",
6
6
  "bin": {