@cliftonc/finius
v0.1.15
Published
Local-first Claude Code usage & cost tracker — OTLP + transcript ingest with a live dashboard.
Downloads
2,285
Maintainers
Readme
Finius
Local-first usage & cost tracker for Claude Code, Codex, and GitHub Copilot.
A Hono server ingests OTLP HTTP/JSON metrics & logs (and JSONL/rollout
transcripts from Claude Code, Codex, and GitHub Copilot — both the copilot CLI and VS Code Copilot
Chat) into SQLite — or PostgreSQL for a team server; a React + Vite dashboard renders cost, token, session, person, and model
breakdowns with live SSE updates. Everything runs on your machine — no data leaves your laptop
(unless you choose to deploy it on a server for your team!).
How Finius compares to ccusage, ccflare, the Grafana/OpenObserve OTEL route, and other Claude Code / Codex / Copilot usage trackers.
Quick start (local)
The fastest way to get running locally is the finius CLI. You need Node 22.5+ (Finius uses the
built-in node:sqlite module; developed on Node 24).
npx @cliftonc/finius # first run: installs finius globally, then walks you through setup
finius serve # start the server + dashboard at http://localhost:8787
finius import all # optional: import old Claude Code + Codex + Copilot sessionsThen launch Claude Code, Codex, or GitHub Copilot in a new terminal and start coding — the dashboard at http://localhost:8787 updates live as telemetry arrives.
That's it. Three things just happened:
npx @cliftonc/finiusinstalledfiniusglobally and ranfinius setup, which saved~/.finius/config.jsonand — with your consent — edited~/.claude/settings.jsonto add the OTLP env vars plus aSessionEnd+PreCompacthook (finius hook) that uploads each session transcript. If Codex is installed, setup likewise offers to add itsStophook and OTEL logging to~/.codex/config.toml. If GitHub Copilot is installed, setup offers to enable VS Code Copilot Chat's OpenTelemetry exporter (in VS Code'ssettings.json) and to add thecopilotCLI's OTLP env vars to your shell profile. Copilot reports usage live over OTLP, not via a session-end hook — unlike Claude Code/Codex there's no transcript-upload hook (Copilot exposes noSessionEndequivalent), so token/cost arrive from the live OTLP stream and chat transcripts are picked up only when you runfinius import copilot.finius servestarted a single process exposing the API and the dashboard on one port. Its data lives under~/.finius(override withFINIUS_DB_PATH/FINIUS_BLOB_DIR).- Any Claude Code session you run now reports usage to that local server. If you ran
finius import all, Finius also backfilled historical Claude Code, Codex, and VS Code Copilot Chat transcripts already on disk. Usefinius import claude,finius import codex, orfinius import copilotto import only one agent.
Re-run finius setup any time to reconfigure, or finius doctor to diagnose telemetry that isn't
arriving.
Going further?
setup/quick-start.mdis a step-by-step guide covering the local (no-auth) flow above in more detail, team deployment on a shared server (real domain + TLS termination, choosing auth), and setting up GitHub OAuth login gated by org membership.
Running from source (development)
Prefer to hack on Finius itself? Clone the repo and run the dev servers.
1. Install & start
npm install
npm run devnpm run dev runs the API and UI together (via concurrently):
- UI → http://localhost:5173 (Vite dev server; proxies
/api,/otlp,/eventsto the API) - API → http://localhost:8787
Run them separately if you prefer: npm run dev:server (API only) or npm run dev:client (UI only).
2. Point Claude Code at the server
In the shell where you launch Claude Code, use the bundled helper — it checks the server is up,
exports the OTLP env vars, then runs claude:
./scripts/run-claude.sh # forwards any extra args to `claude`Or export the variables manually:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/json
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:8787/otlp/v1/metrics
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:8787/otlp/v1/logs
claudeRun a Claude Code session and the dashboard updates live (SSE) as telemetry arrives.
3. (Optional) production build
npm run build # tsc -> dist + vite build -> dist/client
npm start # node dist/server/index.js, serves the built UI from dist/client on :8787When a build exists, npm start serves the UI and API from the single port http://localhost:8787.
Using the dashboard
- Home — KPIs (cost, tokens, cache, lines, edits, sessions, people), tokens/cost/lines/edits charts over time, plus Models / Users / Sources breakdowns.
- Sessions, People, Models — lists ranked by recency / cost. Click any row (or any Home breakdown row) to drill into the Home view filtered to that session, person, or model.
- Filters — time range, source, user, and model selects apply everywhere. All view and filter state lives in the URL query string, so any view is shareable/bookmarkable and back/forward works.
Importing transcripts
Besides live OTLP telemetry, you can backfill from JSONL transcripts:
POST /api/import/jsonl— body{ content, source?, sessionId? }(or raw JSONL text).POST /api/import/claude-hook— body{ transcript_path, session_id?, cwd? }; reads a local Claude Code transcript file (restricted to~/.claude/projectsor the givencwd).
Imports are idempotent — re-sending the same file (matched by content hash) is detected and skipped.
The original transcript is stored as a file and can be viewed from the session drill-down
(GET /api/sessions/:id/transcript).
Storage
Data is stored in data/finius.sqlite by default. Override with FINIUS_DB_PATH=/path/to/db.sqlite.
Override the API port with PORT. Imported transcript files live under <db-dir>/transcripts
(override with FINIUS_BLOB_DIR).
PostgreSQL (server mode)
For a self-hosted / team server you can back finius serve with PostgreSQL instead of the local
SQLite file. Choose it during finius setup and you get two paths:
- Spin up a local Postgres in Docker — if setup detects a running Docker daemon, it offers to
provision a managed container for you: a persistent
finius-postgres(postgres:16-alpine) with a named volume and--restart unless-stopped, a generated password, and the resulting connection URL saved to~/.finius/config.json. Nothing else to configure —finius servejust uses it. (The host port defaults to 55432 so it won't collide with a system Postgres on 5432.) - Connect to an existing Postgres — point setup at any server by URL (the only option when Docker isn't available).
Either way the URL is persisted to config; you can also bypass setup entirely with
FINIUS_DATABASE_URL=postgres://user:pass@host:5432/finius. Postgres uses the pg driver, which is an
optional peer dependency — install it alongside finius (npm i -g pg) since it isn't bundled.
Migrations run automatically at startup; finius doctor reports Postgres reachability. Switching
backends starts from an empty database (there's no cross-backend data migration). SQLite remains the
zero-config default for local use.
To try Postgres against the test suite (requires Docker): npm run pg:up starts a throwaway
instance, npm run test:pg runs the suite against it, and npm run pg:down tears it down.
Dashboard reads are served from a pre-aggregated hourly metric_rollup; raw metric_points keep
the full-resolution data for the live view and drill-downs.
Raw batch retention
The full OTLP payload of each ingest batch is kept in raw_batches only to allow replaying history
into new metric classifications. Control it with:
FINIUS_RAW_PAYLOADS=retain(default) |off—offkeeps only the dedup hash, not the payload.FINIUS_RAW_RETENTION_DAYS=7(default) — age cutoff used by the prune endpoint below.FINIUS_CRON_TOKEN=<secret>— enablesPOST /api/maintenance/prune-raw-batches. Without it the endpoint is disabled (returns 503). Wire a cron to it:curl -fsS -X POST -H "Authorization: Bearer $FINIUS_CRON_TOKEN" \ http://127.0.0.1:8787/api/maintenance/prune-raw-batches
Other commands
npm test # vitest run
npm run typecheck # tsc --noEmit (strict)