You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
### Usage object (the fields that matter for cost)
68
-
69
-
```json
70
-
"usage": {
71
-
"input_tokens": 2739,
72
-
"output_tokens": 823,
73
-
"cache_read_input_tokens": 23154,
74
-
"cache_creation_input_tokens": 2125,
75
-
"cache_creation": {
76
-
"ephemeral_5m_input_tokens": 0,
77
-
"ephemeral_1h_input_tokens": 2125
78
-
}
79
-
}
80
-
```
81
-
82
-
-`output_tokens` already includes thinking tokens — there is no separate counter
83
-
-`cache_creation` sub-object breaks down 5m/1h tiers; `cache_creation_input_tokens` is the flat total (fallback when sub-object is absent in older logs)
84
-
- Extra fields (`server_tool_use`, `service_tier`, `inference_geo`, `speed`, `iterations`) are informational only
85
-
86
-
### Streaming dedup
87
-
88
-
One API call produces multiple JSONL entries sharing the same `requestId`. `input_tokens` and cache fields are identical across them; `output_tokens` grows. The last entry has the final count — our map-based dedup (overwrite) handles this correctly.
89
-
90
-
### Special entries
28
+
Claude Code stores logs at `~/.claude/projects/<project-slug>/`. Sessions are `<uuid>.jsonl` with subagents in `<uuid>/subagents/agent-<id>.jsonl`.
91
29
92
-
-`model: "<synthetic>"` + `isApiErrorMessage: true` — rate-limit/error placeholders with all-zero tokens. Filtered out to avoid inflating request counts.
93
-
-`isSidechain: true` — present on subagent entries. Informational only; we process all assistant entries regardless.
30
+
- Only `type: "assistant"` entries carry `message.model` and `message.usage` — skip all others
31
+
-`output_tokens` already includes thinking tokens — no separate counter
32
+
-`cache_creation` sub-object breaks down 5m/1h tiers; `cache_creation_input_tokens` is the flat total (fallback for older logs)
33
+
- One API call produces multiple entries sharing the same `requestId` — dedup by keeping the last (highest `output_tokens`)
34
+
-`model: "<synthetic>"` + `isApiErrorMessage: true` are rate-limit placeholders — filter out to avoid inflating counts
94
35
95
36
## Conventions
96
37
97
-
-**Flat package structure** — all code in `package main`, one concern per file
98
-
-**Dedup by requestId** — streaming duplicates collapsed by keeping the last entry per `requestId` in a map
99
-
-**Externalized pricing** — all model pricing (input, output, cache read/write tiers, long context), global settings (long context threshold, web search cost), family prefixes, display names, and default model live in `pricing.json`. Embedded via `//go:embed`, with a remote-cached copy fetched from the repo every 24h. `initPricing()` prefers cached over embedded. Adding a new model or adjusting pricing requires only editing `pricing.json` — no code changes or binary release needed. Cache fields in JSON are optional — `fillCacheDefaults()` derives them from input price using standard multipliers (0.1x read, 1.25x write-5m, 2x write-1h) when absent
100
-
-**Pricing resolution** — exact model ID → longest family prefix match → `defaultPricing`
101
-
-**Cache write tiers are trusted from JSONL** — Claude Code now correctly reports `ephemeral_5m_input_tokens` and `ephemeral_1h_input_tokens` per model (e.g. Haiku → 5m, Opus/Sonnet → 1h). Fallback for old logs without `cache_creation` sub-object defaults to 1h. `CacheWrite5m`/`CacheWrite1h` remain separate fields (different pricing multipliers)
102
-
-**Shared file parsing** — `parseFile()` in parser.go is used by both `parseLogs` (directory walk) and `parseSession` (statusline single-session)
103
-
-**Local timezone everywhere** — local midnight for cutoffs, `parsed.Local()` for date bucketing. Never use `UTC()` for user-facing date logic
104
-
-**MCP detection is best-effort** — all MCP detection functions return nil/empty on error; statusline never fails due to missing config
105
-
-**MCP sources** — six detection paths: `mcpServers` in settings.json, marketplace `enabledPlugins` with `.mcp.json` walk, project-level `.mcp.json` via `cwd` from transcript, `settings.local.json` in project `.claude/`, top-level `mcpServers` in `~/.claude.json`, and per-project `mcpServers` in `~/.claude.json`
106
-
-**Config file** — `~/.goccc.json` stores currency code, cached rate, timestamp, and cost thresholds (`warn_threshold`/`alert_threshold`). `initConfig()` loads everything once: thresholds first (with swap-correction if misordered), then currency. Exchange rates auto-fetched and cached for 24h. `-currency-symbol` and `-currency-rate` flags override config (both required together). JSON output cost fields always in USD
107
-
-**Session end hook** — `-session-end` reads `SessionEnd` JSON from stdin, parses the session transcript, and outputs a one-line cost summary. Uses `os.Exit(2)` + ANSI escape to overwrite Claude Code's "hook failed" prefix. Silently exits on any error — never breaks session teardown
108
-
-**Customizable statusline** — `statusline` key in `~/.goccc.json` configures segment order, visibility, separator, and per-segment emoji/label overrides. No config = current defaults. Segments with no data auto-hide (e.g. `5h`/`7d` on API billing, `mcp` when none detected). `"|"` in the segments array forces a line break. Config structs and segment registry live in `statusline_config.go`
109
-
-**Rate limit windows** — `formatRateLimitUsage` handles both 5h and 7d windows via `rateLimitWindow` struct. Uses pointer fields — nil when absent (API billing users). Emoji switches to 🪫 at ≤25% remaining. Color thresholds inverted vs cost (yellow ≤50%, red ≤25% remaining)
38
+
-**Flat package** — all code in `package main`, one concern per file
39
+
-**Externalized pricing** — all pricing lives in `pricing.json` (embedded via `//go:embed`, remote-cached 24h). Adding a model or adjusting pricing = edit `pricing.json` only, no code changes. Cache fields are optional — `fillCacheDefaults()` derives from input price
40
+
-**Pricing resolution** — exact model ID → longest family prefix → `defaultPricing`
41
+
-**Local timezone everywhere** — local midnight for cutoffs, `parsed.Local()` for date bucketing. Never `UTC()` for user-facing dates
42
+
-**MCP detection is best-effort** — returns nil/empty on error; statusline never fails due to missing config
43
+
-**Config** — `~/.goccc.json` stores currency, thresholds, and statusline config. `initConfig()` loads once. JSON output costs always in USD
44
+
-**Session end hook** — uses `os.Exit(2)` + ANSI escape to overwrite Claude Code's "hook failed" prefix. Silently exits on any error
45
+
-**Statusline segments** — registry in `statusline_config.go`. Segments with no data auto-hide. `"|"` forces line break
110
46
111
47
## Don't
112
48
113
-
- Don't add or change pricing by editing Go code — update`pricing.json` instead (models, families, display_names, long_context_threshold, web_search_cost)
49
+
- Don't change pricing in Go code — edit`pricing.json` (models, families, display_names, long_context_threshold, web_search_cost)
114
50
- Don't use `log.Fatal` or `panic` — use `fmt.Fprintf(os.Stderr, ...)` + `os.Exit(1)`
115
51
- Don't use UTC for day boundaries — use `time.Date(...)` with `now.Location()` for local midnight
116
52
- Don't add JSON tags to `Bucket` — it's never directly marshalled; `printJSON` defines its own output structs
A fast, zero-dependency CLI cost calculator and [statusline provider](#claude-code-statusline) for [Claude Code](https://code.claude.com/docs/en/overview) — single binary, no runtime needed.
8
-
9
-
Parses JSONL conversation logs and subagent sessions from `~/.claude/projects/`, deduplicates streaming responses, and breaks down spending by model, day, project, and branch — with accurate cache-tier and web search pricing.
7
+
A fast, zero-dependency CLI cost calculator and [customizable statusline](#claude-code-statusline) for [Claude Code](https://code.claude.com/docs/en/overview). Breakdowns by model, day, project, and branch. Single binary, no runtime needed.
To display costs in your local currency, create `~/.goccc.json`:
68
-
69
-
```json
70
-
{
71
-
"currency": "ZAR"
72
-
}
73
-
```
74
-
75
-
goccc will auto-fetch the exchange rate from USD and cache it for 24 hours. If the API is unreachable, the last cached rate is used. Set `currency` to any [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) code (e.g., `EUR`, `GBP`, `ZAR`, `JPY`).
76
-
77
-
For one-off overrides without a config file, use both flags together:
78
-
79
-
```bash
80
-
goccc -currency-symbol "€" -currency-rate 0.92
81
-
```
82
-
83
-
JSON output always reports costs in USD for backward compatibility, with a `currency` metadata object when a non-USD currency is active.
84
-
85
-
> See also: [Configuration](#configuration) for threshold customization.
86
-
87
63
## Claude Code Statusline
88
64
89
-
goccc can serve as a [Claude Code statusline](https://code.claude.com/docs/en/statusline)provider — a live cost dashboard right in your terminal prompt.
65
+
goccc can serve as a [Claude Code statusline](https://code.claude.com/docs/en/statusline) — a fully customizable, live cost dashboard right in your terminal prompt.
-**🔌 MCPs** — active MCP servers (from settings, marketplace plugins, and project config; respects per-project disables)
99
-
-**🔋 5h window** — remaining percentage of the 5-hour usage window with elapsed time (subscription users only; hidden for API billing). Emoji switches to 🪫 below 25%
75
+
-**🔋 5h / 7d window** — remaining percentage of the usage window with elapsed time (subscription users only; hidden for API billing). Emoji switches to 🪫 below 25%
100
76
-**🤖 Model** — current model
101
77
102
-
Cost and context values are color-codedyellow → red as they increase. The 5h window is color-coded in reverse — yellow below 50%, red below 25%.
78
+
Values are color-coded: cost and context turn yellow → red as they increase; rate limit windows are inverted — yellow below 50%, red below 25% remaining.
103
79
104
80
### Setup
105
81
106
82
Add to `~/.claude/settings.json`:
107
83
108
-
**Using Homebrew** (recommended — fast, no runtime needed):
109
-
110
84
```json
111
85
{
112
86
"statusLine": {
@@ -116,16 +90,7 @@ Add to `~/.claude/settings.json`:
116
90
}
117
91
```
118
92
119
-
**Using Go** (requires Go installed; binary is cached after first download):
120
-
121
-
```json
122
-
{
123
-
"statusLine": {
124
-
"type": "command",
125
-
"command": "go run github.com/backstabslash/goccc@latest -statusline"
126
-
}
127
-
}
128
-
```
93
+
Works with any [install method](#installation). To run without installing: `go run github.com/backstabslash/goccc@latest -statusline`.
129
94
130
95
### Customization
131
96
@@ -144,28 +109,41 @@ The statusline is fully customizable via `~/.goccc.json`. With no config, you ge
144
109
}
145
110
```
146
111
147
-
**`segments`** — ordered list of segments to display. Only listed segments are shown. Use `"|"` to force a line break (as shown above).
112
+
**`segments`** — ordered list of segments to display. Only listed segments are shown. Use `"|"` to force a line break (multi-line layout). Segments with no data auto-hide.
148
113
149
114
Available segments:
150
115
151
-
| Segment | Default | Auto-hides when |
152
-
| ---------| ---------| ------------------- |
153
-
|`session_cost`|`💸 $X.XX session`| cost is $0 |
154
-
|`today_cost`|`💰 $X.XX today`| cost is $0 |
155
-
|`ctx`|`💭 XX% ctx`| — |
156
-
|`model`|`🤖 Model Name`| — |
157
-
|`mcp`|`🔌 N MCPs (...)`| no MCPs detected |
158
-
|`5h`|`🔋 XX% (X/5h)`| absent (API billing) |
159
-
|`7d`|`🔋 XX% (X/7d)`| absent (API billing) |
160
-
|`tokens`|`📊 XK in / XK out`| both zero |
161
-
|`lines`|`📝 +N -N`| both zero |
162
-
|`duration`|`⏱️ Xm`| zero |
163
-
|`cwd`|`📁 dirname`| empty |
164
-
|`version`|`🏷️ X.Y.Z`| empty |
116
+
| Segment | Default | Auto-hides when | Overrides |
117
+
| --- | --- | ---|--- |
118
+
|`session_cost`|`💸 $X.XX session`| cost is $0 | emoji, label |
119
+
|`today_cost`|`💰 $X.XX today`| cost is $0 | emoji, label |
120
+
|`ctx`|`💭 XX% ctx`| — | emoji, label |
121
+
|`model`|`🤖 Model Name`| — | emoji |
122
+
|`mcp`|`🔌 N MCPs (...)`| no MCPs detected | emoji, label |
Cost values are color-coded yellow (warning) and red (alert) when they exceed thresholds. The defaults are $25 and $50 per day. To customize:
188
+
### Local Currency
206
189
207
-
```json
208
-
{
209
-
"warn_threshold": 30,
210
-
"alert_threshold": 75
211
-
}
212
-
```
190
+
Set `"currency": "EUR"` (or any ISO 4217 code) in `~/.goccc.json`. goccc auto-fetches the exchange rate from USD and caches it for 24 hours. If the API is unreachable, the last cached rate is used. For one-off overrides without a config file, use `-currency-symbol "€" -currency-rate 0.92` together.
213
191
214
-
Thresholds are in USD (before currency conversion). They apply to the terminal output, statusline, and session exit hook.
192
+
JSON output always reports costs in USD, with a `currency` metadata object when a non-USD currency is active.
215
193
216
194
## Flags
217
195
218
196
| Flag | Short | Default | Description |
219
197
| --- | --- | --- | --- |
220
198
|`-days`|`-d`|`0`| Only show the last N calendar days (0 = all time) |
221
-
|`-project`|`-p`|| Filter by project name (substring, case-insensitive) |
222
-
|`-daily`||`false`| Show daily breakdown |
199
+
|`-project`|`-p`|— | Filter by project name (substring, case-insensitive) |
200
+
|`-daily`|— |`false`| Show daily breakdown |
223
201
|`-monthly`|`-m`|`false`| Show monthly breakdown (mutually exclusive with `-daily`) |
224
-
|`-projects`||`false`| Show per-project breakdown |
225
-
|`-all`||`false`| Show all breakdowns (daily + projects) |
202
+
|`-projects`|— |`false`| Show per-project breakdown |
203
+
|`-all`|— |`false`| Show all breakdowns (daily + projects) |
226
204
|`-top`|`-n`|`0`| Max entries in breakdowns (0 = all) |
0 commit comments