@wafermapxyz/cli 0.4.8 → 0.4.9
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 +8 -0
- package/README.md +2 -0
- package/dist/cli.js +52 -7
- package/dist/index.js +44 -3
- package/dist/types/src/core.d.ts +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.9 (7 Nov 2026)
|
|
4
|
+
|
|
5
|
+
**`wafermap call <llm> --stream`: watch the answer arrive.**
|
|
6
|
+
|
|
7
|
+
- With `--stream`, an LLM resource answers as server-sent events and the CLI prints each piece as it comes. The payment is the same signature as before; Wafermap settles it only after the last piece, so a model that stops halfway charges nothing (the CLI then exits with `stream_broken`).
|
|
8
|
+
- Free calls ($WAFERMAP Pro) and `--pay wafermap` still answer in one JSON body; `--stream` prints that the usual way.
|
|
9
|
+
- `Wafermap.call()` takes `onDelta(text)` for the same thing from code.
|
|
10
|
+
|
|
3
11
|
## 0.4.8 (5 Nov 2026)
|
|
4
12
|
|
|
5
13
|
**Two more payment networks: Arc and Soneium.**
|
package/README.md
CHANGED
|
@@ -84,6 +84,8 @@ Pass `approve: (a) => boolean` to confirm each payment or swap yourself, and `le
|
|
|
84
84
|
|
|
85
85
|
A wallet that holds at least 100,000 $WAFERMAP on Robinhood Chain pays 50% less for Wafermap's own resources when it pays on an EVM network (Robinhood Chain, Base, Arbitrum, Polygon, Arc, Soneium). Nothing to set up: `wafermap call` names the wallet to Wafermap and the quote comes back discounted; the server re-checks the balance of the wallet that signs. Marketplace listings and Solana payments keep the regular price. Check a wallet: https://wafermap.xyz/api/holder?address=0x…, or run `wafermap holder`.
|
|
86
86
|
|
|
87
|
+
LLM resources can **stream**: `wafermap call gemma-4 prompt="hi" --stream` prints the answer as it arrives, and the payment settles only once the model finishes.
|
|
88
|
+
|
|
87
89
|
Wafermap's own resources can also be **paid in $WAFERMAP at 50% off, and the tokens are burned**: `wafermap approve-wafermap` once (a Permit2 approval, one Robinhood Chain transaction), then `wafermap call <id> --pay wafermap`. Each payment is a Permit2 signature for one exact amount, usable for two minutes only by Wafermap's relayer (fixed in the client), checked against a market price the client reads itself.
|
|
88
90
|
|
|
89
91
|
With 1,000,000 $WAFERMAP the wallet is **Pro**: 10 free calls a day (UTC) to resources listed at $0.01 or less. `wafermap call` sends a holder pass (a signed message, no transaction, valid 24 hours) to Wafermap only; when a call is free, nothing is signed for payment and it prints `✓ Free call`. Holders of 100,000 also unlock x402 Tracker Pro: `wafermap pass` prints a pass for its API. Details: https://wafermap.xyz/docs#holders
|
package/dist/cli.js
CHANGED
|
@@ -932,19 +932,59 @@ var init_core = __esm({
|
|
|
932
932
|
throw new WafermapError("approve_failed", e?.shortMessage ?? e?.message ?? "The approval failed.");
|
|
933
933
|
}
|
|
934
934
|
}
|
|
935
|
+
/** Reads /v1's server-sent events for an LLM call: delta pieces as they arrive, then receipt (or error), then done. */
|
|
936
|
+
async readStream(res, onDelta) {
|
|
937
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
938
|
+
let buf = "";
|
|
939
|
+
let receipt = null;
|
|
940
|
+
let done = null;
|
|
941
|
+
let error = null;
|
|
942
|
+
for (; ; ) {
|
|
943
|
+
const { value, done: end } = await reader.read();
|
|
944
|
+
if (end) break;
|
|
945
|
+
buf += value;
|
|
946
|
+
let i;
|
|
947
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
948
|
+
const block = buf.slice(0, i);
|
|
949
|
+
buf = buf.slice(i + 2);
|
|
950
|
+
const ev = /^event: (.+)$/m.exec(block)?.[1];
|
|
951
|
+
let data = null;
|
|
952
|
+
try {
|
|
953
|
+
data = JSON.parse(/^data: (.*)$/m.exec(block)?.[1] ?? "null");
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
if (ev === "delta" && typeof data?.text === "string") onDelta(data.text);
|
|
957
|
+
else if (ev === "receipt") receipt = data;
|
|
958
|
+
else if (ev === "done") done = data;
|
|
959
|
+
else if (ev === "error") error = data;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return { receipt, done, error };
|
|
963
|
+
}
|
|
935
964
|
async call(id, input = {}, o = {}) {
|
|
936
965
|
const inToken = o.payIn === "wafermap";
|
|
966
|
+
const stream = !!o.onDelta && !inToken;
|
|
937
967
|
if (inToken && id.startsWith("m/")) throw new WafermapError("token_not_offered", "Marketplace listings are paid in USDG or USDC to their seller, not in $WAFERMAP.");
|
|
938
968
|
if (inToken && !await this.wafermapApproved().catch(() => true)) throw new WafermapError("permit2_not_approved", "Paying in $WAFERMAP needs a one-time approval of Permit2. Run `wafermap approve-wafermap` (one transaction on Robinhood Chain, paid in ETH), then call again.");
|
|
939
969
|
const pass = id.startsWith("m/") ? null : await this.holderPass();
|
|
940
|
-
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
970
|
+
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...stream ? { accept: "text/event-stream" } : {}, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
941
971
|
const settle = (r2, out2) => {
|
|
942
972
|
if (!r2.paid || !r2.id) return;
|
|
943
|
-
const status = r2.res.status === 402 ? "refused" : r2.res.ok ? "ok" : "failed";
|
|
973
|
+
const status = r2.res.status === 402 ? "refused" : r2.res.ok && !out2?.streamError ? "ok" : "failed";
|
|
944
974
|
this.outcome(r2.id, "resource", id, r2.paid, status, out2?.receipt?.tx ?? r2.paid.tx, typeof out2?.receipt?.amountUsd === "number" ? out2.receipt.amountUsd : null);
|
|
945
975
|
};
|
|
946
976
|
const host = new URL(this.baseUrl).host;
|
|
947
|
-
const read = (r2) => this.body(
|
|
977
|
+
const read = (r2) => this.body(
|
|
978
|
+
r2.res,
|
|
979
|
+
async () => {
|
|
980
|
+
if (!(stream && r2.res.ok && (r2.res.headers.get("content-type") ?? "").includes("text/event-stream"))) return r2.res.json();
|
|
981
|
+
const s = await this.readStream(r2.res, o.onDelta);
|
|
982
|
+
return { resource: id, result: { text: s.done?.text ?? "", model: s.done?.model, usage: s.done?.usage }, receipt: s.receipt, streamError: s.error };
|
|
983
|
+
},
|
|
984
|
+
{},
|
|
985
|
+
host,
|
|
986
|
+
r2.paid?.auth ?? (r2.paid ? {} : null)
|
|
987
|
+
);
|
|
948
988
|
let r = await go();
|
|
949
989
|
let out = await read(r);
|
|
950
990
|
settle(r, out);
|
|
@@ -959,6 +999,7 @@ var init_core = __esm({
|
|
|
959
999
|
const { res, paid } = r;
|
|
960
1000
|
const refused = this.rejected(res, out, paid);
|
|
961
1001
|
if (refused) throw refused;
|
|
1002
|
+
if (out?.streamError) throw new WafermapError(out.streamError.code ?? "stream_failed", out.streamError.message ?? "The answer did not finish. You were not charged.");
|
|
962
1003
|
if (!res.ok) throw new WafermapError(out?.error?.code ?? "http_error", out?.error?.message ?? (typeof out?.error === "string" ? out.error : `HTTP ${res.status}`));
|
|
963
1004
|
return out;
|
|
964
1005
|
}
|
|
@@ -1247,7 +1288,7 @@ var VERSION;
|
|
|
1247
1288
|
var init_version = __esm({
|
|
1248
1289
|
"src/version.ts"() {
|
|
1249
1290
|
"use strict";
|
|
1250
|
-
VERSION = "0.4.
|
|
1291
|
+
VERSION = "0.4.9";
|
|
1251
1292
|
}
|
|
1252
1293
|
});
|
|
1253
1294
|
|
|
@@ -1810,6 +1851,7 @@ Pay
|
|
|
1810
1851
|
wafermap resources [words] Wafermap's pay-per-call catalog
|
|
1811
1852
|
wafermap call <id> [field=value ...] run a resource and pay its price (--json '{...}' for complex input)
|
|
1812
1853
|
wafermap call <id> ... --pay wafermap pay a Wafermap resource in $WAFERMAP at 50% off (the tokens are burned)
|
|
1854
|
+
wafermap call <llm> ... --stream print the answer as it arrives; you pay only once the model finishes
|
|
1813
1855
|
wafermap approve-wafermap one-time approval so the wallet can pay in $WAFERMAP (Robinhood Chain, ETH gas)
|
|
1814
1856
|
wafermap holder what this wallet's $WAFERMAP unlocks: holder price, Pro free calls left today
|
|
1815
1857
|
wafermap pass print a holder pass (X-Wafermap-Pass) for scripts and x402 Tracker Pro
|
|
@@ -1831,7 +1873,7 @@ function parse(argv) {
|
|
|
1831
1873
|
if (a.startsWith("--")) {
|
|
1832
1874
|
const [k, v] = a.slice(2).split("=", 2);
|
|
1833
1875
|
if (v !== void 0) out.flags[k] = v;
|
|
1834
|
-
else if (argv[i + 1] && !argv[i + 1].startsWith("--") && !["yes", "post", "import"].includes(k)) out.flags[k] = argv[++i];
|
|
1876
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith("--") && !["yes", "post", "import", "stream"].includes(k)) out.flags[k] = argv[++i];
|
|
1835
1877
|
else out.flags[k] = true;
|
|
1836
1878
|
} else out._.push(a);
|
|
1837
1879
|
}
|
|
@@ -2050,12 +2092,15 @@ Total $${total.toFixed(4)} in ${pays.length} payments. From 0.3.3 every payment
|
|
|
2050
2092
|
const id = a._[0] ?? die("Which resource? e.g. wafermap call gas-now network=base");
|
|
2051
2093
|
const wm = openWallet(await password(), approver(yes));
|
|
2052
2094
|
const payIn = a.flags.pay === "wafermap" ? "wafermap" : a.flags.pay === void 0 || a.flags.pay === "usd" ? void 0 : die("--pay must be wafermap (or leave it out to pay in USDG/USDC)");
|
|
2053
|
-
|
|
2095
|
+
let streamed = false;
|
|
2096
|
+
const onDelta = a.flags.stream === true ? (t) => (streamed = true, process.stdout.write(t)) : void 0;
|
|
2097
|
+
const out = await wm.call(id, inputOf(a, 1), { network: net(a.flags.network), payIn, onDelta });
|
|
2098
|
+
if (streamed) process.stdout.write("\n");
|
|
2054
2099
|
if (out.receipt?.asset === "WAFERMAP") console.error(`\u2713 Paid ${out.receipt.amountTokens} $WAFERMAP (\u2248 $${out.receipt.amountUsd}), burned \xB7 ${EXPLORER(out.receipt.network)}/tx/${out.receipt.tx}`);
|
|
2055
2100
|
else if (out.receipt) console.error(`\u2713 Paid $${out.receipt.amountUsd} ${out.receipt.asset} \xB7 ${EXPLORER(out.receipt.network)}/tx/${out.receipt.tx}`);
|
|
2056
2101
|
else if (out.free) console.error(`\u2713 Free call ($WAFERMAP Pro) \xB7 ${out.free.left} of ${out.free.perDay} left today. Nothing was paid.`);
|
|
2057
2102
|
if (typeof a.flags.out === "string") await saveMedia(out.result, a.flags.out);
|
|
2058
|
-
return printResult(out.result);
|
|
2103
|
+
return streamed ? void 0 : printResult(out.result);
|
|
2059
2104
|
}
|
|
2060
2105
|
case "pay": {
|
|
2061
2106
|
const url = a._[0] ?? die("Which URL? e.g. wafermap pay https://example.com/api city=Paris");
|
package/dist/index.js
CHANGED
|
@@ -871,19 +871,59 @@ var Wafermap = class {
|
|
|
871
871
|
throw new WafermapError("approve_failed", e?.shortMessage ?? e?.message ?? "The approval failed.");
|
|
872
872
|
}
|
|
873
873
|
}
|
|
874
|
+
/** Reads /v1's server-sent events for an LLM call: delta pieces as they arrive, then receipt (or error), then done. */
|
|
875
|
+
async readStream(res, onDelta) {
|
|
876
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
877
|
+
let buf = "";
|
|
878
|
+
let receipt = null;
|
|
879
|
+
let done = null;
|
|
880
|
+
let error = null;
|
|
881
|
+
for (; ; ) {
|
|
882
|
+
const { value, done: end } = await reader.read();
|
|
883
|
+
if (end) break;
|
|
884
|
+
buf += value;
|
|
885
|
+
let i;
|
|
886
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
887
|
+
const block = buf.slice(0, i);
|
|
888
|
+
buf = buf.slice(i + 2);
|
|
889
|
+
const ev = /^event: (.+)$/m.exec(block)?.[1];
|
|
890
|
+
let data = null;
|
|
891
|
+
try {
|
|
892
|
+
data = JSON.parse(/^data: (.*)$/m.exec(block)?.[1] ?? "null");
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
if (ev === "delta" && typeof data?.text === "string") onDelta(data.text);
|
|
896
|
+
else if (ev === "receipt") receipt = data;
|
|
897
|
+
else if (ev === "done") done = data;
|
|
898
|
+
else if (ev === "error") error = data;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return { receipt, done, error };
|
|
902
|
+
}
|
|
874
903
|
async call(id, input = {}, o = {}) {
|
|
875
904
|
const inToken = o.payIn === "wafermap";
|
|
905
|
+
const stream = !!o.onDelta && !inToken;
|
|
876
906
|
if (inToken && id.startsWith("m/")) throw new WafermapError("token_not_offered", "Marketplace listings are paid in USDG or USDC to their seller, not in $WAFERMAP.");
|
|
877
907
|
if (inToken && !await this.wafermapApproved().catch(() => true)) throw new WafermapError("permit2_not_approved", "Paying in $WAFERMAP needs a one-time approval of Permit2. Run `wafermap approve-wafermap` (one transaction on Robinhood Chain, paid in ETH), then call again.");
|
|
878
908
|
const pass = id.startsWith("m/") ? null : await this.holderPass();
|
|
879
|
-
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
909
|
+
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...stream ? { accept: "text/event-stream" } : {}, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
880
910
|
const settle = (r2, out2) => {
|
|
881
911
|
if (!r2.paid || !r2.id) return;
|
|
882
|
-
const status = r2.res.status === 402 ? "refused" : r2.res.ok ? "ok" : "failed";
|
|
912
|
+
const status = r2.res.status === 402 ? "refused" : r2.res.ok && !out2?.streamError ? "ok" : "failed";
|
|
883
913
|
this.outcome(r2.id, "resource", id, r2.paid, status, out2?.receipt?.tx ?? r2.paid.tx, typeof out2?.receipt?.amountUsd === "number" ? out2.receipt.amountUsd : null);
|
|
884
914
|
};
|
|
885
915
|
const host = new URL(this.baseUrl).host;
|
|
886
|
-
const read = (r2) => this.body(
|
|
916
|
+
const read = (r2) => this.body(
|
|
917
|
+
r2.res,
|
|
918
|
+
async () => {
|
|
919
|
+
if (!(stream && r2.res.ok && (r2.res.headers.get("content-type") ?? "").includes("text/event-stream"))) return r2.res.json();
|
|
920
|
+
const s = await this.readStream(r2.res, o.onDelta);
|
|
921
|
+
return { resource: id, result: { text: s.done?.text ?? "", model: s.done?.model, usage: s.done?.usage }, receipt: s.receipt, streamError: s.error };
|
|
922
|
+
},
|
|
923
|
+
{},
|
|
924
|
+
host,
|
|
925
|
+
r2.paid?.auth ?? (r2.paid ? {} : null)
|
|
926
|
+
);
|
|
887
927
|
let r = await go();
|
|
888
928
|
let out = await read(r);
|
|
889
929
|
settle(r, out);
|
|
@@ -898,6 +938,7 @@ var Wafermap = class {
|
|
|
898
938
|
const { res, paid } = r;
|
|
899
939
|
const refused = this.rejected(res, out, paid);
|
|
900
940
|
if (refused) throw refused;
|
|
941
|
+
if (out?.streamError) throw new WafermapError(out.streamError.code ?? "stream_failed", out.streamError.message ?? "The answer did not finish. You were not charged.");
|
|
901
942
|
if (!res.ok) throw new WafermapError(out?.error?.code ?? "http_error", out?.error?.message ?? (typeof out?.error === "string" ? out.error : `HTTP ${res.status}`));
|
|
902
943
|
return out;
|
|
903
944
|
}
|
package/dist/types/src/core.d.ts
CHANGED
|
@@ -155,9 +155,12 @@ export declare class Wafermap {
|
|
|
155
155
|
* transaction on Robinhood Chain, paid in ETH. Each payment after that is a signature for one exact amount,
|
|
156
156
|
* spendable only by Wafermap's relayer for two minutes. */
|
|
157
157
|
approveWafermap(): Promise<string>;
|
|
158
|
+
/** Reads /v1's server-sent events for an LLM call: delta pieces as they arrive, then receipt (or error), then done. */
|
|
159
|
+
private readStream;
|
|
158
160
|
call(id: string, input?: Record<string, unknown>, o?: {
|
|
159
161
|
network?: Net;
|
|
160
162
|
payIn?: "stablecoin" | "wafermap";
|
|
163
|
+
onDelta?: (text: string) => void;
|
|
161
164
|
}): Promise<{
|
|
162
165
|
resource: string;
|
|
163
166
|
result: any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wafermapxyz/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Wafermap for agents: an encrypted local wallet that pays x402 resources, discovers and pays third-party x402 services (USDG on Robinhood Chain; USDC on Solana, Base, Arbitrum, Polygon, Arc, Soneium), and swaps on Robinhood Chain and Base. CLI, SDK and MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|