@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
@@ -0,0 +1,330 @@
1
+ # TokenFlow design tokens — the single source every surface compiles from.
2
+ #
3
+ # npm run design compile → src/ui/styles.css (generated block),
4
+ # site/styles.css (generated block),
5
+ # menubar/TokenFlow/DesignTokens.swift
6
+ # npm run design:check compile in memory, diff against the files, run gates
7
+ #
8
+ # Roles, not values: --plane / --text-muted / --accent stay true when a skin
9
+ # flips mode. Hex values are quoted because `#` starts a YAML comment.
10
+ # Doctrine and the reasoning behind every rule: docs/design-system.md.
11
+
12
+ version: 1
13
+ name: tokenflow
14
+
15
+ # ------------------------------------------------------------------ fonts ---
16
+ # The dashboard and the menu bar stay on the platform's own faces: they ship
17
+ # optical sizing and tracking tables, and the offline snapshot must open from
18
+ # file:// with nothing to fetch. The landing page self-hosts its faces (below).
19
+ fonts:
20
+ sans: "system-ui, -apple-system, 'Segoe UI', sans-serif"
21
+ mono: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace"
22
+ serif: "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', Georgia, serif"
23
+
24
+ # ------------------------------------------------------------------- type ---
25
+ # Size-specific tracking and leading: large text tightens, small text opens.
26
+ # One letter-spacing for every size is wrong somewhere.
27
+ type:
28
+ micro:
29
+ size: 10.5
30
+ tracking: ".02em"
31
+ leading: 1.3
32
+ label:
33
+ size: 11.5
34
+ tracking: ".012em"
35
+ leading: 1.35
36
+ caption:
37
+ size: 12.5
38
+ tracking: ".004em"
39
+ leading: 1.4
40
+ body:
41
+ size: 13.5
42
+ tracking: "0"
43
+ leading: 1.45
44
+ title:
45
+ size: 15
46
+ tracking: "-.006em"
47
+ leading: 1.3
48
+ figure:
49
+ size: 25
50
+ tracking: "-.015em"
51
+ leading: 1.12
52
+ hero:
53
+ size: 42
54
+ tracking: "-.025em"
55
+ leading: 1.05
56
+ display:
57
+ size: 60
58
+ tracking: "-.03em"
59
+ leading: 1.0
60
+
61
+ # ------------------------------------------------------------------ space ---
62
+ # A 4-pt scale with the two half-steps the dense layouts actually use.
63
+ space: [2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 32, 40, 56, 72]
64
+
65
+ radius:
66
+ xs: 5
67
+ sm: 8
68
+ md: 12
69
+ lg: 16
70
+ pill: 999
71
+
72
+ # ----------------------------------------------------------------- motion ---
73
+ # Two tiers. `ui` governs the dashboard and menu bar: nothing over 300ms, press
74
+ # feedback instant, no animation on keyboard-initiated actions. `narrative`
75
+ # governs the landing page and other reading surfaces, where a reveal may take
76
+ # a beat. Under prefers-reduced-motion every primitive degrades to an instant,
77
+ # complete state — never to a half-state.
78
+ motion:
79
+ ui:
80
+ press: 120ms
81
+ fast: 160ms
82
+ base: 220ms
83
+ slow: 300ms
84
+ ease-out: "cubic-bezier(.23, 1, .32, 1)"
85
+ ease-in-out: "cubic-bezier(.77, 0, .175, 1)"
86
+ ease-drawer: "cubic-bezier(.32, .72, 0, 1)"
87
+ stagger: 40ms
88
+ stagger-max: 6
89
+ narrative:
90
+ fast: 180ms
91
+ base: 320ms
92
+ slow: 700ms
93
+ ease: "cubic-bezier(.2, .7, .3, 1)"
94
+ ease-out: "cubic-bezier(.16, 1, .3, 1)"
95
+ stagger: 85ms
96
+ stagger-max: 7
97
+
98
+ # ----------------------------------------------------------------- status ---
99
+ # Reserved for state. Never a series colour, never themed, always paired with
100
+ # an icon or a word. Values are the dataviz reference status palette.
101
+ status:
102
+ good: "#0ca30c"
103
+ warning: "#fab219"
104
+ serious: "#ec835a"
105
+ critical: "#d03b3b"
106
+ # On light surfaces `warning` sits near series-4 (yellow) and `critical` near
107
+ # series-8 (red). Status is never drawn as a chart mark and always travels
108
+ # with an icon or a word, so the two can share a hue family without one
109
+ # impersonating the other. The compiler reports these as warnings, not passes.
110
+ relief:
111
+ light: [warning, critical]
112
+
113
+ # ----------------------------------------------------------------- charts ---
114
+ # Authored ramps, never derived. The categorical steps belong to the MODE, not
115
+ # the skin, and were validated against every skin's chart surface, so a skin
116
+ # restyles the room and never the data. Fixed order, assigned by entity: a
117
+ # filter that removes a series must not repaint the survivors. Past eight,
118
+ # fold into "Other".
119
+ series:
120
+ dark: ["#3987e5", "#d95926", "#199e70", "#c98500", "#d55181", "#008300", "#9085e9", "#e66767"]
121
+ light: ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", "#e87ba4", "#008300", "#4a3aa7", "#e34948"]
122
+ # Slots documented under 3:1 on light chart surfaces. The relief ships with
123
+ # every chart: a table twin and direct labels, so colour never carries the
124
+ # value alone. The compiler warns on these instead of failing.
125
+ relief:
126
+ light: [3, 4, 5]
127
+
128
+ # One hue, light → dark, for magnitude.
129
+ sequential:
130
+ dark: ["#184f95", "#1c5cab", "#256abf", "#2a78d6", "#3987e5", "#5598e7", "#86b6ef"]
131
+ light: ["#cde2fb", "#9ec5f4", "#6da7ec", "#3987e5", "#2a78d6", "#1c5cab", "#104281"]
132
+
133
+ # Two hues and a neutral grey midpoint, for polarity (period compare, vs-median).
134
+ # Never a hue at the midpoint.
135
+ diverging:
136
+ dark: ["#d95926", "#e39a78", "#6b7385", "#7fb0f0", "#3987e5"]
137
+ light: ["#eb6834", "#f2b28f", "#9aa2b3", "#8dbaf0", "#2a78d6"]
138
+
139
+ # ------------------------------------------------------------------ skins ---
140
+ # A skin restyles the room: surfaces, ink, borders, accent, density, the faces
141
+ # used for headings and figures. It never touches the series steps above.
142
+ # Hierarchy is carried by lightness, never by hue; the accent is an accent.
143
+ skins:
144
+ aurora:
145
+ label: Aurora
146
+ note: "Indigo-slate, layered, luminous"
147
+ default: true
148
+ display: sans
149
+ figure: sans
150
+ density:
151
+ radius: 14
152
+ radius-sm: 9
153
+ pad-card: "14px 16px"
154
+ gap-grid: 14px
155
+ fig-scale: 1
156
+ sec-space: 26px
157
+ tracking-h: "-0.015em"
158
+ dark:
159
+ plane: "#0a0d14"
160
+ surface-1: "#111624"
161
+ surface-2: "#171d2e"
162
+ surface-3: "#1f2637"
163
+ text-primary: "#f3f6fc"
164
+ text-secondary: "#a7b2c9"
165
+ text-muted: "#7d879e"
166
+ grid: "#1e2436"
167
+ axis: "#2b3348"
168
+ border: "rgba(154, 172, 255, 0.11)"
169
+ border-strong: "rgba(154, 172, 255, 0.20)"
170
+ hairline: "rgba(154, 172, 255, 0.066)"
171
+ accent: "#8f9dff"
172
+ accent-solid: "#4a5ae0"
173
+ accent-ink: "#ffffff"
174
+ shadow: "0 1px 2px rgba(0, 0, 0, .45), 0 14px 40px rgba(4, 8, 20, .45)"
175
+ glow: "radial-gradient(1100px 460px at 8% -10%, rgba(94, 114, 255, .17), transparent 62%), radial-gradient(820px 420px at 92% -14%, rgba(25, 158, 112, .11), transparent 60%)"
176
+ hero-glow: "radial-gradient(460px 220px at 8% 0%, rgba(94, 114, 255, .22), transparent 72%)"
177
+ light:
178
+ plane: "#f5f7fc"
179
+ surface-1: "#ffffff"
180
+ surface-2: "#f1f4fa"
181
+ surface-3: "#e6eaf4"
182
+ text-primary: "#0d1220"
183
+ text-secondary: "#4b5468"
184
+ text-muted: "#6c7488"
185
+ grid: "#e4e8f1"
186
+ axis: "#c7cddb"
187
+ border: "rgba(13, 18, 32, 0.09)"
188
+ border-strong: "rgba(13, 18, 32, 0.17)"
189
+ hairline: "rgba(13, 18, 32, 0.054)"
190
+ accent: "#3d4dd6"
191
+ accent-solid: "#3d4dd6"
192
+ accent-ink: "#ffffff"
193
+ shadow: "0 1px 2px rgba(16, 24, 48, .06), 0 12px 32px rgba(16, 24, 48, .08)"
194
+ glow: "radial-gradient(1100px 460px at 8% -10%, rgba(94, 114, 255, .10), transparent 62%)"
195
+ hero-glow: "radial-gradient(420px 180px at 12% 0%, rgba(94, 114, 255, .09), transparent 70%)"
196
+
197
+ terminal:
198
+ label: Terminal
199
+ note: "Near-black, hairlines, mono"
200
+ display: mono
201
+ figure: mono
202
+ density:
203
+ radius: 5
204
+ radius-sm: 4
205
+ pad-card: "11px 13px"
206
+ gap-grid: 9px
207
+ fig-scale: 0.94
208
+ sec-space: 20px
209
+ tracking-h: "0.01em"
210
+ dark:
211
+ plane: "#06070a"
212
+ surface-1: "#0c0e13"
213
+ surface-2: "#11141a"
214
+ surface-3: "#171b22"
215
+ text-primary: "#eef2f6"
216
+ text-secondary: "#9aa4ad"
217
+ text-muted: "#737b85"
218
+ grid: "#171b22"
219
+ axis: "#262c34"
220
+ border: "rgba(255, 255, 255, 0.09)"
221
+ border-strong: "rgba(255, 255, 255, 0.16)"
222
+ hairline: "rgba(255, 255, 255, 0.054)"
223
+ accent: "#24d67e"
224
+ accent-solid: "#24d67e"
225
+ accent-ink: "#04140b"
226
+ shadow: "0 0 0 1px rgba(255, 255, 255, .05), 0 10px 26px rgba(0, 0, 0, .55)"
227
+ glow: "none"
228
+ hero-glow: "none"
229
+ light:
230
+ plane: "#f7f8f9"
231
+ surface-1: "#ffffff"
232
+ surface-2: "#f2f4f5"
233
+ surface-3: "#e8eaec"
234
+ text-primary: "#0a0c0f"
235
+ text-secondary: "#494f57"
236
+ text-muted: "#666d75"
237
+ grid: "#e8eaec"
238
+ axis: "#c9cdd2"
239
+ border: "rgba(10, 12, 15, 0.10)"
240
+ border-strong: "rgba(10, 12, 15, 0.18)"
241
+ hairline: "rgba(10, 12, 15, 0.06)"
242
+ accent: "#0b6b3d"
243
+ accent-solid: "#0b6b3d"
244
+ accent-ink: "#ffffff"
245
+ shadow: "0 0 0 1px rgba(10, 12, 15, .05), 0 8px 20px rgba(10, 12, 15, .07)"
246
+ glow: "none"
247
+ hero-glow: "none"
248
+
249
+ editorial:
250
+ label: Editorial
251
+ note: "Warm charcoal, serif figures"
252
+ display: serif
253
+ figure: serif
254
+ density:
255
+ radius: 8
256
+ radius-sm: 6
257
+ pad-card: "20px 22px"
258
+ gap-grid: 18px
259
+ fig-scale: 1.12
260
+ sec-space: 38px
261
+ tracking-h: "-0.005em"
262
+ dark:
263
+ plane: "#16130c"
264
+ surface-1: "#201c13"
265
+ surface-2: "#282318"
266
+ surface-3: "#322b1f"
267
+ text-primary: "#f8f4ec"
268
+ text-secondary: "#c4bcab"
269
+ text-muted: "#998f7d"
270
+ grid: "#262218"
271
+ axis: "#3a3529"
272
+ border: "rgba(255, 240, 214, 0.10)"
273
+ border-strong: "rgba(255, 240, 214, 0.18)"
274
+ hairline: "rgba(255, 240, 214, 0.06)"
275
+ accent: "#e0a852"
276
+ accent-solid: "#e0a852"
277
+ accent-ink: "#1a1409"
278
+ shadow: "0 1px 2px rgba(0, 0, 0, .4), 0 12px 34px rgba(20, 14, 4, .38)"
279
+ glow: "radial-gradient(900px 420px at 88% -12%, rgba(224, 168, 82, .11), transparent 60%)"
280
+ hero-glow: "none"
281
+ light:
282
+ plane: "#f9f6ef"
283
+ surface-1: "#fffdf8"
284
+ surface-2: "#f4f0e6"
285
+ surface-3: "#eae5d8"
286
+ text-primary: "#1a1712"
287
+ text-secondary: "#544e42"
288
+ text-muted: "#736c60"
289
+ grid: "#e8e2d4"
290
+ axis: "#cbc4b2"
291
+ border: "rgba(26, 23, 18, 0.10)"
292
+ border-strong: "rgba(26, 23, 18, 0.18)"
293
+ hairline: "rgba(26, 23, 18, 0.06)"
294
+ accent: "#8a5514"
295
+ accent-solid: "#8a5514"
296
+ accent-ink: "#ffffff"
297
+ shadow: "0 1px 2px rgba(60, 45, 20, .06), 0 12px 30px rgba(60, 45, 20, .08)"
298
+ glow: "radial-gradient(900px 420px at 88% -12%, rgba(224, 168, 82, .13), transparent 60%)"
299
+ hero-glow: "none"
300
+
301
+ # ---------------------------------------------------------------- landing ---
302
+ # The landing page is a single-world dark surface in the Aurora room, with the
303
+ # instrument-panel voice of design-kit's `house-plex` pairing: a geometric
304
+ # grotesque for display, IBM Plex Sans for body, and the same tabular Plex Mono
305
+ # the receipts use for figures. A sans display is the default for a developer
306
+ # tool; a serif would need a brand reason this product does not have. Faces are
307
+ # self-hosted under site/fonts/ (OFL-1.1, copied from the design-kit cache); a
308
+ # product that promises nothing leaves your machine does not load fonts from a
309
+ # third party.
310
+ landing:
311
+ world: aurora
312
+ mode: dark
313
+ pairing: house-plex
314
+ fonts:
315
+ display:
316
+ family: Space Grotesk
317
+ file: space-grotesk
318
+ weights: [500, 600, 700]
319
+ fallback: sans
320
+ tracking: "-.018em"
321
+ body:
322
+ family: IBM Plex Sans
323
+ file: ibm-plex-sans
324
+ weights: [400, 500, 600]
325
+ fallback: sans
326
+ mono:
327
+ family: IBM Plex Mono
328
+ file: ibm-plex-mono
329
+ weights: [400, 500, 600]
330
+ fallback: mono
@@ -180,8 +180,9 @@ Every chart ships a **table twin** toggled per card, so no value is reachable on
180
180
 
181
181
  ## Privacy by construction
182
182
 
183
- There is no HTTP client anywhere in this codebase not for telemetry, not for pricing, not for
184
- updates. The server binds to loopback and refuses non-GET requests from a foreign origin without a
183
+ The product makes no network call on its own. The only outbound HTTP is the opt-in
184
+ `tokenflow sync --to`, which posts your two sync files to a team server you run, and the GitHub
185
+ Action, which runs in CI. Nothing for telemetry, pricing, or updates. The server binds to loopback and refuses non-GET requests from a foreign origin without a
185
186
  token. Adapters read token counts and metadata and discard content; the schema has nowhere to put
186
187
  a prompt. SQLite sources are read from a temp snapshot so a live editor is never disturbed.
187
188
 
@@ -189,5 +190,5 @@ a prompt. SQLite sources are read from a temp snapshot so a live editor is never
189
190
 
190
191
  `user`, `machine`, `session_id`, `project` and `repository` are first-class, and the cube is
191
192
  additive — so cubes from several machines can be merged by concatenating rows and re-summing
192
- duplicated dimension tuples. V1 ships no identity, auth or sync, deliberately. Nothing here makes
193
- them impossible to add.
193
+ duplicated dimension tuples. Identity stays opt-in per person. Sync is file-based, or to a server you run
194
+ (`tokenflow team serve`) behind a shared token. No hosted service holds anyone's usage.
package/docs/cli.md CHANGED
@@ -142,6 +142,143 @@ tokenflow digest --out week.md # write instead of print
142
142
 
143
143
  ---
144
144
 
145
+ ### `receipt`
146
+ What a branch or pull request cost. Spend is attributed per turn to the branch checked out when
147
+ the turn ran, so a long session that moves across branches is split across them. Repositories are
148
+ identified by walking up from each recorded working directory to `.git` and following a worktree's
149
+ `gitdir:` pointer back to the main checkout — every `.worktrees/<x>` counts as the repo it belongs
150
+ to. With `--gh`, merged pull requests are fetched through the GitHub CLI and joined by head branch;
151
+ each matched receipt then carries `+/−` lines and cost per 100 changed lines.
152
+
153
+ Each receipt also splits the estimate into **context** dollars (cache reads + writes: the cost of
154
+ re-sending the conversation so far) and **work** dollars (fresh input + output). Context is the
155
+ cheap path per token — a high share is not waste, it is where the lever is.
156
+
157
+ ```bash
158
+ tokenflow receipt # every repo, top branches by spend
159
+ tokenflow receipt --repo ~/code/api --gh # one repo, joined to its merged PRs
160
+ tokenflow receipt --repo api --branch feat/x # one branch as a PR-comment receipt
161
+ tokenflow receipt --repo ~/code/api --gh --pr 478 --md
162
+ tokenflow receipt --prs prs.json # PR list you exported yourself:
163
+ # gh pr list --state merged --json number,headRefName,additions,deletions,title,mergedAt
164
+ tokenflow receipt --sessions # where the money goes across sessions
165
+ tokenflow receipt --sessions --cap 25,100,500 # dollars above each candidate per-session cap
166
+ tokenflow receipt --from 2026-06-01 --top 30 --automated '^release/' # flag automated PRs
167
+ tokenflow receipt --json # everything, machine-readable
168
+ tokenflow receipt --csv > receipts.csv # one row per branch, every repository
169
+ tokenflow receipt --branch feat/x --svg r.svg --png r.png # a shareable receipt card
170
+ ```
171
+
172
+ A merged PR owns the turns on its branch **up to the merge**, including work done before the PR
173
+ was opened (that work is how the PR came to exist). Turns *after* the merge are follow-up on a
174
+ checkout that kept the old branch name; they appear beside the receipt as "After the merge, same
175
+ branch" and are never counted inside it. A branch with several merged PRs attributes each turn to
176
+ the PR it shipped in. Long-lived branches (`main`, `staging`, …) are flagged: a receipt there is
177
+ for a period of work, not one change. A branch name that is deleted and reused inherits the
178
+ earlier branch's pre-merge turns under this rule; give reused names a suffix if that matters.
179
+
180
+ Sessions on a detached `HEAD` or with no branch recorded are reported as *unattributed*, never
181
+ guessed. Unpriced turns are counted and excluded from the total, never shown as `$0`.
182
+ `--sessions` labels its cap table as an upper bound: the dollars above a cap are what a guard could
183
+ have held back at most, not a saving.
184
+
185
+ `--csv` writes one row per branch (`repo, branch, costUsd, contextShare, sessions, turns,
186
+ subagentTurns, first, last, longLived, prNumber, mergedAt, changedLines, costPer100Lines`) across
187
+ every repository the plain-text table would show; combine with `--repo` to scope it to one. `--svg`
188
+ writes a shareable card built from a single branch or PR receipt (`--branch` or `--pr`); add `--png`
189
+ to also render a PNG through a local Chromium/Chrome (the SVG is still written if none is found).
190
+ Every colour on the card comes from `design/tokens.yaml`, so it matches the configured skin and
191
+ mode. See [exports-and-budgets.md](exports-and-budgets.md).
192
+
193
+ ### `week`
194
+ "Your AI week": spend this calendar week (Monday through today) against last week, context share,
195
+ turns, sessions, the three costliest branches, and the single most expensive session, computed
196
+ from the same store as everything else.
197
+
198
+ ```bash
199
+ tokenflow week # text summary
200
+ tokenflow week --svg week.svg # a shareable card
201
+ tokenflow week --svg week.svg --png week.png # card + a local screenshot
202
+ tokenflow week --json # everything, machine-readable
203
+ ```
204
+
205
+ See [exports-and-budgets.md](exports-and-budgets.md).
206
+
207
+ ### `budget`
208
+ Monthly spend cap with projected-overrun alerts, deduplicated so a state only alerts once.
209
+
210
+ ```bash
211
+ tokenflow budget --set 200 # $200/month, warn at 80% projected
212
+ tokenflow budget # current state: safe / approaching / over
213
+ ```
214
+
215
+ A `budgets:` list in `config.yaml` adds caps scoped to one repository or to the whole team,
216
+ alongside the single monthly cap above; `tokenflow budget` prints both when any are configured. See
217
+ [configuration.md](configuration.md) for the `budgets:` shape and
218
+ [exports-and-budgets.md](exports-and-budgets.md) for how each scope's spend is computed.
219
+
220
+ ---
221
+
222
+ ### `guard`
223
+ A circuit breaker for the session you are in, run as a Claude Code hook. It reads the live
224
+ transcript with the same adapter and price table as everything else, then reports the running
225
+ spend, the size of the prompt now being re-sent, and the median cost of the last ten turns — and
226
+ warns or blocks against thresholds **you** declared. With none declared it is informational and
227
+ never blocks.
228
+
229
+ ```bash
230
+ tokenflow guard --install # print the settings.json hooks block (not written)
231
+ tokenflow guard --set warnCostUsd=25,maxCostUsd=200,warnContextTokens=200000
232
+ tokenflow guard --set maxCostUsd= # clear one threshold
233
+ tokenflow guard --session ~/.claude/projects/<slug>/<id>.jsonl # judge one transcript
234
+ ```
235
+
236
+ Hook contract: stdin carries `{session_id, transcript_path, hook_event_name, …}`. A warning is
237
+ returned as JSON — `hookSpecificOutput.additionalContext` reaches the model,
238
+ `systemMessage` reaches you. A declared cap that is reached exits `2`, which blocks a
239
+ `PreToolUse` tool call or rejects a `UserPromptSubmit` prompt with the reason on stderr; `Stop`
240
+ and `SessionStart` are never blocked. The read is incremental: each session's byte offset and open
241
+ streaming groups are kept under `~/.tokenflow/guard/`, so a large transcript is read once.
242
+
243
+ Keys: `warnCostUsd`, `maxCostUsd`, `warnContextTokens`, `maxContextTokens`, `warnMarginalUsd`.
244
+
245
+ A repository can declare its own caps in `.tokenflow/policy.yaml`, committed with the code; a
246
+ declared key there wins over the personal config, key by key.
247
+
248
+ ```bash
249
+ tokenflow guard --policy # effective policy for the current directory
250
+ tokenflow guard --policy --cwd <dir> # for some other directory / repository
251
+ ```
252
+
253
+ Codex CLI has no blocking hook, so its integration only warns, through an OS notification:
254
+
255
+ ```bash
256
+ tokenflow guard --install --codex # print the notify = [...] line for config.toml
257
+ tokenflow guard --install --codex --apply # append it, only if safe to do so
258
+ ```
259
+
260
+ See [guard-codex.md](guard-codex.md) for the policy file shape and the Codex `notify` contract.
261
+
262
+ ---
263
+
264
+ ### `hooks`
265
+ Installs a `pre-push` git hook that attaches a receipt to the pushed commit as a git note under
266
+ `refs/notes/tokenflow`, so the receipt travels with the code instead of living only on this
267
+ machine. The [GitHub Action](../action/README.md) reads that note on a pull request and posts or
268
+ updates one comment.
269
+
270
+ ```bash
271
+ tokenflow hooks install # write .git/hooks/pre-push
272
+ tokenflow hooks uninstall # remove it, restoring anything it replaced
273
+ tokenflow hooks status # installed? chained? where?
274
+ ```
275
+
276
+ The hook never blocks a push: every failure is reported on stderr with exit `0`. A hook it
277
+ replaces is kept as `pre-push.tokenflow-chained` and always runs first, keeping its own exit
278
+ code, so a pre-existing gate still gates. See [receipt-schema.md](receipt-schema.md).
279
+
280
+ ---
281
+
145
282
  ### `providers`
146
283
  What is detected, connected, or disabled, with the reason for each. `--json`.
147
284
 
@@ -168,6 +305,50 @@ Missing values are written as empty cells, never as `0`.
168
305
 
169
306
  ---
170
307
 
308
+ ## Team & sync
309
+
310
+ ### `sync`
311
+ Optional, off by default: exchange daily rollups (and, unless disabled, a branch/PR receipt
312
+ ledger) between machines through a folder you already sync, or by pushing to a server you run.
313
+
314
+ ```bash
315
+ tokenflow sync # push this machine's files, then show the merged view
316
+ tokenflow sync --pull # merged view only, no push
317
+ tokenflow sync --off # disable sync entirely
318
+ tokenflow sync --to <url> --token <t> # push both files to a team server instead of a folder
319
+ ```
320
+
321
+ `--to` (or `sync.to` in `config.yaml`) sends the exact same two files to
322
+ `tokenflow team serve` (or any server that speaks its `/api/rollup` contract) instead of writing
323
+ them into `sync.dir`; there is no merged view to pull from a server, so this only pushes. The
324
+ token can come from `--token`, `sync.token`, or the `TOKENFLOW_SYNC_TOKEN` environment variable.
325
+ `sync.receipts: false` skips the ledger file. See [ledger.md](ledger.md) and
326
+ [team-server.md](team-server.md).
327
+
328
+ ### `team`
329
+ Per-developer usage, joined from every machine's synced rollups, plus the branch/PR cost ledger
330
+ once any machine has pushed one.
331
+
332
+ ```bash
333
+ tokenflow team # aggregated view from the shared sync folder
334
+ tokenflow team --json # machine-readable
335
+ tokenflow team serve # self-hosted server: same aggregate, over HTTP
336
+ ```
337
+
338
+ `team serve` runs one process on a machine the team owns (LAN or Docker) so every machine can push
339
+ with `sync --to` instead of writing into a shared folder.
340
+
341
+ | Flag | Default | Meaning |
342
+ |---|---|---|
343
+ | `--host` | `127.0.0.1` | bind address; non-loopback requires a token |
344
+ | `--port` | `7790` | bind port |
345
+ | `--dir` | `<TOKENFLOW_HOME>/team` | where uploaded rollups are stored |
346
+ | `--token` | — (falls back to `TOKENFLOW_TEAM_TOKEN`) | shared bearer token |
347
+
348
+ See [team-server.md](team-server.md) for the auth model and routes.
349
+
350
+ ---
351
+
171
352
  ## Live
172
353
 
173
354
  These four read the watcher's snapshot (`data/status.json`) when it is fresh
@@ -261,6 +442,22 @@ fetched, and whether the source is official or third-party — plus the service-
261
442
  that get applied per request, and the one premium tier that is deliberately *not* applied
262
443
  (long-context).
263
444
 
445
+ ### `pricing diff`
446
+ Compares a candidate price table to the current effective one (the overrides file layered on the
447
+ built-in table, same as every other command resolves it) and prints what would change: models
448
+ added, rates changed with the percent change per field, and current overrides the candidate says
449
+ nothing about.
450
+
451
+ ```bash
452
+ tokenflow pricing diff table.json # print the diff only
453
+ tokenflow pricing diff table.json --apply # print, then confirm interactively (y/N)
454
+ tokenflow pricing diff table.json --apply --yes # print and apply, no prompt (scripts, CI)
455
+ ```
456
+
457
+ `--apply` merges the candidate's models into the overrides file; it never replaces it, and a
458
+ model the candidate does not mention keeps its existing override untouched. Refused when the
459
+ candidate has an invalid entry, or (without `--yes`) when stdin is not an interactive terminal.
460
+
264
461
  ### `import <file>`
265
462
  Generic import for CSV / TSV / JSON / JSONL / SQLite. With no `--field` flags it prints the
266
463
  columns it found, infers a mapping, and previews five normalized rows.
@@ -328,6 +525,13 @@ tokenflow config import backup.json # restore on another machine
328
525
  Runtime, `node:sqlite` availability, timezone, every path, store size, cube rows, sessions,
329
526
  pending compaction, and every adapter's detection status. Start here when something is off.
330
527
 
528
+ After the Store section, an **Audit** block runs seven data-quality checks over the last three
529
+ months of records: git worktrees fragmenting a repository's spend, a recorded cwd outside any
530
+ repository, Codex records with no git branch, unpriced models, a stale built-in price table,
531
+ session-level sources present in per-turn views, and records explicitly marked
532
+ `metadata.repoResolved === false`. Each line names the check, its severity (`ok` / `info` / `warn`
533
+ / `fail`), and a one-line fix when it is not clean.
534
+
331
535
  ### `validate`
332
536
  Re-validates every stored record against the schema and reports the failure modes by frequency.
333
537
  Exits non-zero if anything is invalid.