@vimoxshah/tokenflow 1.1.2 → 1.2.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 (86) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/Dockerfile.team +20 -0
  3. package/README.md +30 -11
  4. package/bin/tokenflow.js +147 -12
  5. package/design/tokens.yaml +330 -0
  6. package/docs/architecture.md +5 -4
  7. package/docs/cli.md +204 -0
  8. package/docs/configuration.md +117 -2
  9. package/docs/design-system.md +187 -0
  10. package/docs/exports-and-budgets.md +85 -0
  11. package/docs/guard-codex.md +132 -0
  12. package/docs/ledger.md +144 -0
  13. package/docs/live-mode.md +40 -0
  14. package/docs/media/overview-aurora-dark.png +0 -0
  15. package/docs/media/receipts-aurora-dark.png +0 -0
  16. package/docs/providers-otel.md +179 -0
  17. package/docs/providers.md +54 -1
  18. package/docs/receipt-schema.md +74 -0
  19. package/docs/roadmap.md +182 -0
  20. package/docs/team-server.md +170 -0
  21. package/docs/ui-views.md +322 -0
  22. package/package.json +7 -2
  23. package/schemas/receipt.v0.json +160 -0
  24. package/scripts/build-menubar-app.sh +3 -1
  25. package/scripts/design-build.js +475 -0
  26. package/src/analytics/anatomy.js +467 -0
  27. package/src/analytics/branch-compare.js +159 -0
  28. package/src/analytics/cache-health.js +141 -0
  29. package/src/analytics/live-view.js +266 -0
  30. package/src/analytics/receipt-schema.js +214 -0
  31. package/src/analytics/receipt.js +709 -0
  32. package/src/analytics/rhythm.js +184 -0
  33. package/src/analytics/whatif.js +263 -0
  34. package/src/commands/budget-scopes.js +133 -0
  35. package/src/commands/doctor-checks.js +400 -0
  36. package/src/commands/guard.js +531 -0
  37. package/src/commands/hooks.js +238 -0
  38. package/src/commands/pricing-diff.js +316 -0
  39. package/src/commands/receipt.js +226 -0
  40. package/src/commands/team-serve.js +407 -0
  41. package/src/commands/week.js +86 -0
  42. package/src/core/annotations.js +97 -0
  43. package/src/core/budget.js +33 -0
  44. package/src/core/bundle.js +45 -2
  45. package/src/core/ingest.js +33 -0
  46. package/src/core/live-status.js +227 -2
  47. package/src/core/policy.js +103 -0
  48. package/src/core/receipt-note.js +123 -0
  49. package/src/core/repo.js +64 -0
  50. package/src/core/sync.js +163 -26
  51. package/src/core/team.js +0 -0
  52. package/src/export/html-snapshot.js +28 -1
  53. package/src/export/menubar.js +21 -0
  54. package/src/export/receipt-card.js +210 -0
  55. package/src/export/week-card.js +185 -0
  56. package/src/providers/mock/index.js +383 -52
  57. package/src/providers/openai/index.js +31 -1
  58. package/src/providers/otel/index.js +656 -0
  59. package/src/server/routes/annotations.js +42 -0
  60. package/src/server/routes/cache-health.js +95 -0
  61. package/src/server/routes/index.js +54 -0
  62. package/src/server/routes/session.js +157 -0
  63. package/src/server/server.js +47 -1
  64. package/src/ui/app.js +541 -308
  65. package/src/ui/charts.js +95 -0
  66. package/src/ui/first-run.js +144 -0
  67. package/src/ui/index.html +4 -1
  68. package/src/ui/palette.js +335 -0
  69. package/src/ui/styles/anatomy.css +117 -0
  70. package/src/ui/styles/annotations.css +40 -0
  71. package/src/ui/styles/branches.css +99 -0
  72. package/src/ui/styles/cache.css +6 -0
  73. package/src/ui/styles/first-run.css +31 -0
  74. package/src/ui/styles/live.css +100 -0
  75. package/src/ui/styles/palette.css +85 -0
  76. package/src/ui/styles/rhythm.css +8 -0
  77. package/src/ui/styles/whatif.css +55 -0
  78. package/src/ui/styles.css +303 -196
  79. package/src/ui/views/anatomy.js +567 -0
  80. package/src/ui/views/annotations.js +121 -0
  81. package/src/ui/views/branches.js +304 -0
  82. package/src/ui/views/cache.js +232 -0
  83. package/src/ui/views/index.js +85 -0
  84. package/src/ui/views/live.js +683 -0
  85. package/src/ui/views/rhythm.js +206 -0
  86. package/src/ui/views/whatif.js +196 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,186 @@
3
3
  All notable changes to TokenFlow are recorded here. Versions follow
4
4
  [semantic versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 1.2.0 — 2026-09-06
7
+
8
+ Spend attributed to the unit of work, a guard that acts while a session is
9
+ still running, and a design system that every surface compiles from. Nothing
10
+ here reads prompt or code content.
11
+
12
+ ### Added
13
+
14
+ - **Command palette.** ⌘K or Ctrl+K, or the "⌘K" chip at the end of the tab
15
+ bar: every tab, the quick ranges, each skin and mode, export, refresh and
16
+ clear filters, matched by subsequence or word prefix, with the last eight
17
+ commands first. It opens and closes with no animation because it is used
18
+ dozens of times a day. Works from the offline snapshot too. Inside a text
19
+ field, Ctrl+K keeps its editing meaning and ⌘K still opens the palette.
20
+ - **First-run screen.** The first time the live dashboard opens, and again
21
+ after an upgrade, one screen says what was found on this machine: what each
22
+ source contributed, which detected adapters have read nothing yet, and why.
23
+ Never shown from a saved snapshot.
24
+ - **Tab bar that wraps.** Twenty tabs wrap to two rows at 1280 and 1024 pixels
25
+ with the active tab always visible.
26
+ - **A `--hairline` token.** The subtlest divider now has its own colour role in
27
+ `design/tokens.yaml` for every skin and mode, so views stop guessing.
28
+ - **A design system with a compiler.** `design/tokens.yaml` is the single
29
+ source for colour roles, the two axes (mode × skin), a type scale with
30
+ size-specific tracking, a 4-pt space scale, radii, two motion tiers and the
31
+ authored chart ramps. `npm run design` compiles it into a generated block in
32
+ the dashboard stylesheet, a generated block in the landing stylesheet and
33
+ `menubar/TokenFlow/DesignTokens.swift`, and refuses to write when a gate
34
+ fails: ink contrast, series-on-surface contrast, adjacent-series perceptual
35
+ distance, sequential monotonicity, status-vs-series distinctness, the 300ms
36
+ ceiling on UI motion. `test/design.test.js` fails the build when a generated
37
+ block is hand-edited or stale. Doctrine in `docs/design-system.md`.
38
+ - **Receipts view** in the dashboard: spend per repository and branch, with
39
+ context share, sessions, turns, subagent share and rank against the repo's
40
+ median. Click a branch for the receipt and copy it as a PR comment. Receipts
41
+ ship inside the data bundle, computed once per refresh and streamed rather
42
+ than materialized, so the offline snapshot has them too. The scan costs
43
+ about two seconds on a 160K-turn store, so the watcher's status snapshot
44
+ and the one-line CLI summaries opt out of it.
45
+ - **Story strip** on the Overview: the three insights that matter for the
46
+ current slice, as sentences, with their numbers set in the figure face.
47
+ - **Deep links**: `#tab=receipts&skin=terminal&mode=light` opens the dashboard
48
+ on that view and look. The tab is reflected in the URL as you switch.
49
+ - **Motion and type polish through the tokens**: feedback on press for every
50
+ pressable, popovers that scale from their trigger, tooltips that respond in
51
+ a frame, cards that stagger in only when the view changes, a translucent
52
+ header material, hover states gated to pointer devices, and reduced-motion
53
+ and reduced-transparency that degrade to a complete state. Muted ink was
54
+ nudged in every skin to clear 4.5:1 on cards.
55
+ - **Menu bar on the same tokens.** Accent, status colours and the categorical
56
+ palette now come from the generated Swift file; the saturated gradients on
57
+ the brand mark and the milestone banner became solid accent surfaces, per the
58
+ system's own rule that a gradient sits only behind a single number.
59
+ - **Landing page rewritten around the finding, not the features**: a real
60
+ receipt in the hero, the three numbers from nine weeks of logs, the
61
+ marginal-cost curve, the receipt rules with the correction that produced
62
+ them, the guard, and the cap table with its upper-bound caveat. Built on the
63
+ same tokens, with self-hosted fonts and no third-party requests; the GSAP
64
+ and three.js dependencies are gone.
65
+
66
+ - **`tokenflow receipt`** — what a branch or pull request cost. Each turn is
67
+ attributed to the branch checked out when it ran, so one long session that
68
+ moved across branches is split across them. Repositories are identified by
69
+ walking up from the recorded working directory to `.git` and following a
70
+ worktree's `gitdir:` pointer home, so `.worktrees/<x>` no longer counts as
71
+ its own project. `--gh` joins merged pull requests by head branch and prices
72
+ each at `$ per 100 changed lines`; `--md` renders a PR-comment receipt. A PR
73
+ owns the turns on its branch up to the merge; turns after the merge are
74
+ follow-up on a checkout that kept the branch name and are shown beside the
75
+ receipt, never inside it. Long-lived branches are flagged as such. The
76
+ estimate is split into *context* dollars (cache reads + writes: re-sending
77
+ the conversation so far) and *work* dollars (fresh input + output).
78
+ `--sessions` reports how concentrated spend is across sessions, the median
79
+ cost of a turn by how deep into a session it is, and the dollars above each
80
+ candidate per-session cap — labelled an upper bound, not a saving.
81
+ - **`tokenflow guard`** — a Claude Code hook that reads the live transcript,
82
+ prices it, and reports running spend, current prompt size and the median cost
83
+ of the last ten turns, then warns or blocks against thresholds you declared
84
+ with `guard --set`. Nothing declared means informational only, never a
85
+ block. Reads are incremental per session. `--install` prints the hooks block
86
+ and does not write your settings.
87
+ - **`tokenflow team serve`.** A self-hosted team server for the folder-sync
88
+ rollups: one process on a machine your team owns, fed by the same
89
+ per-machine files (`<id>.jsonl`, `<id>.receipts.json`) the folder sync
90
+ already writes. A single shared bearer token gates writes and reads once
91
+ configured; a non-loopback host always requires one. `GET /health` always
92
+ answers; `GET /api/team` and `GET /` return the same aggregate `tokenflow
93
+ team` prints. See `docs/team-server.md` and `Dockerfile.team`.
94
+ - **The Ledger: cost per branch and per merged PR, across the team.**
95
+ `sync.push()` now also writes `<machineId>.receipts.json`, a whole-state,
96
+ opt-in-anonymous ledger of branch receipts (unless `sync.receipts: false`).
97
+ `tokenflow team` joins every synced ledger into `receipts.totals`,
98
+ `byRepo`, `byMonth`, `perMergedPr`, `concentration` and `longLivedShare`.
99
+ `sync.to` (plus `sync.token` or `TOKENFLOW_SYNC_TOKEN`) pushes both files to
100
+ a server instead of a shared folder; `tokenflow sync --to <url> --token
101
+ <t>` runs it from the CLI. See `docs/ledger.md`.
102
+ - **Receipts travel with the code.** `tokenflow hooks install` writes a
103
+ `pre-push` hook that attaches a receipt to the pushed commit as a git note
104
+ under `refs/notes/tokenflow`. The hook never blocks: every failure is
105
+ reported on stderr with exit 0, and a hook it replaces is kept and chained,
106
+ running first and keeping its own exit code. Receipt schema v0
107
+ (`schemas/receipt.v0.json`, `src/analytics/receipt-schema.js`) makes the
108
+ note machine readable. A zero-dependency GitHub Action (`action/`) reads
109
+ the note on a pull request and posts or updates one PR comment. See
110
+ `docs/receipt-schema.md`.
111
+ - **Per-repository guard policy and a Codex guard.** `.tokenflow/policy.yaml`
112
+ declares guard thresholds that travel with a repository and win over the
113
+ personal config, key by key; `tokenflow guard --policy [--cwd <dir>]`
114
+ shows the effective policy and each value's source. Codex CLI has no
115
+ blocking hook, so `tokenflow guard --install --codex [--apply]` wires up
116
+ its `notify` setting instead: it reads only `thread-id` and `cwd` from
117
+ Codex's payload and sends one OS notification on warn or block, never a
118
+ block itself. See `docs/guard-codex.md`.
119
+ - **Exports that travel, and budgets per repo and per team.** `tokenflow
120
+ receipt` gains `--svg`, `--png` and `--csv`: a shareable receipt card, or
121
+ one CSV row per branch across every repository. `tokenflow week` builds
122
+ "your AI week" as text, JSON, or the same kind of card (`--svg`, `--png`).
123
+ A `budgets:` list in `config.yaml` adds caps scoped to one repository or to
124
+ the whole team, alongside the existing monthly budget; `tokenflow budget`
125
+ prints both. See `docs/exports-and-budgets.md`.
126
+ - **`tokenflow doctor` data-quality audit and `tokenflow pricing diff`.**
127
+ Doctor now runs seven checks over the last three months of records:
128
+ worktree-split spend, cwd outside any repository, Codex records with no
129
+ git branch, unpriced models, a stale price table, session-level sources in
130
+ per-turn views, and `metadata.repoResolved === false`. `tokenflow pricing
131
+ diff <table.json> [--apply] [--yes]` compares a candidate price table to
132
+ the current effective one and merges it in only on explicit confirmation.
133
+ - **Live status carries sessions, receipts, guard state and sparklines.**
134
+ `data/status.json` now includes `liveSessions` (up to 8 sessions active
135
+ within 10 minutes of the last refresh), `receiptsToday` (the top 3
136
+ branches by spend today), `guard` (declared thresholds plus the last
137
+ verdict, sourced `cache` or `derived`) and `sparklines` (24 hourly buckets
138
+ per source). The xbar/SwiftBar text shows a "Live now" line and a "Guard:"
139
+ line from the same data.
140
+ - **The native menu bar shows the same live state.** A "Live, as of" section
141
+ lists running sessions with a context gauge and the guard's colour dot;
142
+ "Today's receipts" lists the day's top branches; "Guard" shows the
143
+ declared caps, the last verdict, and Raise-cap / Clear-caps buttons that
144
+ write through `tokenflow guard --set`, never by editing config.yaml
145
+ directly; a "Last 24 hours by source" sparkline section closes it out. A
146
+ transient card slides in from the status item only when a live session's
147
+ guard level rises, never on a recovery, and only one shows at a time.
148
+ Density (compact/comfortable) and the popover's global keyboard shortcut
149
+ are now preferences.
150
+ - **Demo data is schema-complete.** The synthetic dataset now has a hot
151
+ repository and branch, five repositories, and volume that stays within
152
+ 10% across weekdays, so every new view has something realistic to render
153
+ offline.
154
+ - **`otel`, an OpenTelemetry (GenAI) adapter.** Reads a standards-based
155
+ OTLP JSON export (`resourceSpans`/`resourceLogs`, `gen_ai.usage.*`
156
+ attributes) from any tool's file exporter, and Gemini CLI's own file
157
+ telemetry (`~/.gemini/telemetry.log` by default, or `sources.otel.paths`),
158
+ deduplicating Gemini's paired usage events. See `docs/providers-otel.md`.
159
+ - **Seven new dashboard views, each a registered module, not an app.js
160
+ edit.** Session anatomy: a session's turn-by-turn cost waterfall, growing
161
+ context and subagent fan-out, backed by `GET /api/session`. Live v2: the
162
+ four "right now" sections above, as its own tab. Cache health: hit rate,
163
+ write split and churn events with their dollar cost. What-if: reprice the
164
+ same tokens at another model's rates, labelled as a price-only comparison.
165
+ Compare branches: any two branch receipts on a symmetric log scale.
166
+ Rhythm: deep-work sessions, project switching, and the hour marginal cost
167
+ peaks. Annotations: mark a day and see it on every daily chart, stored in
168
+ `annotations.json`. All seven route through the view registry
169
+ (`src/ui/views/index.js`) and the route registry
170
+ (`src/server/routes/index.js`) documented in `docs/ui-views.md`.
171
+
172
+ ### Fixed
173
+
174
+ - **Git worktrees no longer split one repository's spend.** A session in
175
+ `<repo>/.worktrees/<x>` used to file under `x`; ingestion now resolves
176
+ `project` and `repository` through the main checkout at ingest time
177
+ (`src/core/repo.js`, `metadata.repoResolved`), and `tokenflow doctor`
178
+ flags any repository still fragmented across worktree names. Repair
179
+ existing history with `tokenflow refresh --full`.
180
+ - **Codex sessions now carry a branch and a repository name.** CLI builds
181
+ 0.149 and later report a `git` block on `session_meta` (`branch`,
182
+ `repository_url`); the openai adapter sets `git_branch` and `repository`
183
+ from it, falling back to the cwd basename exactly as before when the block
184
+ is absent.
185
+
6
186
  ## 1.1.2 — 2026-09-03
7
187
 
8
188
  Makes the downloadable app usable on a machine that is not the one that built
@@ -0,0 +1,20 @@
1
+ # TokenFlow team server — tiny image, zero runtime dependencies, no `npm install`.
2
+ # See docs/team-server.md.
3
+ #
4
+ # docker build -f Dockerfile.team -t tokenflow-team .
5
+ # docker run -d -e TOKENFLOW_TEAM_TOKEN=<shared-secret> -p 7790:7790 \
6
+ # -v tokenflow-team-data:/data tokenflow-team
7
+ FROM node:22-alpine
8
+
9
+ WORKDIR /app
10
+ COPY package.json ./
11
+ COPY bin ./bin
12
+ COPY src ./src
13
+
14
+ ENV TOKENFLOW_HOME=/data
15
+ VOLUME ["/data"]
16
+ EXPOSE 7790
17
+
18
+ # --host 0.0.0.0 so the container's port mapping can reach the server; a
19
+ # non-loopback bind requires TOKENFLOW_TEAM_TOKEN to be set at `docker run`.
20
+ CMD ["node", "bin/tokenflow.js", "team", "serve", "--host", "0.0.0.0", "--port", "7790"]
package/README.md CHANGED
@@ -39,12 +39,12 @@ per-source and per-model usage, capacity meters and forecast, in light and dark.
39
39
 
40
40
  | Layer | What's inside |
41
41
  |---|---|
42
- | **Ingestion** | 10 adapters (Anthropic, OpenAI/Codex, OpenCode, Hermes, Cline, Cursor, Headroom gateway, git, generic CSV/JSONL/SQLite import, demo). Incremental byte-offset resumes, re-read windows for upserted rows, budgeted refresh that stops cleanly mid-corpus |
42
+ | **Ingestion** | 11 adapters (Anthropic, OpenAI/Codex, OpenCode, Hermes, Cline, Cursor, Headroom gateway, git, OpenTelemetry/GenAI (otel), generic CSV/JSONL/SQLite import, demo). Incremental byte-offset resumes, re-read windows for upserted rows, budgeted refresh that stops cleanly mid-corpus |
43
43
  | **Correctness** | Cache read/write/input kept as mutually exclusive buckets; vendor convention differences handled; streaming re-reports collapsed to max-of-run; `null` never coerced to 0; measured gateway cost kept separate from estimates |
44
44
  | **Costing** | Versioned price table with per-entry source URLs and fetch dates; service-tier multipliers; long-TTL cache-write tier priced separately; unpriced models surface as `null` with a configure action — never silent `$0` |
45
- | **Analytics** | 12 views: overview KPIs, composition, provider/model intelligence, interfaces, hour×weekday heatmap + calendar, peaks, efficiency ratios, cost with coverage, git correlations (labelled), period compare, searchable data explorer, per-field data health |
46
- | **Live mode** | Watcher daemon, native Swift menu bar app (provider/source/model breakdowns, capacity meters with reset countdowns & ETAs, forecast with stated confidence, MAD-based anomaly alerts, appearance toggle), SwiftBar/xbar plugin |
47
- | **Engineering** | Zero runtime dependencies · 145 tests · lint invariants (e.g. "no `\|\| 0` on a token field") · tsc-clean JSDoc types · CI on macOS/Linux/Windows × Node 22/24 |
45
+ | **Analytics** | 20 views: overview KPIs with a story strip, receipts per branch and pull request, session anatomy, live, composition, provider/model intelligence, interfaces, hour×weekday heatmap + calendar, peaks, efficiency ratios, cache health, cost with coverage, model what-if, git correlations (labelled), rhythm and focus, period compare, compare branches, searchable data explorer, annotations, per-field data health |
46
+ | **Live mode** | Watcher daemon, native Swift menu bar app (live sessions, today's receipts, guard state, provider/source/model breakdowns, capacity meters with reset countdowns & ETAs, forecast with stated confidence, MAD-based anomaly alerts, per-source sparklines, appearance toggle), SwiftBar/xbar plugin |
47
+ | **Engineering** | Zero runtime dependencies · 520 tests · lint invariants (e.g. "no `\|\| 0` on a token field") · tsc-clean JSDoc types · CI on macOS/Linux/Windows × Node 22/24 |
48
48
 
49
49
  Zero runtime dependencies. Nothing leaves your machine. No API keys, no accounts, no telemetry.
50
50
 
@@ -121,7 +121,8 @@ dashboard.
121
121
 
122
122
  | Question | Where |
123
123
  |---|---|
124
- | How much AI did I use, and how has that changed? | OverviewKPIs, daily series, trend |
124
+ | What did this branch or pull request cost? | Receiptsspend per repository and branch, context share, copy as a PR comment |
125
+ | How much AI did I use, and how has that changed? | Overview — the three insights that matter, then KPIs, daily series, trend |
125
126
  | Input vs output vs cache? | Token composition — four buckets that sum to the total |
126
127
  | Which provider and model do I rely on? | Provider & model share, growth, per-model efficiency |
127
128
  | When do I use AI most? | Time patterns — hour/weekday profiles, heatmap, calendar |
@@ -133,9 +134,14 @@ dashboard.
133
134
  <details>
134
135
  <summary>All pages</summary>
135
136
 
136
- Overview · Token composition · Providers & models · Interfaces · Time patterns · Peaks ·
137
- Efficiency · Cost · Productivity · Compare · Data explorer (searchable/sortable/exportable) ·
138
- Data health
137
+ Overview · Receipts · Session anatomy · Live · Token composition · Providers & models · Interfaces ·
138
+ Time patterns · Peaks · Efficiency · Cache health · Cost · What-if · Productivity · Rhythm · Compare ·
139
+ Compare branches · Data explorer (searchable/sortable/exportable) · Annotations · Data health.
140
+ Deep-link any of them: `#tab=receipts&skin=terminal&mode=light`.
141
+
142
+ Press ⌘K (or Ctrl+K) anywhere in the dashboard for the command palette: jump to a tab, change the
143
+ range, skin or mode, export, refresh or clear filters without touching the mouse. The first time
144
+ the live dashboard opens it explains what was found on this machine and what was not, and why.
139
145
 
140
146
  </details>
141
147
 
@@ -196,12 +202,23 @@ node bin/tokenflow.js dashboard # live UI at http://127.0.0.1:7799 (loopbac
196
202
  node bin/tokenflow.js watch # auto-refresh every N seconds (default 120)
197
203
  node bin/tokenflow.js import f.csv # CSV/JSONL/SQLite via saved field mapping
198
204
  node bin/tokenflow.js export --csv # --all for everything; --html for offline snapshot
199
- node bin/tokenflow.js digest # shareable markdown summary (--format text, --from/--to, --out f.md)
205
+ node bin/tokenflow.js digest # shareable weekly digest (--format text, --from/--to, --out f.md)
206
+ node bin/tokenflow.js week # this week's spend vs last week (--svg/--png card, --json)
200
207
  node bin/tokenflow.js models-compare # cost/usage efficiency per model — your data
201
208
  node bin/tokenflow.js budget --set 200 # monthly cap + forecast alerts (fires once per state/month)
202
209
  node bin/tokenflow.js schedule --install --at "Monday 09:00" # weekly digest via launchd
203
210
  node bin/tokenflow.js diagnostics # local observability — nothing transmitted
204
211
  node bin/tokenflow.js team # per-developer team view (needs sync + opt-in names)
212
+ node bin/tokenflow.js team serve # self-hosted team server (LAN/Docker), no shared folder needed
213
+ node bin/tokenflow.js sync --to <url> --token <t> # push this machine's rollups to a team server
214
+ node bin/tokenflow.js receipt --repo . --gh # what each branch / merged PR cost (sees through worktrees)
215
+ node bin/tokenflow.js receipt --sessions # where the money goes: concentration, context vs work, marginal cost/turn
216
+ node bin/tokenflow.js receipt --csv # one row per branch, every repository
217
+ node bin/tokenflow.js hooks install # pre-push git hook: attach a receipt note, never blocks
218
+ node bin/tokenflow.js guard --install # Claude Code hook: warn/block a session against caps you declare
219
+ node bin/tokenflow.js guard --install --codex # same circuit breaker for Codex CLI (warns only)
220
+ node bin/tokenflow.js pricing diff table.json --apply # merge a candidate price table in
221
+ node bin/tokenflow.js doctor # environment, store, adapters, and a data-quality audit
205
222
  node bin/tokenflow.js up # refresh → rebuild offline HTML → serve + open
206
223
 
207
224
  npm link # optional: global `tokenflow` command
@@ -247,7 +264,7 @@ from `file://` with no server. A full CSV export doubles as a portable dataset:
247
264
  ## Contributing
248
265
 
249
266
  ```bash
250
- npm test # 102 tests: normalization, adapters, analytics, store, formatting
267
+ npm test # 520 tests: normalization, adapters, analytics, store, receipts, guard
251
268
  npm run lint # project invariants (incl. "no || 0 on a token field")
252
269
  npm run typecheck # tsc over JSDoc types — must be zero errors
253
270
  npm run validate # self-check: runtime, adapters, store↔cube agreement
@@ -276,8 +293,10 @@ Easiest contribution: an adapter (`src/providers/<id>/index.js`) plus a fixture
276
293
 
277
294
  Local data → local normalization → local analytics → local dashboard. The server binds to
278
295
  `127.0.0.1`. No prompt text, code, or file content is ever stored — adapters read token counts and
279
- discard the rest. No telemetry. Two features can touch the network, both strictly opt-in:
296
+ discard the rest. No telemetry. Three features can touch the network, all strictly opt-in:
280
297
 
298
+ - **Team server push** (`sync.to` in config, or `tokenflow sync --to <url>`) sends the same two
299
+ sync files to a server you run with `tokenflow team serve`, behind a bearer token. Nothing else.
281
300
  - **Digest delivery** (`delivery:` in config) — sends the digest you generate to your own
282
301
  Telegram chat, email, or webhook. Credentials live only in `~/.tokenflow/config.yaml`.
283
302
  - **Multi-machine sync** (`sync:` in config) — exchanges daily totals (date, tokens, requests,
package/bin/tokenflow.js CHANGED
@@ -80,12 +80,16 @@ async function main() {
80
80
  case 'forecast': return cmdForecast();
81
81
  case 'menubar': return cmdMenubar();
82
82
  case 'digest': return cmdDigest();
83
+ case 'week': return cmdWeek();
83
84
  case 'schedule': return cmdSchedule();
84
85
  case 'budget': return cmdBudget();
85
86
  case 'sync': return cmdSync();
86
87
  case 'models-compare': return cmdModelsCompare();
87
88
  case 'diagnostics': return cmdDiagnostics();
88
89
  case 'team': return cmdTeam();
90
+ case 'receipt': case 'receipts': return cmdReceipt();
91
+ case 'hooks': return cmdHooks();
92
+ case 'guard': return cmdGuard();
89
93
  default:
90
94
  console.error(`${C.red}Unknown command "${cmd}".${C.r}\n`);
91
95
  return help(1);
@@ -264,7 +268,7 @@ async function cmdStatus() {
264
268
  const line = barLine(st, String(flags.mode || 'auto'), String(flags.prefix || 'TF'));
265
269
  return console.log(flags.json ? JSON.stringify(line, null, 2) : line.text);
266
270
  }
267
- const b = buildBundle();
271
+ const b = buildBundle({ receipts: false });
268
272
  if (flags.json) return console.log(JSON.stringify({ meta: b.meta, health: b.health }, null, 2));
269
273
  const h = b.health;
270
274
  if (!h.records) {
@@ -446,6 +450,16 @@ async function cmdExport() {
446
450
  // ================================================================== pricing ==
447
451
 
448
452
  async function cmdPricing() {
453
+ if (argv[1] === 'diff') {
454
+ const { run } = await import('../src/commands/pricing-diff.js');
455
+ const file = argv[2];
456
+ if (!file) throw new Error('usage: tokenflow pricing diff <file.json|-> [--apply] [--yes]');
457
+ const res = await run({ file, apply: !!flags.apply, yes: !!flags.yes });
458
+ if (res.stdout) console.log(res.stdout);
459
+ if (res.stderr) console.error(res.stderr);
460
+ if (res.exitCode) process.exitCode = res.exitCode;
461
+ return;
462
+ }
449
463
  const p = paths();
450
464
  const cur = readJson(p.pricing, { models: {} });
451
465
  if (flags.set) {
@@ -488,7 +502,7 @@ async function cmdPricing() {
488
502
  return;
489
503
  }
490
504
 
491
- const b = buildBundle();
505
+ const b = buildBundle({ receipts: false });
492
506
  const v = computeView(b, {});
493
507
  const book = buildPriceBook(readJson(p.pricing, {}));
494
508
  console.log(`\n ${C.b}Pricing${C.r} ${C.dim}built-in table ${PRICING_TABLE_VERSION} · overrides in ${p.pricing}${C.r}\n`);
@@ -747,6 +761,9 @@ async function cmdDoctor() {
747
761
  console.log(` cube ${int(store.cube().rows.length)} rows`);
748
762
  console.log(` sessions ${int(Object.keys(store.sessions().rows).length)}`);
749
763
  console.log(` stale gens ${(store.state.stale || []).length}${(store.state.stale || []).length ? ` ${C.y}run 'tokenflow compact'${C.r}` : ''}`);
764
+ const { auditChecks, renderChecks } = await import('../src/commands/doctor-checks.js');
765
+ console.log(`\n ${C.b}Audit${C.r} ${C.dim}data-quality checks over the last 3 months${C.r}`);
766
+ renderChecks(auditChecks({ store, config: cfg, now: new Date() }));
750
767
  console.log(`\n ${C.b}Providers${C.r}`);
751
768
  await cmdProviders();
752
769
  console.log(` ${C.dim}Troubleshooting guide: docs/troubleshooting.md${C.r}\n`);
@@ -1166,6 +1183,49 @@ async function cmdDigest() {
1166
1183
  }
1167
1184
  }
1168
1185
 
1186
+ /** `tokenflow week` — "Your AI week": this week's spend vs last week, as text, JSON, or a card. */
1187
+ async function cmdWeek() {
1188
+ const { run } = await import('../src/commands/week.js');
1189
+ const out = run(flags);
1190
+ if (flags.json) return console.log(JSON.stringify(out.json, null, 2));
1191
+ console.log(out.text);
1192
+ }
1193
+
1194
+ /** `tokenflow receipt` — AI spend attributed to a branch / pull request. */
1195
+ async function cmdReceipt() {
1196
+ const { run } = await import('../src/commands/receipt.js');
1197
+ const out = run(flags);
1198
+ if (flags.json) return console.log(JSON.stringify(out.json, null, 2));
1199
+ if (typeof flags.out === 'string') {
1200
+ fs.writeFileSync(flags.out, out.text + '\n');
1201
+ return console.log(`${C.g}✓${C.r} wrote ${flags.out}`);
1202
+ }
1203
+ console.log(out.text);
1204
+ }
1205
+
1206
+ /** `tokenflow hooks` — install/uninstall the pre-push git hook that attaches a receipt note. */
1207
+ async function cmdHooks() {
1208
+ const { run } = await import('../src/commands/hooks.js');
1209
+ const action = argv[1];
1210
+ const out = run({ ...flags, action, args: argv.slice(2) });
1211
+ if (out.stdout) process.stdout.write(out.stdout + '\n');
1212
+ if (out.stderr) process.stderr.write(out.stderr + '\n');
1213
+ process.exitCode = out.exitCode;
1214
+ }
1215
+
1216
+ /** `tokenflow guard` — the in-session circuit breaker, run as a Claude Code hook. */
1217
+ async function cmdGuard() {
1218
+ const { run } = await import('../src/commands/guard.js');
1219
+ // Hooks run in a shell whose PATH may not include this node (nvm, asdf), so
1220
+ // the printed command pins the interpreter and the CLI that are running now.
1221
+ // `run()` is synchronous except for the `--codex-notify` branch, which
1222
+ // returns a Promise — awaiting unconditionally handles both.
1223
+ const out = await run({ ...flags, bin: `${process.execPath} ${path.join(root(), 'bin', 'tokenflow.js')}` });
1224
+ if (out.stdout) process.stdout.write(out.stdout + '\n');
1225
+ if (out.stderr) process.stderr.write(out.stderr + '\n');
1226
+ process.exitCode = out.exitCode;
1227
+ }
1228
+
1169
1229
  /** `tokenflow schedule` — install/remove the weekly digest LaunchAgent. */
1170
1230
  async function cmdSchedule() {
1171
1231
  const sched = await import('../src/core/schedule.js');
@@ -1191,9 +1251,19 @@ async function cmdBudget() {
1191
1251
  saveConfig(merge(cfg, { budget }));
1192
1252
  console.log(`${C.g}✓${C.r} monthly budget set to $${v.toLocaleString('en-US')}`);
1193
1253
  }
1254
+ // Scoped budgets (repo | team) are independent of the single monthly cap
1255
+ // below, so they are evaluated once and printed either way — a user who
1256
+ // only declared `budgets:` and never set a monthly cap must still see them.
1257
+ const { evaluateScopedBudgets, renderScopedBudgets } = await import('../src/commands/budget-scopes.js');
1258
+ const scopedRows = evaluateScopedBudgets({ config: cfg });
1259
+
1194
1260
  if (!budget.monthly) {
1195
1261
  console.log('No monthly budget configured. Set one:');
1196
1262
  console.log(` ${C.b}tokenflow budget --set 200${C.r} # $200/month, warn at 80% projected`);
1263
+ if (scopedRows.length) {
1264
+ console.log(`\n${C.b}Scoped budgets${C.r}`);
1265
+ console.log(renderScopedBudgets(scopedRows));
1266
+ }
1197
1267
  return;
1198
1268
  }
1199
1269
 
@@ -1204,7 +1274,14 @@ async function cmdBudget() {
1204
1274
  const today = new Date().toISOString().slice(0, 10);
1205
1275
 
1206
1276
  const st = computeBudgetState(status, { monthly: budget.monthly, warnAtPct: budget.warnAtPct }, today);
1207
- if (!st) { console.log('No usage data yet.'); return; }
1277
+ if (!st) {
1278
+ console.log('No usage data yet.');
1279
+ if (scopedRows.length) {
1280
+ console.log(`\n${C.b}Scoped budgets${C.r}`);
1281
+ console.log(renderScopedBudgets(scopedRows));
1282
+ }
1283
+ return;
1284
+ }
1208
1285
 
1209
1286
  const { fire } = shouldAlert(st, { force: !!flags.force });
1210
1287
 
@@ -1228,6 +1305,11 @@ async function cmdBudget() {
1228
1305
  } else if (st.state !== 'safe' && st.state !== 'unknown') {
1229
1306
  console.log(C.dim + ' (already alerted for this state this month — no spam)');
1230
1307
  }
1308
+
1309
+ if (scopedRows.length) {
1310
+ console.log(`\n${C.b}Scoped budgets${C.r}`);
1311
+ console.log(renderScopedBudgets(scopedRows));
1312
+ }
1231
1313
  }
1232
1314
 
1233
1315
  /** `tokenflow sync` — optional multi-machine aggregation via a shared folder. */
@@ -1241,6 +1323,25 @@ async function cmdSync() {
1241
1323
  return;
1242
1324
  }
1243
1325
 
1326
+ // A team server is a separate destination from the folder-sync `isEnabled`
1327
+ // gate below: `push()`'s remote branch needs only `sync.enabled`, never
1328
+ // `sync.dir`, so `--to` must not be blocked by "no folder configured". This
1329
+ // path only ever pushes — there is no merged view to pull from a server.
1330
+ const id = machineId();
1331
+ const to = typeof flags.to === 'string' ? flags.to : (cfg.sync?.to || null);
1332
+ const token = typeof flags.token === 'string' ? flags.token : undefined;
1333
+ if (to) {
1334
+ try {
1335
+ const r = await push({ config: cfg, to, token });
1336
+ if ('pushedTo' in r) console.log(`${C.g}✓${C.r} pushed → ${r.pushedTo}`);
1337
+ else console.log(r.days ? `${C.g}✓${C.r} pushed ${r.days} days` : `${C.dim}nothing to push yet${C.r}`);
1338
+ console.log(C.dim + ` this machine's id: ${id}${C.r}`);
1339
+ } catch (e) {
1340
+ throw Object.assign(new Error(e.message), { exitCode: 1 });
1341
+ }
1342
+ return;
1343
+ }
1344
+
1244
1345
  if (!isEnabled(cfg)) {
1245
1346
  console.log(`Multi-machine sync is ${C.b}OFF${C.r} by default. To enable it:
1246
1347
 
@@ -1253,22 +1354,21 @@ async function cmdSync() {
1253
1354
  dir: ~/Sync/TokenFlow # that shared folder
1254
1355
  machineName: MacBook Pro # label shown in aggregated views
1255
1356
 
1256
- 3. Run ${C.b}tokenflow sync --push${C.r} on each machine.
1357
+ 3. Run ${C.b}tokenflow sync${C.r} on each machine.
1358
+
1359
+ A shared folder is not the only option: ${C.b}tokenflow sync --to <url> --token <t>${C.r}
1360
+ pushes to a self-hosted team server instead — see docs/team-server.md.
1257
1361
 
1258
1362
  What is shared: daily totals only (date, tokens, requests, est. cost).
1259
1363
  What is never shared: prompts, code, file paths, credentials.`);
1260
1364
  return;
1261
1365
  }
1262
1366
 
1263
- const id = machineId();
1264
- if (flags.push || flags.pull === undefined) {
1265
- // default action with no sub-flag = push + pull
1266
- }
1267
1367
  try {
1268
1368
  if (!flags.pull) {
1269
- const r = push({ config: cfg });
1369
+ const r = await push({ config: cfg });
1270
1370
  console.log(r.days
1271
- ? `${C.g}✓${C.r} pushed ${r.days} days → ${path.basename(r.file)}`
1371
+ ? `${C.g}✓${C.r} pushed ${r.days} days → ${path.basename(r.file || '')}`
1272
1372
  : `${C.dim}nothing to push yet${C.r}`);
1273
1373
  }
1274
1374
  const merged = pull({ config: cfg });
@@ -1312,6 +1412,13 @@ async function cmdDiagnostics() {
1312
1412
 
1313
1413
  /** `tokenflow team` — per-developer usage from the shared sync folder (P4-B). */
1314
1414
  async function cmdTeam() {
1415
+ // `team serve` runs a self-hosted server for machines that push with
1416
+ // `--to` instead of writing into a shared folder — it needs no `sync.dir`
1417
+ // of its own, so this must not fall through to the folder-sync gate below.
1418
+ if (argv[1] === 'serve') {
1419
+ const { run } = await import('../src/commands/team-serve.js');
1420
+ return run(flags);
1421
+ }
1315
1422
  const cfg = loadConfig();
1316
1423
  if (!cfg.sync?.enabled || !cfg.sync?.dir) {
1317
1424
  console.error(`${C.red}Team view reads the shared sync folder.${C.r}
@@ -1460,28 +1567,56 @@ function help(code = 0) {
1460
1567
  ${C.b}Intelligence${C.r}
1461
1568
  tokenflow models-compare cost/request, tokens/request, cache-hit% per model
1462
1569
  (--from/--to <date> to pick the window)
1570
+ tokenflow week this week's spend vs last week (--svg/--png card, --json)
1463
1571
  tokenflow budget --set 200 monthly cap → projected-overrun alerts (dedup'd)
1464
1572
  tokenflow budget current state: safe / approaching / over
1465
- tokenflow digest --deliver build "Your AI Week" and send via configured channels
1573
+ (also evaluates any scoped budgets: repo | team)
1574
+ tokenflow digest --deliver build the weekly digest and send via configured channels
1466
1575
  tokenflow schedule --install --at "Monday 09:00" weekly digest via launchd
1467
1576
  tokenflow schedule --status is the digest schedule installed?
1468
1577
  tokenflow team per-developer usage from the shared sync folder
1578
+ tokenflow team serve self-hosted team server for the folder-sync rollups (--host --port --token --dir; non-loopback host needs a token)
1469
1579
  tokenflow diagnostics version, providers, store freshness, feature states
1470
1580
 
1581
+ ${C.b}Receipts & guard${C.r}
1582
+ tokenflow receipt spend per branch, per repository (sees through worktrees)
1583
+ tokenflow receipt --repo <path> --gh
1584
+ join branches to merged pull requests via gh
1585
+ tokenflow receipt --repo <path> --gh --pr 478 --md
1586
+ one PR-comment receipt (cost, context share, $/100 lines)
1587
+ tokenflow receipt --csv one row per branch, every repository, as CSV
1588
+ tokenflow receipt --branch <b> --svg r.svg [--png r.png]
1589
+ a shareable receipt card (SVG, or SVG + PNG)
1590
+ tokenflow receipt --sessions where the money goes: concentration, context vs work,
1591
+ marginal cost per turn, dollars above a per-session cap
1592
+ tokenflow hooks install|uninstall|status
1593
+ pre-push git hook that attaches a receipt note (never blocks)
1594
+ tokenflow guard --install print the Claude Code hooks block for the circuit breaker
1595
+ tokenflow guard --set warnCostUsd=25,maxCostUsd=200
1596
+ declare thresholds (none declared = informational only)
1597
+ tokenflow guard --session <f> judge one transcript file
1598
+ tokenflow guard --policy [--cwd <dir>]
1599
+ effective guard policy for a directory, and each value's source
1600
+ tokenflow guard --install --codex [--apply]
1601
+ wire up Codex CLI's notify hook (warns only, never blocks)
1602
+
1471
1603
  ${C.b}Sync (optional, off by default)${C.r}
1472
1604
  tokenflow sync push this machine's daily rollups + show merged view
1473
1605
  tokenflow sync --off disable sync entirely
1606
+ tokenflow sync --to <url> --token <t> push both sync files to a team server
1474
1607
 
1475
1608
  ${C.b}Configure${C.r}
1476
1609
  tokenflow pricing show which models have a price, and from where
1477
1610
  tokenflow pricing --sources provenance of every built-in rate + tier multipliers
1478
1611
  tokenflow pricing --set "m=3,15,0.3,3.75"
1612
+ tokenflow pricing diff <table.json> [--apply] [--yes]
1613
+ compare a candidate price table to the current one
1479
1614
  tokenflow import <file> CSV / JSON / JSONL / SQLite with field mapping
1480
1615
  tokenflow restore <file.csv> rebuild the store from a full export, re-priced
1481
1616
  tokenflow config show|path|export|import
1482
1617
 
1483
1618
  ${C.b}Maintain${C.r}
1484
- tokenflow doctor environment, paths, store, adapters
1619
+ tokenflow doctor environment, paths, store, adapters, data-quality audit
1485
1620
  tokenflow validate re-validate every stored record
1486
1621
  tokenflow compact drop superseded records after a rewrite
1487
1622
  tokenflow reset --yes delete ingested data (keeps config)