taximeter 0.1.0 → 0.1.2

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/docs/RECORDING.md DELETED
@@ -1,75 +0,0 @@
1
- # Record the 20-second demo
2
-
3
- `demo.gif` is a 20-second recording of the included simulation running on Windows,
4
- captured on 2026-09-08. The simulation runs entirely against a local fixture and
5
- an in-memory ledger. Its signatures and settlement reports are synthetic.
6
-
7
- From a source checkout, prepare the built package:
8
-
9
- ```sh
10
- npm ci
11
- npm run build
12
- node docs/demo.mjs
13
- ```
14
-
15
- The simulation prints 20 allowed payments, the documented 402 block for payment
16
- 21, and an exact total of `140` atomic units. It creates no wallet, key, or persistent
17
- ledger and closes its listeners when finished.
18
-
19
- ## Captured Windows recording
20
-
21
- The shipped GIF captures an actual `cmd.exe` session through Windows ConPTY using
22
- [node-pty 1.1.0](https://github.com/microsoft/node-pty). The recorder types
23
- `node docs/demo.mjs`, stores the terminal's output with its original timing in an
24
- asciicast v2 file, and waits until 20 seconds have elapsed. It checks the block and
25
- exact ledger total in the captured output and requires the command shell to exit
26
- successfully. The terminal is 104 columns by 32 rows.
27
-
28
- [agg 1.9.0](https://github.com/asciinema/agg/releases/tag/v1.9.0) renders that capture
29
- using Consolas at 18 px, line height 1.1, the GitHub dark theme, and a 20 fps cap.
30
- The final frame is held long enough for the complete GIF to last 20 seconds.
31
- The resulting GIF is 1049×653 and 96,519 bytes. The final block response and ledger
32
- total remain visible together. No demo output is fabricated or replaced.
33
-
34
- The source capture and encoding tools are local development artifacts and are
35
- excluded from the repository and npm package. FFmpeg converts the GIF to an H.264
36
- MP4 with one padding row and column for even dimensions. The following recipes
37
- can reproduce the demonstration from a source checkout.
38
-
39
- ## VHS
40
-
41
- Install VHS and its ffmpeg/ttyd prerequisites from the
42
- [official VHS instructions](https://github.com/charmbracelet/vhs#installation).
43
- Run from the repository root on a system supported by VHS (WSL is suitable on Windows):
44
-
45
- ```sh
46
- vhs docs/demo.tape
47
- ```
48
-
49
- The tape types exactly `node docs/demo.mjs`, then leaves the resulting block and
50
- total visible. It writes `docs/demo.gif` at 960×660. Review the output before
51
- committing the replacement; it must fit within the package's 2 MB tarball limit.
52
-
53
- ## asciinema alternative
54
-
55
- With asciinema and agg installed from their official projects, record the same
56
- script and convert it:
57
-
58
- ```sh
59
- mkdir -p tmp
60
- asciinema rec --cols 104 --rows 32 --command "node docs/demo.mjs" tmp/demo.cast
61
- agg tmp/demo.cast docs/demo.gif
62
- ```
63
-
64
- This quick alternative uses agg's default final pause and produces a shorter GIF.
65
- For a 20-second result, increase `--last-frame-duration` by the difference between
66
- 20 seconds and the measured GIF duration, then render again. The verified Windows
67
- rendering helper calculates that pause automatically.
68
-
69
- Run these commands in a POSIX shell or WSL. Do not enter account data,
70
- wallet material, or real payment authorizations while recording. The expected
71
- terminal script is only the local demo command above.
72
-
73
- The Windows ConPTY/agg recording above was executed and visually checked. The VHS
74
- and asciinema alternatives remain documented recipes, not verified Windows runs.
75
- Recording tools are optional and are not package dependencies.
package/docs/demo.mjs DELETED
@@ -1,145 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createServer, request } from "node:http";
3
- import { setTimeout as delay } from "node:timers/promises";
4
- import { z } from "zod";
5
- import { closeProxy, createProxy, Ledger, parseConfig, totals } from "../dist/index.js";
6
-
7
- // Synthetic envelopes only: no signing, wallet, facilitator, or real payment.
8
- const asset = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
9
- const payTo = "0x209693bc6afc0c5328ba36faf03c514ef312287c";
10
- const payer = "0x857b06519e91e3a54538791bdbb0e22373e36b66";
11
- const encode = (value) => Buffer.from(JSON.stringify(value)).toString("base64");
12
- const accepted = {
13
- scheme: "exact",
14
- network: "eip155:8453",
15
- asset,
16
- payTo,
17
- amount: "7",
18
- maxTimeoutSeconds: 60,
19
- extra: { name: "USD Coin", version: "2" },
20
- };
21
- const upstream = createServer((req, res) => {
22
- const header = z.string().optional().parse(req.headers["payment-signature"]);
23
- if (!header) {
24
- const resource = z
25
- .url()
26
- .parse(`http://${z.string().parse(req.headers.host)}${z.string().parse(req.url)}`);
27
- res.writeHead(402, {
28
- "PAYMENT-REQUIRED": encode({
29
- x402Version: 2,
30
- resource: { url: resource },
31
- accepts: [accepted],
32
- }),
33
- });
34
- res.end("Synthetic offer");
35
- return;
36
- }
37
- const payment = z
38
- .object({ payload: z.object({ authorization: z.object({ nonce: z.string() }) }) })
39
- .parse(JSON.parse(Buffer.from(header, "base64").toString("utf8")));
40
- res.writeHead(200, {
41
- "PAYMENT-RESPONSE": encode({
42
- success: true,
43
- network: "eip155:8453",
44
- payer,
45
- transaction: payment.payload.authorization.nonce,
46
- }),
47
- });
48
- res.end("Synthetic settlement reported");
49
- });
50
- async function listen(server) {
51
- await new Promise((resolve, reject) => {
52
- server.once("error", reject);
53
- server.listen(0, "127.0.0.1", () => {
54
- server.off("error", reject);
55
- resolve();
56
- });
57
- });
58
- return z.object({ port: z.number() }).parse(server.address()).port;
59
- }
60
- const ledger = new Ledger(":memory:");
61
- const proxy = createProxy({
62
- ledger,
63
- config: parseConfig({ budgets: { perTask: null, perAgent: null, global: { amount: "140" } } }),
64
- });
65
- try {
66
- const upstreamPort = await listen(upstream);
67
- const proxyPort = await listen(proxy);
68
- const resource = `http://127.0.0.1:${upstreamPort}/data`;
69
- const offer = await fetch(resource);
70
- assert.equal(offer.status, 402);
71
- const challenge = z
72
- .object({
73
- x402Version: z.literal(2),
74
- resource: z.object({ url: z.url() }),
75
- accepts: z.array(z.object({ amount: z.literal("7") })),
76
- })
77
- .parse(
78
- JSON.parse(
79
- Buffer.from(z.string().parse(offer.headers.get("payment-required")), "base64").toString(
80
- "utf8",
81
- ),
82
- ),
83
- );
84
- assert.equal(challenge.resource.url, resource);
85
- await offer.arrayBuffer();
86
- console.log("Taximeter | local simulation | no money moves");
87
- console.log("Budget: 140 atomic units. Each payment: 7 atomic units.");
88
- for (let index = 1; index <= 21; index++) {
89
- const payload = {
90
- signature: `0x${"12".repeat(65)}`,
91
- authorization: {
92
- from: payer,
93
- to: payTo,
94
- value: "7",
95
- validAfter: "0",
96
- validBefore: "9999999999",
97
- nonce: `0x${index.toString(16).padStart(64, "0")}`,
98
- },
99
- };
100
- const result = await new Promise((resolve, reject) => {
101
- const req = request(
102
- {
103
- hostname: "127.0.0.1",
104
- port: proxyPort,
105
- path: resource,
106
- headers: {
107
- "PAYMENT-SIGNATURE": encode({
108
- x402Version: 2,
109
- accepted,
110
- resource: { url: resource },
111
- payload,
112
- }),
113
- "Taximeter-Task": "demo",
114
- "Taximeter-Agent": "local-fixture",
115
- },
116
- },
117
- (res) => {
118
- let body = "";
119
- res.setEncoding("utf8");
120
- res.on("data", (chunk) => {
121
- body += z.string().parse(chunk);
122
- });
123
- res.on("error", reject);
124
- res.on("end", () => resolve({ status: res.statusCode, body }));
125
- },
126
- );
127
- req.on("error", reject);
128
- req.end();
129
- });
130
- assert.equal(result.status, index <= 20 ? 200 : 402);
131
- console.log(
132
- `Payment ${index.toString().padStart(2)} | ${index <= 20 ? "allowed" : "BLOCKED"} | total ${totals(ledger.view())[0]?.amount} atomic units`,
133
- );
134
- if (index === 21) console.log(result.body);
135
- await delay(80);
136
- }
137
- assert.equal(totals(ledger.view())[0]?.amount, "140");
138
- assert.equal(ledger.events().filter((row) => row.status === "blocked").length, 1);
139
- console.log("Exact ledger total: 140 atomic units. One blocked event recorded.");
140
- } finally {
141
- if (proxy.listening) await closeProxy(proxy);
142
- upstream.closeAllConnections();
143
- if (upstream.listening) await new Promise((resolve) => upstream.close(resolve));
144
- ledger.close();
145
- }
package/docs/demo.tape DELETED
@@ -1,15 +0,0 @@
1
- Output docs/demo.gif
2
- Set Shell "bash"
3
- Set FontSize 18
4
- Set Width 960
5
- Set Height 660
6
- Set TypingSpeed 50ms
7
- Set Padding 20
8
- Hide
9
- Type "clear"
10
- Enter
11
- Sleep 500ms
12
- Show
13
- Type "node docs/demo.mjs"
14
- Enter
15
- Sleep 18s