Skip to content
All posts
EngineeringArticle

I Built a Cost Dashboard for Claude Code. Then I Put It on a Raspberry Pi.

Claude Code writes all session data to local JSONL files. I wrote a tool that reads them, computes exact costs per session, and exposes a REST API. Then I deployed it on my always-on Pi and wired it into Telegram.

2026-03-276 min readAAli Rezaiyan
I Built a Cost Dashboard for Claude Code. Then I Put It on a Raspberry Pi.

I run Claude Code 24/7 on a Raspberry Pi 5.

It handles Telegram messages, runs scripts, manages services. It is genuinely useful. It is also spending money continuously, and for a while I had no real-time visibility into how much.

I could check the Anthropic usage dashboard in a browser. But that is not the developer experience I wanted. What I wanted was to type /cost in Telegram and see today's spend in under a second, from anywhere.

So I built Ledger.

The Insight: The Data Is Already There

Claude Code stores every conversation as a JSONL file in ~/.claude/projects/. Each line is a structured event: user messages, assistant responses, tool calls. The assistant entries include full token usage data — input tokens, output tokens, cache writes, cache reads, even web search counts.

That is everything you need to compute an exact cost per conversation. No API key required. No external service. The data is sitting on your filesystem.

The pricing model is more detailed than it first appears:

  • Input tokens — the base cost for what you send to the model
  • Output tokens — typically 5x the input rate
  • Cache writes (1-hour TTL) — 3.75x input rate
  • Cache writes (5-minute TTL) — 1.25x input rate
  • Cache reads — 0.10x input rate

Get the cache math wrong and your cost estimates can be off by 20-40%. Ledger handles the detailed breakdown when available, with a fallback to the rolled-up cache_creation_input_tokens field for older conversation files.

What Ledger Does

Ledger reads all the JSONL files in your Claude projects directory, parses every assistant entry, and aggregates cost, token counts, cache hit rates, and timing data. Entirely local. No network calls.

It exposes three interfaces:

  • CLIledger prints a cost summary and exits. ledger today gives you just today.
  • TUIledger tui launches an interactive terminal dashboard built with Ink
  • Web dashboardledger open starts an Express server and opens a React + Recharts dashboard at localhost:4200

The REST API behind the web dashboard is what makes the Pi integration possible. Endpoints like /api/summary, /api/conversations, and /api/status return structured JSON that any client can consume.

Slash Commands for Session Tracking

Beyond cost tracking, Ledger includes slash commands that you drop into your project's commands/ directory. They let Claude Code track its own work sessions:

/project:session-start feature "Add user auth"
# ... work happens ...
/project:session-update "OAuth flow working, writing tests"
# ... more work ...
/project:session-end

Each session becomes a Markdown file with YAML frontmatter — start time, end time, cost, commits, lines changed, model used. Ledger reads these alongside the JSONL data and computes efficiency metrics across sessions.

The Efficiency Metrics

Beyond raw costs, two metrics turned out to be the most useful.

Cache Hit Rate

Cache hit rate is cache reads divided by total cache activity (reads + writes). A high rate means Claude Code is reusing cached context instead of re-sending it every turn.

For an always-on daemon with stable system prompts, you want this above 70%. When a session starts cold after a restart, the rate drops. When the system has been running steadily, it climbs back. Watching this number over time tells you whether your setup is actually benefiting from prompt caching or just paying the write cost.

Dead-End Detection

A dead-end session is one with meaningful cost (more than $0.10) but zero commits. It spent money and produced nothing mergeable.

Sometimes that is fine — exploration, research, debugging. But when a session that was supposed to produce code produces nothing, you want to know.

Ledger flags these automatically. Over time, tracking dead-end rate by session type — feature, bug, refactor, explore — gives you real signal about where AI-assisted development is efficient and where it is not.

Deploying on the Raspberry Pi

My Pi 5 already runs Claude Code as a daemon via a tmux session managed by systemd. Adding Ledger as a second service was straightforward.

Install Node.js 22 LTS, then:

npm install -g @rezaiyan/ledger

The only difference between Mac and Pi is the JSONL directory path. On Mac: /Users/ali/.claude/projects. On Pi: /home/ali/.claude/projects. Ledger accepts a --dir flag, so no code changes are needed — just pass the right path in the service unit:

[Unit]
Description=Ledger — Claude Code cost and session API
After=network.target

[Service]
Type=simple
User=ali
ExecStart=/usr/local/bin/ledger serve \
  --dir /home/ali/.claude/projects \
  --sessions-dir /home/ali/sessions \
  --port 4200
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

At idle, the server uses about 30 MB of RAM. The JSONL parse on startup takes a couple of seconds for a large conversation history, then caches results with a 30-second TTL. Subsequent API calls return instantly.

Updating is a one-liner:

ssh ali@pi5.local "npm install -g @rezaiyan/ledger && sudo systemctl restart ledger"

Wiring Into Telegram

The Pi already runs a Python admin bot for controlling Claude Code — handling restarts, monitoring health, managing a command queue. Adding Ledger integration meant adding a small HTTP helper and five new commands.

LEDGER_URL = "http://localhost:4200"

def ledger_api(path):
    try:
        req = urllib.request.Request(f"{LEDGER_URL}/api/{path}")
        with urllib.request.urlopen(req, timeout=5) as r:
            return json.loads(r.read()), None
    except urllib.error.URLError:
        return None, "Ledger service is not running."

The commands:

  • /overview — today's cost, active session, this month's total
  • /cost — today, this month, all-time spend in three lines
  • /sessions [N] — last N conversations with cost, cache rate, duration, and model
  • /lastsession — full detail on the most recent conversation
  • /efficiency — cache hit rate, average cost per conversation, dead-end count

All respond in under a second. No cloud. Everything stays local on the Pi.

What This Actually Changed

Having real-time cost visibility changed how I think about the Pi setup in three concrete ways.

Model selection. I run haiku on the Pi daemon because it is 5-10x cheaper than sonnet for the conversational and automation tasks the bot handles. Before Ledger, that was a guess. After Ledger, I could see per-conversation costs and confirm the model choice was right — sonnet on the Mac for interactive development, haiku on the Pi for always-on work.

Session discipline. I started tracking which tasks produce dead-end sessions. Exploratory research turned out to be the biggest source of sunk cost — not because it fails, but because I was not framing those sessions with clear deliverable expectations. Knowing that changed how I structure them.

Restart decisions. The cache hit rate makes the cost of restarts visible. When the Pi has been running steadily, the rate sits above 80%. After a restart, it drops to near zero and climbs back slowly. Each unnecessary restart has a concrete dollar cost now, not just an abstract "warm cache" argument.

Try It

npm install -g @rezaiyan/ledger

ledger          # cost report
ledger today    # today's summary
ledger tui      # interactive dashboard
ledger open     # web dashboard at localhost:4200

The source is at github.com/rezaiyan/ledger. MIT licensed.

If you are running Claude Code seriously — on a Pi, a VPS, or just your laptop — you should know what each session costs. The data is already on your filesystem. You just have to read it.