ccost 0.0.4 → 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.
Files changed (2) hide show
  1. package/README.md +437 -0
  2. package/package.json +4 -3
package/README.md ADDED
@@ -0,0 +1,437 @@
1
+ # ccost <img src="https://raw.githubusercontent.com/toolsu/ccost/main/logo.svg" alt="ccost" width="35" />
2
+
3
+ Analyze Claude Code token usage and cost from your local conversation history.
4
+
5
+ Reads JSONL files from `~/.claude/projects/`, deduplicates streaming entries, calculates costs using LiteLLM pricing, and outputs as table, JSON, Markdown, HTML, CSV, TSV, or braille chart (See [Screenshot](#screenshots)).
6
+
7
+ The [`ccost sl` subcommand](#statusline-analysis) analyzes `~/.claude/statusline.jsonl` for rate limit tracking, session summaries, budget estimation, and cost comparison (see [`ccost` vs `ccost sl`](#ccost-vs-ccost-sl)).
8
+
9
+ Blazing fast CLI and library written in Rust with thorough tests. Also powers [Claude Code Dashboard](https://github.com/toolsu/claude-code-dashboard), a Tauri desktop app for visualizing your Claude Code usage and more.
10
+
11
+ [![crates.io](https://img.shields.io/crates/v/ccost)](https://crates.io/crates/ccost) [![npm](https://img.shields.io/npm/v/ccost)](https://www.npmjs.com/package/ccost) [![CI](https://github.com/toolsu/ccost/actions/workflows/ci.yml/badge.svg)](https://github.com/toolsu/ccost/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-MIT-brightgreen)](https://github.com/toolsu/ccost/blob/main/LICENSE)
12
+ [中文文档](https://github.com/toolsu/ccost/blob/main/README.zh.md)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm i -g ccost
18
+ ```
19
+
20
+ Or download prebuilt binaries from [GitHub Releases](https://github.com/toolsu/ccost/releases).
21
+
22
+ Or via Cargo (compiles from source):
23
+
24
+ ```bash
25
+ cargo install ccost
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ ccost # default: by day, then by model
32
+ ccost --from 2026-03-01 --to 2026-03-31 # date range
33
+ ccost --per model --cost decimal # by model, 2-decimal cost
34
+ ccost --chart cost # cost chart over time
35
+
36
+ ccost sl # 5h rate-limit windows (default)
37
+ ccost sl --per action # rate limit timeline
38
+ ccost sl --per 5h --cost decimal # budget estimation
39
+ ccost sl --chart 5h # rate limit chart
40
+ ```
41
+
42
+ ## CLI Reference
43
+
44
+ ### Grouping
45
+
46
+ | Flag | Description |
47
+ |------|-------------|
48
+ | `--per <dim>` | Group by: `day`, `hour`, `month`, `session`, `project`, `model`. Up to 2 for nested grouping. Default: `--per day --per model`. |
49
+ | `--order <order>` | Sort: `asc` (default) or `desc`. |
50
+
51
+ Two-level grouping creates parent-child rows, e.g. `--per day --per model` shows each day with per-model breakdowns.
52
+
53
+ ### Date Filtering
54
+
55
+ | Flag | Description |
56
+ |------|-------------|
57
+ | `--from <date>` | Start date (inclusive). `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. |
58
+ | `--to <date>` | End date (inclusive). |
59
+ | `--5hfrom`, `--5hto` | 5-hour window starting/ending at given time. |
60
+ | `--1wfrom`, `--1wto` | 1-week window starting/ending at given time. |
61
+
62
+ `--5hfrom`/`--5hto` are mutually exclusive. Same for `--1wfrom`/`--1wto`. The `5h` and `1w` shortcuts cannot be combined with `--from`/`--to`.
63
+
64
+ ### Record Filtering
65
+
66
+ | Flag | Description |
67
+ |------|-------------|
68
+ | `--project <name>` | Filter by project path (case-insensitive substring). |
69
+ | `--model <name>` | Filter by model name (case-insensitive substring). |
70
+ | `--session <id>` | Filter by session ID (case-insensitive substring). |
71
+
72
+ ### Timezone
73
+
74
+ | Flag | Description |
75
+ |------|-------------|
76
+ | `--tz <tz>` | `UTC`, fixed offset (`+08:00`), or IANA name (`Asia/Shanghai`). Default: local. |
77
+
78
+ ### Cost Display
79
+
80
+ | Flag | Description |
81
+ |------|-------------|
82
+ | `--cost <mode>` | `true` (default): integer `$1`. `decimal`: `$1.23`. `false`: hide costs. |
83
+
84
+ ### Output
85
+
86
+ | Flag | Description |
87
+ |------|-------------|
88
+ | `--output <fmt>` | `json`, `markdown`, `html`, `txt`, `csv`, `tsv`. Writes to file (default: `ccost.<ext>`). |
89
+ | `--filename <path>` | Custom output filename. |
90
+ | `--table <mode>` | `auto` (default), `full`, `compact`. Auto picks compact when terminal < 120 cols. |
91
+ | `--copy <fmt>` | Copy to clipboard. Uses `pbcopy`/`xclip`/`wl-copy`/OSC52. Independent of `--output`. |
92
+
93
+ ### Chart
94
+
95
+ | Flag | Description |
96
+ |------|-------------|
97
+ | `--chart <mode>` | Braille line chart: `cost` or `token`. |
98
+
99
+ Granularity auto-selects based on range (hour/day/month). Only `--output txt` works with `--chart`.
100
+
101
+ ### Pricing
102
+
103
+ | Flag | Description |
104
+ |------|-------------|
105
+ | `--live-pricing` | Fetch latest from LiteLLM (requires network). |
106
+ | `--pricing-data <path>` | Custom pricing JSON file. |
107
+
108
+ ### Other
109
+
110
+ | Flag | Description |
111
+ |------|-------------|
112
+ | `--claude-dir <dir>` | Override Claude config directory. Default: `~/.claude` and `~/.config/claude`. |
113
+ | `--help` | Show help. |
114
+ | `--version` | Show version. |
115
+
116
+ ## Statusline Analysis
117
+
118
+ `ccost sl` command can analyze `~/.claude/statusline.jsonl`, a file produced by a Claude Code statusline hook that snapshots rate limits, cost, duration, and context window usage on every action (see [`ccost` vs `ccost sl`](#ccost-vs-ccost-sl)).
119
+
120
+ ### Setup for statusline.jsonl
121
+
122
+ #### Automatic Setup
123
+
124
+ Run in Claude Code on any OS:
125
+
126
+ ```
127
+ /statusline Set up a command-type statusline using ~/.claude/statusline.sh (or ~/.claude/statusline.ps1 on Windows). The script should read stdin and append {"ts":<unix_epoch>,"data":<stdin_json>} to ~/.claude/statusline.jsonl. Create the script and configure settings.json.
128
+ ```
129
+
130
+ #### Manual Setup
131
+
132
+ ##### Linux, macOS, WSL
133
+
134
+ Or set it up manually: add to `~/.claude/settings.json`:
135
+
136
+ ```json
137
+ "statusLine": {
138
+ "type": "command",
139
+ "command": "~/.claude/statusline.sh"
140
+ }
141
+ ```
142
+
143
+ Create `~/.claude/statusline.sh`:
144
+
145
+ ```bash
146
+ #!/bin/bash
147
+ input=$(cat)
148
+ echo "{\"ts\":$(date +%s),\"data\":$input}" >> ~/.claude/statusline.jsonl
149
+ ```
150
+
151
+ ```sh
152
+ chmod +x ~/.claude/statusline.sh
153
+ ```
154
+
155
+ If you already have a statusline script, just append the `echo` line.
156
+
157
+ ##### Windows (PowerShell)
158
+
159
+ Add to `~/.claude/settings.json`:
160
+
161
+ ```json
162
+ "statusLine": {
163
+ "type": "command",
164
+ "command": "powershell -NoProfile -File ~/.claude/statusline.ps1"
165
+ }
166
+ ```
167
+
168
+ Create `~/.claude/statusline.ps1`:
169
+
170
+ ```powershell
171
+ $input = $Input | Out-String
172
+ $ts = [int](New-TimeSpan -Start (Get-Date '1970-01-01') -End (Get-Date).ToUniversalTime()).TotalSeconds
173
+ $line = "{""ts"":$ts,""data"":" + $input.Trim() + "}"
174
+ Add-Content -Path "$env:USERPROFILE\.claude\statusline.jsonl" -Value $line -Encoding UTF8
175
+ ```
176
+
177
+ ### View Modes
178
+
179
+ | Command | Description |
180
+ |---------|-------------|
181
+ | `ccost sl` | 5-hour windows (default) |
182
+ | `ccost sl --per action` | Rate limit timeline with per-action cost |
183
+ | `ccost sl --per session` | By session |
184
+ | `ccost sl --per project` | By project |
185
+ | `ccost sl --per day` | By day |
186
+ | `ccost sl --per 1h` | 1-hour windows within 5h windows |
187
+ | `ccost sl --per 5h` | 5-hour rate-limit windows |
188
+ | `ccost sl --per 1w` | 1-week rate-limit windows |
189
+ | `ccost sl --chart 5h` | 5-hour rate limit % chart |
190
+ | `ccost sl --chart 1w` | 1-week rate limit % chart |
191
+ | `ccost sl --chart cost` | Cumulative cost chart |
192
+
193
+ The `session/project/day/1h/5h/1w` views share columns: `Cost`, `Duration`, `API Time`, `Lines +/-`, `Sess`, `5h%`, `1w%`. Window views (`1h/5h/1w`) add `Est 5h Budget` or `Est 1w Budget`. The `1h` view also adds `5h Resets`. The `action` view has its own layout: `Time`, `Cost`, `5h%`, `1w%`, `5h Resets`, `Session`.
194
+
195
+ The `5h%` and `1w%` columns show the observed min-max range (e.g. `1-29%`).
196
+
197
+ ### Statusline Flags
198
+
199
+ | Flag | Description |
200
+ |------|-------------|
201
+ | `--file <path>` | Statusline file. Default: `~/.claude/statusline.jsonl`. |
202
+ | `--nopromo` | Disable promo adjustment for budget estimation. See below. |
203
+ | `--cost-diff` | Compare statusline cost with LiteLLM pricing. Only with `--per session`. |
204
+
205
+ All shared flags (`--from`, `--to`, `--tz`, `--session`, `--project`, `--model`, `--cost`, `--output`, `--filename`, `--copy`, `--order`, `--table`) work with `ccost sl`.
206
+
207
+ ### Segments and Continued Sessions
208
+
209
+ When a session is closed and resumed, cumulative counters reset to zero. `ccost sl` detects these resets and sums across segments correctly.
210
+
211
+ When a session is continued via `claude --continue`, a new session ID is created but cumulative counters (cost, duration, lines) carry over from the predecessor without resetting. `ccost sl` detects this by subtracting each session's first record values as a baseline, so only the work done in the current session instance is counted.
212
+
213
+ Rate limits are account-level and do not reset with sessions.
214
+
215
+ Note: `--per day` assigns each session to the day it started. Sessions spanning midnight are not split.
216
+
217
+ ### Budget Estimation
218
+
219
+ Window views estimate the total rate-limit budget from cost and the delta in rate-limit percentage:
220
+
221
+ ```
222
+ Est 5h Budget = Cost * 100 / delta_5h% (for --per 1h and --per 5h)
223
+ Est 1w Budget = Cost * 100 / delta_1w% (for --per 1w)
224
+ ```
225
+
226
+ Where `delta% = max% - min%` within the window. This uses how much of the rate limit was actually consumed, not the absolute peak.
227
+
228
+ Since `ccost sl` only sees Claude Code cost, usage from other Claude products (web, desktop, mobile) inflates the denominator without increasing the numerator. This makes the estimate a **lower bound** on the real budget, with the gap proportional to non-CC usage.
229
+
230
+ ### Promo Adjustment
231
+
232
+ Budget estimates are adjusted by default for known 2x usage promo periods (Dec 2025, Mar 2026). During these promos the rate-limit budget doubles, so the same spend uses half the percentage. Use `--nopromo` to disable:
233
+
234
+ ```sh
235
+ ccost sl --per 5h --nopromo
236
+ ```
237
+
238
+ For windows that partially overlap a promo period, the adjustment scales proportionally.
239
+
240
+ ## Examples
241
+
242
+ ```bash
243
+ # HTML report with decimal cost
244
+ ccost --per project --cost decimal --output html --filename report.html
245
+
246
+ # Filter to a specific session
247
+ ccost --session abc123 --from 2026-03-23
248
+
249
+ # UTC, descending
250
+ ccost --tz UTC --order desc --from 2026-03-01
251
+
252
+ # Opus models only, no cost
253
+ ccost --model opus --cost false
254
+
255
+ # 5-hour window from a specific time
256
+ ccost --5hfrom 2026-03-24T10:00:00
257
+
258
+ # Live pricing
259
+ ccost --live-pricing
260
+
261
+ # Cost chart for last week
262
+ ccost --chart cost --1wto 2026-03-25
263
+
264
+ # CSV by month
265
+ ccost --per month --output csv --filename usage.csv
266
+
267
+ # Two-level: project > model
268
+ ccost --per project --per model --table full
269
+
270
+ # Copy JSON to clipboard
271
+ ccost --copy json
272
+
273
+ # Statusline: budget estimation
274
+ ccost sl --per 5h --cost decimal
275
+
276
+ # Statusline: cost comparison
277
+ ccost sl --per session --cost-diff --cost decimal
278
+
279
+ # Statusline: rate limit chart
280
+ ccost sl --chart 5h
281
+ ```
282
+
283
+ ## Library API
284
+
285
+ ccost is also a Rust library.
286
+
287
+ ```toml
288
+ [dependencies]
289
+ ccost = { path = "../ccost" }
290
+ ```
291
+
292
+ ### Pipeline
293
+
294
+ ```
295
+ load_records -> calculate_cost -> group_records -> format_*
296
+ ```
297
+
298
+ ```rust
299
+ use ccost::*;
300
+
301
+ let options = LoadOptions {
302
+ from: Some("2026-03-01".to_string()),
303
+ to: Some("2026-03-31".to_string()),
304
+ tz: Some("UTC".to_string()),
305
+ model: Some("opus".to_string()),
306
+ ..Default::default()
307
+ };
308
+
309
+ let result = load_records(&options);
310
+ let pricing = load_pricing();
311
+ let priced = calculate_cost(&result.records, Some(&pricing));
312
+
313
+ let dimensions = vec![GroupDimension::Day, GroupDimension::Model];
314
+ let group_options = GroupOptions {
315
+ order: SortOrder::Asc,
316
+ tz: Some("UTC".to_string()),
317
+ };
318
+ let grouped = group_records(&priced, &dimensions, Some(&group_options));
319
+
320
+ use ccost::formatters::table::TableOptions;
321
+ let table_opts = TableOptions {
322
+ dimension_label: "Date/Model".to_string(),
323
+ price_mode: PriceMode::Decimal,
324
+ compact: false,
325
+ color: Some(true),
326
+ };
327
+ let table = format_table(&grouped.data, &grouped.totals, &table_opts);
328
+ print!("{}", table);
329
+ ```
330
+
331
+ ### Key Types
332
+
333
+ ```rust
334
+ struct LoadOptions {
335
+ claude_dir: Option<String>,
336
+ from: Option<String>, // YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
337
+ to: Option<String>,
338
+ tz: Option<String>, // UTC, +HH:MM, or IANA name
339
+ project: Option<String>, // substring filter
340
+ model: Option<String>,
341
+ session: Option<String>,
342
+ }
343
+
344
+ enum GroupDimension { Day, Hour, Month, Session, Project, Model }
345
+
346
+ struct GroupedData {
347
+ label: String,
348
+ input_tokens: u64,
349
+ output_tokens: u64,
350
+ cache_creation_tokens: u64,
351
+ cache_read_tokens: u64,
352
+ input_cost: f64,
353
+ cache_creation_cost: f64,
354
+ cache_read_cost: f64,
355
+ output_cost: f64,
356
+ total_cost: f64,
357
+ children: Option<Vec<GroupedData>>,
358
+ }
359
+ ```
360
+
361
+ ### Formatters
362
+
363
+ | Function | Output |
364
+ |----------|--------|
365
+ | `format_table()` | Unicode box-drawing table with optional ANSI colors |
366
+ | `format_txt()` | Same table, no colors |
367
+ | `format_json()` | JSON with `meta`, `data`, `totals`, `dedup` |
368
+ | `format_markdown()` | GFM Markdown table |
369
+ | `format_html()` | Self-contained HTML with sortable columns |
370
+ | `format_csv()` | RFC 4180 CSV |
371
+ | `format_tsv()` | Tab-separated values |
372
+ | `render_chart()` | Braille line chart |
373
+
374
+ ## How It Works
375
+
376
+ ### Data Source
377
+
378
+ Reads JSONL conversation logs from Claude Code's local storage:
379
+ - `~/.claude/projects/*/` (session files and `subagents/`)
380
+ - `~/.config/claude/projects/*/` (alternate location)
381
+
382
+ Both are scanned and deduplicated via symlink detection. Subagent transcripts are found in both the old (`<project>/subagents/`) and new (`<project>/<session-uuid>/subagents/`) directory structures.
383
+
384
+ ### Deduplication
385
+
386
+ Streaming entries share the same `messageId:requestId` key with increasing `output_tokens`. Only the highest-output record per key is kept. This dedup is global across all files, preventing double-counting between parent sessions and subagent files.
387
+
388
+ ### Pricing
389
+
390
+ Per-record cost via model-to-price lookup:
391
+ 1. Exact match
392
+ 2. Strip date suffix (`-YYYYMMDD` / `@YYYYMMDD`), retry
393
+ 3. Substring match among `claude-*` keys
394
+
395
+ Bundled at compile time from LiteLLM. Use `--live-pricing` for latest or `--pricing-data` for custom.
396
+
397
+ ### `ccost` vs `ccost sl`
398
+
399
+ Below we'll compare `ccost` (data from `~/.claude/projects/`) and `ccost sl` (data from `statusline.jsonl`).
400
+
401
+ `~/.claude/projects/` contains per-request JSONL logs with detailed token counts (input, output, cache creation, cache read) for every API call. `ccost` computes cost from these counts using LiteLLM pricing.
402
+
403
+ `~/.claude/statusline.jsonl` contains periodic snapshots from Claude Code's statusline, including the server-reported cumulative cost and the 5-hour and 1-week rate-limit usage percentages, which are not available anywhere else.
404
+
405
+ As shown by `ccost sl --per session --cost-diff`, when LiteLLM pricing is accurate, the cost computed from `~/.claude/projects/` closely matches the server-reported cost in `statusline.jsonl`. The statusline cost may occasionally undercount because it does not always include subagent costs. Since `~/.claude/projects/` has full per-request token breakdowns, it may be the more reliable source for cost.
406
+
407
+ The primary value of `statusline.jsonl` is the rate-limit percentages, not cost. This is why [Claude Code Dashboard](https://github.com/toolsu/claude-code-dashboard) uses cost data computed from `~/.claude/projects/` combined with the 5-hour and 1-week rate-limit percentages from `statusline.jsonl`.
408
+
409
+ ### Project Path Decoding
410
+
411
+ Directory names like `-home-user-workspace-test` decode to `/home/user/workspace/test`.
412
+
413
+ ## Screenshots
414
+
415
+ ### Terminal
416
+
417
+ ![Terminal output](https://raw.githubusercontent.com/toolsu/ccost/main/screenshot_terminal.png)
418
+
419
+ ### Statusline Analysis (`ccost sl`)
420
+
421
+ ![Statusline output](https://raw.githubusercontent.com/toolsu/ccost/main/screenshot_terminal_sl.png)
422
+
423
+ ### HTML Report
424
+
425
+ ![HTML report](https://raw.githubusercontent.com/toolsu/ccost/main/screenshot_html.png)
426
+
427
+ ## Development
428
+
429
+ ```bash
430
+ cargo test # run all tests
431
+ cargo build --release # optimized build
432
+ cargo run -- --help # CLI help
433
+ ```
434
+
435
+ ## License
436
+
437
+ MIT
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ccost",
3
- "version": "0.0.4",
4
- "description": "Analyze Claude Code token usage and costs from local conversation history",
3
+ "version": "0.1.0",
4
+ "description": "Analyze Claude Code token usage and costs from local conversation history (Rust CLI & lib)",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/toolsu/ccost"
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "files": [
18
18
  "install.js",
19
- "run.js"
19
+ "run.js",
20
+ "README.md"
20
21
  ],
21
22
  "engines": {
22
23
  "node": ">=14"