llm-cost-attribution 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Riddim Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # llm-cost-attribution
2
+
3
+ Per-issue token, turn, and quota analytics for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Codex CLI](https://github.com/openai/codex) sessions. Reads the CLIs' own session JSONLs — **no telemetry pipeline, no database, no API keys**.
4
+
5
+ ```bash
6
+ npx llm-cost-attribution EPAC-1940
7
+ ```
8
+
9
+ ```
10
+ ════════════════════════════════════════════════════════════════════════
11
+ LLM COST — EPAC-1940
12
+ ════════════════════════════════════════════════════════════════════════
13
+ Sessions found: 5
14
+ Total turns: 414
15
+ Total tokens: 61,357,012
16
+
17
+ ────────────────────────────────────────────────────────────────────────
18
+ CODEX (4 sessions)
19
+ ────────────────────────────────────────────────────────────────────────
20
+ Models: gpt-5-codex
21
+ Turns: 340
22
+ Tokens:
23
+ input uncached 1,517,206
24
+ cache read 51,024,768
25
+ output (visible) 44,683
26
+ output (reasoning) 18,649
27
+ grand total 52,605,306
28
+ Quota (plan_type=pro, 345 samples):
29
+ 5h window 58% → 64% used (peak 64%)
30
+ 7d window 56% → 57% used (peak 57%)
31
+ ```
32
+
33
+ ## Designed for Symphony workflows
34
+
35
+ [OpenAI Symphony's specification](https://github.com/openai/symphony/blob/main/SPEC.md) requires that each issue gets its own filesystem workspace, and that the coding agent's `cwd` equals that workspace path:
36
+
37
+ - **§4.1.4 Workspace** — "Filesystem workspace assigned to one issue identifier."
38
+ - **Workspace path formula** — `<workspace.root>/<sanitized_issue_identifier>`.
39
+ - **Invariant 1** — "Run the coding agent only in the per-issue workspace path... validate: `cwd == workspace_path`."
40
+
41
+ Because of those requirements, the working directory of every Claude Code or Codex CLI session that Symphony (or any Symphony-spec-conformant orchestrator) launches always carries the issue identifier as its last path component. The CLI agents in turn record that `cwd` in every session JSONL they create. So the issue identifier is already in the transcript — no custom telemetry pipeline needed to join.
42
+
43
+ This package's default `--cwd-pattern` matches the two most common `workspace.root` configurations:
44
+
45
+ 1. The Symphony spec default: `<system-temp>/symphony_workspaces/<ISSUE-ID>` (e.g. `/tmp/symphony_workspaces/EPAC-1940`).
46
+ 2. A common in-repo override: `<repo>/.symphony/workspaces/<ISSUE-ID>` (used by Autopilot and the Riddim factory's Symphony config).
47
+
48
+ For any other `workspace.root` setting, pass `--cwd-pattern '<regex>'` with one capture group for the issue identifier — see "[The convention](#the-convention)" below.
49
+
50
+ ## How it works
51
+
52
+ Both CLIs persist every session they run as JSONL:
53
+
54
+ - **Claude Code** writes `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl` for every interactive and non-interactive run (encoded-cwd is the absolute working directory with `/` and `.` replaced by `-`).
55
+ - **Codex CLI** writes `~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl` for every run, with the working directory recorded in the first `session_meta` event.
56
+
57
+ Each file carries provider-reported token usage per turn — the same numbers your Anthropic / OpenAI account is billed against:
58
+
59
+ | Provider | Tokens captured |
60
+ |---|---|
61
+ | Claude | `input_tokens`, `cache_read_input_tokens`, `cache_creation.{ephemeral_5m,1h}_input_tokens`, `output_tokens` |
62
+ | Codex | `input_tokens`, `cached_input_tokens`, `output_tokens`, `reasoning_output_tokens` (deltaed from cumulative) |
63
+ | Codex (additionally) | `rate_limits.{primary,secondary}.used_percent` per turn |
64
+
65
+ This package walks both directories, filters sessions whose working directory matches an issue identifier you ask for, and aggregates.
66
+
67
+ ## The convention
68
+
69
+ You map sessions to issues via the **working directory at session start**. By default this package matches the Symphony-spec convention:
70
+
71
+ ```
72
+ <repo>/.symphony/workspaces/<ISSUE-ID>
73
+ ```
74
+
75
+ A regex extracts `<ISSUE-ID>`. If your workflow uses a different layout, pass `--cwd-pattern '<regex>'` with one capture group:
76
+
77
+ ```bash
78
+ # Your workflow uses ../repo-worktrees/<ID>
79
+ llm-cost FOO-12 --cwd-pattern '-([A-Z]+-\d+)$'
80
+
81
+ # Your workflow uses ~/issues/<id>/
82
+ llm-cost 1234 --cwd-pattern '/issues/(\d+)$'
83
+ ```
84
+
85
+ If your workflow doesn't give each issue its own working directory (e.g. you switch branches in a single checkout), this package can't disambiguate sessions for you — see "[What it doesn't (and can't) do](#what-it-doesnt-and-cant-do)" below.
86
+
87
+ ## Install
88
+
89
+ ```bash
90
+ # One-shot via npx
91
+ npx llm-cost-attribution EPAC-1940
92
+
93
+ # Install globally
94
+ npm install -g llm-cost-attribution
95
+ llm-cost EPAC-1940
96
+ ```
97
+
98
+ Requires Node 20+. Zero runtime dependencies.
99
+
100
+ ## CLI
101
+
102
+ ```
103
+ llm-cost <ISSUE-ID> [options]
104
+ llm-cost <ISSUE-ID> --from-usage <usage.jsonl-or-dir>
105
+ llm-cost list
106
+ llm-cost backfill --out <usage.jsonl-path>
107
+ llm-cost --help
108
+
109
+ Options:
110
+ --cwd-pattern <regex> JS regex matching the cwd; one capture group is the issue ID.
111
+ Default matches both `<system-temp>/symphony_workspaces/<ID>`
112
+ and `<repo>/.symphony/workspaces/<ID>` (raw or Claude-encoded).
113
+ --claude-dir <path> Override ~/.claude/projects.
114
+ --codex-dir <path> Override ~/.codex/sessions.
115
+ --from-usage <path> Read from a usage.jsonl file or directory of `usage*.jsonl`
116
+ files instead of the CLI transcripts. See "Delete transcripts,
117
+ keep cost history" below.
118
+ --out <path> (backfill only) Destination usage.jsonl path. Appended.
119
+ --json Emit JSON instead of a table.
120
+ -h, --help Print help.
121
+ ```
122
+
123
+ ## Delete transcripts, keep cost history (optional)
124
+
125
+ Transcripts are large — a few MB per session, growing to gigabytes across an active factory — and most of the bytes are conversation content the cost tool doesn't need. So `llm-cost` can **bake** every transcript into a small append-only JSONL file (~1 KB per turn, no prompt or response content), then read cost queries from that file instead. After the bake, transcripts are safe to delete.
126
+
127
+ ```bash
128
+ # Bake every transcript on this machine into one file.
129
+ llm-cost backfill --out ~/llm-cost-history.jsonl
130
+
131
+ # Cost queries now run against the much smaller file:
132
+ llm-cost EPAC-1940 --from-usage ~/llm-cost-history.jsonl
133
+
134
+ # Once you've verified the numbers match, transcripts are safe to delete:
135
+ rm -rf ~/.claude/projects ~/.codex/sessions
136
+ ```
137
+
138
+ Real-world numbers from a working factory:
139
+
140
+ | | Before backfill | After backfill |
141
+ |---|---:|---:|
142
+ | Disk footprint | 5.0 GB | 125 MB (40× smaller) |
143
+ | `llm-cost EPAC-1940` query time | ~3 min (full Codex scan) | ~0.3 s |
144
+
145
+ The backfill is lossless for everything the cost analysis cares about — including the Codex per-window quota readout, the Claude cache-tier split (5m vs 1h), and the Codex reasoning-vs-visible output split. Token grand totals, turn counts, models, timestamps, and workspace-path provenance are preserved exactly. The bake file can also be checked into a private repo, shipped to a billing host, or queried from CI without access to the machine that produced the agent sessions.
146
+
147
+ This whole flow is a built-in feature of the package — you don't need to know anything about the file format to use it. As a side benefit: the format follows the [Symphony Coding-Agent Cost Telemetry Extension spec](https://github.com/RiddimSoftware/groove/blob/main/specs/symphony-cost-telemetry-extension/SPEC.md), so any other tool that conforms can read or write the same file (e.g. a Symphony-spec-conformant orchestrator can emit `usage.jsonl` directly during runs, skipping the bake step entirely). That interop is purely optional; the package works exactly the same whether you care about the spec or not.
148
+
149
+ ## Library
150
+
151
+ ```js
152
+ import {
153
+ computeIssueCost,
154
+ computeIssueCostFromUsage,
155
+ backfillUsageFromTranscripts,
156
+ listKnownIssues,
157
+ } from 'llm-cost-attribution';
158
+
159
+ // Read from transcripts directly:
160
+ const rollup = await computeIssueCost('EPAC-1940');
161
+ console.log(rollup.combinedTokens);
162
+ console.log(rollup.providerTotals.codex.quotaSamples);
163
+
164
+ // Or read from a backfilled usage.jsonl:
165
+ const rollup2 = await computeIssueCostFromUsage('EPAC-1940', '~/llm-cost-history.jsonl');
166
+
167
+ // Backfill programmatically:
168
+ const result = await backfillUsageFromTranscripts({
169
+ outFile: '/tmp/usage.jsonl',
170
+ onProgress: ({ phase, processed, total }) => console.log(`${phase}: ${processed}/${total}`),
171
+ });
172
+ console.log(`Wrote ${result.recordsWritten} records`);
173
+ ```
174
+
175
+ Pass `{ cwdPattern, claudeProjectsDir, codexSessionsDir }` to override defaults on any of the above.
176
+
177
+ ## What it doesn't (and can't) do
178
+
179
+ - **Story-point estimate axis.** Estimates live in your issue tracker (Linear / Jira / GitHub Projects), not in the CLI transcripts. To get cost-vs-estimate rollups you'd need to join issue-tracker data — out of scope for this package.
180
+ - **Attempt counts.** The CLI doesn't record "this was attempt #N of M"; if you ran `claude` 5 times on the same issue, this package sees 5 sessions but can't tell you which one shipped.
181
+ - **PR-merge state, CI status, reviewer verdicts.** These come from GitHub, not from the CLIs — and the Symphony spec explicitly out-of-scopes them (§2.2 Non-Goals, §11.5): ticket mutations and PR outcomes are delegated to the coding agent's tooling, not recorded by the orchestrator. This package stops at the same boundary: "what's in the CLI transcript."
182
+ - **Anything in the Claude Desktop app, claude.ai, ChatGPT, or direct API SDK calls.** Only Claude Code CLI and Codex CLI sessions are stored in the directories this package reads.
183
+
184
+ ## Pricing
185
+
186
+ `llm-cost` shows API-equivalent dollar cost per bucket alongside the raw token counts, using a built-in rate table sourced from [anthropic.com/pricing](https://www.anthropic.com/pricing) and [platform.openai.com/docs/pricing](https://platform.openai.com/docs/pricing):
187
+
188
+ ```
189
+ API-equivalent pricing (gpt-5.5 @ rates verified 2026-05-22):
190
+ input uncached $7.59 (1.5M × $5.00/1M)
191
+ cache read $25.51 (51.0M × $0.500/1M)
192
+ output (visible) $1.34 (44.7K × $30.00/1M)
193
+ output (reasoning) $0.56 (18.6K × $30.00/1M)
194
+ ───────────────────────────────────────────
195
+ total API cost $35.00 [hypothetical — your Codex Pro plan covers this]
196
+ ```
197
+
198
+ **This is a counterfactual, not your actual spend.** If you're on a subscription plan (Claude Max, Codex Pro, etc.), the dollar number represents what the same token volume would have cost on pay-as-you-go API — useful for comparison, but the marginal cost of running it on your actual plan is captured by the Codex quota readout above (`5h primary 58% → 64% used`), not by the dollar total.
199
+
200
+ The CLI warns when the bundled rate table is more than 90 days old. Pass `--no-pricing` to suppress the block entirely.
201
+
202
+ ## License
203
+
204
+ MIT