Setup Cost Tracking
Installs genuine cost capture for every Claude Code session on this machine. Cost data
(cost.total_cost_usd etc.) is computed by Claude Code itself and is available only
in the statusline payload — hooks never receive it. So this skill wires a logger into the
statusline path while preserving whatever statusline and hooks the user already has.
What gets installed
Bundled in this skill's scripts/ directory, copied to ~/.claude/scripts/:
| Script |
Role |
cost-logger.js |
Capture engine. Silent, crash-proof. Writes the log files below. |
statusline-wrapper.js |
Used when the user already has a statusline: logs cost, then delegates the payload to their original command and passes its output through untouched. |
statusline-command.js |
Used when the user has no statusline: full display (model, context bar, cost, cache stats) that also logs. |
turn-logger.js |
Stop hook: stamps turn boundaries so cost samples can later be grouped into turns for analytics. No cost data itself. |
Log outputs (created lazily, no setup needed):
~/.claude/cost-log.jsonl — one row per session, exact cumulative totals (upserted).
~/.claude/projects/<mapped-dir>/<session>.cost.jsonl — cost samples at ~5s resolution during activity.
~/.claude/projects/<mapped-dir>/<session>.turn-boundaries.jsonl — one row per turn end, with transcript_path for deep-dive analytics.
Installation procedure
Follow these steps exactly. Use real JSON manipulation (a node -e script or jq) for
every settings edit — never string/regex edits on settings files.
Step 0 — Preflight
command -v node — if Node.js is missing, stop and tell the user it's required.
Record the absolute path (call it $NODE). Use $NODE in every command you write
into settings, not bare node (statusline/hook commands don't always get the user's
full shell PATH).
- Read
~/.claude/settings.json (treat as {} if absent) and ~/.claude/settings.local.json
(if present). The effective statusLine is the local one if defined there, else the
user one. Remember which file defines it — edits must go to that same file.
Step 1 — Copy scripts
mkdir -p ~/.claude/scripts
- Copy the four
.js files from this skill's scripts/ directory into ~/.claude/scripts/
and chmod +x them. Overwriting previous versions of these four files is fine (that's
how upgrades work). Never overwrite ~/.claude/scripts/statusline-original.json
if it exists.
Step 2 — Wire the statusline (the load-bearing step)
Decide by the effective statusLine:
Before the first edit to any settings file, back it up:
cp settings.json settings.json.bak-cost-tracking (same pattern for the local file).
One backup per run is enough.
Step 3 — Add the turn-boundary hook (additive, never replaces)
If no existing hooks.Stop entry's command mentions turn-logger.js, append this entry to
the hooks.Stop array (create the array/path if missing), leaving every existing hook alone:
{ "matcher": "", "hooks": [ { "type": "command", "command": "$NODE /Users/<user>/.claude/scripts/turn-logger.js" } ] }
This hook carries no cost data — it only stamps turn boundaries and the idle timer. If the
user objects to hooks, skipping this step still leaves session cost capture fully working.
Step 4 — Verify
- Build a fake statusline payload with
session_id "cost-setup-verify", a workspace.current_dir
of /tmp/cost-setup-verify, and nonzero cost.total_cost_usd / cost.total_api_duration_ms /
model.display_name values.
- Pipe it via stdin into the exact
statusLine.command now in settings (run through sh -c).
- Case B: expect the rendered multi-line statusline.
- Case C: expect the original statusline's output (the wrapper delegates). If their original
command errors on the synthetic payload, that's their script's quirk — check instead that
the log file below was written.
- Confirm
~/.claude/projects/-tmp-cost-setup-verify/cost-setup-verify.cost.jsonl now exists
and contains the fake cost.
- Clean up all verification artifacts: that projects subdirectory, the
cost-setup-verify line
in ~/.claude/cost-log.jsonl, and /tmp/statusline-*-cost-setup-verify state files.
Step 5 — Report
Tell the user, concretely:
- which case applied (fresh install / wrapped their existing statusline / already installed),
- every file created or modified (settings edits, backups, copied scripts),
- where their cost data will accumulate (the three log paths above),
- that changes take effect in new sessions,
- how to uninstall: restore the
.bak-cost-tracking settings backup (or, by hand: point
statusLine.command back to the command saved in ~/.claude/scripts/statusline-original.json
and remove the turn-logger Stop hook entry), then delete the four scripts.
Invariants
- Never delete or rewrite the user's hooks, statusline command, or unrelated settings keys.
All changes are additive or a single-value swap that is recorded for reversal.
- Idempotent: re-running on an installed machine must change nothing and say so.
- If any step fails midway, restore the settings backup and report what happened.
1---2name: setup-cost-tracking3description: Install genuine per-session cost tracking for Claude Code by wiring bundled logger scripts into the user's statusline — preserving any existing statusline and hooks. Use when the user asks to set up, install, enable, or repair cost tracking / cost logging / session cost capture. Idempotent; safe to re-run.4---5
6# Setup Cost Tracking
7
8Installs genuine cost capture for every Claude Code session on this machine. Cost data
9(`cost.total_cost_usd` etc.) is computed by Claude Code itself and is available **only**
10in the statusline payload — hooks never receive it. So this skill wires a logger into the
11statusline path while preserving whatever statusline and hooks the user already has.
12
13## What gets installed
14
15Bundled in this skill's `scripts/` directory, copied to `~/.claude/scripts/`:
16
17| Script | Role |
18|---|---|
19| `cost-logger.js` | Capture engine. Silent, crash-proof. Writes the log files below. |
20| `statusline-wrapper.js` | Used when the user already has a statusline: logs cost, then delegates the payload to their original command and passes its output through untouched. |
21| `statusline-command.js` | Used when the user has no statusline: full display (model, context bar, cost, cache stats) that also logs. |
22| `turn-logger.js` | Stop hook: stamps turn boundaries so cost samples can later be grouped into turns for analytics. No cost data itself. |
23
24Log outputs (created lazily, no setup needed):
25
26- `~/.claude/cost-log.jsonl` — one row per session, exact cumulative totals (upserted).
27- `~/.claude/projects/<mapped-dir>/<session>.cost.jsonl` — cost samples at ~5s resolution during activity.
28- `~/.claude/projects/<mapped-dir>/<session>.turn-boundaries.jsonl` — one row per turn end, with `transcript_path` for deep-dive analytics.
29
30## Installation procedure
31
32Follow these steps exactly. Use real JSON manipulation (a `node -e` script or `jq`) for
33every settings edit — never string/regex edits on settings files.
34
35### Step 0 — Preflight
36
371. `command -v node` — if Node.js is missing, stop and tell the user it's required.
38 Record the absolute path (call it `$NODE`). Use `$NODE` in every command you write
39 into settings, not bare `node` (statusline/hook commands don't always get the user's
40 full shell PATH).
412. Read `~/.claude/settings.json` (treat as `{}` if absent) and `~/.claude/settings.local.json`
42 (if present). The **effective** `statusLine` is the local one if defined there, else the
43 user one. Remember which file defines it — edits must go to that same file.
44
45### Step 1 — Copy scripts
46
471. `mkdir -p ~/.claude/scripts`
482. Copy the four `.js` files from this skill's `scripts/` directory into `~/.claude/scripts/`
49 and `chmod +x` them. Overwriting previous versions of these four files is fine (that's
50 how upgrades work). **Never** overwrite `~/.claude/scripts/statusline-original.json`
51 if it exists.
52
53### Step 2 — Wire the statusline (the load-bearing step)
54
55Decide by the effective `statusLine`:
56
57- **Case A — already ours**: its command contains `statusline-wrapper.js` or
58 `statusline-command.js`. Nothing to change; report "already installed" and continue to Step 3.
59- **Case B — none configured**: set in `~/.claude/settings.json`:
60 ```json
61 "statusLine": { "type": "command", "command": "$NODE /Users/<user>/.claude/scripts/statusline-command.js", "padding": 0 }
62 ```
63 (expand `$NODE` and the home directory to absolute paths).
64- **Case C — user has their own statusline**: preserve it.
65 1. Write `~/.claude/scripts/statusline-original.json` containing exactly
66 `{ "command": "<their current statusLine.command>" }`.
67 2. In the file that defines `statusLine`, replace **only** the `command` value with
68 `$NODE /Users/<user>/.claude/scripts/statusline-wrapper.js`. Keep `type`, `padding`,
69 `refreshInterval`, and any other sibling keys untouched.
70
71Before the first edit to any settings file, back it up:
72`cp settings.json settings.json.bak-cost-tracking` (same pattern for the local file).
73One backup per run is enough.
74
75### Step 3 — Add the turn-boundary hook (additive, never replaces)
76
77If no existing `hooks.Stop` entry's command mentions `turn-logger.js`, append this entry to
78the `hooks.Stop` array (create the array/path if missing), leaving every existing hook alone:
79
80```json
81{ "matcher": "", "hooks": [ { "type": "command", "command": "$NODE /Users/<user>/.claude/scripts/turn-logger.js" } ] }
82```
83
84This hook carries no cost data — it only stamps turn boundaries and the idle timer. If the
85user objects to hooks, skipping this step still leaves session cost capture fully working.
86
87### Step 4 — Verify
88
891. Build a fake statusline payload with `session_id` `"cost-setup-verify"`, a `workspace.current_dir`
90 of `/tmp/cost-setup-verify`, and nonzero `cost.total_cost_usd` / `cost.total_api_duration_ms` /
91 `model.display_name` values.
922. Pipe it via stdin into the exact `statusLine.command` now in settings (run through `sh -c`).
93 - Case B: expect the rendered multi-line statusline.
94 - Case C: expect the *original* statusline's output (the wrapper delegates). If their original
95 command errors on the synthetic payload, that's their script's quirk — check instead that
96 the log file below was written.
973. Confirm `~/.claude/projects/-tmp-cost-setup-verify/cost-setup-verify.cost.jsonl` now exists
98 and contains the fake cost.
994. Clean up all verification artifacts: that projects subdirectory, the `cost-setup-verify` line
100 in `~/.claude/cost-log.jsonl`, and `/tmp/statusline-*-cost-setup-verify` state files.
101
102### Step 5 — Report
103
104Tell the user, concretely:
105- which case applied (fresh install / wrapped their existing statusline / already installed),
106- every file created or modified (settings edits, backups, copied scripts),
107- where their cost data will accumulate (the three log paths above),
108- that changes take effect in new sessions,
109- how to uninstall: restore the `.bak-cost-tracking` settings backup (or, by hand: point
110 `statusLine.command` back to the command saved in `~/.claude/scripts/statusline-original.json`
111 and remove the turn-logger Stop hook entry), then delete the four scripts.
112
113## Invariants
114
115- **Never delete or rewrite the user's hooks, statusline command, or unrelated settings keys.**
116 All changes are additive or a single-value swap that is recorded for reversal.
117- Idempotent: re-running on an installed machine must change nothing and say so.
118- If any step fails midway, restore the settings backup and report what happened.