@wasit-dev/cli 0.1.1 → 0.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/README.md +4 -3
- package/dist/index.js +125 -11
- package/package.json +7 -10
package/README.md
CHANGED
|
@@ -131,14 +131,15 @@ All keys are **testnet only**. `wasit` also reads a `.env` file in the current w
|
|
|
131
131
|
|
|
132
132
|
## What's checked
|
|
133
133
|
|
|
134
|
-
Thirteen checks across x402 and MPP, each traced to a written spec clause — the full catalogue, with pass criteria and spec references, is in [`docs/CHECKS.md`](https://github.com/
|
|
134
|
+
Thirteen checks across x402 and MPP, each traced to a written spec clause — the full catalogue, with pass criteria and spec references, is in [`docs/CHECKS.md`](https://github.com/wasit-dev/wasit/blob/main/docs/CHECKS.md).
|
|
135
135
|
|
|
136
136
|
## Related
|
|
137
137
|
|
|
138
|
+
- [Website](https://usewasit.dev)
|
|
138
139
|
- [`@wasit-dev/core`](https://www.npmjs.com/package/@wasit-dev/core) — the check suite this CLI runs, if you're building your own tooling on top
|
|
139
140
|
- [`@wasit-dev/server`](https://www.npmjs.com/package/@wasit-dev/server) — the same checks as MCP tools, for Claude Code and other agents
|
|
140
|
-
- [Full documentation](https://github.com/
|
|
141
|
+
- [Full documentation](https://github.com/wasit-dev/wasit) — CLI guide, MCP guide, configuration, design notes
|
|
141
142
|
|
|
142
143
|
## License
|
|
143
144
|
|
|
144
|
-
Apache-2.0 — see [LICENSE](https://github.com/
|
|
145
|
+
Apache-2.0 — see [LICENSE](https://github.com/wasit-dev/wasit/blob/main/LICENSE).
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "dotenv/config";
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
-
import { checkStatus, runMppChannelSuite, runMppChargeSuite, runX402PaymentChecks, runX402ReadChecks, summarize, } from "@wasit-dev/core";
|
|
4
|
+
import { CHECK_CATALOGUE, PROTOCOL_IDS, checkStatus, runMppChannelSuite, runMppChargeSuite, runX402PaymentChecks, runX402ReadChecks, summarize, toStructuredRun, } from "@wasit-dev/core";
|
|
5
|
+
/** Which wasit subcommand runs each protocol's checks — for `wasit checks` output. */
|
|
6
|
+
const COMMAND_BY_PROTOCOL = {
|
|
7
|
+
x402: "test",
|
|
8
|
+
"mpp-charge": "mpp-charge",
|
|
9
|
+
"mpp-channel": "mpp-channel",
|
|
10
|
+
};
|
|
5
11
|
/**
|
|
6
12
|
* Accumulates repeated --header flags into one object.
|
|
7
13
|
*
|
|
@@ -23,21 +29,54 @@ const program = new Command();
|
|
|
23
29
|
program
|
|
24
30
|
.name("wasit")
|
|
25
31
|
.description("Protocol-compliance testing for x402 / MPP on Stellar")
|
|
26
|
-
.version("0.1.0")
|
|
32
|
+
.version("0.1.0")
|
|
33
|
+
.addHelpText("after", `
|
|
34
|
+
Examples:
|
|
35
|
+
$ wasit checks
|
|
36
|
+
$ wasit test --target https://api.example.com/paid-endpoint --read-only
|
|
37
|
+
$ wasit mpp-charge --target https://api.example.com/paid-endpoint --payer-key S...
|
|
38
|
+
$ wasit mpp-channel --target https://api.example.com/paid-endpoint
|
|
39
|
+
|
|
40
|
+
Run "wasit <command> --help" for that command's own options and cost notes.
|
|
41
|
+
Add --json to test/mpp-charge/mpp-channel for machine-readable output.
|
|
42
|
+
Full check catalogue (every check ID, spec reference, pass criteria):
|
|
43
|
+
https://github.com/wasit-dev/wasit/blob/main/docs/CHECKS.md`);
|
|
44
|
+
/**
|
|
45
|
+
* Prints an advisory/status line meant for a human watching the terminal —
|
|
46
|
+
* never part of the run's actual result. Routed to stderr when --json is
|
|
47
|
+
* set, so stdout stays parseable JSON and a script piping it (jq, etc.)
|
|
48
|
+
* never has to skip past prose first.
|
|
49
|
+
*/
|
|
50
|
+
function note(json, message) {
|
|
51
|
+
if (json) {
|
|
52
|
+
console.error(message);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(message);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
27
58
|
/**
|
|
28
59
|
* Prints results and returns the process exit code.
|
|
29
60
|
*
|
|
30
61
|
* A skipped check carries `pass: false` so it can never be counted as
|
|
31
62
|
* conformance, but it is not a failure and must not fail the run. An errored
|
|
32
63
|
* check produced no verdict at all and is reported separately again.
|
|
64
|
+
*
|
|
65
|
+
* `--json` reuses `toStructuredRun()` from core — the same reshape the MCP
|
|
66
|
+
* server calls for its own structured output — rather than defining a
|
|
67
|
+
* second JSON shape here that could drift from it.
|
|
33
68
|
*/
|
|
34
|
-
function report(results) {
|
|
69
|
+
function report(results, json) {
|
|
70
|
+
const counts = summarize(results);
|
|
71
|
+
if (json) {
|
|
72
|
+
console.log(JSON.stringify(toStructuredRun(results), null, 2));
|
|
73
|
+
return counts.exitCode;
|
|
74
|
+
}
|
|
35
75
|
for (const result of results) {
|
|
36
76
|
const flag = result.destructive ? " [destructive]" : "";
|
|
37
77
|
console.log(`${checkStatus(result)} ${result.id} ${result.name}${flag}`);
|
|
38
78
|
console.log(` ${result.detail}\n`);
|
|
39
79
|
}
|
|
40
|
-
const counts = summarize(results);
|
|
41
80
|
const line = [`${counts.passed} passed`];
|
|
42
81
|
if (counts.failed > 0)
|
|
43
82
|
line.push(`${counts.failed} failed`);
|
|
@@ -52,6 +91,48 @@ function report(results) {
|
|
|
52
91
|
}
|
|
53
92
|
return counts.exitCode;
|
|
54
93
|
}
|
|
94
|
+
program
|
|
95
|
+
.command("checks")
|
|
96
|
+
.description("List the check catalogue: every check wasit can run, by ID")
|
|
97
|
+
.option("--protocol <name>", `Filter to one protocol (${PROTOCOL_IDS.join(", ")}) — matches the wasit subcommand that runs it`)
|
|
98
|
+
.option("--json", "Print as JSON instead of a formatted list", false)
|
|
99
|
+
.addHelpText("after", `
|
|
100
|
+
Examples:
|
|
101
|
+
$ wasit checks
|
|
102
|
+
$ wasit checks --protocol mpp-channel
|
|
103
|
+
$ wasit checks --json
|
|
104
|
+
|
|
105
|
+
Full pass-criteria prose, spec citations, and revision notes for every check
|
|
106
|
+
live in docs/CHECKS.md — this command is a quick reference, not a
|
|
107
|
+
replacement for it.`)
|
|
108
|
+
.action((opts) => {
|
|
109
|
+
const protocol = opts.protocol;
|
|
110
|
+
if (protocol !== undefined && !PROTOCOL_IDS.includes(protocol)) {
|
|
111
|
+
console.error(`Unknown --protocol "${protocol}". Expected one of: ${PROTOCOL_IDS.join(", ")}.`);
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const entries = CHECK_CATALOGUE.filter((entry) => protocol === undefined || entry.protocol === protocol);
|
|
115
|
+
if (opts.json === true) {
|
|
116
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
let currentProtocol;
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (entry.protocol !== currentProtocol) {
|
|
122
|
+
currentProtocol = entry.protocol;
|
|
123
|
+
console.log(`\n${currentProtocol} (wasit ${COMMAND_BY_PROTOCOL[currentProtocol]})`);
|
|
124
|
+
}
|
|
125
|
+
const flags = [
|
|
126
|
+
entry.negative ? "negative" : null,
|
|
127
|
+
entry.destructive ? "destructive" : null,
|
|
128
|
+
entry.costsFunds ? "costs funds" : null,
|
|
129
|
+
].filter((flag) => flag !== null);
|
|
130
|
+
const flagText = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
131
|
+
console.log(` ${entry.id} ${entry.name}${flagText}`);
|
|
132
|
+
console.log(` ${entry.summary}`);
|
|
133
|
+
}
|
|
134
|
+
console.log(`\n${entries.length} check${entries.length === 1 ? "" : "s"}.`);
|
|
135
|
+
});
|
|
55
136
|
program
|
|
56
137
|
.command("test")
|
|
57
138
|
.description("Run x402 compliance checks against a target service")
|
|
@@ -63,7 +144,19 @@ program
|
|
|
63
144
|
.option("--body <json>", "Request body, sent verbatim. Implies Content-Type: application/json.")
|
|
64
145
|
.option("--header <name:value>", "Extra request header the endpoint needs before it will issue a challenge. Repeatable.", collectHeader)
|
|
65
146
|
.option("--read-only", "Skip payment checks (X402-06/07)", false)
|
|
147
|
+
.option("--json", "Print results as JSON instead of formatted text", false)
|
|
148
|
+
.addHelpText("after", `
|
|
149
|
+
Examples:
|
|
150
|
+
$ wasit test --target https://api.example.com/paid-endpoint --read-only
|
|
151
|
+
$ wasit test --target https://api.example.com/paid-endpoint --payer-key S...
|
|
152
|
+
$ wasit test --target https://api.example.com/paid-endpoint --read-only --json
|
|
153
|
+
|
|
154
|
+
X402-01..05 (challenge/header checks) always run and cost nothing. X402-06/07
|
|
155
|
+
(real payment checks) run only when a payer key is available and --read-only
|
|
156
|
+
is not set: X402-06 settles a payment, X402-07 attempts one with a corrupted
|
|
157
|
+
signature. See docs/CHECKS.md for what each check ID verifies.`)
|
|
66
158
|
.action(async (opts) => {
|
|
159
|
+
const jsonMode = opts.json === true;
|
|
67
160
|
const shape = {
|
|
68
161
|
...(opts.method ? { method: opts.method } : {}),
|
|
69
162
|
...(opts.body !== undefined ? { body: opts.body } : {}),
|
|
@@ -72,13 +165,13 @@ program
|
|
|
72
165
|
const results = await runX402ReadChecks({ target: opts.target, ...shape });
|
|
73
166
|
const payerKey = opts.payerKey ?? process.env.STELLAR_PRIVATE_KEY;
|
|
74
167
|
if (opts.readOnly) {
|
|
75
|
-
|
|
168
|
+
note(jsonMode, "(--read-only set: skipping payment checks)\n");
|
|
76
169
|
}
|
|
77
170
|
else if (!payerKey) {
|
|
78
|
-
|
|
171
|
+
note(jsonMode, "(no payer key: set STELLAR_PRIVATE_KEY in .env or pass --payer-key — skipping payment checks)\n");
|
|
79
172
|
}
|
|
80
173
|
else {
|
|
81
|
-
|
|
174
|
+
note(jsonMode, "X402-06 settles a real payment and X402-07 attempts one. Testnet funds will move.\n");
|
|
82
175
|
results.push(...(await runX402PaymentChecks({
|
|
83
176
|
target: opts.target,
|
|
84
177
|
network: opts.network,
|
|
@@ -86,7 +179,7 @@ program
|
|
|
86
179
|
...shape,
|
|
87
180
|
})));
|
|
88
181
|
}
|
|
89
|
-
process.exit(report(results));
|
|
182
|
+
process.exit(report(results, jsonMode));
|
|
90
183
|
});
|
|
91
184
|
program
|
|
92
185
|
.command("mpp-channel")
|
|
@@ -103,7 +196,19 @@ program
|
|
|
103
196
|
.option("--expect-refund-period <ledgers>", "MPP-10: expected refund waiting period")
|
|
104
197
|
.option("--allow-destructive", "Enable MPP-13. Closing settles on-chain and permanently ends the channel.", false)
|
|
105
198
|
.option("--destructive-channel <address>", "Channel MPP-13 is permitted to close (default: CHANNEL_CONTRACT_DISPOSABLE)")
|
|
199
|
+
.option("--json", "Print results as JSON instead of formatted text", false)
|
|
200
|
+
.addHelpText("after", `
|
|
201
|
+
Examples:
|
|
202
|
+
$ wasit mpp-channel --target https://api.example.com/paid-endpoint
|
|
203
|
+
$ wasit mpp-channel --target https://api.example.com/paid-endpoint --allow-destructive --destructive-channel C...
|
|
204
|
+
$ wasit mpp-channel --target https://api.example.com/paid-endpoint --json
|
|
205
|
+
|
|
206
|
+
MPP-10, MPP-11, MPP-12, MPP-14 run by default and are non-destructive.
|
|
207
|
+
MPP-13 (channel close) only runs with --allow-destructive, and only against
|
|
208
|
+
the channel named by --destructive-channel — running it permanently ends
|
|
209
|
+
that channel.`)
|
|
106
210
|
.action(async (opts) => {
|
|
211
|
+
const jsonMode = opts.json === true;
|
|
107
212
|
const commitmentSecretHex = opts.commitmentKey ?? process.env.COMMITMENT_SECRET_HEX;
|
|
108
213
|
if (!commitmentSecretHex) {
|
|
109
214
|
console.error("No commitment key. Pass --commitment-key or set COMMITMENT_SECRET_HEX in .env.");
|
|
@@ -128,7 +233,7 @@ program
|
|
|
128
233
|
...(Number.isInteger(refundWaitingPeriod) ? { refundWaitingPeriod } : {}),
|
|
129
234
|
},
|
|
130
235
|
});
|
|
131
|
-
process.exit(report(results));
|
|
236
|
+
process.exit(report(results, jsonMode));
|
|
132
237
|
});
|
|
133
238
|
program
|
|
134
239
|
.command("mpp-charge")
|
|
@@ -137,19 +242,28 @@ program
|
|
|
137
242
|
.option("--payer-key <key>", "Payer secret key, S... (default: MPP_PAYER_SECRET)")
|
|
138
243
|
.option("--network <network>", "CAIP-2 network id (default: MPP_STELLAR_NETWORK)")
|
|
139
244
|
.option("--rpc-url <url>", "Override the default Soroban RPC endpoint")
|
|
245
|
+
.option("--json", "Print results as JSON instead of formatted text", false)
|
|
246
|
+
.addHelpText("after", `
|
|
247
|
+
Examples:
|
|
248
|
+
$ wasit mpp-charge --target https://api.example.com/paid-endpoint --payer-key S...
|
|
249
|
+
$ wasit mpp-charge --target https://api.example.com/paid-endpoint --payer-key S... --json
|
|
250
|
+
|
|
251
|
+
Runs MPP-01 only. Not idempotent and has no read-only mode: every run settles
|
|
252
|
+
a real payment and moves testnet funds, because charge mode has no dry run.`)
|
|
140
253
|
.action(async (opts) => {
|
|
254
|
+
const jsonMode = opts.json === true;
|
|
141
255
|
const payerSecretKey = opts.payerKey ?? process.env.MPP_PAYER_SECRET;
|
|
142
256
|
if (!payerSecretKey) {
|
|
143
257
|
console.error("No payer key. Pass --payer-key or set MPP_PAYER_SECRET in .env.");
|
|
144
258
|
process.exit(2);
|
|
145
259
|
}
|
|
146
|
-
|
|
260
|
+
note(jsonMode, "MPP-01 settles a real payment. If the target is reachable, testnet funds will move.\n");
|
|
147
261
|
const results = await runMppChargeSuite({
|
|
148
262
|
target: opts.target,
|
|
149
263
|
network: opts.network ?? process.env.MPP_STELLAR_NETWORK ?? "stellar:testnet",
|
|
150
264
|
payerSecretKey,
|
|
151
265
|
...(opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {}),
|
|
152
266
|
});
|
|
153
|
-
process.exit(report(results));
|
|
267
|
+
process.exit(report(results, jsonMode));
|
|
154
268
|
});
|
|
155
269
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wasit-dev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for x402/MPP protocol-conformance testing on Stellar \u2014 verifies a service's payment flow settles on-chain per spec.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
],
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/
|
|
14
|
+
"url": "git+https://github.com/wasit-dev/wasit.git",
|
|
15
15
|
"directory": "packages/cli"
|
|
16
16
|
},
|
|
17
|
-
"homepage": "https://github.com/
|
|
17
|
+
"homepage": "https://github.com/wasit-dev/wasit#readme",
|
|
18
18
|
"bugs": {
|
|
19
|
-
"url": "https://github.com/
|
|
19
|
+
"url": "https://github.com/wasit-dev/wasit/issues"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"stellar",
|
|
@@ -39,11 +39,8 @@
|
|
|
39
39
|
"build": "tsc -p tsconfig.json"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"commander": "^12.0.0",
|
|
46
|
-
"dotenv": "^16.0.0",
|
|
47
|
-
"mppx": "^0.8.14"
|
|
42
|
+
"@wasit-dev/core": "^0.2.0",
|
|
43
|
+
"commander": "^15.0.0",
|
|
44
|
+
"dotenv": "^17.4.2"
|
|
48
45
|
}
|
|
49
46
|
}
|