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/CHANGELOG.md +12 -0
- package/README.md +11 -10
- package/SECURITY.md +1 -1
- package/dist/{chunk-I7YXGTRZ.js → chunk-DWBUWM3G.js} +13 -3
- package/dist/cli/index.js +7 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -5
- package/dist/ui/assets/{index-DOE5bD9P.js → index-Bts-0doc.js} +10 -10
- package/dist/ui/index.html +1 -1
- package/docs/SDK.md +7 -8
- package/package.json +3 -8
- package/CODE_OF_CONDUCT.md +0 -31
- package/CONTRIBUTING.md +0 -141
- package/DECISIONS.md +0 -54
- package/SPEC-NOTES.md +0 -369
- package/SPEC.md +0 -474
- package/VERIFICATION.md +0 -943
- package/docs/RECORDING.md +0 -75
- package/docs/demo.mjs +0 -145
- package/docs/demo.tape +0 -15
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