@vimoxshah/tokenflow 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CONTRIBUTING.md +84 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/Refresh & Open Dashboard.command +22 -0
  5. package/SECURITY.md +42 -0
  6. package/bin/tokenflow.js +1342 -0
  7. package/docs/architecture.md +193 -0
  8. package/docs/cli.md +390 -0
  9. package/docs/configuration.md +281 -0
  10. package/docs/creating-provider.md +262 -0
  11. package/docs/data-model.md +213 -0
  12. package/docs/getting-started.md +266 -0
  13. package/docs/live-mode.md +199 -0
  14. package/docs/media/architecture-hero.svg +86 -0
  15. package/docs/media/cost-editorial-dark.png +0 -0
  16. package/docs/media/health-terminal-light.png +0 -0
  17. package/docs/media/menubar-dark.png +0 -0
  18. package/docs/media/menubar-light.png +0 -0
  19. package/docs/media/models-terminal-dark.png +0 -0
  20. package/docs/media/overview-aurora-dark.png +0 -0
  21. package/docs/media/time-aurora-light.png +0 -0
  22. package/docs/providers.md +309 -0
  23. package/docs/skill.md +64 -0
  24. package/docs/troubleshooting.md +207 -0
  25. package/examples/config.example.yaml +92 -0
  26. package/examples/demo-data/README.md +38 -0
  27. package/examples/demo-data/sample-usage.csv +11 -0
  28. package/package.json +74 -0
  29. package/scripts/build-dmg.sh +33 -0
  30. package/scripts/build-menubar-app.sh +67 -0
  31. package/scripts/lint.js +111 -0
  32. package/scripts/validate-install.js +140 -0
  33. package/skills/tokenflow/SKILL.md +392 -0
  34. package/skills/tokenflow/examples/config.yaml +92 -0
  35. package/skills/tokenflow/examples/generic-mapping.json +26 -0
  36. package/skills/tokenflow/examples/session-transcript.md +191 -0
  37. package/skills/tokenflow/providers/adapter-template.js +135 -0
  38. package/skills/tokenflow/providers/detection-matrix.md +142 -0
  39. package/skills/tokenflow/schemas/config.schema.json +107 -0
  40. package/skills/tokenflow/schemas/normalized-record.json +63 -0
  41. package/src/analytics/aggregate.js +247 -0
  42. package/src/analytics/anomalies.js +222 -0
  43. package/src/analytics/capacity.js +278 -0
  44. package/src/analytics/comparison.js +96 -0
  45. package/src/analytics/dimensions.js +230 -0
  46. package/src/analytics/efficiency.js +138 -0
  47. package/src/analytics/forecast.js +202 -0
  48. package/src/analytics/index.js +327 -0
  49. package/src/analytics/insights.js +283 -0
  50. package/src/analytics/milestones.js +91 -0
  51. package/src/analytics/peak.js +106 -0
  52. package/src/analytics/productivity.js +166 -0
  53. package/src/analytics/token-usage.js +267 -0
  54. package/src/commands/diagnostics.js +88 -0
  55. package/src/commands/digest.js +155 -0
  56. package/src/commands/models-compare.js +96 -0
  57. package/src/core/budget.js +142 -0
  58. package/src/core/bundle.js +191 -0
  59. package/src/core/config.js +202 -0
  60. package/src/core/delivery.js +109 -0
  61. package/src/core/geo.js +99 -0
  62. package/src/core/ingest.js +457 -0
  63. package/src/core/interface-map.js +55 -0
  64. package/src/core/jsonl.js +124 -0
  65. package/src/core/live-status.js +417 -0
  66. package/src/core/model-map.js +157 -0
  67. package/src/core/notify.js +83 -0
  68. package/src/core/pricing.js +288 -0
  69. package/src/core/prompt-analytics.js +127 -0
  70. package/src/core/registry.js +107 -0
  71. package/src/core/restore.js +261 -0
  72. package/src/core/schedule.js +120 -0
  73. package/src/core/schema.js +316 -0
  74. package/src/core/sqlite.js +96 -0
  75. package/src/core/store.js +493 -0
  76. package/src/core/sync.js +151 -0
  77. package/src/core/units.js +147 -0
  78. package/src/core/validate.js +123 -0
  79. package/src/core/watch.js +287 -0
  80. package/src/core/yaml.js +209 -0
  81. package/src/export/bundler.js +107 -0
  82. package/src/export/csv.js +100 -0
  83. package/src/export/html-snapshot.js +101 -0
  84. package/src/export/menubar.js +158 -0
  85. package/src/index.js +18 -0
  86. package/src/providers/anthropic/index.js +294 -0
  87. package/src/providers/cline/index.js +120 -0
  88. package/src/providers/cursor/index.js +143 -0
  89. package/src/providers/generic/index.js +268 -0
  90. package/src/providers/git/index.js +188 -0
  91. package/src/providers/headroom/index.js +114 -0
  92. package/src/providers/hermes/index.js +299 -0
  93. package/src/providers/mock/index.js +117 -0
  94. package/src/providers/openai/index.js +370 -0
  95. package/src/providers/opencode/index.js +245 -0
  96. package/src/sdk.js +46 -0
  97. package/src/server/server.js +264 -0
  98. package/src/ui/app.js +2473 -0
  99. package/src/ui/charts.js +925 -0
  100. package/src/ui/index.html +42 -0
  101. package/src/ui/styles.css +644 -0
@@ -0,0 +1,86 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 880 300" width="100%" role="img" aria-label="TokenFlow architecture: local AI tool logs flow through adapters into a token store, then to dashboard, CLI, and menu bar — nothing leaves the machine.">
2
+ <defs>
3
+ <linearGradient id="tfh-accent" x1="0" y1="0" x2="1" y2="0">
4
+ <stop offset="0" stop-color="#3987e5"/><stop offset="1" stop-color="#8b5cf6"/>
5
+ </linearGradient>
6
+ <linearGradient id="tfh-card" x1="0" y1="0" x2="0" y2="1">
7
+ <stop offset="0" stop-color="#1c2333"/><stop offset="1" stop-color="#141926"/>
8
+ </linearGradient>
9
+ </defs>
10
+ <style>
11
+ .tfh-title{font:600 13px -apple-system,'Segoe UI',sans-serif;fill:#eef1f7}
12
+ .tfh-sub{font:400 10px -apple-system,'Segoe UI',sans-serif;fill:#98a1b5}
13
+ .tfh-chip{font:500 10px ui-monospace,Menlo,monospace;fill:#c7cede}
14
+ .tfh-num{font:700 20px -apple-system,'Segoe UI',sans-serif;fill:url(#tfh-accent)}
15
+ .tfh-cap{font:400 9.5px -apple-system,'Segoe UI',sans-serif;fill:#98a1b5}
16
+ .card{fill:url(#tfh-card);stroke:#2a3348;rx:12}
17
+ .arrow{stroke:url(#tfh-accent);stroke-width:1.6;fill:none;stroke-dasharray:4 5;opacity:.85}
18
+ </style>
19
+
20
+ <!-- sources -->
21
+ <g>
22
+ <rect class="card" x="16" y="34" width="150" height="232" rx="12"/>
23
+ <text class="tfh-title" x="30" y="56">Your tools' logs</text>
24
+ <text class="tfh-chip" x="30" y="84">~/.claude/*.jsonl</text>
25
+ <text class="tfh-chip" x="30" y="106">~/.codex/sessions</text>
26
+ <text class="tfh-chip" x="30" y="128">opencode.db</text>
27
+ <text class="tfh-chip" x="30" y="150">~/.hermes/state.db</text>
28
+ <text class="tfh-chip" x="30" y="172">~/.cline · ~/.cursor</text>
29
+ <text class="tfh-chip" x="30" y="194">CSV · JSONL · SQLite</text>
30
+ <text class="tfh-cap" x="30" y="230">read-only, incremental,</text>
31
+ <text class="tfh-cap" x="30" y="244">byte-offset resumable</text>
32
+ </g>
33
+
34
+ <!-- engine -->
35
+ <g>
36
+ <rect class="card" x="220" y="34" width="190" height="232" rx="12"/>
37
+ <text class="tfh-title" x="234" y="56">TokenFlow engine</text>
38
+ <text class="tfh-cap" x="234" y="74">zero dependencies · Node 22+</text>
39
+ <text class="tfh-chip" x="234" y="100">adapters → normalize</text>
40
+ <text class="tfh-chip" x="234" y="122">cache ≠ input accounting</text>
41
+ <text class="tfh-chip" x="234" y="144">stream dedup (max-of-run)</text>
42
+ <text class="tfh-chip" x="234" y="166">versioned price table</text>
43
+ <text class="tfh-chip" x="234" y="188">pre-aggregated cube</text>
44
+ <text class="tfh-cap" x="234" y="222">null ≠ 0 · measured ≠ estimated</text>
45
+ <text class="tfh-cap" x="234" y="236">145 tests enforce it</text>
46
+ </g>
47
+
48
+ <!-- surfaces -->
49
+ <g>
50
+ <rect class="card" x="464" y="34" width="180" height="70" rx="12"/>
51
+ <text class="tfh-title" x="478" y="58">Web dashboard</text>
52
+ <text class="tfh-cap" x="478" y="76">12 views · loopback only · themes</text>
53
+
54
+ <rect class="card" x="464" y="118" width="180" height="70" rx="12"/>
55
+ <text class="tfh-title" x="478" y="142">Native menu bar app</text>
56
+ <text class="tfh-cap" x="478" y="160">cost, limits, forecast, source &amp;</text>
57
+ <text class="tfh-cap" x="478" y="173">model breakdown, theme toggle</text>
58
+
59
+ <rect class="card" x="464" y="202" width="180" height="64" rx="12"/>
60
+ <text class="tfh-title" x="478" y="226">CLI + exports</text>
61
+ <text class="tfh-cap" x="478" y="244">refresh · status · watch · CSV/HTML</text>
62
+ </g>
63
+
64
+ <!-- stats column -->
65
+ <g>
66
+ <rect class="card" x="688" y="34" width="176" height="232" rx="12"/>
67
+ <text class="tfh-num" x="702" y="72">15+</text>
68
+ <text class="tfh-cap" x="702" y="88">providers &amp; gateways</text>
69
+ <text class="tfh-num" x="702" y="124">35+</text>
70
+ <text class="tfh-cap" x="702" y="140">models auto-classified</text>
71
+ <text class="tfh-num" x="702" y="176">12</text>
72
+ <text class="tfh-cap" x="702" y="192">analytics views</text>
73
+ <text class="tfh-num" x="702" y="228">0</text>
74
+ <text class="tfh-cap" x="702" y="244">runtime dependencies</text>
75
+ </g>
76
+
77
+ <!-- arrows -->
78
+ <path class="arrow" d="M170 150 H 214"/>
79
+ <path class="arrow" d="M414 120 C 440 110, 442 80, 458 70"/>
80
+ <path class="arrow" d="M414 150 H 458"/>
81
+ <path class="arrow" d="M414 180 C 440 190, 442 220, 458 230"/>
82
+
83
+ <!-- privacy band -->
84
+ <rect x="16" y="278" width="848" height="16" rx="4" fill="#10141d"/>
85
+ <text class="tfh-cap" x="440" y="290" text-anchor="middle">local-first — logs are read in place, normalized on this machine, and never uploaded anywhere</text>
86
+ </svg>
Binary file
Binary file
Binary file
@@ -0,0 +1,309 @@
1
+ # Providers
2
+
3
+ Every adapter answers three questions honestly: **where it looks**, **what it can report**, and
4
+ **what it cannot know**. The third one matters most — a source that doesn't log token counts must
5
+ say so rather than emit zeros.
6
+
7
+ Enable and disable adapters with:
8
+
9
+ ```bash
10
+ tokenflow providers
11
+ tokenflow provider add <id>
12
+ tokenflow provider remove <id>
13
+ ```
14
+
15
+ ---
16
+
17
+ ## `anthropic` — Claude Code / Claude Agent SDK
18
+
19
+ | | |
20
+ |---|---|
21
+ | Reads | `<claude-home>/projects/<slug>/<sessionId>.jsonl` |
22
+ | Homes | `$CLAUDE_CONFIG_DIR`, `~/.claude`, `~/.config/claude`, and any `~/.claude-*` sibling containing `projects/` (people running several accounts keep them side by side) |
23
+ | Measurement | `primary` |
24
+ | Reports | `input`, `output`, `cache_read`, `cache_write`, `cache_refresh` (1h ephemeral), `reasoning` (thinking), session, project (from `cwd`), git branch, interface (from `entrypoint`), sidechain/subagent flag, CLI version, service tier |
25
+ | Cannot know | cost (no rate in the transcript) |
26
+
27
+ ### The two traps it handles
28
+
29
+ **Streaming snapshots.** A single assistant message is written to the transcript several times as
30
+ it streams. Every line carries the same `requestId` + `message.id` with a *growing*
31
+ `output_tokens` and constant prompt-side counts. Measured on a real corpus: 2,493 of 5,060 usage
32
+ rows were re-reports. Summing them inflates output by 3–5x. The adapter groups by
33
+ `(requestId, message.id)` and keeps the **per-field maximum**.
34
+
35
+ **Synthetic messages.** Locally generated assistant entries have `requestId: null` and an
36
+ all-zero usage block — they are not API calls. Counting them would add fake zero-token requests
37
+ and skew "requests" and "tokens per request". They are skipped and counted separately as
38
+ `synthetic`.
39
+
40
+ A group can straddle the end of a live file. Rather than defer it (which would silently drop the
41
+ last record of every settled file, forever), it is emitted at EOF and its totals are remembered;
42
+ if the file grows and the same group reappears with larger counts, only the **delta** is emitted.
43
+
44
+ ### Configuration
45
+
46
+ ```yaml
47
+ sources:
48
+ anthropic:
49
+ paths: ["~/.claude", "~/.claude-work"] # skip auto-discovery
50
+ ```
51
+
52
+ ---
53
+
54
+ ## `openai` — Codex CLI / Codex IDE / Codex Desktop
55
+
56
+ | | |
57
+ |---|---|
58
+ | Reads | `~/.codex/sessions/**/*.jsonl` and `~/.codex/archived_sessions/**` (`$CODEX_HOME` respected) |
59
+ | Measurement | `primary` |
60
+ | Reports | fresh `input`, `cache_read`, `cache_write`, `output`, `reasoning`, session, thread, turn id, project (from `cwd`), interface (from `source` / `originator`), gateway, reasoning effort, service tier, context window, time-to-first-token, subagent role |
61
+ | Cannot know | cost; `cache_write` on older CLI builds that never emitted the field (recorded as `null`, not `0`) |
62
+
63
+ ### Token semantics
64
+
65
+ Codex uses OpenAI's convention where `input_tokens` **includes** `cached_input_tokens`. The
66
+ adapter converts:
67
+
68
+ ```
69
+ input_tokens = input_tokens − cached_input_tokens (fresh only)
70
+ cache_read_tokens = cached_input_tokens
71
+ ```
72
+
73
+ ### One record per turn, and why the obvious reading is wrong
74
+
75
+ Codex emits a `token_count` event repeatedly while a turn runs, and `last_token_usage` is
76
+ **re-reported as the turn's context grows** — 27k, then 29k, then 30k, all describing the same
77
+ growing conversation. `total_token_usage` is a running **sum** of those re-reports, so it is not a
78
+ usable cumulative counter.
79
+
80
+ On a real corpus that error is enormous: one subagent session claimed **7.5 B tokens in
81
+ 2.5 minutes** (49,739 `token_count` events for 266 actual turns), and a single day totalled
82
+ **82.8 B** instead of **1.8 B** — a **45x** inflation.
83
+
84
+ The adapter instead splits each turn's `last_token_usage` series into monotonically
85
+ non-decreasing **runs** (a drop means a context compaction or a new call) and sums each run's
86
+ maximum. This reduces to the identity for simple sessions with one event per turn, and was
87
+ cross-checked against an independent gateway billing log which agreed with the reconstruction
88
+ and not with the naive sum.
89
+
90
+ Each record carries `metadata.token_count_events` and `metadata.usage_segments` so the
91
+ reconstruction is auditable rather than a hidden fudge.
92
+
93
+ ### Gateways
94
+
95
+ `model_provider: "headroom"` (or any non-vendor value) is recorded as `gateway`, not as the
96
+ vendor. The vendor comes from the model name.
97
+
98
+ ---
99
+
100
+ ## `cline` — Cline CLI
101
+
102
+ | | |
103
+ |---|---|
104
+ | Reads | `~/.cline/data/sessions/<id>/<id>.json` (+ `.messages.json` for a message count) |
105
+ | Measurement | `activity` |
106
+ | Reports | session, model (so the vendor is derived — e.g. `deepseek/deepseek-v4-flash` → DeepSeek), provider, duration, status, exit code, project, git branch, team, agent message count |
107
+ | **Cannot know** | **any token count — Cline's session files contain none** |
108
+
109
+ Every token field is `null`. The dashboard counts these sessions in activity metrics and excludes
110
+ them from token totals, rather than adding a session's worth of zeros. `metadata.tokens_reported`
111
+ is `false` so this is visible in the Data Explorer.
112
+
113
+ ---
114
+
115
+ ## `cursor` — AI code activity
116
+
117
+ | | |
118
+ |---|---|
119
+ | Reads | `~/.cursor/ai-tracking/ai-code-tracking.db` (read-only, via `node:sqlite`) |
120
+ | Measurement | `activity` |
121
+ | Reports | AI-authored code hashes (model, file, extension, conversation, attribution source: `composer` / `tab` / `human`), and per-commit AI vs human line attribution (`linesAdded`, `composerLinesAdded`, `humanLinesAdded`, AI percentage) |
122
+ | Cannot know | token counts (Cursor does not store them locally) |
123
+
124
+ This is the productivity-correlation source: it measures **work output**, which is the thing token
125
+ counts are so often wrongly assumed to prove. `model: "default"` and `NULL` are Cursor's own
126
+ placeholders and are recorded as unknown rather than guessed.
127
+
128
+ The database is snapshotted to a temp directory before reading (with its `-wal`/`-shm` sidecars)
129
+ so a live editor is never disturbed and a hot transaction never yields a stale page.
130
+
131
+ ---
132
+
133
+ ## `opencode` — OpenCode
134
+
135
+ | | |
136
+ |---|---|
137
+ | Reads | `$XDG_DATA_HOME/opencode/opencode.db` (`~/.local/share/opencode/opencode.db`), via `node:sqlite` |
138
+ | Measurement | `primary` |
139
+ | Reports | per-request tokens: fresh input, cache read, cache write, output, reasoning; model, provider/gateway, session, project, agent mode, duration |
140
+ | Cannot know | git branch (not stored per message); a surface field — interface stays `Unknown` |
141
+
142
+ One record per assistant message. Token semantics were verified against a live corpus: `input`
143
+ is **exclusive** of cache reads (on long sessions `cache.read` exceeds `input`, which is
144
+ impossible under OpenAI's inclusive convention), and the five reported fields are disjoint
145
+ addends of the source's own total.
146
+
147
+ Two things the adapter handles that would otherwise corrupt the numbers:
148
+
149
+ - **Rows are updated in place.** An assistant row is inserted when the request starts and its
150
+ totals are revised as the response finalises — on a real corpus every token-bearing row had
151
+ `time_updated != time_created`. A naive cursor would freeze the first partial snapshot. The
152
+ adapter re-reads a recent window ordered by `time_updated` and emits only the *delta* for a
153
+ message whose totals grew — the same tails mechanism the Anthropic adapter uses for streamed
154
+ snapshots.
155
+ - **Reasoning is sometimes additive.** Most providers report reasoning as a subset of output,
156
+ but some report it on top. Where `reasoning > output` the only reading that satisfies the
157
+ schema's subset invariant without losing tokens is `output += reasoning`.
158
+
159
+ A non-vendor `providerID` (e.g. `opencode`'s own gateway) is recorded as `gateway`; vendor
160
+ slugs route direct. Zero-token assistant rows are aborted requests, not free API calls, and are
161
+ skipped.
162
+
163
+ ```yaml
164
+ sources:
165
+ opencode:
166
+ db: "~/.local/share/opencode/opencode.db" # override auto-discovery
167
+ ```
168
+
169
+ ---
170
+
171
+ ## `hermes` — Hermes agent
172
+
173
+ | | |
174
+ |---|---|
175
+ | Reads | `~/.hermes/state.db` (`$HERMES_HOME` honoured), via `node:sqlite` |
176
+ | Measurement | `primary` |
177
+ | Reports | per-session-per-model tokens: input, cache read, cache write, output, reasoning; billing provider (as gateway), measured cost when recorded, session source (cli/cron/whatsapp/telegram), cwd, git branch/repo, task, call count, duration |
178
+ | Cannot know | per-request granularity — usage is aggregated per session × model; within-group timing |
179
+
180
+ Hermes records its own LLM traffic in `session_model_usage` (one row per session × model ×
181
+ billing provider × task) joined to `sessions`. There is no per-request log — `messages.token_count`
182
+ is unpopulated — so the finest honest granularity is one record per group, timestamped at the
183
+ group's first API call.
184
+
185
+ Token semantics: `input_tokens` is already **exclusive** of cache reads (verified: cache reads
186
+ exceed input many-fold on real sessions). The columns are `NOT NULL DEFAULT 0`, so an
187
+ unreported split and a true zero are indistinguishable in this source; zeros are passed through
188
+ as zeros rather than invented into nulls.
189
+
190
+ Three things worth knowing:
191
+
192
+ - **Rows grow.** A session's usage rows are upserted as it makes more calls. Like the opencode
193
+ adapter, this one re-reads a recent window ordered by `last_seen` and emits only deltas; the
194
+ base record sits at `first_seen`, a delta at the `last_seen` that revealed it.
195
+ - **Gateway vs vendor.** `billing_provider` ("nous", "openrouter", "openai-codex") is routing,
196
+ so it becomes `gateway` — unless it IS the vendor ("anthropic" billing for a claude model is
197
+ a direct call). Unmapped models stay provider `unknown` with their gateway preserved;
198
+ `"default"` is treated as the placeholder it is.
199
+ - **Measured cost wins.** When hermes recorded `actual_cost_usd > 0` that is passed through as
200
+ a measured cost and the price-table estimate stands down. Its `estimated_cost_usd` is ignored:
201
+ two competing estimates would make the Cost page unauditable.
202
+
203
+ ```yaml
204
+ sources:
205
+ hermes:
206
+ db: "~/.hermes/state.db" # override auto-discovery
207
+ ```
208
+
209
+ ---
210
+
211
+ ## `headroom` — local LLM gateway (overlay)
212
+
213
+ | | |
214
+ |---|---|
215
+ | Reads | `~/.headroom/savings_events.jsonl` |
216
+ | Measurement | `overlay` — **excluded from token totals by default** |
217
+ | Reports | **measured** `cost_usd` per request, post-compression prompt tokens actually sent, the compression delta (`before` → `after`), model, client |
218
+ | Cannot know | output tokens, cache split (the savings log has neither) |
219
+
220
+ The proxy sees the same requests the Codex adapter already recorded. Adding both would double
221
+ count every routed token — hence `overlay`. What only the gateway knows is what the request
222
+ actually **cost**, which gives the Cost page an independent cross-check against the price-table
223
+ estimate instead of a single unverifiable number.
224
+
225
+ ---
226
+
227
+ ## `git` — activity correlation
228
+
229
+ | | |
230
+ |---|---|
231
+ | Reads | `git log --numstat` per repository |
232
+ | Measurement | `activity` |
233
+ | Reports | commits, files changed, insertions, deletions, author, branch, message |
234
+ | Cannot know | anything about tokens |
235
+
236
+ Repositories come from, in order: `sources.git.repos`, `sources.git.scanRoots` (searched for
237
+ `.git`, depth 3), and `autoFromUsage: true` — the working directories already observed in ingested
238
+ usage records, which means zero configuration for the common case where you code where you prompt.
239
+
240
+ ```yaml
241
+ sources:
242
+ git:
243
+ scanRoots: ["~/code"]
244
+ author: "you@example.com" # optional
245
+ since: "2026-01-01" # optional
246
+ autoFromUsage: true
247
+ ```
248
+
249
+ ---
250
+
251
+ ## `generic` — CSV / TSV / JSON / JSONL / SQLite
252
+
253
+ The escape hatch that keeps this project useful for providers nobody has written an adapter for.
254
+
255
+ ```bash
256
+ tokenflow import ~/Downloads/openrouter.csv # infers a mapping and previews it
257
+ tokenflow import usage.jsonl --field timestamp=ts --field input_tokens=prompt_tokens
258
+ tokenflow import app.db --format sqlite --table usage --field timestamp=created_at
259
+ ```
260
+
261
+ The mapping is saved to `$TOKENFLOW_HOME/mappings/<name>.json` and reused automatically:
262
+
263
+ ```json
264
+ {
265
+ "name": "openrouter-export",
266
+ "format": "csv",
267
+ "files": ["~/Downloads/openrouter-*.csv"],
268
+ "timestampFormat": "iso",
269
+ "defaults": { "client": "openrouter", "interface": "API" },
270
+ "fields": {
271
+ "timestamp": "created_at",
272
+ "model": "model",
273
+ "input_tokens": "prompt_tokens",
274
+ "output_tokens": "completion_tokens",
275
+ "cache_read_tokens": "cached_tokens",
276
+ "estimated_cost": "cost_usd",
277
+ "session_id": "generation_id"
278
+ }
279
+ }
280
+ ```
281
+
282
+ Unmapped token fields stay `null`. A row whose timestamp cannot be parsed is rejected rather than
283
+ defaulted to "now". `timestampFormat` accepts `iso`, `epoch_ms`, `epoch_s`.
284
+
285
+ ---
286
+
287
+ ## `mock` — demo data
288
+
289
+ Deterministic synthetic usage so a new contributor can run `npm run demo` and see a realistic
290
+ dashboard without connecting anything. Only activates when explicitly asked for
291
+ (`TOKENFLOW_DEMO=1` or `providers: [mock]`), so it can never contaminate a real dataset by
292
+ accident. Every record carries `metadata.demo = true` and `machine: "demo-machine"`, and the
293
+ dashboard shows a persistent banner whenever any of it is in scope.
294
+
295
+ ---
296
+
297
+ ## What is deliberately not here
298
+
299
+ - **Vendor billing APIs.** They would need credentials and a network client. This project has
300
+ neither by design. If you can export a CSV from a billing console, `tokenflow import` will take it.
301
+ - **Prompt or completion text.** Adapters read counts and metadata and discard content. There is
302
+ nowhere in the schema to put a prompt.
303
+ - **Anything that writes to a source.** Adapters open source files read-only, and SQLite sources
304
+ are read from a temp copy.
305
+
306
+ ## Adding your own
307
+
308
+ See [creating-provider.md](creating-provider.md). Drop a file in
309
+ `$TOKENFLOW_HOME/providers/<id>.js` and it is loaded on the next run — no core changes.
package/docs/skill.md ADDED
@@ -0,0 +1,64 @@
1
+ # Agent skill
2
+
3
+ `skills/tokenflow/` is a self-contained skill that teaches an AI coding agent to install,
4
+ configure, extend and debug this project on an unfamiliar machine. The point is that you should be
5
+ able to say:
6
+
7
+ > "Install and configure the Tokenflow for my environment."
8
+
9
+ …and get a working dashboard without explaining anything about the internals.
10
+
11
+ ## What the agent can do with it
12
+
13
+ - probe the machine for supported AI tools and report what it found
14
+ - write a correct `config.yaml`, including non-standard source paths
15
+ - run a resumable ingest that fits inside short-lived shells
16
+ - validate the result and explain the data-health numbers
17
+ - add a new provider adapter for an unsupported tool
18
+ - build a generic import mapping from an arbitrary CSV/JSON/SQLite export
19
+ - configure pricing, and explain why cost is blank until it is
20
+ - diagnose inflated or missing numbers using the documented failure modes
21
+ - explain any number on the dashboard, including what it deliberately does not claim
22
+
23
+ ## Layout
24
+
25
+ ```
26
+ skills/tokenflow/
27
+ SKILL.md the instructions the agent reads
28
+ providers/
29
+ detection-matrix.md where each tool stores usage, per OS
30
+ adapter-template.js a commented starting point
31
+ schemas/
32
+ normalized-record.json JSON Schema for the unified record
33
+ config.schema.json JSON Schema for config.yaml
34
+ examples/
35
+ config.yaml a fully worked configuration
36
+ generic-mapping.json a saved import mapping
37
+ session-transcript.md the awkward real-world shapes, annotated
38
+ ```
39
+
40
+ ## Using it
41
+
42
+ **Claude Code / Cowork:** copy the folder into `.claude/skills/` (project) or `~/.claude/skills/`
43
+ (global) and ask for it by name. **Any other agent:** point it at `SKILL.md` — it is plain
44
+ Markdown with no tool-specific assumptions.
45
+
46
+ ```
47
+ > Install and configure the Tokenflow for my environment.
48
+ > Add an adapter for <tool>; its logs are at <path> and look like <sample>.
49
+ > My Aug 15 total looks 40x too high. Diagnose it.
50
+ > Explain what "cache hit rate 94.8%" on my dashboard actually measures.
51
+ ```
52
+
53
+ ## Design notes
54
+
55
+ The skill front-loads the **invariants**, not the API surface, because those are what an agent
56
+ gets wrong: missing is not zero, cache is not input, a gateway is not a vendor, interface is never
57
+ inferred from the model, streaming logs re-report usage, and tokens are not productivity. Each one
58
+ is stated with the concrete failure it prevents — including the measured 45x inflation that
59
+ motivated the Codex reconstruction — because an agent that knows *why* a rule exists applies it to
60
+ cases the rule didn't enumerate.
61
+
62
+ It also tells the agent to verify rather than assert: run `npm run validate`, cross-check a
63
+ suspicious day against an independent source, and write the "re-reading the same bytes emits
64
+ nothing new" test for any new adapter.