setup-cost-tracking
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,
Files5
View on GitHubSKILL.mddev-pipeline/skills/setup-cost-tracking/SKILL.md
GitHubSetup 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, withtranscript_pathfor 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$NODEin every command you write into settings, not barenode(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 effectivestatusLineis 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
.jsfiles from this skill'sscripts/directory into~/.claude/scripts/andchmod +xthem. Overwriting previous versions of these four files is fine (that's how upgrades work). Never overwrite~/.claude/scripts/statusline-original.jsonif it exists.
Step 2 — Wire the statusline (the load-bearing step)
Decide by the effective statusLine:
- Case A — already ours: its command contains
statusline-wrapper.jsorstatusline-command.js. Nothing to change; report "already installed" and continue to Step 3. - Case B — none configured: set in
~/.claude/settings.json:
(expand"statusLine": { "type": "command", "command": "$NODE /Users/<user>/.claude/scripts/statusline-command.js", "padding": 0 }$NODEand the home directory to absolute paths). - Case C — user has their own statusline: preserve it.
- Write
~/.claude/scripts/statusline-original.jsoncontaining exactly{ "command": "<their current statusLine.command>" }. - In the file that defines
statusLine, replace only thecommandvalue with$NODE /Users/<user>/.claude/scripts/statusline-wrapper.js. Keeptype,padding,refreshInterval, and any other sibling keys untouched.
- Write
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", aworkspace.current_dirof/tmp/cost-setup-verify, and nonzerocost.total_cost_usd/cost.total_api_duration_ms/model.display_namevalues. - Pipe it via stdin into the exact
statusLine.commandnow in settings (run throughsh -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.jsonlnow exists and contains the fake cost. - Clean up all verification artifacts: that projects subdirectory, the
cost-setup-verifyline in~/.claude/cost-log.jsonl, and/tmp/statusline-*-cost-setup-verifystate 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-trackingsettings backup (or, by hand: pointstatusLine.commandback to the command saved in~/.claude/scripts/statusline-original.jsonand 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.