Skip to content
jaedyncPublic

About

Analytics dashboard for Claude Code. Aggregate usage, costs, and productivity metrics across all your machines.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Tokenfold

Analytics dashboard for Claude Code that aggregates usage across multiple machines into a single view. Track sessions, token costs, model usage, active coding time, and more.

A lightweight client runs on each machine (via cron or launchd), pushing session events to a central server. The server deduplicates, stores everything in SQLite, and serves a Bauhaus-styled dashboard.

Tokenfold Dashboard

Features

  • Aggregate token data from every device into one dashboard
  • Captures Claude Desktop session titles, MCP toggle state, and lifecycle metadata (macOS only)
  • Per-model, per-day, per-project cost breakdown with live LiteLLM pricing
  • Active time tracking: thinking time, tool execution, main sessions, subagents
  • Per-machine stats for prompts, tokens, and costs
  • Weekly budget utilization gauge via Anthropic's OAuth API
  • Personal provider limit groups for Claude, Codex, OpenCode Go, and observed OpenCode Zen spend
  • 24-hour hourly activity heatmap
  • Stdlib-only Python client, no pip install needed
  • Message content is stripped before sending; only metadata and token counts are transmitted

Quick Start

1. Deploy the server

git clone https://github.com/jaedync/tokenfold.git
cd tokenfold
cp .env.example .env
# Edit .env - set STATS_API_KEY to a random secret
docker compose up -d

The dashboard is now at http://localhost:5000.

2. Set up the client

Copy client/claude-stats-push.py to each machine you use Claude Code on.

Linux (cron):

# Copy the script
cp client/claude-stats-push.py ~/tokenfold-push.py

# Add to crontab (runs every 5 minutes)
(crontab -l 2>/dev/null; echo "*/5 * * * * TOKENFOLD_URL=https://your-server.example.com TOKENFOLD_API_KEY=your-secret python3 ~/tokenfold-push.py") | crontab -

macOS (launchd — recommended):

macOS note: always use launchd, not cron. macOS cron runs with a minimal PATH that resolves python3 to Apple’s stock /usr/bin/python3, which is Python 3.9 on current macOS versions. The script is written for Python 3.9+ via from __future__ import annotations, so stock Python works fine — but launchd is the conventional macOS user-agent runner and survives reboots cleanly.

# Copy the script
cp client/claude-stats-push.py /usr/local/bin/tokenfold-push.py

# Copy and edit the plist
cp client/com.tokenfold.push.plist ~/Library/LaunchAgents/
# Edit ~/Library/LaunchAgents/com.tokenfold.push.plist: set TOKENFOLD_URL and TOKENFOLD_API_KEY

# Load (starts immediately since RunAtLoad=true)
launchctl load ~/Library/LaunchAgents/com.tokenfold.push.plist

# Tail the log to verify
tail -f /tmp/tokenfold-push.log

Client Environment Variables

Variable Default Description
TOKENFOLD_URL (required) Server URL (e.g. https://your-server.example.com)
TOKENFOLD_API_KEY (required) API key matching the server's STATS_API_KEY
TOKENFOLD_MACHINE hostname Display name for this machine
TOKENFOLD_VERBOSE 0 Set to 1 for debug logging to stderr
TOKENFOLD_CURSOR ~/.tokenfold-cursor.json Cursor file path (tracks sync progress)

Legacy CLAUDE_STATS_* env vars are still supported as fallbacks.

Server Environment Variables

Variable Default Description
STATS_API_KEY (required) API key for authenticating client pushes
TOKENFOLD_READ_API_KEY (empty) Optional read-only key for GET /api/ha only (display hosts). Every write route refuses it. Empty disables it
TZ America/Chicago Timezone for daily bucketing
DB_PATH /app/data/tokenfold.db SQLite database path
STATS_OWNER (empty) Display name shown on the dashboard
NOTIFY_TOKEN (empty) Auth token for /api/notify (falls back to STATS_API_KEY)
HA_URL (empty) Home Assistant URL (e.g. http://homeassistant.local:8123)
HA_TOKEN (empty) Home Assistant long-lived access token
HA_DEVICES (empty) Comma-separated HA notify targets (e.g. mobile_app_phone)

Claude quota collector (optional)

If a Meridian proxy with a Claude Pro or Max login runs on the server host, the quota-collector service keeps Claude quota current at all times. Without it, quota arrives only while a Pi session reports it.

# .env
COMPOSE_PROFILES=meridian

docker compose up -d --build
docker logs tokenfold-quota-collector   # logs only health changes

The service polls MERIDIAN_URL (loopback only, default http://127.0.0.1:3456) every 120 seconds and posts to POST /api/usage/claude with STATS_API_KEY. Anthropic rate-limits the OAuth usage endpoint for each token. A 60-second poll got HTTP 429 on every second request, so 120 seconds gives the same freshness without rejected requests. The service uses host networking to reach Meridian on loopback, publishes no port, and runs as an unprivileged user on a read-only filesystem. COLLECTOR_MACHINE (default: the host name) labels the reports. COLLECTOR_INTERVAL_S (30 to 3600) changes the cadence. TOKENFOLD_URL must use https unless it is a loopback address.

Notification Relay (optional)

Tokenfold includes an optional notification relay that forwards Claude Code hook events to Home Assistant as mobile push notifications. Get notified when Claude finishes a response, needs permission, or asks a question.

Server setup

Set these environment variables (or add to .env):

NOTIFY_TOKEN=a-random-secret         # Auth token for hook scripts
HA_URL=http://homeassistant.local:8123
HA_TOKEN=your-long-lived-access-token
HA_DEVICES=mobile_app_phone1,mobile_app_phone2

If these are left empty, the /api/notify endpoint still works but silently skips the HA relay.

Claude Code hook setup

On each machine you want notifications from:

# Save your Tokenfold server URL and notify token
echo "https://your-tokenfold-server.example.com" > ~/.config/notify-relay-url
echo "your-notify-token" > ~/.config/notify-relay-token

# Copy the hook script
cp client/notify-relay.sh ~/notify-relay.sh

Then add to ~/.claude/settings.json:

{
  "hooks": {
    "Stop": [{ "hooks": [{ "type": "command", "command": "bash ~/notify-relay.sh", "timeout": 10 }] }],
    "Notification": [{ "hooks": [{ "type": "command", "command": "bash ~/notify-relay.sh", "timeout": 10 }] }]
  }
}

Codex hook setup

# Copy the hook script
cp client/codex-notify-relay.sh ~/codex-notify-relay.sh

Add to ~/.codex/config.toml:

notify = ["bash", "~/codex-notify-relay.sh"]

Architecture

Claude Code CLI  ->  ~/.claude/projects/**/*.jsonl
                        |
              client/claude-stats-push.py  (cron, every 5min)
                        |
              POST /api/ingest  (X-API-Key auth, Claude JSONL batches)
              POST /api/ingest/pi (X-API-Key auth, typed Pi normalized batches)
              POST /api/provider-usage (X-API-Key auth, best-effort quota snapshots)
                        |
              app/ingest.py  (privacy-safe normalization and ID namespacing)
                        |
              SQLite  (WAL mode, events + tool_uses tables)
                        |
              app/aggregator.py  (cached rebuild on invalidation)
                        |
              GET /  (dashboard; GET /classic: the old one)
              GET /api/stats  (JSON API)

API Endpoints

Method Path Auth Description
GET / No Dashboard: Runway, Ledger, History, Lab (/next is an alias)
GET /classic No The old dashboard
GET /api/stats No Full stats JSON
GET /api/stats/version No Cache version (for polling)
POST /api/ingest X-API-Key Ingest Claude Code JSONL session events
POST /api/ingest/pi X-API-Key Ingest typed, privacy-scrubbed Pi Agent events; provider-aware reported costs and required dotfleet work/personal account class
POST /api/desktop-metadata X-API-Key Ingest Claude Desktop session metadata (macOS)
POST /api/usage X-API-Key Push Anthropic OAuth usage/quota data (Claude-only; not Pi)
POST /api/usage/claude X-API-Key Personal Claude quota observed through Meridian (Pi reporter and quota-collector)
POST /api/provider-usage X-API-Key Merge best-effort Codex/OpenCode quota snapshots from Pi, filed under the reporter's account_class (work = enterprise scope, personal = personal scope)
POST /api/notify Bearer Notification relay to Home Assistant
GET /health No Health check

Development

pip install -r requirements.txt
STATS_API_KEY=test uvicorn app.main:app --reload --host 127.0.0.1 --port 5000

License

MIT

About

Analytics dashboard for Claude Code. Aggregate usage, costs, and productivity metrics across all your machines.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages