taximeter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +141 -0
- package/DECISIONS.md +54 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/SECURITY.md +69 -0
- package/SPEC-NOTES.md +369 -0
- package/SPEC.md +474 -0
- package/VERIFICATION.md +943 -0
- package/dist/chunk-I7YXGTRZ.js +1167 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +494 -0
- package/dist/index.d.ts +404 -0
- package/dist/index.js +182 -0
- package/dist/ui/assets/index-DOE5bD9P.js +101 -0
- package/dist/ui/assets/index-De4zKRDL.css +1 -0
- package/dist/ui/index.html +15 -0
- package/docs/RECORDING.md +75 -0
- package/docs/SDK.md +192 -0
- package/docs/demo.gif +0 -0
- package/docs/demo.mjs +145 -0
- package/docs/demo.tape +15 -0
- package/package.json +79 -0
- package/taximeter.config.example.json +16 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Taximeter specification
|
|
2
|
+
|
|
3
|
+
Taximeter is an open-source TypeScript meter and ledger for agent payments.
|
|
4
|
+
This specification defines the product, architecture, engineering standards, and
|
|
5
|
+
release acceptance criteria. The quickstart should provide useful results in
|
|
6
|
+
under 30 seconds. Implementation decisions and protocol corrections are recorded
|
|
7
|
+
in `DECISIONS.md` and `SPEC-NOTES.md`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0. Protocol verification
|
|
12
|
+
|
|
13
|
+
The x402 descriptions below are a summary. Protocol details must be checked against
|
|
14
|
+
the primary sources before implementation:
|
|
15
|
+
|
|
16
|
+
- `https://github.com/coinbase/x402` — README, `specs/x402-specification-v2.md`,
|
|
17
|
+
`specs/schemes/exact/scheme_exact_evm.md`
|
|
18
|
+
- `https://docs.x402.org`
|
|
19
|
+
- The actual published npm packages (`@x402/core`, `@x402/evm`, `@x402/fetch`,
|
|
20
|
+
`@x402/express`) — read their `README` and exported types.
|
|
21
|
+
|
|
22
|
+
Then write `SPEC-NOTES.md` in the repo root recording:
|
|
23
|
+
|
|
24
|
+
1. The exact v1 and v2 wire formats you found, with real field names.
|
|
25
|
+
2. Every place where the brief below disagrees with the real spec.
|
|
26
|
+
3. Which version(s) you decided to support and why.
|
|
27
|
+
|
|
28
|
+
**The real spec wins over this brief, every time.** If they conflict, follow the spec and
|
|
29
|
+
note it. Do not invent field names.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 1. What you are building
|
|
34
|
+
|
|
35
|
+
### The problem, in one paragraph
|
|
36
|
+
|
|
37
|
+
AI agents now pay for things by themselves — API calls, data, model tokens — thousands of
|
|
38
|
+
tiny payments per task, across several payment rails (x402/USDC, Stripe MPP, cards). The
|
|
39
|
+
rails answer one question well: *can this payment go through?* They do not answer: *has
|
|
40
|
+
this agent blown its budget, which task did this spend belong to, and how do I turn four
|
|
41
|
+
rails' records into one invoice?* Today that ledger responsibility falls on whoever runs
|
|
42
|
+
the agent — which means nobody does it.
|
|
43
|
+
|
|
44
|
+
The analogy to keep in your head while designing: **Visa exists, Ramp doesn't.** Card
|
|
45
|
+
networks move money; expense-management systems govern it. `taximeter` is the
|
|
46
|
+
expense‑management layer for agent payments.
|
|
47
|
+
|
|
48
|
+
### What `taximeter` is
|
|
49
|
+
|
|
50
|
+
A local‑first meter and ledger that sits between an agent and the payment rails. It
|
|
51
|
+
records every payment, enforces budgets in real time, attributes spend to tasks, and
|
|
52
|
+
exports one clean statement.
|
|
53
|
+
|
|
54
|
+
**Name: `taximeter`.** npm package `taximeter`, CLI binary `taximeter` with the short
|
|
55
|
+
alias `txm`. The name was verified unregistered on the npm registry on 2026‑09‑06 — if it
|
|
56
|
+
has been taken since, fall back in this order: `tabmeter`, `spendbook`, `agenttab` (all
|
|
57
|
+
verified free on the same date), and record the change in `SPEC-NOTES.md`.
|
|
58
|
+
|
|
59
|
+
**The tagline is the product's whole pitch — use it verbatim in the README, the
|
|
60
|
+
`package.json` description, and the GitHub repo description:**
|
|
61
|
+
|
|
62
|
+
> A taximeter for your AI agents.
|
|
63
|
+
|
|
64
|
+
Carry the metaphor deliberately, and know its exact limits:
|
|
65
|
+
|
|
66
|
+
- A taximeter **watches small charges accumulate and shows a running total in a place the
|
|
67
|
+
passenger can see.** That is precisely what this tool does.
|
|
68
|
+
- A taximeter **does not take the money** — the driver does. This maps exactly onto the
|
|
69
|
+
non‑custody promise in §1. Lean on this when explaining what the tool is not.
|
|
70
|
+
- Do **not** stretch the metaphor into cars, taxis, rides, drivers, or yellow cabs
|
|
71
|
+
anywhere in the naming, UI copy, or visual design. No taxi imagery, no checkered
|
|
72
|
+
patterns, no yellow-cab color scheme. The name earns its keep in one sentence; after
|
|
73
|
+
that the product is a ledger and should look like one (§5).
|
|
74
|
+
|
|
75
|
+
**Capitalization — apply this table exactly and consistently. There is one rule behind it:
|
|
76
|
+
lowercase in every technical context, sentence case in prose.** "Taximeter" is also a
|
|
77
|
+
common English noun, so capitalizing it in prose is what marks it as the product rather
|
|
78
|
+
than the device in a cab.
|
|
79
|
+
|
|
80
|
+
| Context | Form | Note |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| npm package | `taximeter` | npm rejects uppercase in new package names — not a choice |
|
|
83
|
+
| CLI binary + alias | `taximeter`, `txm` | never capitalized; shells are case-sensitive |
|
|
84
|
+
| GitHub repo | `taximeter` | match the package name |
|
|
85
|
+
| Prose, headings, README H1, docs body | `Taximeter` | proper noun; `# Taximeter` |
|
|
86
|
+
| Inline reference to the command | `` `taximeter start` `` | code-styled, lowercase |
|
|
87
|
+
| TypeScript types / classes | `TaximeterConfig`, `PaymentEvent` | PascalCase |
|
|
88
|
+
| Env vars | `TAXIMETER_DB`, `TAXIMETER_PORT` | SCREAMING_SNAKE_CASE |
|
|
89
|
+
| Config file | `taximeter.config.json` | lowercase |
|
|
90
|
+
| State directory | `~/.taximeter/` | lowercase |
|
|
91
|
+
| HTTP headers | `Taximeter-Task`, `Taximeter-Agent` | Title-Case-With-Hyphens; **no `X-` prefix** — RFC 6648 deprecated it |
|
|
92
|
+
| Error codes / enum values | `blocked_by_taximeter` | snake_case, lowercase |
|
|
93
|
+
| Wordmark / logo | lowercase `taximeter` | a deliberate lowercase wordmark alongside sentence-case prose is fine and common |
|
|
94
|
+
|
|
95
|
+
Never write `TaxiMeter`, `TAXIMETER` (outside env vars), or `Taxi Meter`. Do not open a
|
|
96
|
+
sentence with the lowercase form — rewrite the sentence instead.
|
|
97
|
+
|
|
98
|
+
### Explicit non‑goals — do not build these
|
|
99
|
+
|
|
100
|
+
These are not "later"; building them would make the project worse:
|
|
101
|
+
|
|
102
|
+
- **No custody.** It never holds funds, never holds private keys, never signs a payment.
|
|
103
|
+
- **No facilitator.** It does not verify or settle on‑chain. It observes and gates.
|
|
104
|
+
- **No cloud, no account, no telemetry.** Everything runs on localhost. Zero network calls
|
|
105
|
+
except passing traffic through to the upstream the agent was already calling.
|
|
106
|
+
- **No Stripe MPP or card rails in v1.** But the internal design must be adapter‑shaped so
|
|
107
|
+
a second rail is a new file, not a refactor. Write the `Rail` interface now; implement
|
|
108
|
+
only the x402 adapter.
|
|
109
|
+
- **No auth, no multi‑tenant, no RBAC.** Single developer on one machine.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 2. Architecture
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
agent process taximeter upstream
|
|
117
|
+
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
118
|
+
│ fetch(...) │ ──────► │ proxy (rail │ ─────► │ API server │
|
|
119
|
+
│ │ │ adapter: x402) │ │ (402 flow) │
|
|
120
|
+
└──────────────┘ ◄────── │ │ │ ◄───── └──────────────┘
|
|
121
|
+
│ ▼ │
|
|
122
|
+
│ policy engine │ ← budgets, allow/deny
|
|
123
|
+
│ │ │
|
|
124
|
+
│ ▼ │
|
|
125
|
+
│ ledger (SQLite)│
|
|
126
|
+
│ │ │
|
|
127
|
+
│ ▼ │
|
|
128
|
+
│ dashboard + API │ → localhost UI, CSV/JSON export
|
|
129
|
+
└──────────────────┘
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Three entry points, one core:
|
|
133
|
+
|
|
134
|
+
1. **Proxy mode** (primary) — `taximeter start` runs an HTTP proxy. The agent points at it via
|
|
135
|
+
`HTTP_PROXY` / `HTTPS_PROXY` or an explicit base URL. Zero code change in the agent.
|
|
136
|
+
2. **SDK mode** — `import { withMeter } from 'taximeter'` wraps a `fetch` implementation for
|
|
137
|
+
agents that can't use a proxy. Same core, different intake.
|
|
138
|
+
3. **Dashboard** — served by the same process on a second port.
|
|
139
|
+
|
|
140
|
+
### Core modules (each independently unit‑testable, no I/O in pure logic)
|
|
141
|
+
|
|
142
|
+
| Module | Responsibility |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `rails/types.ts` | The `Rail` interface: `detect(req,res)`, `parse(...) → PaymentEvent \| null` |
|
|
145
|
+
| `rails/x402.ts` | Detect 402 responses and the payment replay; parse v1 and v2 shapes |
|
|
146
|
+
| `ledger/` | Append‑only event store; derived running totals; queries |
|
|
147
|
+
| `policy/` | Pure functions: given a proposed payment + current totals + config → allow or deny with a reason |
|
|
148
|
+
| `proxy/` | HTTP/HTTPS proxy wiring; calls rail → policy → ledger; blocks or forwards |
|
|
149
|
+
| `sdk/` | `withMeter(fetch, opts)` |
|
|
150
|
+
| `server/` | Dashboard static files + a small read‑only JSON API |
|
|
151
|
+
| `export/` | CSV, JSON, and a simple invoice |
|
|
152
|
+
| `cli/` | `start`, `report`, `export`, `reset`, `doctor` |
|
|
153
|
+
|
|
154
|
+
**Rule:** `policy/` and `ledger/` derivations must be pure and synchronous. All I/O lives
|
|
155
|
+
at the edges. This is what makes the tests fast and the logic trustworthy.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 3. Data model
|
|
160
|
+
|
|
161
|
+
Append‑only events. Never update a row; derive everything.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
type PaymentEvent = {
|
|
165
|
+
id: string; // uuid v7 (sortable)
|
|
166
|
+
ts: string; // ISO 8601, UTC
|
|
167
|
+
rail: 'x402'; // future: 'stripe-mpp' | 'card'
|
|
168
|
+
status: 'observed' | 'blocked';
|
|
169
|
+
reason?: string; // set when blocked
|
|
170
|
+
amount: string; // integer string in the asset's smallest unit — NEVER a float
|
|
171
|
+
decimals: number; // e.g. 6 for USDC
|
|
172
|
+
asset: string; // contract address or symbol
|
|
173
|
+
assetSymbol?: string; // 'USDC'
|
|
174
|
+
network: string; // e.g. 'eip155:8453'
|
|
175
|
+
payTo: string;
|
|
176
|
+
payer?: string;
|
|
177
|
+
resource: string; // the URL being paid for
|
|
178
|
+
host: string; // derived, indexed
|
|
179
|
+
txHash?: string;
|
|
180
|
+
taskId?: string; // from Taximeter-Task header or SDK option
|
|
181
|
+
agentId?: string; // from Taximeter-Agent header or SDK option
|
|
182
|
+
raw: string; // the original parsed payload, JSON string, for auditability
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Money rule, non‑negotiable:** all amounts are integer strings in the asset's smallest
|
|
187
|
+
unit. Never `number`, never floating point, anywhere in the codebase — including the UI and
|
|
188
|
+
the CSV. Convert to a display string only at the last render step. Write a lint rule or at
|
|
189
|
+
minimum a test that asserts no `parseFloat`/`Number()` touches an amount field.
|
|
190
|
+
|
|
191
|
+
**Storage:** SQLite via `better-sqlite3`. One table `events`, indices on `(ts)`,
|
|
192
|
+
`(taskId)`, `(agentId)`, `(host)`. Default path `~/.taximeter/ledger.db`, overridable.
|
|
193
|
+
Migrations in `migrations/` with a `schema_version` table — even for v1.
|
|
194
|
+
|
|
195
|
+
### Config
|
|
196
|
+
|
|
197
|
+
`taximeter.config.json` in cwd, or `~/.taximeter/config.json`, or flags. Precedence:
|
|
198
|
+
flags > cwd > home > defaults. Ship a documented default that works with zero config.
|
|
199
|
+
|
|
200
|
+
```jsonc
|
|
201
|
+
{
|
|
202
|
+
"budgets": {
|
|
203
|
+
"perTask": { "amount": "5000000", "asset": "USDC" }, // 5 USDC
|
|
204
|
+
"perAgent": { "amount": "50000000", "asset": "USDC", "window": "24h" },
|
|
205
|
+
"global": { "amount": "100000000", "asset": "USDC", "window": "24h" }
|
|
206
|
+
},
|
|
207
|
+
"policy": {
|
|
208
|
+
"allowHosts": [], // empty = allow all
|
|
209
|
+
"denyHosts": [],
|
|
210
|
+
"allowPayTo": [],
|
|
211
|
+
"maxSinglePayment": "1000000" // 1 USDC — anything larger is blocked
|
|
212
|
+
},
|
|
213
|
+
"ports": { "proxy": 8402, "dashboard": 8403 },
|
|
214
|
+
"db": "~/.taximeter/ledger.db"
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 4. Behaviour that must be exactly right
|
|
221
|
+
|
|
222
|
+
1. **Observe, don't break.** If `taximeter` cannot parse a response, it forwards it
|
|
223
|
+
untouched and records a `parse_failed` diagnostic. **It must never corrupt or drop a
|
|
224
|
+
request it doesn't understand.** This is the single most important behaviour — a tool
|
|
225
|
+
that breaks agents will be uninstalled in one minute.
|
|
226
|
+
2. **Block cleanly.** When a policy denies a payment, do not forward the replay. Return a
|
|
227
|
+
`402` to the agent with a JSON body: `{ error: 'blocked_by_taximeter', reason, budget,
|
|
228
|
+
spent, remaining }`. Record a `blocked` event. The agent must be able to understand
|
|
229
|
+
what happened from the response alone.
|
|
230
|
+
3. **Budget arithmetic is exact.** Use `BigInt` for all totals. Windowed budgets are
|
|
231
|
+
computed from the event log, not from a running counter that can drift.
|
|
232
|
+
4. **Multi‑asset.** Never sum across different assets. If two assets are in play, report
|
|
233
|
+
them separately and say so. Do not invent an exchange rate.
|
|
234
|
+
5. **Idempotent.** The same payment observed twice (retry, proxy replay) must not double
|
|
235
|
+
count. Deduplicate on nonce/txHash/resource+ts within a small window; document the rule.
|
|
236
|
+
6. **Graceful upstream failure.** If the upstream 5xx's after a payment was signed, record
|
|
237
|
+
the event with a `settlement_unknown` flag. Do not silently drop it.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 5. The dashboard
|
|
242
|
+
|
|
243
|
+
This is where most tools of this kind look cheap. Do not build a generic admin template.
|
|
244
|
+
|
|
245
|
+
### Art direction
|
|
246
|
+
|
|
247
|
+
It is a **ledger**, not an analytics dashboard. It should feel like a well‑set financial
|
|
248
|
+
document that happens to be live.
|
|
249
|
+
|
|
250
|
+
- **Type:** one grotesque for UI text, one monospace for every number. Load from Google
|
|
251
|
+
Fonts with real fallback stacks. All figures use `font-variant-numeric: tabular-nums` so
|
|
252
|
+
columns align.
|
|
253
|
+
- **Color:** a single accent. Choose a restrained one and use it only for the accent role —
|
|
254
|
+
the live total, the budget meter fill, active nav. Semantic colors (blocked = warning)
|
|
255
|
+
are separate from the accent and always ship with a text label, never color alone.
|
|
256
|
+
Neutrals should carry a slight hue bias toward the accent, not pure grey.
|
|
257
|
+
- **Both themes.** Define the complete light palette as CSS custom properties on bare
|
|
258
|
+
`:root`; redefine only the tokens under `@media (prefers-color-scheme: dark)`. Never
|
|
259
|
+
define a color only inside a media query. `body` sets an explicit background token.
|
|
260
|
+
- **No** gradients, glassmorphism, emoji section markers, drop shadows on every card, or
|
|
261
|
+
`rounded-2xl` on everything. Spend border/fill/shadow by role: lift the one thing that
|
|
262
|
+
matters, leave the rest flat.
|
|
263
|
+
- **Density over decoration.** A ledger view should show ~30 rows without scrolling on a
|
|
264
|
+
laptop.
|
|
265
|
+
|
|
266
|
+
### Screens
|
|
267
|
+
|
|
268
|
+
1. **Now** (default). A hero figure: current spend against the active budget, with a meter.
|
|
269
|
+
Below it: live event stream, newest first, ~30 rows, with a colored left edge only on
|
|
270
|
+
`blocked` rows. This screen is the whole product — someone should understand `taximeter`
|
|
271
|
+
in five seconds of looking at it.
|
|
272
|
+
2. **By task / by agent / by host.** A sortable table with a proportional bar in the cell,
|
|
273
|
+
not a separate chart. Tabular numerals, right‑aligned amounts.
|
|
274
|
+
3. **Timeline.** One sparkline of spend over the session plus a bar of spend per hour.
|
|
275
|
+
Single hue, direct value labels on the endpoints only, chart text in theme tokens, all
|
|
276
|
+
labels inside the viewBox, `overflow-x: auto` on the container.
|
|
277
|
+
4. **Export.** Buttons for CSV / JSON / invoice, with a preview of what will be produced.
|
|
278
|
+
|
|
279
|
+
### Implementation
|
|
280
|
+
|
|
281
|
+
React + Vite, prebuilt into `dist/ui/` at publish time and served as static files by the
|
|
282
|
+
Node process. `npx` must not trigger a frontend build. Poll a read‑only JSON API every
|
|
283
|
+
1000 ms; no WebSocket in v1. The page must render a meaningful empty state that explains
|
|
284
|
+
how to point an agent at the proxy — never a blank screen.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 6. The README
|
|
289
|
+
|
|
290
|
+
The README is the product's landing page. Prioritize a working quickstart and a
|
|
291
|
+
short path to the first useful result, with detailed reference material in the docs.
|
|
292
|
+
|
|
293
|
+
Structure, in this order:
|
|
294
|
+
|
|
295
|
+
1. **Name + one line.** What it does, no adjectives. Then, immediately:
|
|
296
|
+
```bash
|
|
297
|
+
npx taximeter start
|
|
298
|
+
```
|
|
299
|
+
2. **A 20‑second demo.** Leave a `docs/demo.gif` placeholder and write
|
|
300
|
+
`docs/RECORDING.md` with the exact `asciinema`/`vhs` commands to produce it, including
|
|
301
|
+
the script of what to type. Reference it from the README.
|
|
302
|
+
3. **Why** — three sentences maximum, using the Visa/Ramp framing.
|
|
303
|
+
4. **Quickstart** — three steps, each a single copyable command. Then a
|
|
304
|
+
"you should now see this" block showing real expected output.
|
|
305
|
+
5. **How it works** — the ASCII architecture diagram from §2, plus four sentences.
|
|
306
|
+
6. **Configuration** — a table of every option, its default, and one line of meaning.
|
|
307
|
+
7. **What this is not** — the non‑goals from §1 stated plainly. This builds more trust than
|
|
308
|
+
a feature list.
|
|
309
|
+
8. **Contributing / license (MIT).**
|
|
310
|
+
|
|
311
|
+
Rules: no more than three badges. No emoji as section markers. Every code block must be
|
|
312
|
+
copy‑pasteable and actually work. Write in plain English — short sentences, active voice,
|
|
313
|
+
no marketing adjectives.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 7. Engineering standards
|
|
318
|
+
|
|
319
|
+
**Stack (pin exact versions in `package.json`, no `^` on tooling):**
|
|
320
|
+
|
|
321
|
+
- TypeScript 5.x, `strict: true`, `noUncheckedIndexedAccess: true`, no `any` outside
|
|
322
|
+
narrowly-scoped `// eslint-disable` with a reason.
|
|
323
|
+
- Node 20+ (`engines` field). ESM only, with a CJS build via `tsup` if trivial.
|
|
324
|
+
- `better-sqlite3` for storage; `commander` for CLI; `zod` for all config and wire‑format
|
|
325
|
+
parsing — every external input is parsed, never cast.
|
|
326
|
+
- **Biome** for lint + format (one tool, one config). No ESLint + Prettier pair.
|
|
327
|
+
- `vitest` for tests; `tsup` for build; `changesets` for versioning.
|
|
328
|
+
|
|
329
|
+
**Testing — this is not optional:**
|
|
330
|
+
|
|
331
|
+
- **Unit tests** for `policy/`, `ledger/` derivations, and both x402 wire‑format parsers.
|
|
332
|
+
These are pure functions; aim for ≥90% coverage here.
|
|
333
|
+
- **A fixture upstream server** in `test/fixtures/` that speaks real x402: returns a 402
|
|
334
|
+
with payment requirements, accepts the replay, returns 200. Both v1 and v2 shapes.
|
|
335
|
+
- **An integration test** that runs the proxy against the fixture, sends 100 payments
|
|
336
|
+
through, and asserts the ledger totals exactly, to the smallest unit.
|
|
337
|
+
- **A budget test**: configure a cap that allows exactly 20 payments; assert #21 is blocked,
|
|
338
|
+
that the response body is the documented shape, and that a `blocked` event is recorded.
|
|
339
|
+
- **A "don't break things" test**: send traffic the parser does not understand and assert
|
|
340
|
+
the response reaches the client byte‑identical.
|
|
341
|
+
- **A determinism test**: the same event log always produces the same totals.
|
|
342
|
+
- Snapshot the CSV export. Golden files in `test/__snapshots__/`.
|
|
343
|
+
|
|
344
|
+
**CI** — GitHub Actions, one workflow: typecheck → lint → test → build, on push and PR,
|
|
345
|
+
Node 20 and 22 matrix. A second workflow publishes to npm on a changeset release.
|
|
346
|
+
|
|
347
|
+
**Repo hygiene:** Conventional Commits. `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`,
|
|
348
|
+
`LICENSE` (MIT), `.editorconfig`, `.nvmrc`, issue and PR templates. `CHANGELOG.md`
|
|
349
|
+
generated by changesets. A `SECURITY.md` that says plainly: this tool never handles keys or
|
|
350
|
+
funds, and here is how to report an issue.
|
|
351
|
+
|
|
352
|
+
**`.gitignore` — write it deliberately, not from a generic template.** The correctness test
|
|
353
|
+
is mechanical: after running `npm ci && npm test && npm run build && npm pack`,
|
|
354
|
+
`git status --porcelain` must print **nothing**. Anything a normal workflow generates must
|
|
355
|
+
be ignored; anything a contributor needs must not be.
|
|
356
|
+
|
|
357
|
+
Must be ignored:
|
|
358
|
+
|
|
359
|
+
```gitignore
|
|
360
|
+
# deps & build
|
|
361
|
+
node_modules/
|
|
362
|
+
dist/
|
|
363
|
+
*.tsbuildinfo
|
|
364
|
+
.vite/
|
|
365
|
+
|
|
366
|
+
# test & coverage
|
|
367
|
+
coverage/
|
|
368
|
+
.nyc_output/
|
|
369
|
+
test-results/
|
|
370
|
+
tmp/
|
|
371
|
+
|
|
372
|
+
# npm pack output
|
|
373
|
+
*.tgz
|
|
374
|
+
|
|
375
|
+
# local ledger state — SQLite writes three files, ignore all of them
|
|
376
|
+
*.db
|
|
377
|
+
*.db-wal
|
|
378
|
+
*.db-shm
|
|
379
|
+
.taximeter/
|
|
380
|
+
|
|
381
|
+
# local user config (an example file is committed instead)
|
|
382
|
+
taximeter.config.json
|
|
383
|
+
|
|
384
|
+
# secrets — this project never needs one, but a contributor's test setup might
|
|
385
|
+
.env
|
|
386
|
+
.env.*
|
|
387
|
+
!.env.example
|
|
388
|
+
|
|
389
|
+
# development logs
|
|
390
|
+
*.log
|
|
391
|
+
|
|
392
|
+
# editors & OS
|
|
393
|
+
.DS_Store
|
|
394
|
+
Thumbs.db
|
|
395
|
+
.idea/
|
|
396
|
+
.vscode/*
|
|
397
|
+
!.vscode/extensions.json
|
|
398
|
+
!.vscode/settings.json
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Must **not** be ignored — these are deliverables and belong in the repo:
|
|
402
|
+
`README.md`, `SPEC.md`, `SPEC-NOTES.md`, `DECISIONS.md`, `VERIFICATION.md`,
|
|
403
|
+
`taximeter.config.example.json`, `.changeset/`, `migrations/`,
|
|
404
|
+
`test/__snapshots__/`, `.github/`.
|
|
405
|
+
|
|
406
|
+
Two related rules:
|
|
407
|
+
|
|
408
|
+
- **Do not create a `.npmignore`.** The `files` whitelist in `package.json` already
|
|
409
|
+
controls the tarball, and having both is a known source of "why is this file missing
|
|
410
|
+
from the published package" bugs.
|
|
411
|
+
- **Add a `.gitattributes`** with `* text=auto eol=lf` and `*.sh text eol=lf`. Contributors
|
|
412
|
+
on Windows will otherwise commit CRLF, which breaks the lint step and produces
|
|
413
|
+
whole-file diffs in CI.
|
|
414
|
+
|
|
415
|
+
**Package:** `files` whitelist so the tarball ships only `dist/`, `dist/ui/`, and docs.
|
|
416
|
+
`bin` maps `taximeter` and `txm`. Verify `npm pack` output is under 2 MB and contains no source maps of the
|
|
417
|
+
UI.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 8. Build order — commit at every step
|
|
422
|
+
|
|
423
|
+
Work in this order. After each step, run the full test suite and make a Conventional
|
|
424
|
+
Commit. Do not move on with failing tests.
|
|
425
|
+
|
|
426
|
+
1. Repo scaffold, tooling, CI, a trivial passing test.
|
|
427
|
+
2. `SPEC-NOTES.md` from your primary‑source reading.
|
|
428
|
+
3. Types + zod schemas for the wire formats and config.
|
|
429
|
+
4. `ledger/` with migrations + unit tests.
|
|
430
|
+
5. `policy/` pure functions + unit tests (write the tests first here).
|
|
431
|
+
6. `rails/x402.ts` parsers + unit tests against real captured payloads.
|
|
432
|
+
7. Fixture upstream server.
|
|
433
|
+
8. `proxy/` + the integration and "don't break things" tests.
|
|
434
|
+
9. `cli/` — `start`, `report`, `export`, `reset`, `doctor`.
|
|
435
|
+
10. `export/` + snapshot tests.
|
|
436
|
+
11. `sdk/` `withMeter`.
|
|
437
|
+
12. Dashboard — data API first, then UI, then the art‑direction pass.
|
|
438
|
+
13. README, `docs/RECORDING.md`, contributing docs.
|
|
439
|
+
14. `npm pack` and a clean‑machine smoke test (fresh temp dir, `npx ./package.tgz start`).
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 9. Acceptance criteria — the build is done when all of these pass
|
|
444
|
+
|
|
445
|
+
Verify each one and paste the evidence into a `VERIFICATION.md`.
|
|
446
|
+
|
|
447
|
+
- [ ] On a clean machine with no config file, `npx <pkg> start` boots the proxy and
|
|
448
|
+
dashboard and prints the two URLs plus a one‑line "point your agent here" hint.
|
|
449
|
+
- [ ] `npm test` passes; coverage on `policy/` and `ledger/` is ≥ 90%.
|
|
450
|
+
- [ ] The integration test pushes 100 payments and the ledger total matches the expected
|
|
451
|
+
value **exactly**, as an integer string.
|
|
452
|
+
- [ ] With a budget allowing 20 payments, payment 21 is blocked, the client receives the
|
|
453
|
+
documented JSON body, and a `blocked` event exists in the ledger.
|
|
454
|
+
- [ ] Unparseable traffic passes through byte‑identical.
|
|
455
|
+
- [ ] `taximeter export --csv out.csv` produces a file whose total equals the dashboard total.
|
|
456
|
+
- [ ] The dashboard renders correctly in both light and dark, at 1440px and 390px wide,
|
|
457
|
+
with a real empty state.
|
|
458
|
+
- [ ] `npm pack` tarball < 2 MB; `files` whitelist honoured.
|
|
459
|
+
- [ ] After `npm ci && npm test && npm run build && npm pack`, `git status --porcelain`
|
|
460
|
+
prints nothing — the `.gitignore` is complete and nothing generated is tracked.
|
|
461
|
+
- [ ] README's quickstart, executed literally on a clean machine, works.
|
|
462
|
+
- [ ] `SPEC-NOTES.md` documents every place this brief disagreed with the real spec.
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 10. Resolving uncertainty
|
|
467
|
+
|
|
468
|
+
- Resolve protocol ambiguity against the primary source. Support the narrowest
|
|
469
|
+
verified interpretation and document its limits in `SPEC-NOTES.md`.
|
|
470
|
+
- Product decisions must preserve the agent's traffic. Record each material
|
|
471
|
+
decision and its rationale in `DECISIONS.md`.
|
|
472
|
+
- Document unsupported requirements and specification corrections explicitly.
|
|
473
|
+
- Delivery priorities favor correct CLI behavior, ledger arithmetic, and traffic
|
|
474
|
+
preservation before dashboard polish.
|