toknmtr — self-hostable Claude Code usage & analytics dashboard
Server (SvelteKit + SQLite Docker container) + per-machine agent that parses Claude Code JSONL transcripts. docker-compose one-command deploy; raw transcript view + search gated behind SHOW_TRANSCRIPTS (hidden by default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
71a60ab054
74 changed files with 15613 additions and 0 deletions
101
README.md
Normal file
101
README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# toknmtr
|
||||
|
||||
Self-hosted **Claude Code usage & analytics dashboard**. An **agent** on each of your
|
||||
machines parses Claude Code's JSONL transcripts into a full event log and pushes it to a
|
||||
**server** (one SvelteKit + SQLite container). The server stores everything — usage, tool
|
||||
calls, commands, raw prompts/responses — and serves a dashboard plus a full-text-searchable
|
||||
session archive.
|
||||
|
||||
It's two halves:
|
||||
|
||||
- **Server** (`src/`, shipped as a Docker image) — the dashboard, the ingest API, the DB.
|
||||
- **Agent** (`agent/` + `ops/`, runs on each machine) — parses `~/.claude/projects/**/*.jsonl`
|
||||
and POSTs to the server. Not part of the image; you run it wherever you use Claude Code.
|
||||
|
||||
The server image contains **no data** — the SQLite DB is created empty in a mounted volume on
|
||||
first run. All conversation content only ever comes from *your* agent pushing *your*
|
||||
transcripts to *your* server.
|
||||
|
||||
> **New here? Follow [DEPLOY.md](DEPLOY.md)** — a full step-by-step walkthrough (prerequisites,
|
||||
> server, agent, security, updating). The sections below are the quick reference.
|
||||
|
||||
---
|
||||
|
||||
## 1. Run the server (Docker)
|
||||
|
||||
Requires Docker with the Compose plugin. From the repo root:
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
# edit .env: set API_TOKEN to a long random secret
|
||||
# openssl rand -hex 32
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
The dashboard is now at **http://localhost:3001**. The DB lives in the `toknmtr-data`
|
||||
Docker volume; the container restarts unless stopped.
|
||||
|
||||
**Privacy note — the dashboard has no auth.** By default the two surfaces that expose
|
||||
verbatim prompt/response text (the per-session **transcript view** and full-text **search**)
|
||||
are **hidden**; charts, KPIs, and session metadata stay visible. Only set
|
||||
`SHOW_TRANSCRIPTS=true` in `.env` once you've put the dashboard behind auth (reverse proxy,
|
||||
VPN, etc.). Don't expose it to the public internet as-is.
|
||||
|
||||
### Config (`.env`)
|
||||
|
||||
| Var | Purpose |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| `API_TOKEN` | **Required.** Bearer token the agent must send to `/api/ingest`. |
|
||||
| `SHOW_TRANSCRIPTS` | `true` reveals transcript view + search. Unset = hidden (safe). |
|
||||
| `PORT` | Host port is set in `docker-compose.yml` (`3001:3000`). |
|
||||
|
||||
---
|
||||
|
||||
## 2. Feeding it data (the agent)
|
||||
|
||||
The server starts empty. To populate it, run the agent on each machine where you use Claude
|
||||
Code (needs Node 24+ for `--experimental-strip-types`). One-time setup per machine:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.toknmtr
|
||||
cat > ~/.toknmtr/env <<'EOF'
|
||||
TOKNMTR_URL=http://<server-host>:3001
|
||||
TOKNMTR_TOKEN=<the same API_TOKEN you set on the server>
|
||||
EOF
|
||||
chmod 600 ~/.toknmtr/env
|
||||
```
|
||||
|
||||
Then, from a clone of this repo on that machine:
|
||||
|
||||
```sh
|
||||
# one-time: ingest all transcripts already on disk
|
||||
node --experimental-strip-types agent/run.ts --backfill
|
||||
|
||||
# ongoing capture — register the Stop hook so every turn pushes incrementally
|
||||
ops/install-hook.sh
|
||||
```
|
||||
|
||||
`ops/install-hook.sh` adds a near-zero-cost Claude Code `Stop` hook (additive + reversible
|
||||
with `--remove`). An optional `ops/install-cron.sh` adds a reconcile sweep as a safety net.
|
||||
Full agent/capture docs — the `~/.toknmtr/env` format, the hook-vs-cron tradeoff, backfill,
|
||||
and troubleshooting — are in **[`ops/README.md`](ops/README.md)**.
|
||||
|
||||
Ingest is an idempotent upsert (keyed on `host + session_id + uuid`), so re-running the
|
||||
backfill or overlapping hook/cron sweeps never duplicates data.
|
||||
|
||||
---
|
||||
|
||||
## 3. Development
|
||||
|
||||
Node 24, SvelteKit 2 (Svelte 5), TypeScript, `better-sqlite3`.
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run dev # dev server
|
||||
npm run check # typecheck
|
||||
npm run build # production build → build/ (run with `node build`)
|
||||
npm run lint # prettier + eslint
|
||||
```
|
||||
|
||||
Pricing lives server-side in `src/lib/server/pricing.ts` — adding a model is a one-line
|
||||
update. Because the subscription is flat-rate, all `$` figures are *notional* (API-equivalent).
|
||||
Loading…
Add table
Add a link
Reference in a new issue