Self-host aggregator for Claude Code token usage. Watches the JSONL transcripts Claude Code writes locally, runs them through a single SQLite store with per-user / per-machine / per-model labels, and exposes:
- Public SSE counter — drop a single live number on a marketing page (
/api/usage/totals,/api/usage/stream) - Prometheus
/metrics— labelled counters for Grafana dashboards - Authenticated ingestion API — agents on remote dev machines push their own transcripts in (
POST /api/ingest/messages)
Works for one developer on a laptop (zero-config) and for a team of N machines, with the same binary.
You're a single developer; just want a personal cost dashboard for your own Claude Code usage.
git clone https://github.com/wigtn/wigtoken
cd wigtoken
npm install
npm startThat's it. The daemon picks up ~/.claude/projects automatically and starts on http://localhost:10103.
curl http://localhost:10103/api/usage/totals | jq
curl http://localhost:10103/metricsPoint a Grafana → Prometheus stack at localhost:10103 and import grafana/dashboard.json for the full breakdown. Or just open the JSON endpoints in a browser.
You have multiple developers on multiple machines and want one consolidated view.
-
Run the server somewhere reachable (Linux box, NAS, container in your cluster):
docker run -d --name wigtoken \ -p 10103:10103 \ -e MODE=team \ -e ALLOWED_ORIGINS=https://your-site.com \ -v $PWD/data:/data/db \ ghcr.io/wigtn/wigtoken:latestOr use
docker-compose.example.yml. -
Issue an admin bootstrap token (printed once on first run) and use it to mint per-developer ingest tokens:
ADMIN=wts_xxx curl -X POST https://your-server/api/admin/tokens \ -H "Authorization: Bearer $ADMIN" \ -H "Content-Type: application/json" \ -d '{"user":"alice","scope":"ingest","label":"alice-laptop"}'
-
Each developer runs the agent on their machine:
npx @wigtoken-temp/agent \ --server https://your-server \ --token wts_alice_token \ --machine $(hostname)Or as a launchd / systemd service — see
agent/README.md.
The agent watches ~/.claude/projects on each machine and streams new messages over HTTPS. Server-side dedupe by message_id makes it safe to restart, retry, or run multiple agents against the same transcripts.
┌────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ scrape ┌──────────────┐ → ┌──────────┐ │
│ │ wigtoken │←──────│ Prometheus │ →│ Grafana │ │
│ │ server │ └──────────────┘ └──────────┘ │
│ │ │ │
│ │ /api/ingest │←── HTTPS+Bearer ──┐ │
│ │ /api/usage/*│ │ │
│ │ /metrics │←── public reads ──┐│ │
│ │ SQLite │ ││ │
│ └─────────────┘ ││ │
└─────────────────────────────────────┼┼──────────────────────┘
││
││
┌────────────────┐ ┌────────────────┐ │ ┌──────────────────┐
│ agent (mac) │ │ agent (linux) │ │ │ public web hero │
│ ~/.claude/... │ │ ~/.claude/... │ │ │ counter │
│ → push batches │ │ → push batches │ │ └──────────────────┘
└────────┬───────┘ └────────┬───────┘ │
└──────────────────┴─────────┘
per-user tokens
For solo: agent and server run as one process on the same box, no token, no HTTPS.
Both end up calling POST /api/ingest/messages with an ingest-scope bearer token; you pick the trade-off:
| Agent | Hook | |
|---|---|---|
| Footprint | LaunchAgent / systemd unit running @wigtoken-temp/agent |
one block in ~/.claude/settings.json |
| Offline retry | ✅ disk queue + backoff | ❌ one-shot POST per turn |
| Setup | npm install + token + plist | settings.json edit |
| Best for | shared/public networks, critical usage | trusted networks, quick demos |
Full hook walkthrough → docs/HOOKS.md. Agent → agent/README.md.
| Method | Path | Notes |
|---|---|---|
GET |
/health |
Liveness probe |
GET |
/api/usage/totals |
Single-snapshot JSON — drives the hero counter |
GET |
/api/usage/stream |
SSE stream; pushes a totals event whenever new tokens land |
GET |
/api/usage/breakdown |
JSON view of the per-label breakdown the metrics endpoint exposes |
GET |
/metrics |
Prometheus exposition; labels: user, machine, model, model_family, kind |
| Method | Path | Required scope |
|---|---|---|
POST |
/api/ingest/messages |
ingest |
GET |
/api/admin/tokens |
admin |
POST |
/api/admin/tokens |
admin |
DELETE |
/api/admin/tokens/:id |
admin |
GET |
/api/admin/audit |
admin |
admin scope implicitly satisfies any other scope.
Every knob is an environment variable; flags are on the agent side only. See .env.example for the full list. Highlights:
| Var | Default | Purpose |
|---|---|---|
MODE |
auto-detected | solo or team |
CLAUDE_PROJECTS_DIR |
~/.claude/projects |
Where to watch (solo) or the multi-tenant root (team) |
STATS_DB_PATH |
<repo>/data/stats.db |
SQLite location |
PORT |
10103 |
HTTP port |
ALLOWED_ORIGINS |
http://localhost:3000 |
CORS allowlist |
WATCH_POLLING |
true |
Force chokidar polling — false on native Linux/macOS for fsevents |
SCAN_INTERVAL_MS |
5000 |
Backup readdir walk |
The daemon auto-detects from CLAUDE_PROJECTS_DIR's top-level layout:
- Subdirectories starting with
-(Claude Code's-encoded-cwd/pattern) → solo - Subdirectories that look like usernames (no leading dash) → team
MODE=solo|team overrides the heuristic.
Costs are estimated against Anthropic's public per-token rates. All values flow from one rate table in src/pricing.ts:
| Family | input | cache_creation | cache_read | output |
|---|---|---|---|---|
| Opus 4.x | $15 / Mtok | $18.75 | $1.50 | $75 |
| Sonnet 4.x | $3 | $3.75 | $0.30 | $15 |
| Haiku 4.5 | $0.80 | $1.00 | $0.08 | $4 |
The input-equivalent weight (used for wigtn_tokens_weighted_total) follows the input-relative ratios — 1 : 1.25 : 0.1 : 5, identical across families.
These are estimates, not your bill. Max-plan flat-rate users won't pay the USD numbers shown here; the metric is for relative comparison and waste-spotting, not accounting.
- The daemon stores counts, model identifiers, and message IDs. Message bodies are never persisted —
parseLine()doesn't even read them. - The agent ships only what's already in the request payload schema (
agent/src/parser.ts); same guarantee. /api/admin/auditrecords every ingest call with token id, IP, byte count — stored in the same SQLite file, no remote logs.
- HTTPS strongly recommended for any non-loopback deployment. The daemon itself binds plain HTTP — terminate TLS at nginx, Caddy, or Cloudflare.
- Bearer tokens are 256-bit random; only the SHA-256 hash is on disk.
ingest-scope tokens can only forge data under their own user label (the user is taken from the token, never from the payload).- Per-token rate limit defaults to ~100 req/min sustained; raise it for noisy CI.
npm install
npm run dev # hot-reload via tsx watch
npm run typecheck # tsc --noEmitSingle-file modules; deliberately no build step. The agent is a separate package under agent/ with its own package.json.
MIT.