@vimoxshah/tokenflow 1.1.1 → 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 (87) hide show
  1. package/CHANGELOG.md +228 -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-dmg.sh +11 -2
  25. package/scripts/build-menubar-app.sh +58 -7
  26. package/scripts/design-build.js +475 -0
  27. package/src/analytics/anatomy.js +467 -0
  28. package/src/analytics/branch-compare.js +159 -0
  29. package/src/analytics/cache-health.js +141 -0
  30. package/src/analytics/live-view.js +266 -0
  31. package/src/analytics/receipt-schema.js +214 -0
  32. package/src/analytics/receipt.js +709 -0
  33. package/src/analytics/rhythm.js +184 -0
  34. package/src/analytics/whatif.js +263 -0
  35. package/src/commands/budget-scopes.js +133 -0
  36. package/src/commands/doctor-checks.js +400 -0
  37. package/src/commands/guard.js +531 -0
  38. package/src/commands/hooks.js +238 -0
  39. package/src/commands/pricing-diff.js +316 -0
  40. package/src/commands/receipt.js +226 -0
  41. package/src/commands/team-serve.js +407 -0
  42. package/src/commands/week.js +86 -0
  43. package/src/core/annotations.js +97 -0
  44. package/src/core/budget.js +33 -0
  45. package/src/core/bundle.js +45 -2
  46. package/src/core/ingest.js +33 -0
  47. package/src/core/live-status.js +227 -2
  48. package/src/core/policy.js +103 -0
  49. package/src/core/receipt-note.js +123 -0
  50. package/src/core/repo.js +64 -0
  51. package/src/core/sync.js +163 -26
  52. package/src/core/team.js +0 -0
  53. package/src/export/html-snapshot.js +28 -1
  54. package/src/export/menubar.js +21 -0
  55. package/src/export/receipt-card.js +210 -0
  56. package/src/export/week-card.js +185 -0
  57. package/src/providers/mock/index.js +383 -52
  58. package/src/providers/openai/index.js +31 -1
  59. package/src/providers/otel/index.js +656 -0
  60. package/src/server/routes/annotations.js +42 -0
  61. package/src/server/routes/cache-health.js +95 -0
  62. package/src/server/routes/index.js +54 -0
  63. package/src/server/routes/session.js +157 -0
  64. package/src/server/server.js +47 -1
  65. package/src/ui/app.js +541 -308
  66. package/src/ui/charts.js +95 -0
  67. package/src/ui/first-run.js +144 -0
  68. package/src/ui/index.html +4 -1
  69. package/src/ui/palette.js +335 -0
  70. package/src/ui/styles/anatomy.css +117 -0
  71. package/src/ui/styles/annotations.css +40 -0
  72. package/src/ui/styles/branches.css +99 -0
  73. package/src/ui/styles/cache.css +6 -0
  74. package/src/ui/styles/first-run.css +31 -0
  75. package/src/ui/styles/live.css +100 -0
  76. package/src/ui/styles/palette.css +85 -0
  77. package/src/ui/styles/rhythm.css +8 -0
  78. package/src/ui/styles/whatif.css +55 -0
  79. package/src/ui/styles.css +303 -196
  80. package/src/ui/views/anatomy.js +567 -0
  81. package/src/ui/views/annotations.js +121 -0
  82. package/src/ui/views/branches.js +304 -0
  83. package/src/ui/views/cache.js +232 -0
  84. package/src/ui/views/index.js +85 -0
  85. package/src/ui/views/live.js +683 -0
  86. package/src/ui/views/rhythm.js +206 -0
  87. package/src/ui/views/whatif.js +196 -0
@@ -11,8 +11,10 @@ edit it. This page is the complete reference for when you do. `tokenflow config
11
11
  *effective* config (defaults merged with your file), and `tokenflow config path` prints the data
12
12
  home.
13
13
 
14
- Nothing here is ever transmitted anywhere. There are no keys, tokens, or accounts to configure
15
- if a field looks like it wants a credential, you are reading the wrong project.
14
+ Nothing here is transmitted anywhere unless you explicitly point it somewhere you own. The two
15
+ exceptions are opt-in: `sync.token` (a bearer token for your own team server, never a vendor
16
+ credential) and `delivery:` (your own webhook/Telegram/email). If a field looks like it wants a
17
+ vendor API key or account, you are reading the wrong project.
16
18
 
17
19
  ---
18
20
 
@@ -69,6 +71,10 @@ sources:
69
71
  scanRoots: ["~/code", "~/work"] # directories to scan for repositories
70
72
  repos: [] # or list them explicitly
71
73
  autoFromUsage: true # also use working dirs seen in usage records
74
+ otel:
75
+ # OpenTelemetry GenAI file exports. Defaults look for ~/.gemini/telemetry.log
76
+ # and ~/.tokenflow/otel/*.{jsonl,ndjson,json,log}; add paths of your own.
77
+ paths: ["~/code/my-project/.gemini/telemetry.log"]
72
78
 
73
79
  store:
74
80
  keepRaw: true # keep request-level records (Data Explorer + full export)
@@ -101,6 +107,23 @@ ui:
101
107
  port: 7799 # the dashboard binds 127.0.0.1 on this port
102
108
  defaultRange: all # all | 7d | 30d | 90d | mtd
103
109
  defaultFrom: null # e.g. "2026-03-14" — a floor for the default view only
110
+
111
+ # Optional, off by default. See the `sync` and `budgets` sections below.
112
+ sync:
113
+ enabled: false
114
+ dir: ~/Sync/TokenFlow # a shared folder both machines can see
115
+ machineName: MacBook Pro
116
+ developerName: null # opt-in only; omit to stay anonymous in team views
117
+ receipts: true # set false to skip the branch/PR ledger file
118
+ to: null # POST to a server instead of writing into `dir`
119
+ token: null # bearer token for `to` (or env TOKENFLOW_SYNC_TOKEN)
120
+
121
+ budgets:
122
+ - id: api-monthly
123
+ scope: repo # total | repo | team
124
+ repo: api # required when scope is repo
125
+ monthlyUsd: 150
126
+ warnAt: 0.8 # optional, default 0.8
104
127
  ```
105
128
 
106
129
  ---
@@ -176,6 +199,96 @@ the exact reset instant in your timezone. Invalid definitions are reported via
176
199
  `tokenflow capacity --json` (`invalid`) and by the dashboard — never silently
177
200
  ignored. See [live-mode.md](live-mode.md) for semantics.
178
201
 
202
+ ### `guard` — thresholds for the in-session circuit breaker
203
+
204
+ Read by `tokenflow guard` when it runs as a Claude Code hook. Absent or all-null means the hook
205
+ reports and never blocks. Values are declared, never inferred.
206
+
207
+ ```yaml
208
+ guard:
209
+ warnCostUsd: 25 # session spend at which the hook warns (context + systemMessage)
210
+ maxCostUsd: 200 # session spend at which PreToolUse / UserPromptSubmit are blocked
211
+ warnContextTokens: 200000 # prompt size (fresh + cache read + cache write) at which to warn
212
+ maxContextTokens: null # prompt size at which to block
213
+ warnMarginalUsd: null # median cost of the last ten turns at which to warn
214
+ ```
215
+
216
+ `tokenflow guard --set warnCostUsd=25,maxCostUsd=200` writes these; an empty value clears one.
217
+
218
+ A repository can declare its own caps in `.tokenflow/policy.yaml`, committed with the code, so
219
+ the cap travels with everyone who clones it:
220
+
221
+ ```yaml
222
+ # <repo root>/.tokenflow/policy.yaml
223
+ guard:
224
+ warnCostUsd: 10
225
+ maxCostUsd: 50
226
+ note: "This repo's sessions run long — reasoning-heavy refactors, not chat."
227
+ ```
228
+
229
+ A key declared there wins over `~/.tokenflow/config.yaml`, key by key; a key declared nowhere is
230
+ `null` (informational only). `tokenflow guard --policy [--cwd <dir>]` shows the effective value
231
+ for each key and which file it came from. `note` is optional and shown beside a triggered
232
+ warning/block and beside `guard --policy`'s output. See [guard-codex.md](guard-codex.md).
233
+
234
+ ### `sources.otel.paths` — OpenTelemetry (GenAI) file exports
235
+
236
+ ```yaml
237
+ sources:
238
+ otel:
239
+ paths: ["~/code/my-project/.gemini/telemetry.log", "~/.tokenflow/otel"]
240
+ ```
241
+
242
+ Reads a standards-based OTLP JSON export from any tool's file/collector exporter, and Gemini
243
+ CLI's own file telemetry. With no `paths` configured, the adapter still looks in its defaults:
244
+ `~/.gemini/telemetry.log` and `~/.tokenflow/otel/*.{jsonl,ndjson,json,log}`. See
245
+ [providers-otel.md](providers-otel.md).
246
+
247
+ ### `sync` — optional multi-machine aggregation
248
+
249
+ Off by default. Exchanges daily rollups (and, unless disabled, a branch/PR receipt ledger)
250
+ between machines through a folder you already sync, or by pushing to a server you run:
251
+
252
+ ```yaml
253
+ sync:
254
+ enabled: true
255
+ dir: ~/Sync/TokenFlow # a shared folder both machines can see
256
+ machineName: MacBook Pro # label shown in aggregated views
257
+ developerName: null # OPTIONAL, opt-in only — per-developer team views
258
+ receipts: true # set false to skip the branch/PR ledger file
259
+ to: null # POST to a server instead of writing into `dir`
260
+ token: null # bearer token for `to` (or env TOKENFLOW_SYNC_TOKEN)
261
+ ```
262
+
263
+ `to` and `dir` are alternatives, not both required: set `to` (a `tokenflow team serve` URL, or
264
+ any server speaking its contract) to push there instead of `dir`. What is shared is always the
265
+ same coarse shape — date, tokens, requests, estimated cost, plus the branch/PR ledger unless
266
+ `receipts: false` — never prompts, code, file paths, or credentials. See
267
+ [ledger.md](ledger.md) and [team-server.md](team-server.md).
268
+
269
+ ### `budgets` — caps scoped to a repository or a team
270
+
271
+ Alongside the single monthly `budget` above, declare as many scoped budgets as you like:
272
+
273
+ ```yaml
274
+ budgets:
275
+ - id: api-monthly
276
+ scope: repo # total | repo | team
277
+ repo: api # required when scope is repo; matched the same way
278
+ # `tokenflow receipt` resolves a repository (worktrees
279
+ # folded into their main checkout)
280
+ monthlyUsd: 150
281
+ warnAt: 0.8 # optional, default 0.8 (a fraction, like limits[].warnAt)
282
+ - id: team-monthly
283
+ scope: team
284
+ monthlyUsd: 1000
285
+ ```
286
+
287
+ `total` sums every priced turn in the store this month; `repo` sums turns attributed to that
288
+ repository; `team` reads this machine's shared sync folder, if enabled, and says so plainly
289
+ (`no team data`) rather than reporting a number nobody synced. `tokenflow budget` prints every
290
+ scoped row alongside the monthly cap. See [exports-and-budgets.md](exports-and-budgets.md).
291
+
179
292
  ### `watch` — the background refresher
180
293
 
181
294
  ```yaml
@@ -230,6 +343,8 @@ never a plausible-looking `$0`.
230
343
  | `TOKENFLOW_HOME` | move the whole data home (config, pricing, data, cache) |
231
344
  | `TOKENFLOW_DEMO=1` | enable the synthetic demo adapter |
232
345
  | `AI_USAGE_HOME` | legacy alias for `TOKENFLOW_HOME`, still honoured |
346
+ | `TOKENFLOW_SYNC_TOKEN` | bearer token for `sync.to`, instead of `sync.token` |
347
+ | `TOKENFLOW_TEAM_TOKEN` | shared bearer token for `tokenflow team serve` |
233
348
 
234
349
  Running several isolated datasets is just several homes:
235
350
 
@@ -0,0 +1,187 @@
1
+ # Design system
2
+
3
+ One token source, three surfaces, gates that fail closed. This document is the
4
+ doctrine; `design/tokens.yaml` is the source; `scripts/design-build.js` is the
5
+ compiler; `test/design.test.js` is what makes a drift or a hand edit fail CI.
6
+
7
+ ```
8
+ design/tokens.yaml ──► scripts/design-build.js ──► src/ui/styles.css (generated block: dashboard + offline snapshot)
9
+ │ gates site/styles.css (generated block: landing page)
10
+ └──────────────────► menubar/TokenFlow/DesignTokens.swift (whole file: menu bar)
11
+ ```
12
+
13
+ ```bash
14
+ npm run design # compile + write
15
+ npm run design:check # compile in memory, diff, exit 1 on drift or a failed gate
16
+ ```
17
+
18
+ Edit the YAML, run `npm run design`, commit the result. Never edit inside a
19
+ `@generated design-tokens` block; the test diffs it against the compiler.
20
+
21
+ ---
22
+
23
+ ## 1. Roles, not values
24
+
25
+ Every colour is named for the job it does: `--plane`, `--surface-1`,
26
+ `--text-muted`, `--accent`, `--border-strong`. Nothing is named for how it looks.
27
+ A role stays true when a skin flips mode or is replaced; `--grey-200` becomes a
28
+ lie the moment the palette changes. (Borrowed from design-kit's schema.)
29
+
30
+ ## 2. Two axes, kept apart
31
+
32
+ | Axis | Values | Owns |
33
+ |---|---|---|
34
+ | `data-mode` | dark · light | ink polarity, surfaces, **the series steps** |
35
+ | `data-skin` | aurora · terminal · editorial | the room: surfaces, borders, accent, density, display face |
36
+
37
+ The categorical, sequential and diverging steps belong to the **mode**. They were
38
+ validated against every skin's chart surface, so a skin restyles the room and
39
+ never the data. Switching theme must not change what a colour means.
40
+
41
+ **Fidelity beats variety.** Three skins are enough. A fourth is a deliberate
42
+ decision with a stated reason, not a Friday afternoon.
43
+
44
+ ## 3. Hierarchy is carried by lightness, never by hue
45
+
46
+ - The plane is the room. A card **lifts** toward the light in both modes.
47
+ - Wells inside a card (`--surface-2`, `--surface-3`) **nest** away from the card:
48
+ lighter still in dark mode, greyer in light mode. The compiler checks both
49
+ directions.
50
+ - The accent is an accent. It marks the selected thing, the primary action, the
51
+ live pill. It never floods a surface. A gradient is allowed in exactly one
52
+ place: behind a single hero number (`--hero-glow`), never behind a mark whose
53
+ colour has to stay comparable.
54
+ - Status colours (`--good`, `--warning`, `--serious`, `--critical`) are reserved
55
+ for state. They are never a series, never a chart mark, never themed, and
56
+ always travel with an icon or a word. On light surfaces `warning` and
57
+ `critical` sit near series 4 and 8; the compiler reports that as a documented
58
+ relief, not as a pass.
59
+
60
+ ## 4. Type
61
+
62
+ The dashboard and menu bar use the platform's faces (`system-ui`, `ui-monospace`,
63
+ the platform serif). They ship optical sizing and tracking tables, and the
64
+ offline snapshot must open from `file://` with nothing to fetch.
65
+
66
+ Tracking and leading are **size-specific**: large text tightens, small text
67
+ opens. One `letter-spacing` for every size is wrong somewhere.
68
+
69
+ | Step | Size | Tracking | Leading | Used for |
70
+ |---|---|---|---|---|
71
+ | micro | 10.5 | +.02em | 1.3 | axis ticks, badges |
72
+ | label | 11.5 | +.012em | 1.35 | KPI labels, hints |
73
+ | caption | 12.5 | +.004em | 1.4 | table cells, card titles |
74
+ | body | 13.5 | 0 | 1.45 | body, controls |
75
+ | title | 15 | −.006em | 1.3 | dialog titles |
76
+ | figure | 25 | −.015em | 1.12 | KPI values |
77
+ | hero | 42 | −.025em | 1.05 | the one number on a view |
78
+ | display | 60 | −.03em | 1.0 | landing headlines |
79
+
80
+ Every number wears `font-variant-numeric: tabular-nums`. Text wears text tokens,
81
+ never a series colour; a coloured mark beside it carries identity.
82
+
83
+ The landing page has a voice of its own: Space Grotesk for display, IBM Plex
84
+ Sans for body, IBM Plex Mono for figures — design-kit's `house-plex` pairing,
85
+ self-hosted under `site/fonts/` (OFL-1.1). A sans display is the default for a
86
+ developer tool; a serif would need a brand reason this product does not have.
87
+ A product whose promise is "nothing leaves your machine" does not load fonts
88
+ from a third party.
89
+
90
+ ## 5. Space, radius, elevation
91
+
92
+ A 4-pt scale (`--sp-2` … `--sp-72`) with the two half-steps dense layouts use.
93
+ Radii are per skin (`aurora` 14/9, `terminal` 5/4, `editorial` 8/6) because
94
+ corner radius is part of a room's character. Shadows are tinted toward the
95
+ skin's hue, never neutral black; a bigger surface reads as thicker (deeper
96
+ shadow, stronger blur) than a chip.
97
+
98
+ ## 6. Motion — two tiers, one doctrine
99
+
100
+ | Tier | Governs | Values |
101
+ |---|---|---|
102
+ | `ui` | dashboard, menu bar | press 120ms · fast 160ms · base 220ms · slow 300ms · stagger 40ms (max 6) |
103
+ | `narrative` | landing, reveals | fast 180ms · base 320ms · slow 700ms · stagger 85ms (max 7) |
104
+
105
+ Rules (Emil Kowalski, Apple, design-kit, in agreement):
106
+
107
+ - **Should it animate at all?** Something seen a hundred times a day does not.
108
+ Keyboard-initiated actions never animate. The compiler refuses a UI value over
109
+ 300ms.
110
+ - **Every animation carries information** — sequence, causality, comparison,
111
+ magnitude. Motion that only decorates is deleted.
112
+ - **Ease-out for entrances, ease-in-out for on-screen movement, never ease-in.**
113
+ Custom curves (`--ease-out`, `--ease-in-out`, `--ease-drawer`); the built-ins
114
+ are too weak.
115
+ - **Feedback on press, not on release.** Every pressable scales to `.97` on
116
+ `:active` at `--dur-press`.
117
+ - **Popovers scale from their trigger** (`transform-origin` at the trigger);
118
+ modals stay centred.
119
+ - **Tooltips delay once, then are instant** for neighbours.
120
+ - **Enter from `scale(.96)` + opacity, never from `scale(0)`.** Use
121
+ `@starting-style` for entry.
122
+ - **Transitions over keyframes** for anything that can be re-triggered; specify
123
+ properties, never `transition: all`.
124
+ - **Stagger only when a view changes**, 40ms apart, at most six items. Never
125
+ front-load then freeze; once content has resolved it holds still.
126
+ - **Hover states are gated** behind `(hover: hover) and (pointer: fine)`.
127
+ - **Reduced motion degrades to an instant, complete state** — never a broken
128
+ half-state. Transparency and contrast preferences are honoured the same way.
129
+
130
+ ## 7. Charts
131
+
132
+ Ramps are **authored, never derived**. The eight categorical steps, the seven
133
+ sequential steps and the five diverging steps live in the token file and pass
134
+ the compiler's gates on every build:
135
+
136
+ | Gate | Threshold | Shipped |
137
+ |---|---|---|
138
+ | series on each skin's chart surface | ≥ 3:1 | pass in dark; light slots 3–5 are the documented relief |
139
+ | adjacent series distance (CIE76 ΔE) | ≥ 20 | worst 76 dark · 84 light |
140
+ | first three slots, all pairs | ≥ 20 | pass |
141
+ | sequential luminance | strictly monotonic | pass |
142
+ | diverging ends / midpoint | ends ΔE ≥ 40 · midpoint near-neutral | pass |
143
+ | status vs series | ≥ 12 hard, < 20 reported | light `warning`/`critical` are the documented relief |
144
+
145
+ Rules that hold in every chart (dataviz):
146
+
147
+ - Colour is assigned to the **entity, in fixed order, never by rank**. A filter
148
+ that removes a series never repaints the survivors. Past eight, fold into
149
+ "Other".
150
+ - **One axis.** Two measures of different scale are two charts.
151
+ - Sequential is one hue light→dark; diverging is two hues with a neutral grey
152
+ midpoint. Never a rainbow.
153
+ - Thin marks, 2px lines, hairline grid, recessive axes, selective direct labels.
154
+ - A legend is always present for two or more series; a single series needs
155
+ none.
156
+ - **Every chart has a table twin.** No value is reachable only by hovering.
157
+ This is the relief that lets three light-mode slots sit under 3:1.
158
+
159
+ ## 8. Surfaces
160
+
161
+ | Surface | Consumes | Notes |
162
+ |---|---|---|
163
+ | Dashboard | `src/ui/styles.css` generated block | hand-authored components below the block |
164
+ | Offline snapshot | the same stylesheet, inlined | one file; that is why the block lives inside `styles.css` |
165
+ | Menu bar | `DesignTokens.swift` | every role is a dark/light pair resolved by appearance |
166
+ | Landing | `site/styles.css` generated block | single world (Aurora dark), narrative motion tier, self-hosted faces |
167
+
168
+ ## 9. What the compiler cannot measure
169
+
170
+ These are review rules. The compiler prints them; a person enforces them.
171
+
172
+ - **Rationing.** One accent per view. One hero number per view. A status colour
173
+ appears only with the word it qualifies.
174
+ - **The point.** Every view should answer a question in its first line. A grid
175
+ of numbers you have to interpret is a failure of hierarchy, not of data.
176
+ - **Honesty.** Missing is not zero; estimated and measured never mix; a cap
177
+ table is an upper bound; a receipt says what it did not count.
178
+
179
+ ## 10. How to change something
180
+
181
+ 1. Edit `design/tokens.yaml`. Quote every hex (`#` starts a YAML comment).
182
+ 2. `npm run design`. Read the report; a warning is information, a failure is a
183
+ stop.
184
+ 3. `npm test`. `test/design.test.js` checks the gates and that every target is
185
+ current.
186
+ 4. Look at it. Render the dashboard, the snapshot and the landing before
187
+ claiming done; the compiler measures colour, not layout.
@@ -0,0 +1,85 @@
1
+ # Exports that travel, and budgets per repo and per team
2
+
3
+ Two additions on top of `receipt` and the monthly `budget`: receipts and a
4
+ weekly summary you can drop into Slack or a PR, and a budget you can scope to
5
+ one repository or to your whole team.
6
+
7
+ ## Receipt cards: SVG, PNG, and CSV
8
+
9
+ `tokenflow receipt --branch <name>` already prints a PR-comment receipt. Add
10
+ `--svg` to also write it as a card:
11
+
12
+ ```bash
13
+ tokenflow receipt --branch feat/x --svg receipt.svg
14
+ tokenflow receipt --branch feat/x --svg receipt.svg --png receipt.png
15
+ tokenflow receipt --branch feat/x --png receipt.png # writes receipt.png.svg too
16
+ ```
17
+
18
+ The card is a plain SVG file. It opens in a browser, and it drops straight
19
+ into Slack or a PR comment as an image. Every colour on it comes from
20
+ `design/tokens.yaml`, read at render time, so it always matches the skin and
21
+ mode set in `config.yaml` (`ui.skin`, `ui.mode`). Nothing is downloaded and
22
+ nothing is drawn with a colour that is not one of the project's own tokens.
23
+
24
+ `--png` needs a local Chromium or Chrome install (checked at
25
+ `/opt/homebrew/bin/chromium`, `/Applications/Google Chrome.app`, or on your
26
+ `PATH`). When none is found, the command says so in one line and the SVG is
27
+ still there.
28
+
29
+ Add `--csv` to get one row per branch instead, across every repository the
30
+ `receipt` command would otherwise print as a table:
31
+
32
+ ```bash
33
+ tokenflow receipt --csv > receipts.csv
34
+ tokenflow receipt --repo api --csv > api-receipts.csv
35
+ ```
36
+
37
+ Columns: `repo, branch, costUsd, contextShare, sessions, turns, subagentTurns,
38
+ first, last, longLived, prNumber, mergedAt, changedLines, costPer100Lines`. A
39
+ missing value is an empty cell, never `0`, the same rule the rest of
40
+ TokenFlow's exports follow.
41
+
42
+ ## `tokenflow week`: your AI week as a card
43
+
44
+ ```bash
45
+ tokenflow week text summary
46
+ tokenflow week --svg week.svg a shareable card
47
+ tokenflow week --svg week.svg --png week.png
48
+ tokenflow week --json everything, machine readable
49
+ ```
50
+
51
+ Spend this calendar week (Monday through today) against last week (the full
52
+ prior Monday through Sunday), the context share, turns, sessions, the three
53
+ branches that cost the most, the single most expensive session, and one
54
+ sentence of insight. Every number in that sentence is a number: no "a lot" or
55
+ "much higher" without a figure next to it.
56
+
57
+ ## Budgets per repository and per team
58
+
59
+ The existing monthly budget (`tokenflow budget --set 200`) still works exactly
60
+ as before. Alongside it, declare as many scoped budgets as you like:
61
+
62
+ ```yaml
63
+ budgets:
64
+ - id: api-monthly
65
+ scope: repo # total | repo | team
66
+ repo: api # required when scope is repo
67
+ monthlyUsd: 150
68
+ warnAt: 0.8 # optional, default 0.8 (a fraction, like limits[].warnAt)
69
+ - id: team-monthly
70
+ scope: team
71
+ monthlyUsd: 1000
72
+ ```
73
+
74
+ - `total`: every priced turn in the store this month.
75
+ - `repo`: turns this month attributed to that repository, resolved the same
76
+ way `tokenflow receipt` resolves it. A worktree checkout folds into its main
77
+ repository, so a repo's spend is never split across every worktree it ever
78
+ had.
79
+ - `team`: this machine's shared sync folder (`sync.dir`), if `sync.enabled` is
80
+ set. Nothing is invented: with sync off, or with a sync folder that has no
81
+ data yet, the row says so plainly (`no team data`) rather than reporting a
82
+ number nobody synced.
83
+
84
+ Each row reports `spentUsd`, `monthlyUsd`, `share`, and a state: `ok`, `warn`
85
+ (at or above `warnAt`), or `over` (at or above the cap).
@@ -0,0 +1,132 @@
1
+ # The guard for Codex CLI, and caps that travel with a repository
2
+
3
+ `tokenflow guard` started as a Claude Code hook. This page covers the two
4
+ things layered on top of it: a per-repository policy file, and a Codex CLI
5
+ integration that **warns but never blocks**.
6
+
7
+ ## Codex cannot be blocked — read this first
8
+
9
+ Claude Code gives a hook a chance to reject a tool call or a prompt before it
10
+ runs (`PreToolUse` / `UserPromptSubmit`, exit code `2`). Codex CLI has no
11
+ equivalent. Its only extension point after a turn is `notify` in
12
+ `~/.codex/config.toml`: one external program, run once per completed turn,
13
+ with no way to stop anything that already happened.
14
+
15
+ So the Codex integration **only warns**, through an OS notification (the same
16
+ `src/core/notify.js` used everywhere else in TokenFlow — `osascript` on
17
+ macOS, `notify-send` on Linux, a toast on Windows). It cannot cap a runaway
18
+ Codex session the way the Claude Code hook can cap a runaway Claude Code
19
+ session. If you want an enforceable cap, Claude Code's `PreToolUse` /
20
+ `UserPromptSubmit` hook is still the only one that can actually stop a turn.
21
+
22
+ ## What Codex sends, and what TokenFlow reads
23
+
24
+ Codex's `notify` program receives one JSON argument after every turn
25
+ (`agent-turn-complete` is currently the only event). Per Codex's own docs,
26
+ common fields are:
27
+
28
+ ```json
29
+ {
30
+ "type": "agent-turn-complete",
31
+ "thread-id": "0199...",
32
+ "turn-id": "turn-...",
33
+ "cwd": "/Users/you/project",
34
+ "input-messages": ["..."],
35
+ "last-assistant-message": "..."
36
+ }
37
+ ```
38
+
39
+ `tokenflow guard --codex-notify` reads **only** `thread-id` and `cwd`. It
40
+ never reads, logs, or stores `input-messages` or `last-assistant-message` —
41
+ those carry the actual conversation, and TokenFlow's rule is counts and
42
+ metadata only, never prompt or code content.
43
+
44
+ From `thread-id` it locates the matching rollout transcript —
45
+ `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-<timestamp>-<thread-id>.jsonl` (or
46
+ `archived_sessions`) — and ingests it with the same openai adapter and the
47
+ same incremental, offset-based cache `tokenflow refresh` and the Claude Code
48
+ guard already use (`$TOKENFLOW_HOME/guard/codex-<thread-id>.json`), so a long
49
+ session's transcript is re-read only for the bytes appended since the last
50
+ turn. From `cwd` it resolves the effective guard policy (below) and judges
51
+ the session exactly the way the Claude Code hook does — same thresholds, same
52
+ `evaluateGuard`.
53
+
54
+ On `warn` or `block` it sends **one** OS notification with the same text
55
+ `tokenflow guard --session <file>` would print. A `block`-level verdict is
56
+ still only a notification: the title says so explicitly ("warning only —
57
+ Codex has no blocking hook"). `src/core/notify.js` truncates a notification
58
+ body to 240 characters, so a long verdict (several reasons, a suggestion) may
59
+ arrive clipped — the full text is always available by running
60
+ `tokenflow guard --session <the rollout file>` yourself.
61
+
62
+ ## Wiring it up
63
+
64
+ ```bash
65
+ tokenflow guard --install --codex # print the exact notify = [...] line
66
+ tokenflow guard --install --codex --apply # append it to ~/.codex/config.toml, if safe to do so
67
+ ```
68
+
69
+ `--apply` only writes when **both** are true: `~/.codex/config.toml`'s parent
70
+ directory exists (otherwise Codex does not look installed on this machine,
71
+ and nothing is created), and the file declares no `notify` key yet. Codex
72
+ allows exactly one `notify` program — if one is already configured (for
73
+ example a computer-use client, or your own script), `--apply` refuses and
74
+ prints the same instructions `--install --codex` alone would, so you can
75
+ merge it by hand instead of silently losing whatever was there. Restart
76
+ Codex CLI for a config change to take effect.
77
+
78
+ ## Per-repository caps: `.tokenflow/policy.yaml`
79
+
80
+ A threshold set with `tokenflow guard --set` lives in
81
+ `~/.tokenflow/config.yaml` — one machine, one person. A repository can
82
+ instead declare its own caps in a file that travels with it:
83
+
84
+ ```yaml
85
+ # <repo root>/.tokenflow/policy.yaml
86
+ guard:
87
+ warnCostUsd: 10
88
+ maxCostUsd: 50
89
+ warnContextTokens: 150000
90
+ note: "This repo's sessions run long — reasoning-heavy refactors, not chat."
91
+ ```
92
+
93
+ Any of the five guard keys may be declared (see `docs/configuration.md`'s
94
+ `guard` section for what each one means); an invalid value (not a number, or
95
+ not positive) is reported and ignored rather than applied — a typo in a
96
+ checked-in file must not take the hook down for everyone who clones the repo.
97
+ `note` is optional and shown alongside a warning/block that the policy
98
+ triggered, and beside `guard --policy`'s output; it may sit at the top level
99
+ or beside the `guard:` block.
100
+
101
+ **A repository's declared key wins over the personal config, key by key.** A
102
+ key the repository does not declare falls back to `~/.tokenflow/config.yaml`,
103
+ and a key declared nowhere is `null` — informational only, same as today.
104
+
105
+ ```bash
106
+ tokenflow guard --policy # effective policy for the current directory
107
+ tokenflow guard --policy --cwd <dir> # for some other directory / repository
108
+ ```
109
+
110
+ prints each of the five keys, its value, and where it came from —
111
+ `[repo]`, `[config]`, or `[default]` — plus the repository root that was
112
+ found (a plain `.git` directory is enough; a git worktree resolves to its
113
+ main checkout, same as everywhere else in TokenFlow) and any errors in
114
+ `policy.yaml`.
115
+
116
+ When a warning or block fires because of a repo-declared key, one extra line
117
+ is appended to the verdict naming which key(s) came from
118
+ `.tokenflow/policy.yaml` (and the `note`, if one is set) — for both the
119
+ Claude Code hook and the Codex path, since both call the same
120
+ `effectiveGuardPolicy()`.
121
+
122
+ ## Everything this adds, at a glance
123
+
124
+ | Command | What it does |
125
+ | --- | --- |
126
+ | `tokenflow guard --policy [--cwd <dir>]` | Show the effective guard policy and each value's source |
127
+ | `tokenflow guard --install --codex [--apply]` | Wire up (or print) Codex's `notify` line |
128
+ | `tokenflow guard --codex-notify <json>` | What Codex's `notify` actually runs, once per turn |
129
+
130
+ Nothing here makes a network call, reads prompt or code content, or writes
131
+ outside `$TOKENFLOW_HOME` and (only with `--apply`, and only when safe)
132
+ `~/.codex/config.toml`.