@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 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(r2.res, () => r2.res.json(), {}, host, r2.paid?.auth ?? (r2.paid ? {} : null));
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.8";
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
- const out = await wm.call(id, inputOf(a, 1), { network: net(a.flags.network), payIn });
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(r2.res, () => r2.res.json(), {}, host, r2.paid?.auth ?? (r2.paid ? {} : null));
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
  }
@@ -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.8",
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": {