cctrack 0.7.0__tar.gz → 0.9.0__tar.gz
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.
- {cctrack-0.7.0 → cctrack-0.9.0}/CLAUDE.md +8 -5
- {cctrack-0.7.0 → cctrack-0.9.0}/PKG-INFO +70 -13
- cctrack-0.9.0/README.md +199 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/pyproject.toml +1 -1
- {cctrack-0.7.0 → cctrack-0.9.0}/src/cctrack/__init__.py +664 -80
- cctrack-0.9.0/tests/conftest.py +25 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/tests/test_cctrack.py +67 -23
- cctrack-0.9.0/tests/test_codex.py +368 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/tests/test_hook_reporting.py +21 -13
- cctrack-0.9.0/tests/test_sandy.py +298 -0
- cctrack-0.7.0/README.md +0 -142
- {cctrack-0.7.0 → cctrack-0.9.0}/.github/workflows/ci.yml +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/.github/workflows/publish.yml +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/.gitignore +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/LICENSE +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/scripts/diagnose_session_cost.py +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/scripts/trace_project.py +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/src/cctrack/__main__.py +0 -0
- {cctrack-0.7.0 → cctrack-0.9.0}/tests/test_hook.py +0 -0
|
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
cctrack-py is a zero-dependency Python CLI that scans Claude Code
|
|
7
|
+
cctrack-py is a zero-dependency Python CLI that scans Claude Code and Codex CLI log files and reports token usage and estimated cost. It's a Python port of the Go-based [cctrack dashboard](https://github.com/ksred/cctrack), stripped down to a single command that parses logs and prints a cost report.
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
10
10
|
|
|
@@ -31,14 +31,15 @@ uv build
|
|
|
31
31
|
|
|
32
32
|
This is a single-module CLI tool. All logic lives in `src/cctrack/__init__.py`:
|
|
33
33
|
|
|
34
|
-
1. **Rate card** (`RATES`, `get_rates`, `calculate_cost`, `is_known_model`) -
|
|
34
|
+
1. **Rate card** (`RATES`, `_an`/`_oa` row builders, `_family_matches`, `get_rates`, `calculate_cost`, `is_known_model`) - Anthropic and OpenAI prices. **Every model version needs its own row**: `_family_matches` lets a row absorb only the exact id, dated snapshots (`-20250514`, `-2025-08-07`) and non-version suffixes (`-codex`) — never a short-number minor version (`claude-opus-5` does NOT match `claude-opus-5-5`) or a size-tier word (`mini`, `nano`, `pro`, `max`, `luna`, `terra`, `sol`, `astra`). This is deliberate: prefix inheritance silently mispriced opus-4-8 (3×) and opus-5-5 (84%) before. Unknown models are flagged `?` and fall back to Sonnet (Claude) or GPT-5 (OpenAI) rates. `get_rates` takes the usage date (time-limited `intro` pricing) and the request's prompt size (`long_context` tiers: Haiku 5.5 >100K, several OpenAI models >272K). Re-verify against the official pricing pages when adding rows; don't infer a price from a neighbouring model.
|
|
35
35
|
1a. **Energy model** (`ENERGY_WH_PER_MTOK`, `estimate_energy_wh`) - Opt-in (`--energy`) order-of-magnitude estimate keyed off each rate entry's `tier`. Deliberately NOT a function of price: both cache-write TTLs use one figure because a 1h write costs more money but the same compute.
|
|
36
|
-
2. **File discovery** (`default_dirs`, `discover_files`) -
|
|
37
|
-
|
|
36
|
+
2. **File discovery** (`sandy_home`, `_sandy_print_state`, `sandbox_roots`, `default_dirs`, `discover_files`) - Claude transcripts from `~/.claude/projects/` plus each Sandy sandbox's `claude/projects/` (ONLY `projects/` — `claude/` also holds `history.jsonl`, job timelines, tool-audit logs). Sandbox roots come from `sandy --print-state` → `sandboxes[].path` (follows `$SANDY_HOME`), else `$SANDY_HOME/sandboxes/*` (default `~/.sandy`). Per sandy's host-side path contract each root has agent homes `claude/` (→ `~/.claude` in the sandbox) and `codex/` (→ `~/.codex`); everything else under a root is private. **Dot-entries** (`_is_sandbox_name`): skip `.rsync-from.*` (an in-progress `sandy --rsync-from` copy — its hook data was being summed twice) and `*.lock`; any other dot-dir is a REAL sandbox iff it holds `WORKSPACE.json` (a workspace like `~/.dotfiles` → sandbox `.dotfiles-<sha8>`). Sandy's own enumeration, hence `--print-state`, currently omits dot-named sandboxes, so they're always added by scanning `sandboxes/` directly. Tests are isolated from real sandboxes by `tests/conftest.py`.
|
|
37
|
+
2a. **Codex** (`codex_homes`, `discover_codex_files`, `parse_codex_lines`, `parse_codex_events`, `fetch_remote_codex`) - Reads `$CODEX_HOME` (default `~/.codex`) and every sandbox's `codex/` (sandy never sets CODEX_HOME), `sessions/` + `archived_sessions/` rollouts, incl. `.jsonl.zst` (stdlib `compression.zstd` on 3.14+, else the `zstd` CLI, else skipped and counted). Lines are `{timestamp, type, payload}`. Prefer `token_usage_record` items (per-response, dedup by `response_id` — forks replay parent history); fall back to legacy `event_msg`/`token_count` only before a file's first record, counting `last_token_usage` only when `total_token_usage.total_tokens` moved (it's re-emitted unchanged). **`input_tokens` includes `cached_input_tokens` and `cache_write_input_tokens`; `output_tokens` includes reasoning** — uncached input = input − cached − cache_write. Model/cwd from the latest `turn_context`; thread id + git branch from `session_meta`. Codex events carry `agent="codex"`.
|
|
38
|
+
3. **Remote aggregation** (`remote_sandbox_roots`, `fetch_remote_jsonl`, `parse_remote_events`, `fetch_remote_hook_data`, `parse_remote_hook_data`, `fetch_remote_codex`) - One SSH call per host finds its sandbox roots (same rules as locally, cached), then each fetch cats the host's own dirs plus the matching per-sandbox dirs with file separators and parses locally.
|
|
38
39
|
3a. **Hook parsing** (`parse_hook_rows`, `compute_hook_deltas`) - The hook gives **cost only**. Its `input_tokens`/`output_tokens`/`cache_*` fields are context-window SNAPSHOTS (`input_tokens` ≈ `cache_read + cache_write`; `output_tokens` is just the last message's), not cumulative — never use them for token counts. `cost_usd` is a session-lifetime cumulative that is unreliable in two ways handled by `compute_hook_deltas`: (1) it can already be large the first time a session is observed (a long-running/unattended session that only rendered its statusline partway through), so the **first observation is a baseline that is NOT counted** — booking a session's pre-existing cumulative onto its first-seen day is what produced a bogus $3,370 day; (2) it **drops** on resume/compaction (or when parallel processes share one session id), so a drop **rebaselines without counting the new level** — otherwise a day's booked spend can exceed the counter's own peak, which is impossible for real money. Net rule: **book only observed upward increments**; a rolled-up baseline seeds `prev` so already-counted days aren't recounted.
|
|
39
40
|
4. **JSONL parsing** (`parse_lines`, `parse_events`, `dedup_events`) - Extracts `assistant` events with token usage. Deduplicates by `requestId` across all of a host's files (last event wins) — resumed sessions (`--resume`/`--continue`) repeat events with the same `requestId` in new files. Events without `requestId` are never deduped.
|
|
40
41
|
5. **Aggregation** (`aggregate`) - Buckets events into daily and monthly summaries with token counts and costs.
|
|
41
|
-
6. **Output** (`print_report`) - Prints current month-to-date summary with projections, daily breakdown, and previous month summaries. Cost is composed **per source** (one source per machine): hook cost where a source has statusline coverage, accuracy-factor-adjusted JSONL cost otherwise, summed per date. The accuracy factor is measured per source over dates strictly after that source's hook install day.
|
|
42
|
+
6. **Output** (`print_report`) - Prints current month-to-date summary with projections, daily breakdown, and previous month summaries. Cost is composed **per source** (one source per machine): hook cost where a source has statusline coverage, accuracy-factor-adjusted JSONL cost otherwise, summed per date. The accuracy factor is measured per source over dates strictly after that source's hook install day; when unmeasurable, `DEFAULT_ACCURACY_FACTOR` is **1.0** — the old 1.25 "logs undercount ~20-25%" was entirely rate-card error and inflated unmeasured days 25%.
|
|
42
43
|
6a. **Explore** (`print_explore`, `--explore DATE`) - Drills into one day: dollar decomposition by token class, then by model / project / session / machine. Each JSONL event carries its `project` (from `cwd`), `session_id`, `source` (host) and `git_branch` for attribution. Per-session JSONL estimate is shown against the hook's authoritative per-session cost; a >15% day-total divergence is flagged so an inflated estimate (incomplete logs, unknown model) is distinguishable from real spend.
|
|
43
44
|
|
|
44
45
|
Entry point: `cctrack:main` (registered in `pyproject.toml` as a console script).
|
|
@@ -49,6 +50,8 @@ Entry point: `cctrack:main` (registered in `pyproject.toml` as a console script)
|
|
|
49
50
|
- Deduplication is global per host by `requestId` (last wins) - resumed sessions replay history into new files and must not double count. Cross-host `requestId` collisions can't occur (distinct API calls).
|
|
50
51
|
- Hook data is authoritative for **cost only**; every token count comes from the JSONL logs. Never mix the two in one figure - the hook's token fields are snapshots and would double count against JSONL cache columns.
|
|
51
52
|
- Never let one machine's hook coverage suppress another machine's JSONL cost - compose per source.
|
|
53
|
+
- Each coding agent is its own source. Codex sources are `exact`: priced straight from their logs, never replaced by the Claude hook and never scaled by the (Claude-derived) accuracy factor. In `--explore`, hook comparisons use only the Claude slice of the JSONL total.
|
|
54
|
+
- Costs are API list prices. For subscription users (ChatGPT plans, Claude subscriptions) they're API-equivalent values, not invoices.
|
|
52
55
|
- Cumulative counters from the hook are not monotonic and don't start at zero when first observed. `compute_hook_deltas` books only observed upward increments (first observation and post-drop levels are baselines, never counted), so a day's hook cost can never exceed the counter's peak and a session's pre-observation history is never mis-dated onto its first-seen day. Test fixtures must start sessions at ~$0 to mirror real statusline behavior.
|
|
53
56
|
- `session-baselines.json` carries each session's rolled-up cumulative across the 90-day retention boundary so straddling sessions aren't re-counted from zero.
|
|
54
57
|
- Build system is Hatch (`hatchling`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: cctrack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Claude Code cost tracker — scans JSONL logs and reports token usage + estimated cost
|
|
5
5
|
Project-URL: Homepage, https://github.com/rappdw/cctrack
|
|
6
6
|
Project-URL: Repository, https://github.com/rappdw/cctrack
|
|
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
# cctrack
|
|
27
27
|
|
|
28
|
-
A lightweight CLI tool that scans [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
|
28
|
+
A lightweight CLI tool that scans [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Codex CLI](https://github.com/openai/codex) logs and reports token usage and estimated cost. Zero dependencies, runs anywhere Python 3.10+ is available.
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
|
@@ -50,7 +50,11 @@ cctrack --days 7
|
|
|
50
50
|
cctrack --remote dgx macbook-air
|
|
51
51
|
|
|
52
52
|
# Custom log directories
|
|
53
|
-
cctrack --dirs ~/.claude/projects
|
|
53
|
+
cctrack --dirs ~/.claude/projects ~/old-machine/projects
|
|
54
|
+
|
|
55
|
+
# Only one coding agent (default: all found)
|
|
56
|
+
cctrack --agent claude
|
|
57
|
+
cctrack --agent codex
|
|
54
58
|
```
|
|
55
59
|
|
|
56
60
|
## Example output
|
|
@@ -78,12 +82,37 @@ Daily breakdown:
|
|
|
78
82
|
|
|
79
83
|
## What it does
|
|
80
84
|
|
|
81
|
-
1. Walks `~/.claude/projects/` and
|
|
85
|
+
1. Walks `~/.claude/projects/` — and each [Sandy](#sandy-sandboxes) sandbox's `claude/projects/` — for Claude Code transcripts, and each Codex home for Codex rollouts
|
|
82
86
|
2. Parses `assistant` events with token usage
|
|
83
87
|
3. Deduplicates by `requestId` (last event wins, matching Claude Code's semantics)
|
|
84
88
|
4. Calculates cost using Anthropic's published rates per model
|
|
85
89
|
5. Prints daily breakdown with input/output/cache token splits and monthly summaries
|
|
86
90
|
|
|
91
|
+
## Codex CLI
|
|
92
|
+
|
|
93
|
+
cctrack also reads [Codex CLI](https://github.com/openai/codex) usage from `$CODEX_HOME` (default `~/.codex`) and from every [Sandy sandbox](#sandy-sandboxes)'s `codex/` — `sessions/` and `archived_sessions/` — and with `--remote`, from each host. When both agents have spend, the month-to-date block and `--explore` show a per-agent split.
|
|
94
|
+
|
|
95
|
+
How it's counted (verified against the Codex source, not inferred):
|
|
96
|
+
|
|
97
|
+
- **Per-response usage.** Newer Codex versions log a `token_usage_record` for every API response with a unique `response_id`; those are counted and deduplicated across files, so forked and resumed threads (which replay their parent's history) aren't double counted. Older versions only log `token_count` events, which are re-emitted with unchanged totals — only events whose running total moved are counted.
|
|
98
|
+
- **Cached tokens are inside `input_tokens`.** Codex reports cached reads and cache writes as parts of `input_tokens`, and reasoning tokens as part of `output_tokens`. Billable uncached input is `input − cached − cache_write`; reasoning is never added on top.
|
|
99
|
+
- **Its own cost source.** Codex spend is priced directly from its logs and is never replaced by, or scaled with, Claude Code's statusline-hook figures.
|
|
100
|
+
- **Compressed rollouts.** Codex compresses older rollouts to `.jsonl.zst`. Python's standard library only reads zstd from 3.14, so on older Pythons cctrack uses the `zstd` command if it's installed, and otherwise skips those files and says how many it skipped.
|
|
101
|
+
|
|
102
|
+
Costs are at OpenAI's **API list prices**. If you use Codex through a ChatGPT plan, that's an API-equivalent value, not what you're billed — the same is true of Claude Code on a Claude subscription.
|
|
103
|
+
|
|
104
|
+
## Sandy sandboxes
|
|
105
|
+
|
|
106
|
+
cctrack finds every [Sandy](https://github.com/rappdw/sandy) sandbox on the machine (and on `--remote` hosts) and includes its Claude Code and Codex usage. It follows sandy's published host-side path contract:
|
|
107
|
+
|
|
108
|
+
- **Finding sandboxes:** `sandy --print-state` (`sandboxes[].path`) when sandy is installed, which follows `$SANDY_HOME`; otherwise `$SANDY_HOME/sandboxes/*` (default `~/.sandy/sandboxes`).
|
|
109
|
+
- **Per sandbox:** Claude Code transcripts from `claude/projects/`, statusline-hook data from `claude/cctrack/`, Codex rollouts from `codex/sessions/` and `codex/archived_sessions/`. Other files under `claude/` (`history.jsonl`, job timelines, tool-audit logs) aren't transcripts and are ignored.
|
|
110
|
+
- **Never double counted:** the hidden `.rsync-from.<name>.<pid>/` copy that exists while `sandy --rsync-from` is pulling a sandbox in is skipped, as are `.lock` directories. A sandbox's Codex home is never shared with the host's `~/.codex`, and anything copied between hosts with `sandy --rsync` is deduplicated by request/response id.
|
|
111
|
+
|
|
112
|
+
- **Dot-named sandboxes count:** a workspace named like a dotfile directory (e.g. `~/.dotfiles`) gets a sandbox whose name starts with a dot. Sandy's own listing currently omits these, so cctrack scans for them itself — any dot-directory holding `WORKSPACE.json` is a real sandbox.
|
|
113
|
+
|
|
114
|
+
Orphaned sandboxes (workspace deleted, sandbox not yet removed) still count: their usage is real history.
|
|
115
|
+
|
|
87
116
|
## Explore a day
|
|
88
117
|
|
|
89
118
|
When a day's cost looks wrong or surprisingly high, `--explore` breaks it down:
|
|
@@ -124,22 +153,50 @@ cctrack --install-hook --remote dgx macbook-air
|
|
|
124
153
|
|
|
125
154
|
## Rate card
|
|
126
155
|
|
|
127
|
-
Prices per million tokens
|
|
156
|
+
Prices per million tokens. Claude prices are from [Anthropic's pricing page](https://platform.claude.com/docs/en/about-claude/pricing); cache writes are priced per TTL (5-minute and 1-hour).
|
|
128
157
|
|
|
129
|
-
|
|
|
158
|
+
| Claude model | Input | Output | Cache Read | Cache Write 5m | Cache Write 1h |
|
|
130
159
|
|---|---|---|---|---|---|
|
|
160
|
+
| Fable 5.1 / Mythos 5.1 | $10.00 | $50.00 | $0.25 | $12.50 | $20.00 |
|
|
131
161
|
| Fable 5 / Mythos 5 | $10.00 | $50.00 | $1.00 | $12.50 | $20.00 |
|
|
162
|
+
| Opus 5.5 | $4.00 | $20.00 | $0.20 | $5.00 | $8.00 |
|
|
132
163
|
| Opus 5 / 4.8 / 4.7 / 4.6 / 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | $10.00 |
|
|
133
|
-
| Opus 4 / 4
|
|
134
|
-
| Sonnet 5
|
|
135
|
-
| Sonnet 5
|
|
136
|
-
|
|
|
164
|
+
| Opus 4.1 / 4 | $15.00 | $75.00 | $1.50 | $18.75 | $30.00 |
|
|
165
|
+
| Sonnet 5.5 | $2.00 | $10.00 | $0.10 | $2.50 | $4.00 |
|
|
166
|
+
| Sonnet 5 | $2.00 | $10.00 | $0.20 | $2.50 | $4.00 |
|
|
167
|
+
| Sonnet 4.6 / 4.5 / 4 / 3.7 | $3.00 | $15.00 | $0.30 | $3.75 | $6.00 |
|
|
168
|
+
| Haiku 5.5 (prompt ≤ 100K) | $0.10 | $0.50 | $0.01 | $0.125 | $0.20 |
|
|
169
|
+
| Haiku 5.5 (prompt > 100K) | $0.50 | $2.50 | $0.05 | $0.625 | $1.00 |
|
|
170
|
+
| Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | $2.00 |
|
|
137
171
|
| Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | $1.60 |
|
|
138
172
|
| Haiku 3 | $0.25 | $1.25 | $0.03 | $0.30 | $0.50 |
|
|
139
173
|
|
|
140
|
-
|
|
174
|
+
OpenAI prices (for Codex) are from [OpenAI's pricing page](https://developers.openai.com/api/docs/pricing), Standard tier. OpenAI has a single cache-write price, listed only for some models; elsewhere cache writes bill as ordinary input.
|
|
141
175
|
|
|
142
|
-
|
|
176
|
+
| OpenAI model | Input | Cached input | Output | Cache write | Over 272K input |
|
|
177
|
+
|---|---|---|---|---|---|
|
|
178
|
+
| gpt-6-astra | $10.00 | $1.00 | $50.00 | $12.50 | $20 / $2 / $75 / $25 |
|
|
179
|
+
| gpt-6.1-sol | $2.00 | $0.10 | $10.00 | $2.50 | $4 / $0.20 / $15 / $5 |
|
|
180
|
+
| gpt-6-sol | $2.00 | $0.20 | $10.00 | $2.50 | $4 / $0.40 / $15 / $5 |
|
|
181
|
+
| gpt-6-luna | $0.10 | $0.01 | $0.50 | $0.125 | $0.20 / $0.02 / $0.75 / $0.25 |
|
|
182
|
+
| gpt-5.6-sol | $4.00 | $0.40 | $20.00 | $5.00 | $8 / $0.80 / $30 / $10 |
|
|
183
|
+
| gpt-5.6-terra | $2.00 | $0.20 | $12.00 | $2.50 | $4 / $0.40 / $18 / $5 |
|
|
184
|
+
| gpt-5.6-luna | $0.20 | $0.02 | $1.20 | $0.25 | $0.40 / $0.04 / $1.80 / $0.50 |
|
|
185
|
+
| gpt-5.5 | $5.00 | $0.50 | $30.00 | — | $10 / $1 / $45 |
|
|
186
|
+
| gpt-5.5-pro / gpt-5.4-pro | $30.00 | — | $180.00 | — | $60 / — / $270 |
|
|
187
|
+
| gpt-5.4 | $2.50 | $0.25 | $15.00 | — | $5 / $0.50 / $22.50 |
|
|
188
|
+
| gpt-5.4-mini | $0.75 | $0.075 | $4.50 | — | |
|
|
189
|
+
| gpt-5.4-nano | $0.20 | $0.02 | $1.25 | — | |
|
|
190
|
+
| gpt-5.3-codex / gpt-5.2 | $1.75 | $0.175 | $14.00 | — | |
|
|
191
|
+
| gpt-5.2-pro | $21.00 | — | $168.00 | — | |
|
|
192
|
+
| gpt-5.1 / gpt-5 | $1.25 | $0.125 | $10.00 | — | |
|
|
193
|
+
| gpt-5-pro | $15.00 | — | $120.00 | — | |
|
|
194
|
+
| gpt-5-mini | $0.25 | $0.025 | $2.00 | — | |
|
|
195
|
+
| gpt-5-nano | $0.05 | $0.005 | $0.40 | — | |
|
|
196
|
+
|
|
197
|
+
Codex variants without their own price (e.g. `gpt-5.1-codex`) are priced as their base model, consistent with the one variant OpenAI does list (`gpt-5.3-codex` = `gpt-5.2`). Long-context and Haiku 5.5 prompt-length tiers are applied per request.
|
|
198
|
+
|
|
199
|
+
**Every model version has its own row.** A model only matches a row if it is that version or a dated snapshot of it — never a newer minor version. So a newly released model (say `claude-opus-5-7`, or `gpt-5.1-codex-mini`) shows as unknown and is flagged with `?` in the Model column, rather than silently inheriting its predecessor's price. Unknown Claude models are estimated at Sonnet rates, unknown OpenAI models at GPT-5 rates. Pricing is date-aware, so any time-limited introductory price applies only to usage on the days it was in effect.
|
|
143
200
|
|
|
144
201
|
## Energy estimate
|
|
145
202
|
|
cctrack-0.9.0/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# cctrack
|
|
2
|
+
|
|
3
|
+
A lightweight CLI tool that scans [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Codex CLI](https://github.com/openai/codex) logs and reports token usage and estimated cost. Zero dependencies, runs anywhere Python 3.10+ is available.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run directly (no install needed)
|
|
9
|
+
uvx cctrack
|
|
10
|
+
|
|
11
|
+
# Or install globally
|
|
12
|
+
uv tool install cctrack
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Scan local logs and print report
|
|
19
|
+
cctrack
|
|
20
|
+
|
|
21
|
+
# Last 7 days only
|
|
22
|
+
cctrack --days 7
|
|
23
|
+
|
|
24
|
+
# Aggregate with remote machines via SSH
|
|
25
|
+
cctrack --remote dgx macbook-air
|
|
26
|
+
|
|
27
|
+
# Custom log directories
|
|
28
|
+
cctrack --dirs ~/.claude/projects ~/old-machine/projects
|
|
29
|
+
|
|
30
|
+
# Only one coding agent (default: all found)
|
|
31
|
+
cctrack --agent claude
|
|
32
|
+
cctrack --agent codex
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Example output
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
cctrack — Claude Code Cost Report
|
|
39
|
+
══════════════════════════════════
|
|
40
|
+
|
|
41
|
+
April 2026 — month to date (day 1, 1 active)
|
|
42
|
+
───────────────────────────────────────
|
|
43
|
+
Input tokens: 23,381
|
|
44
|
+
Output tokens: 199,069
|
|
45
|
+
Cache read: 30,862,977
|
|
46
|
+
Cache write: 1,051,023
|
|
47
|
+
Total tokens: 32,136,450
|
|
48
|
+
Total cost: $25.85
|
|
49
|
+
Avg/day: $25.85
|
|
50
|
+
Projected/mo: $775.50 (based on 1-day avg)
|
|
51
|
+
|
|
52
|
+
Daily breakdown:
|
|
53
|
+
Date Input Output Cache R Cache W Cost
|
|
54
|
+
──────────── ──────────── ──────────── ──────────── ──────────── ──────────
|
|
55
|
+
2026-04-01 23,381 199,069 30,862,977 1,051,023 $ 25.85
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What it does
|
|
59
|
+
|
|
60
|
+
1. Walks `~/.claude/projects/` — and each [Sandy](#sandy-sandboxes) sandbox's `claude/projects/` — for Claude Code transcripts, and each Codex home for Codex rollouts
|
|
61
|
+
2. Parses `assistant` events with token usage
|
|
62
|
+
3. Deduplicates by `requestId` (last event wins, matching Claude Code's semantics)
|
|
63
|
+
4. Calculates cost using Anthropic's published rates per model
|
|
64
|
+
5. Prints daily breakdown with input/output/cache token splits and monthly summaries
|
|
65
|
+
|
|
66
|
+
## Codex CLI
|
|
67
|
+
|
|
68
|
+
cctrack also reads [Codex CLI](https://github.com/openai/codex) usage from `$CODEX_HOME` (default `~/.codex`) and from every [Sandy sandbox](#sandy-sandboxes)'s `codex/` — `sessions/` and `archived_sessions/` — and with `--remote`, from each host. When both agents have spend, the month-to-date block and `--explore` show a per-agent split.
|
|
69
|
+
|
|
70
|
+
How it's counted (verified against the Codex source, not inferred):
|
|
71
|
+
|
|
72
|
+
- **Per-response usage.** Newer Codex versions log a `token_usage_record` for every API response with a unique `response_id`; those are counted and deduplicated across files, so forked and resumed threads (which replay their parent's history) aren't double counted. Older versions only log `token_count` events, which are re-emitted with unchanged totals — only events whose running total moved are counted.
|
|
73
|
+
- **Cached tokens are inside `input_tokens`.** Codex reports cached reads and cache writes as parts of `input_tokens`, and reasoning tokens as part of `output_tokens`. Billable uncached input is `input − cached − cache_write`; reasoning is never added on top.
|
|
74
|
+
- **Its own cost source.** Codex spend is priced directly from its logs and is never replaced by, or scaled with, Claude Code's statusline-hook figures.
|
|
75
|
+
- **Compressed rollouts.** Codex compresses older rollouts to `.jsonl.zst`. Python's standard library only reads zstd from 3.14, so on older Pythons cctrack uses the `zstd` command if it's installed, and otherwise skips those files and says how many it skipped.
|
|
76
|
+
|
|
77
|
+
Costs are at OpenAI's **API list prices**. If you use Codex through a ChatGPT plan, that's an API-equivalent value, not what you're billed — the same is true of Claude Code on a Claude subscription.
|
|
78
|
+
|
|
79
|
+
## Sandy sandboxes
|
|
80
|
+
|
|
81
|
+
cctrack finds every [Sandy](https://github.com/rappdw/sandy) sandbox on the machine (and on `--remote` hosts) and includes its Claude Code and Codex usage. It follows sandy's published host-side path contract:
|
|
82
|
+
|
|
83
|
+
- **Finding sandboxes:** `sandy --print-state` (`sandboxes[].path`) when sandy is installed, which follows `$SANDY_HOME`; otherwise `$SANDY_HOME/sandboxes/*` (default `~/.sandy/sandboxes`).
|
|
84
|
+
- **Per sandbox:** Claude Code transcripts from `claude/projects/`, statusline-hook data from `claude/cctrack/`, Codex rollouts from `codex/sessions/` and `codex/archived_sessions/`. Other files under `claude/` (`history.jsonl`, job timelines, tool-audit logs) aren't transcripts and are ignored.
|
|
85
|
+
- **Never double counted:** the hidden `.rsync-from.<name>.<pid>/` copy that exists while `sandy --rsync-from` is pulling a sandbox in is skipped, as are `.lock` directories. A sandbox's Codex home is never shared with the host's `~/.codex`, and anything copied between hosts with `sandy --rsync` is deduplicated by request/response id.
|
|
86
|
+
|
|
87
|
+
- **Dot-named sandboxes count:** a workspace named like a dotfile directory (e.g. `~/.dotfiles`) gets a sandbox whose name starts with a dot. Sandy's own listing currently omits these, so cctrack scans for them itself — any dot-directory holding `WORKSPACE.json` is a real sandbox.
|
|
88
|
+
|
|
89
|
+
Orphaned sandboxes (workspace deleted, sandbox not yet removed) still count: their usage is real history.
|
|
90
|
+
|
|
91
|
+
## Explore a day
|
|
92
|
+
|
|
93
|
+
When a day's cost looks wrong or surprisingly high, `--explore` breaks it down:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cctrack --explore 2026-08-03 # a specific day
|
|
97
|
+
cctrack --explore yesterday # or 'today'
|
|
98
|
+
cctrack --explore yesterday --remote air dgx # across machines
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It reports, for that day:
|
|
102
|
+
|
|
103
|
+
- **Where it went** — the day's cost decomposed by token class (output, 1h/5m cache writes, input, cache reads), so a cache-write blowup or a runaway output loop is obvious at a glance.
|
|
104
|
+
- **By model / by project / by machine** — cost and share for each, so you can see which model, which repo, and which host drove it.
|
|
105
|
+
- **Top sessions** — ranked by cost, each with its project and model, and — where the statusline hook covered that session — its **authoritative** cost next to the JSONL estimate.
|
|
106
|
+
|
|
107
|
+
The day's JSONL-estimated total is shown next to the hook-authoritative total. If they diverge by more than ~15% the report says so: a big gap means the estimate is off (incomplete logs, or a model the rate card doesn't know), not that you actually spent that much. This is the fastest way to tell a real expensive day from an accounting artifact.
|
|
108
|
+
|
|
109
|
+
The project label is the basename of each session's working directory (`cwd`). When that's an unfamiliar name — a container path like `/workspace/team-inbox`, or a directory you don't recognize — trace it to its real location:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
cctrack --explore 2026-08-03 --project team-inbox
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This restricts the view to that project and prints each session's full `cwd`, git branch, and the log file on disk (which reveals the host directory or the specific Sandy sandbox it came from). The unfiltered view flags this automatically when a `cwd` looks like a container path. `scripts/trace_project.py` and `scripts/diagnose_session_cost.py` are standalone versions of the trace and per-session cost diagnostics.
|
|
116
|
+
|
|
117
|
+
## Remote aggregation
|
|
118
|
+
|
|
119
|
+
With `--remote`, cctrack SSHs to each host and streams back both the JSONL logs and the host's statusline hook data (`~/.claude/cctrack/`) for local parsing, so authoritative hook costs from every machine are summed per day. Requires SSH key auth.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Aggregate this machine + DGX server + laptop
|
|
123
|
+
cctrack --remote dgx macbook-air
|
|
124
|
+
|
|
125
|
+
# For accurate aggregate costs, install the hook on each host too
|
|
126
|
+
cctrack --install-hook --remote dgx macbook-air
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Rate card
|
|
130
|
+
|
|
131
|
+
Prices per million tokens. Claude prices are from [Anthropic's pricing page](https://platform.claude.com/docs/en/about-claude/pricing); cache writes are priced per TTL (5-minute and 1-hour).
|
|
132
|
+
|
|
133
|
+
| Claude model | Input | Output | Cache Read | Cache Write 5m | Cache Write 1h |
|
|
134
|
+
|---|---|---|---|---|---|
|
|
135
|
+
| Fable 5.1 / Mythos 5.1 | $10.00 | $50.00 | $0.25 | $12.50 | $20.00 |
|
|
136
|
+
| Fable 5 / Mythos 5 | $10.00 | $50.00 | $1.00 | $12.50 | $20.00 |
|
|
137
|
+
| Opus 5.5 | $4.00 | $20.00 | $0.20 | $5.00 | $8.00 |
|
|
138
|
+
| Opus 5 / 4.8 / 4.7 / 4.6 / 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | $10.00 |
|
|
139
|
+
| Opus 4.1 / 4 | $15.00 | $75.00 | $1.50 | $18.75 | $30.00 |
|
|
140
|
+
| Sonnet 5.5 | $2.00 | $10.00 | $0.10 | $2.50 | $4.00 |
|
|
141
|
+
| Sonnet 5 | $2.00 | $10.00 | $0.20 | $2.50 | $4.00 |
|
|
142
|
+
| Sonnet 4.6 / 4.5 / 4 / 3.7 | $3.00 | $15.00 | $0.30 | $3.75 | $6.00 |
|
|
143
|
+
| Haiku 5.5 (prompt ≤ 100K) | $0.10 | $0.50 | $0.01 | $0.125 | $0.20 |
|
|
144
|
+
| Haiku 5.5 (prompt > 100K) | $0.50 | $2.50 | $0.05 | $0.625 | $1.00 |
|
|
145
|
+
| Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | $2.00 |
|
|
146
|
+
| Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | $1.60 |
|
|
147
|
+
| Haiku 3 | $0.25 | $1.25 | $0.03 | $0.30 | $0.50 |
|
|
148
|
+
|
|
149
|
+
OpenAI prices (for Codex) are from [OpenAI's pricing page](https://developers.openai.com/api/docs/pricing), Standard tier. OpenAI has a single cache-write price, listed only for some models; elsewhere cache writes bill as ordinary input.
|
|
150
|
+
|
|
151
|
+
| OpenAI model | Input | Cached input | Output | Cache write | Over 272K input |
|
|
152
|
+
|---|---|---|---|---|---|
|
|
153
|
+
| gpt-6-astra | $10.00 | $1.00 | $50.00 | $12.50 | $20 / $2 / $75 / $25 |
|
|
154
|
+
| gpt-6.1-sol | $2.00 | $0.10 | $10.00 | $2.50 | $4 / $0.20 / $15 / $5 |
|
|
155
|
+
| gpt-6-sol | $2.00 | $0.20 | $10.00 | $2.50 | $4 / $0.40 / $15 / $5 |
|
|
156
|
+
| gpt-6-luna | $0.10 | $0.01 | $0.50 | $0.125 | $0.20 / $0.02 / $0.75 / $0.25 |
|
|
157
|
+
| gpt-5.6-sol | $4.00 | $0.40 | $20.00 | $5.00 | $8 / $0.80 / $30 / $10 |
|
|
158
|
+
| gpt-5.6-terra | $2.00 | $0.20 | $12.00 | $2.50 | $4 / $0.40 / $18 / $5 |
|
|
159
|
+
| gpt-5.6-luna | $0.20 | $0.02 | $1.20 | $0.25 | $0.40 / $0.04 / $1.80 / $0.50 |
|
|
160
|
+
| gpt-5.5 | $5.00 | $0.50 | $30.00 | — | $10 / $1 / $45 |
|
|
161
|
+
| gpt-5.5-pro / gpt-5.4-pro | $30.00 | — | $180.00 | — | $60 / — / $270 |
|
|
162
|
+
| gpt-5.4 | $2.50 | $0.25 | $15.00 | — | $5 / $0.50 / $22.50 |
|
|
163
|
+
| gpt-5.4-mini | $0.75 | $0.075 | $4.50 | — | |
|
|
164
|
+
| gpt-5.4-nano | $0.20 | $0.02 | $1.25 | — | |
|
|
165
|
+
| gpt-5.3-codex / gpt-5.2 | $1.75 | $0.175 | $14.00 | — | |
|
|
166
|
+
| gpt-5.2-pro | $21.00 | — | $168.00 | — | |
|
|
167
|
+
| gpt-5.1 / gpt-5 | $1.25 | $0.125 | $10.00 | — | |
|
|
168
|
+
| gpt-5-pro | $15.00 | — | $120.00 | — | |
|
|
169
|
+
| gpt-5-mini | $0.25 | $0.025 | $2.00 | — | |
|
|
170
|
+
| gpt-5-nano | $0.05 | $0.005 | $0.40 | — | |
|
|
171
|
+
|
|
172
|
+
Codex variants without their own price (e.g. `gpt-5.1-codex`) are priced as their base model, consistent with the one variant OpenAI does list (`gpt-5.3-codex` = `gpt-5.2`). Long-context and Haiku 5.5 prompt-length tiers are applied per request.
|
|
173
|
+
|
|
174
|
+
**Every model version has its own row.** A model only matches a row if it is that version or a dated snapshot of it — never a newer minor version. So a newly released model (say `claude-opus-5-7`, or `gpt-5.1-codex-mini`) shows as unknown and is flagged with `?` in the Model column, rather than silently inheriting its predecessor's price. Unknown Claude models are estimated at Sonnet rates, unknown OpenAI models at GPT-5 rates. Pricing is date-aware, so any time-limited introductory price applies only to usage on the days it was in effect.
|
|
175
|
+
|
|
176
|
+
## Energy estimate
|
|
177
|
+
|
|
178
|
+
`cctrack --energy` adds an estimated inference energy column (Wh/kWh):
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
cctrack --energy
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
This is a **rough estimate, not a measurement** — Anthropic does not publish per-token energy figures or model sizes. It is modelled from logged tokens using the published full-stack per-prompt figures the major labs have disclosed (Google: 0.24 Wh median Gemini text prompt; OpenAI: ~0.34 Wh average ChatGPT query — both include accelerator, host, cooling and idle capacity), apportioned across token classes by relative compute cost and scaled by model tier:
|
|
185
|
+
|
|
186
|
+
- **output tokens dominate** — decode is sequential and memory-bandwidth bound
|
|
187
|
+
- **input and cache writes** are prefill: parallel, far cheaper per token
|
|
188
|
+
- **cache reads** skip prefill entirely (~0.1× input)
|
|
189
|
+
- both cache-write TTLs use the same figure — a 1-hour write costs more money but the same compute
|
|
190
|
+
|
|
191
|
+
Treat results as ±1 order of magnitude. It covers datacenter inference only: not model training, and not your own machine. Scale the whole model with `CCTRACK_ENERGY_SCALE=1.5` if you have better numbers for your workload.
|
|
192
|
+
|
|
193
|
+
## Origins
|
|
194
|
+
|
|
195
|
+
This is a Python rewrite of the Go-based [cctrack dashboard](https://github.com/ksred/cctrack). The Go version provides a full web dashboard with real-time updates, session explorer, and project breakdown. This Python version strips it down to the essentials: a single command that parses logs and prints a cost report. The JSONL parsing logic, deduplication strategy, and rate card are ported directly from the Go implementation.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
[MIT](LICENSE)
|