@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.
- package/CHANGELOG.md +228 -0
- package/Dockerfile.team +20 -0
- package/README.md +30 -11
- package/bin/tokenflow.js +147 -12
- package/design/tokens.yaml +330 -0
- package/docs/architecture.md +5 -4
- package/docs/cli.md +204 -0
- package/docs/configuration.md +117 -2
- package/docs/design-system.md +187 -0
- package/docs/exports-and-budgets.md +85 -0
- package/docs/guard-codex.md +132 -0
- package/docs/ledger.md +144 -0
- package/docs/live-mode.md +40 -0
- package/docs/media/overview-aurora-dark.png +0 -0
- package/docs/media/receipts-aurora-dark.png +0 -0
- package/docs/providers-otel.md +179 -0
- package/docs/providers.md +54 -1
- package/docs/receipt-schema.md +74 -0
- package/docs/roadmap.md +182 -0
- package/docs/team-server.md +170 -0
- package/docs/ui-views.md +322 -0
- package/package.json +7 -2
- package/schemas/receipt.v0.json +160 -0
- package/scripts/build-dmg.sh +11 -2
- package/scripts/build-menubar-app.sh +58 -7
- package/scripts/design-build.js +475 -0
- package/src/analytics/anatomy.js +467 -0
- package/src/analytics/branch-compare.js +159 -0
- package/src/analytics/cache-health.js +141 -0
- package/src/analytics/live-view.js +266 -0
- package/src/analytics/receipt-schema.js +214 -0
- package/src/analytics/receipt.js +709 -0
- package/src/analytics/rhythm.js +184 -0
- package/src/analytics/whatif.js +263 -0
- package/src/commands/budget-scopes.js +133 -0
- package/src/commands/doctor-checks.js +400 -0
- package/src/commands/guard.js +531 -0
- package/src/commands/hooks.js +238 -0
- package/src/commands/pricing-diff.js +316 -0
- package/src/commands/receipt.js +226 -0
- package/src/commands/team-serve.js +407 -0
- package/src/commands/week.js +86 -0
- package/src/core/annotations.js +97 -0
- package/src/core/budget.js +33 -0
- package/src/core/bundle.js +45 -2
- package/src/core/ingest.js +33 -0
- package/src/core/live-status.js +227 -2
- package/src/core/policy.js +103 -0
- package/src/core/receipt-note.js +123 -0
- package/src/core/repo.js +64 -0
- package/src/core/sync.js +163 -26
- package/src/core/team.js +0 -0
- package/src/export/html-snapshot.js +28 -1
- package/src/export/menubar.js +21 -0
- package/src/export/receipt-card.js +210 -0
- package/src/export/week-card.js +185 -0
- package/src/providers/mock/index.js +383 -52
- package/src/providers/openai/index.js +31 -1
- package/src/providers/otel/index.js +656 -0
- package/src/server/routes/annotations.js +42 -0
- package/src/server/routes/cache-health.js +95 -0
- package/src/server/routes/index.js +54 -0
- package/src/server/routes/session.js +157 -0
- package/src/server/server.js +47 -1
- package/src/ui/app.js +541 -308
- package/src/ui/charts.js +95 -0
- package/src/ui/first-run.js +144 -0
- package/src/ui/index.html +4 -1
- package/src/ui/palette.js +335 -0
- package/src/ui/styles/anatomy.css +117 -0
- package/src/ui/styles/annotations.css +40 -0
- package/src/ui/styles/branches.css +99 -0
- package/src/ui/styles/cache.css +6 -0
- package/src/ui/styles/first-run.css +31 -0
- package/src/ui/styles/live.css +100 -0
- package/src/ui/styles/palette.css +85 -0
- package/src/ui/styles/rhythm.css +8 -0
- package/src/ui/styles/whatif.css +55 -0
- package/src/ui/styles.css +303 -196
- package/src/ui/views/anatomy.js +567 -0
- package/src/ui/views/annotations.js +121 -0
- package/src/ui/views/branches.js +304 -0
- package/src/ui/views/cache.js +232 -0
- package/src/ui/views/index.js +85 -0
- package/src/ui/views/live.js +683 -0
- package/src/ui/views/rhythm.js +206 -0
- package/src/ui/views/whatif.js +196 -0
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
15
|
-
|
|
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`.
|