Skip to content
Back to skills

Tokenflow

ASecurity

Install, configure, extend or debug the Tokenflow — a local-first analytics platform for AI token usage across Claude Code, Codex, Cline, Cursor, gateways and generic exports. Use when the user wants to see their own AI token usage, connect a new usage source, add a provider adapter, import a usage export, configure pricing, or when usage numbers look wrong.

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
ai-agentspythongoshellbashsqlnodegitapi

Works with

  • claude code
  • claude desktop
  • cursor
  • terminal
  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 8 files and shows the line behind each finding

Scanned September 19, 2026

npx -y skills add vimoxshah/tokenflow --skill tokenflow --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tokenflow?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tokenflow
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/vimoxshah-tokenflow/badge)](https://www.skillsdirectory.com/skills/vimoxshah-tokenflow)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tokenflow
description: Install, configure, extend or debug the Tokenflow — a local-first analytics platform for AI token usage across Claude Code, Codex, Cline, Cursor, gateways and generic exports. Use when the user wants to see their own AI token usage, connect a new usage source, add a provider adapter, import a usage export, configure pricing, or when usage numbers look wrong.
---

# Tokenflow

A local-first, provider-agnostic analytics platform for AI token usage. Zero dependencies, Node
22.5+. Your job is to get it running against whatever AI tools this machine actually has, and to
be honest about what the data can and cannot support.

```
Clone → Configure → Connect sources → Refresh → Dashboard
```

---

## Read this first: the six invariants

Every mistake worth making here is a violation of one of these. They are stated with the failure
each one prevents, because you will meet cases the rules don't enumerate.

**1. Missing is not zero.** A token field is `number | null`. `null` = "this source does not
report it"; `0` = "it reported zero". Never write `|| 0` on a token field (the linter fails the
build). *Prevents:* Cline logs sessions with no token counts at all — zero-filling them would drag
every average toward zero and invent thousands of free requests.

**2. Cache tokens are not input tokens.** Four mutually exclusive billable buckets sum to the
total: fresh `input`, `cache_read`, `cache_write`, `output`. `cache_refresh` ⊆ `cache_write` and
`reasoning` ⊆ `output` are *subsets* and are never added again. *Prevents:* double counting every
cached prompt token — which on cache-heavy agent usage is >90% of all traffic.

**3. Vendors disagree about `input_tokens`.** Anthropic reports it *excluding* cache; OpenAI
reports it *including* `cached_input_tokens`. An adapter for an OpenAI-convention source must
subtract. *Prevents:* the same double count as (2), arriving through a different door.

**4. Streaming logs re-report the same usage.** A Claude Code transcript writes one assistant
message several times with a growing `output_tokens`; a Codex rollout re-reports a turn's context
as it grows. Take the **maximum of each monotonic run**, never the sum. *Prevents:* a measured
**45x** inflation — one real day read as 82.8 B tokens instead of 1.8 B, and one 2.5-minute
subagent session claiming 7.5 B tokens across 49,739 events for 266 actual turns.

**5. A gateway is not a vendor, and its log is not extra usage.** A proxy (`model_provider:
"headroom"`) is recorded as `gateway`; the vendor comes from the model name. Proxy records are
`measurement: overlay` and excluded from token totals. *Prevents:* counting the same request
twice — once from the client, once from the proxy — while still keeping the one thing only the
gateway knows: a *measured* dollar cost.

**6. Interface is never inferred from the model.** It comes only from an explicit surface field
(`entrypoint`, `originator`, `source`, an IDE marker). No signal → `Unknown`. *Prevents:*
"it's a Claude model so it must be Claude Desktop".

**7. A price needs a source, and a tier is a multiplier.** Every built-in rate names where it was
fetched from and when. Service tier is a *price multiplier* applied per request — OpenAI's Fast
mode (renamed from "priority" on 2026-07-30) is **4x** standard, Anthropic's Batch is 0.5x — and
the long-TTL cache write tier is billed above the 5-minute one. *Prevents:* under-reporting a
Fast-mode-heavy workload by up to 4x, and quoting a confident total built on rates nobody can
check. Long-context premium tiers are knowingly **not** applied, so the estimate is a documented
under-count rather than an unexplained one.

And one framing rule: **token usage is not productivity.** The Productivity section reports
activity *proxies*, and correlations with an independent work signal, with `n` stated. Never
present a token count as an achievement.

---

## Task: install and configure on this machine

### 1. Check the runtime

```bash
node -v          # must be >= 22.5 (node:sqlite, node:test)
```

There are no dependencies to install. `npm install` is a no-op.

### 2. Find out what this machine actually has

Don't guess — probe. See `providers/detection-matrix.md` for locations per tool and OS. The
high-value checks:

```bash
ls -d ~/.claude ~/.claude-* ~/.config/claude 2>/dev/null   # Claude Code (several homes are common)
ls -d ~/.codex/sessions 2>/dev/null                        # Codex CLI/IDE/Desktop
ls -d ~/.cline/data/sessions 2>/dev/null                   # Cline
ls ~/.cursor/ai-tracking/ai-code-tracking.db 2>/dev/null   # Cursor activity
ls ~/.headroom/savings_events.jsonl 2>/dev/null            # local gateway
```

Then let the tool confirm it:

```bash
node bin/tokenflow.js setup
node bin/tokenflow.js providers
```

Two things to notice, and to report to the user:

- **A user may have several Claude Code homes** (`~/.claude-work`, `~/.claude-personal`). The
  adapter auto-discovers any `~/.claude*` containing `projects/`. If they keep one elsewhere, add
  it to `sources.anthropic.paths`.
- **A tool being absent is a normal outcome**, not an error. Say which were found and which
  weren't, and why.

### 3. Handle non-standard locations

```yaml
# ~/.tokenflow/config.yaml
providers: [anthropic, openai, cline, cursor, headroom]
sources:
  anthropic:
    paths: ["/custom/claude-home", "~/.claude-work"]
  openai:
    paths: ["/custom/codex-home"]
  cursor:
    db: "~/.cursor/ai-tracking/ai-code-tracking.db"
timezone: "Asia/Kolkata"        # dates and hours are resolved in this zone at ingest
ui:
  defaultRange: all
  defaultFrom: "2026-03-14"     # optional floor for the default view
```

A full worked example is in `examples/config.yaml`. `config.json` is also accepted.

### 4. Ingest — in chunks if your shell is short-lived

```bash
node bin/tokenflow.js refresh --budget 30      # stops cleanly, saves state
node bin/tokenflow.js refresh --budget 30      # continues at the byte offset
```

Repeat until the report no longer says "time budget reached". This is the right pattern in a
sandbox, a CI step, or any 45-second shell: a multi-gigabyte first ingest completes across several
calls with no partial writes. A first pass on ~5,000 session files typically reads several GB in
under a minute; the second refresh is ~2 seconds because unchanged files are skipped without
being read.

### 5. Verify before you report

```bash
npm run validate      # runtime, adapters, and cube↔records agreement
node bin/tokenflow.js status
```

`validate` is the one that matters: it re-sums the stored facts and compares them with the
aggregates. If they've drifted it tells you to run `tokenflow compact`.

**Then sanity-check the shape of the result.** If one day is 10–100x every other day, treat it as
suspect and investigate before presenting it (see *Task: numbers look wrong*). Reporting a
plausible-looking wrong number is worse than reporting that you're unsure.

### 6. Serve it

```bash
node bin/tokenflow.js up                 # refresh + rebuild the offline file + serve (one command)
node bin/tokenflow.js dashboard          # http://127.0.0.1:7799, loopback only
node bin/tokenflow.js export --html      # one self-contained offline file
```

Use `--html` when the user can't reach a localhost port (remote shell, container, sandbox) — it
inlines the CSS, the analytics and chart code, and the data, and opens from `file://`.

Tell the user how to come back to it, because this is the question they always ask next:

- **`npm start`** (or double-click `Refresh & Open Dashboard.command` on macOS) refreshes,
  rebuilds `tokenflow-dashboard.html`, and opens the live dashboard.
- The offline file cannot re-read logs — it is a file. It reports its own age and probes loopback
  for a live dashboard; if one is running it offers to hand over and refresh there.
- For hands-off freshness, schedule `tokenflow up --no-serve` (launchd/systemd/cron — the plist
  and cron line are in `docs/cli.md`).

If the user wants a particular look, set it in config rather than editing CSS:

```yaml
ui:
  skin: aurora    # aurora | terminal | editorial
  mode: dark      # dark | light
```

Never hand-edit the series colours to match a skin. They belong to the mode, they were validated
for colour-blind separation against every skin's surface, and a "nicer" hue that fails that gate
makes two adjacent stack segments indistinguishable for ~8% of male readers.

---

## Task: connect a source with no adapter

Two routes. Prefer **import** if the tool can export; write an **adapter** if it writes a log
continuously.

### Route A — generic import

```bash
node bin/tokenflow.js import ~/Downloads/usage.csv --dry-run
```

With no `--field` flags it prints the columns, infers a mapping, and previews five normalized
rows. Check the preview: `n/a` means that field is unmapped and will be stored as not-available,
which is correct if the source truly lacks it and wrong if you just didn't map it.

```bash
node bin/tokenflow.js import ~/Downloads/usage.csv \
  --field timestamp=created_at \
  --field model=model \
  --field input_tokens=prompt_tokens \
  --field output_tokens=completion_tokens \
  --field cache_read_tokens=cached_tokens \
  --field estimated_cost=cost_usd \
  --default client=openrouter --default interface=API
```

The mapping is saved and reused on every later refresh. Supports CSV/TSV/JSON/JSONL/SQLite; see
`examples/generic-mapping.json`.

**Before mapping, determine whether the source's input column includes cached tokens.** If it
does, you cannot fix that with a mapping — write an adapter (Route B) so the subtraction happens.

### Route B — write an adapter

Start from `providers/adapter-template.js`. Then, in order:

1. **Read a real sample first.** `head -3 <logfile> | python3 -m json.tool`. Identify: the
   timestamp, the model, every token field, the session id, and any *surface* field.
2. **Work out the token convention.** Does the input field include cached tokens? Is there a
   separate cache-write? Is `reasoning` inside `output`? Write what you concluded in the adapter's
   header comment — that header is the most valuable part of an adapter.
3. **Check for re-reported usage.** Group the events of one logical call and look at the series.
   Monotonically growing → snapshots, take the max. Genuinely disjoint → sum. Getting this
   backwards is invariant 4, and it is the expensive mistake.
4. **Always resume from `ref.start` and return `offset`.** Otherwise every refresh re-emits the
   whole file.
5. **Emit `interfaceSignals: [...]`**, never a guessed `interface`.

Install to `$TOKENFLOW_HOME/providers/<id>.js` (loaded automatically, shadows a built-in with the
same id) or to `src/providers/<id>/index.js` with a fixture and tests.

**Write these two tests.** The first proves the semantics; the second catches the expensive bug:

```js
test('token semantics', async () => {
  const { records } = await ingestFixtureAsync(mine, 'mine.jsonl');
  assert.equal(records[0].input_tokens, 6000);      // fresh, excludes cache
  assert.equal(records[0].cache_write_tokens, null); // unreported stays null
  for (const r of records) assert.ok(validateUsage(r).ok);
});

test('re-reading the same bytes emits nothing new', async () => {
  const first = await ingestFixtureAsync(mine, 'mine.jsonl');
  const again = await ingestFixtureAsync(mine, 'mine.jsonl', {
    start: first.result.offset, state: first.state,
  });
  assert.equal(again.records.length, 0);
});
```

`schemas/normalized-record.json` is the authoritative field list.

---

## Task: numbers look wrong

Work down this list; the causes are ordered by how often they're the answer.

### Too high

1. **Re-reported usage in an adapter** (invariant 4). Symptom: one day or session dwarfs the rest
   by 10–100x. Check `metadata.token_count_events` / `metadata.usage_segments` in the Data
   Explorer — a record reconstructed from hundreds of events is suspect if your adapter summed
   them. Reproduce from the raw source with a throwaway script that computes both the naive sum
   and the sum-of-run-maxima; the gap is the inflation.
2. **Overlay double counting** (invariant 5). Is "Include gateway overlay" on? It adds proxy
   records to client records for the same traffic. Off by default.
3. **Cache read counted as input** (invariants 2, 3). `input + cache_read` should be the prompt
   side with no overlap.

**Cross-check against something independent** — a gateway log, a billing page, a second tool.
Two sources agreeing is worth far more than one source being plausible. When the Codex
reconstruction in this repo was validated, the corrected figure (1.80 B input for a day) matched a
gateway's own billing log (~2.1 B lifetime) while the naive reading (82.8 B) did not.

### Too low / missing

1. **The date filter.** `ui.defaultFrom` sets a floor for the *default view*; the data is still
   in the store and in `--all` exports.
2. **"Active days" means token-active days.** Days where only a no-token source (Cline, Cursor,
   git) was active are counted separately as "activity-only".
3. **Per-adapter coverage.** Data health → Sources lists each adapter's real window. A source that
   started logging in July does not cover March.
4. **The source pruned its own logs.** Claude Code defaults to ~30 days. Nothing can recover data
   the source deleted. Say so plainly.

### Cost is blank

By design — no rate, no number, never a `$0`. `tokenflow pricing` lists models by volume with
their pricing status. Add rates in USD per million tokens:

```bash
node bin/tokenflow.js pricing --sources                              # what is already known, and from where
node bin/tokenflow.js pricing --set "some-model=5,25,0.5,6.25"       # in,out,cacheRead,cacheWrite
node bin/tokenflow.js refresh --full
```

Check `--sources` first: the built-in table already covers the current Anthropic, OpenAI,
DeepSeek, Z.ai and Google line-ups from their own published pages. If a model is genuinely
missing, fetch the vendor's own pricing page — not an aggregator — and add the rate. If you cannot
source it, leave it unpriced and tell the user which models are missing: a made-up rate silently
corrupts every cost figure on the page, and unlike a blank it cannot be spotted.

Do **not** bake a service-tier multiplier into a rate. The tier is recorded per request and the
multiplier is applied automatically.

### Aggregates disagree with the records

```bash
npm run validate
node bin/tokenflow.js compact     # rewrites shards, rebuilds aggregates; always safe
```

---

## Task: explain a number

Be precise about the denominator, and about what the number does not claim.

| Metric | Formula | What it does NOT mean |
|---|---|---|
| Output / input | `out / in` (in = **fresh** input) | not "efficiency" — with a cache-heavy agent `in` is tiny, so this is large and near-meaningless alone |
| Output / prompt sent | `out / (in + cr + cw)` | the honest prompt-heavy vs output-heavy measure |
| Cache hit rate | `cr / (in + cr)` | not cache *savings* — that needs prices |
| Cache / total | `(cr + cw) / total` | share of token activity, not of cost |
| Avg / active day | total ÷ days with **measured tokens** | not ÷ calendar days, and not ÷ activity-only days |
| Peak day | argmax over the filtered slice | may be an artefact — check it against the raw source before celebrating it |
| Estimated cost | Σ tokens × configured rate | an estimate from a price table, covering only priced requests; the coverage % is always shown |
| Measured cost | a gateway's own `cost_usd` | covers only gateway-routed traffic, so it is not comparable to a total |
| Correlation r | Pearson over overlapping days | a correlation, never causation, and never a productivity claim |

If a metric is unavailable, explain *why* rather than showing zero. The dashboard is built to do
this; match its tone.

---

## Command reference

```bash
node bin/tokenflow.js setup                       # detect and write config
node bin/tokenflow.js providers                   # what's connected, and why not
node bin/tokenflow.js provider add|remove <id>
node bin/tokenflow.js refresh [--full] [--provider x] [--budget 30] [--strict]
node bin/tokenflow.js status [--json]
node bin/tokenflow.js dashboard [--port n] [--no-open]
node bin/tokenflow.js up [--budget 60] [--no-serve] [--no-snapshot]
node bin/tokenflow.js restore <full-export.csv> --yes    # rebuild a store from an export
node bin/tokenflow.js export --csv [--all] | --html
node bin/tokenflow.js pricing [--set "model=in,out[,cr[,cw]]"]
node bin/tokenflow.js import <file> [--field a=b] [--dry-run]
node bin/tokenflow.js config show|path|export|import
node bin/tokenflow.js doctor | validate | compact | reset --yes
node bin/tokenflow.js demo
npm test && npm run lint && npm run validate
```

`--json` on `refresh`, `status` and `providers` for scripting.

---

## Working style for this project

- **Verify, don't assert.** Run `npm run validate` and read the shape of the data before
  reporting. If a number surprises you, it is more likely a bug than a discovery.
- **Say what you couldn't determine.** "Cline reports no token counts, so its 22 sessions appear
  in activity metrics only" is a better answer than a total that quietly includes zeros.
- **Never invent a rate, a vendor, or an interface.** Unknown is a valid, supported value
  throughout the schema, and the UI is designed to display it.
- **Prefer fixing the adapter over patching the analytics.** If a number is wrong, it is almost
  always wrong at ingest. The analytics layer is unit-tested against its formulas.
- **Nothing leaves the machine.** There is no HTTP client in this codebase. Don't add one, and
  don't paste session contents anywhere.

## Reference files

| File | Use |
|---|---|
| `providers/detection-matrix.md` | where each tool stores usage, per OS |
| `providers/adapter-template.js` | commented starting point for a new adapter |
| `schemas/normalized-record.json` | authoritative field list + missing-value contract |
| `schemas/config.schema.json` | every config option |
| `examples/config.yaml` | a fully worked multi-source configuration |
| `examples/generic-mapping.json` | a saved import mapping |
| `examples/session-transcript.md` | the awkward real-world log shapes, annotated |

Project docs: `docs/data-model.md`, `docs/providers.md`, `docs/creating-provider.md`,
`docs/architecture.md`, `docs/troubleshooting.md`.

Files in this skill

  • SKILL.md18.1 KB
  • examples/config.yaml2.7 KB
  • examples/generic-mapping.json1022 B
  • examples/session-transcript.md8.2 KB
  • providers/adapter-template.js5.2 KB
  • providers/detection-matrix.md5.9 KB
  • schemas/config.schema.json4.7 KB
  • schemas/normalized-record.json4.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.