@zeroxyz/cli 0.0.8 → 0.0.10

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.
Files changed (2) hide show
  1. package/dist/index.js +168 -153
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command as Command8 } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@zeroxyz/cli",
9
- version: "0.0.8",
9
+ version: "0.0.10",
10
10
  type: "module",
11
11
  bin: {
12
12
  zero: "dist/index.js",
@@ -36,6 +36,8 @@ var package_default = {
36
36
  "@relayprotocol/relay-sdk": "^5.2.1",
37
37
  "@x402/core": "^2.9.0",
38
38
  "@x402/evm": "^2.9.0",
39
+ "@x402/extensions": "^2.9.0",
40
+ "@x402/fetch": "^2.9.0",
39
41
  commander: "^13.0.0",
40
42
  mppx: "^0.5.9",
41
43
  open: "^11.0.0",
@@ -149,11 +151,13 @@ var fetchCommand = (appContext2) => new Command2("fetch").description("Fetch a c
149
151
  if (options.data && !headers["content-type"]) {
150
152
  headers["content-type"] = "application/json";
151
153
  }
154
+ const log = (msg) => console.error(` ${msg}`);
152
155
  const requestInit = {
153
156
  method: options.data ? "POST" : "GET",
154
157
  headers,
155
158
  body: options.data
156
159
  };
160
+ log(`Calling ${url}...`);
157
161
  const response = await fetch(url, requestInit);
158
162
  const paymentReq = detectPaymentRequirement(
159
163
  response.headers,
@@ -162,11 +166,15 @@ var fetchCommand = (appContext2) => new Command2("fetch").description("Fetch a c
162
166
  let finalResponse;
163
167
  let paymentMeta;
164
168
  if (paymentReq) {
169
+ log(
170
+ `Payment required (${paymentReq.protocol}) \u2014 preparing payment...`
171
+ );
165
172
  const result = await paymentService.handlePayment(
166
173
  url,
167
174
  requestInit,
168
175
  paymentReq,
169
- options.maxPay
176
+ options.maxPay,
177
+ log
170
178
  );
171
179
  finalResponse = result.response;
172
180
  paymentMeta = {
@@ -176,6 +184,9 @@ var fetchCommand = (appContext2) => new Command2("fetch").description("Fetch a c
176
184
  amount: result.amount,
177
185
  asset: result.asset
178
186
  };
187
+ log(
188
+ `Paid ${result.amount} ${result.asset} via ${result.protocol} on ${result.chain}`
189
+ );
179
190
  } else {
180
191
  finalResponse = response;
181
192
  }
@@ -222,10 +233,25 @@ var fetchCommand = (appContext2) => new Command2("fetch").description("Fetch a c
222
233
  paymentMode: "charge"
223
234
  }
224
235
  });
236
+ console.error(`
237
+ Run ID: ${runResult.runId}`);
225
238
  console.error(
226
- `
227
- Run \`zero review ${runResult.runId}\` to rate this capability.
228
- `
239
+ ` Leave a review to help other agents discover great capabilities:`
240
+ );
241
+ console.error(
242
+ ` zero review ${runResult.runId} --success --accuracy 5 --value 4 --reliability 5 --content "your feedback"`
243
+ );
244
+ console.error(
245
+ [
246
+ ``,
247
+ ` Tips for a great review:`,
248
+ ` --success / --no-success Did the API return the result you expected?`,
249
+ ` --accuracy 1-5 How correct was the response? (1 = wrong, 5 = perfect)`,
250
+ ` --value 1-5 Was it worth the price? (1 = overpriced, 5 = great deal)`,
251
+ ` --reliability 1-5 Did it respond quickly and without errors? (1 = flaky, 5 = rock solid)`,
252
+ ` --content Free-text is optional but helps other agents pick the best capability.`,
253
+ ``
254
+ ].join("\n")
229
255
  );
230
256
  } catch {
231
257
  }
@@ -502,7 +528,19 @@ var initCommand = (appContext2) => new Command4("init").description("Initialize
502
528
 
503
529
  // src/commands/review-command.ts
504
530
  import { Command as Command5 } from "commander";
505
- var reviewCommand = (appContext2) => new Command5("review").description("Submit a review for a capability run").argument("<runId>", "Run ID to review").option("--success", "The capability succeeded").option("--no-success", "The capability failed").requiredOption("--accuracy <n>", "Accuracy rating (1-5)", Number.parseInt).requiredOption("--value <n>", "Value rating (1-5)", Number.parseInt).requiredOption(
531
+ var reviewCommand = (appContext2) => new Command5("review").description("Submit a review for a capability run").addHelpText(
532
+ "after",
533
+ `
534
+ Tips for a great review:
535
+ --success / --no-success Did the API return the result you expected?
536
+ --accuracy 1-5 How correct was the response? (1 = wrong, 5 = perfect)
537
+ --value 1-5 Was it worth the price? (1 = overpriced, 5 = great deal)
538
+ --reliability 1-5 Did it respond quickly and without errors? (1 = flaky, 5 = rock solid)
539
+ --content Free-text is optional but helps other agents pick the best capability.
540
+
541
+ Example:
542
+ zero review run_abc123 --success --accuracy 5 --value 4 --reliability 5 --content "Fast, accurate translation"`
543
+ ).argument("<runId>", "Run ID to review").option("--success", "The capability succeeded").option("--no-success", "The capability failed").requiredOption("--accuracy <n>", "Accuracy rating (1-5)", Number.parseInt).requiredOption("--value <n>", "Value rating (1-5)", Number.parseInt).requiredOption(
506
544
  "--reliability <n>",
507
545
  "Reliability rating (1-5)",
508
546
  Number.parseInt
@@ -621,6 +659,7 @@ ${address}`);
621
659
  if (url) {
622
660
  await open(url);
623
661
  console.log("Opened funding page in your browser.");
662
+ console.log(`If it didn't open, visit: ${url}`);
624
663
  console.log(`Your wallet address: ${address}`);
625
664
  analyticsService.capture("wallet_funded", {
626
665
  method: "browser",
@@ -810,6 +849,7 @@ var capabilityResponseSchema = z2.object({
810
849
  method: z2.string(),
811
850
  headers: z2.record(z2.string(), z2.string()).nullable(),
812
851
  bodySchema: z2.record(z2.string(), z2.unknown()).nullable(),
852
+ responseSchema: z2.record(z2.string(), z2.unknown()).nullable(),
813
853
  example: z2.object({ request: z2.unknown(), response: z2.unknown() }).nullable(),
814
854
  tags: z2.array(z2.string()).nullable(),
815
855
  displayCostAmount: z2.string(),
@@ -922,18 +962,17 @@ var ApiService = class {
922
962
  // src/services/payment-service.ts
923
963
  import {
924
964
  adaptViemWallet,
965
+ convertViemChainToRelayChain,
925
966
  createClient as createRelayClient,
926
967
  getClient as getRelayClient,
927
968
  MAINNET_RELAY_API
928
969
  } from "@relayprotocol/relay-sdk";
929
970
  import { x402Client as X402Client } from "@x402/core/client";
930
- import {
931
- decodePaymentRequiredHeader,
932
- decodePaymentResponseHeader,
933
- encodePaymentSignatureHeader
934
- } from "@x402/core/http";
971
+ import { decodePaymentResponseHeader, x402HTTPClient } from "@x402/core/http";
935
972
  import { ExactEvmScheme } from "@x402/evm/exact/client";
936
- import { Challenge, Receipt } from "mppx";
973
+ import { createSIWxClientHook } from "@x402/extensions/sign-in-with-x";
974
+ import { wrapFetchWithPayment } from "@x402/fetch";
975
+ import { Receipt } from "mppx";
937
976
  import { Mppx, tempo } from "mppx/client";
938
977
  import {
939
978
  createPublicClient,
@@ -949,8 +988,12 @@ var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
949
988
  var BASE_CHAIN_ID = 8453;
950
989
  var TEMPO_CHAIN_ID = 4217;
951
990
  var TEMPO_TESTNET_CHAIN_ID = 42431;
952
- var X402_PAYMENT_MAX_ATTEMPTS = 6;
953
- var X402_PAYMENT_RETRY_DELAY_MS = 300;
991
+ var DEFAULT_MAX_DEPOSIT = "100";
992
+ var buildRelayClientOptions = () => ({
993
+ baseApiUrl: MAINNET_RELAY_API,
994
+ source: "zero-cli",
995
+ chains: [convertViemChainToRelayChain(base)]
996
+ });
954
997
  var calculateBuffer = (baseBalance) => {
955
998
  const twentyFivePercent = baseBalance / 4n;
956
999
  const twoDollars = 2000000n;
@@ -986,39 +1029,14 @@ var PaymentService = class {
986
1029
  this.account = account;
987
1030
  this.config = config;
988
1031
  }
989
- x402Client = null;
990
- mppxClient = null;
991
1032
  relayInitialized = false;
992
- getX402Client = () => {
993
- if (!this.x402Client) {
994
- if (!this.account) throw new Error("No wallet configured");
995
- this.x402Client = new X402Client().register(
996
- "eip155:*",
997
- new ExactEvmScheme(this.account)
998
- );
999
- }
1000
- return this.x402Client;
1001
- };
1002
- getMppxClient = () => {
1003
- if (!this.mppxClient) {
1004
- if (!this.account) throw new Error("No wallet configured");
1005
- this.mppxClient = Mppx.create({
1006
- polyfill: false,
1007
- methods: [tempo({ account: this.account })]
1008
- });
1009
- }
1010
- return this.mppxClient;
1011
- };
1012
1033
  ensureRelayClient = () => {
1013
1034
  if (!this.relayInitialized) {
1014
- createRelayClient({
1015
- baseApiUrl: MAINNET_RELAY_API,
1016
- source: "zero-cli"
1017
- });
1035
+ createRelayClient(buildRelayClientOptions());
1018
1036
  this.relayInitialized = true;
1019
1037
  }
1020
1038
  };
1021
- bridgeToTempo = async (requiredAmount) => {
1039
+ bridgeToTempo = async (requiredAmount, onProgress) => {
1022
1040
  if (!this.account) throw new Error("No wallet configured");
1023
1041
  this.ensureRelayClient();
1024
1042
  const baseBalance = await this.getBalanceRaw("base");
@@ -1029,6 +1047,9 @@ var PaymentService = class {
1029
1047
  `Insufficient Base USDC to bridge: have ${formatUnits(baseBalance, 6)}, need ${formatUnits(bridgeAmount, 6)} (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer)`
1030
1048
  );
1031
1049
  }
1050
+ onProgress?.(
1051
+ `Bridging ${formatUnits(bridgeAmount, 6)} USDC from Base to Tempo...`
1052
+ );
1032
1053
  const walletClient = createWalletClient({
1033
1054
  account: this.account,
1034
1055
  chain: base,
@@ -1042,7 +1063,10 @@ var PaymentService = class {
1042
1063
  amount: bridgeAmount.toString(),
1043
1064
  tradeType: "EXACT_INPUT",
1044
1065
  user: this.account.address,
1045
- recipient: this.account.address
1066
+ recipient: this.account.address,
1067
+ options: {
1068
+ usePermit: true
1069
+ }
1046
1070
  });
1047
1071
  let bridgeTxHash = null;
1048
1072
  await getRelayClient().actions.execute({
@@ -1056,150 +1080,141 @@ var PaymentService = class {
1056
1080
  });
1057
1081
  return bridgeTxHash;
1058
1082
  };
1059
- handlePayment = async (url, request, paymentRequirement, maxPay) => {
1083
+ handlePayment = async (url, request, paymentRequirement, maxPay, onProgress) => {
1060
1084
  if (!this.account) {
1061
1085
  throw new Error(
1062
1086
  "No wallet configured \u2014 run `zero init` or set ZERO_PRIVATE_KEY"
1063
1087
  );
1064
1088
  }
1065
1089
  if (paymentRequirement.protocol === "x402") {
1090
+ onProgress?.("Paying via x402 on Base...");
1066
1091
  return this.payX402(url, request, paymentRequirement.raw, maxPay);
1067
1092
  }
1068
1093
  if (paymentRequirement.protocol === "mpp") {
1069
- return this.payMpp(url, request, paymentRequirement.raw, maxPay);
1094
+ onProgress?.("Paying via MPP on Tempo...");
1095
+ return this.payMpp(
1096
+ url,
1097
+ request,
1098
+ paymentRequirement.raw,
1099
+ maxPay,
1100
+ onProgress
1101
+ );
1070
1102
  }
1071
1103
  throw new Error("Unrecognized 402 payment protocol");
1072
1104
  };
1073
- toX402RawChallenge = (headerValue) => {
1074
- try {
1075
- return JSON.parse(Buffer.from(headerValue, "base64").toString("utf8"));
1076
- } catch {
1077
- return { encoded: headerValue };
1078
- }
1079
- };
1080
- toX402HeaderValue = (raw) => {
1081
- const encoded = raw?.encoded;
1082
- if (typeof encoded === "string" && encoded.length > 0) {
1083
- return encoded;
1084
- }
1085
- return Buffer.from(JSON.stringify(raw)).toString("base64");
1086
- };
1087
- payX402 = async (url, request, raw, maxPay) => {
1088
- const client = this.getX402Client();
1089
- let currentRaw = raw;
1090
- let amountUsdc = "0";
1091
- for (let attempt = 1; attempt <= X402_PAYMENT_MAX_ATTEMPTS; attempt++) {
1092
- const headerValue = this.toX402HeaderValue(currentRaw);
1093
- const paymentRequired = decodePaymentRequiredHeader(headerValue);
1094
- const requirement = paymentRequired.accepts[0];
1095
- if (!requirement) {
1096
- throw new Error("No accepted payment methods in x402 challenge");
1105
+ payX402 = async (url, request, _raw, maxPay) => {
1106
+ if (!this.account) throw new Error("No wallet configured");
1107
+ let capturedAmount = "0";
1108
+ const client = new X402Client().register(
1109
+ "eip155:*",
1110
+ new ExactEvmScheme(this.account)
1111
+ );
1112
+ client.onBeforePaymentCreation(async (context) => {
1113
+ const requirement = context.paymentRequired.accepts[0];
1114
+ if (!requirement) return;
1115
+ capturedAmount = formatUnits(BigInt(requirement.amount), 6);
1116
+ if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
1117
+ return {
1118
+ abort: true,
1119
+ reason: `Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
1120
+ };
1097
1121
  }
1098
- amountUsdc = formatUnits(BigInt(requirement.amount), 6);
1099
- if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
1100
- throw new Error(
1101
- `Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
1102
- );
1122
+ });
1123
+ const httpClient = new x402HTTPClient(client).onPaymentRequired(
1124
+ createSIWxClientHook(this.account)
1125
+ );
1126
+ const wrappedFetch = wrapFetchWithPayment(fetch, httpClient);
1127
+ const response = await wrappedFetch(url, {
1128
+ method: request.method,
1129
+ headers: request.headers,
1130
+ body: request.body
1131
+ });
1132
+ let txHash = null;
1133
+ const paymentResponseHeader = response.headers.get("payment-response") ?? response.headers.get("x-payment-response");
1134
+ if (paymentResponseHeader) {
1135
+ try {
1136
+ const settlement = decodePaymentResponseHeader(paymentResponseHeader);
1137
+ txHash = settlement.transaction ?? null;
1138
+ } catch {
1103
1139
  }
1104
- const paymentPayload = await client.createPaymentPayload(paymentRequired);
1105
- const signatureHeader = encodePaymentSignatureHeader(paymentPayload);
1106
- const retryResponse = await fetch(url, {
1107
- method: request.method,
1108
- headers: {
1109
- ...request.headers,
1110
- "PAYMENT-SIGNATURE": signatureHeader
1111
- },
1112
- body: request.body
1113
- });
1114
- if (retryResponse.status === 402) {
1115
- if (attempt >= X402_PAYMENT_MAX_ATTEMPTS) {
1116
- throw new Error(
1117
- `x402 payment failed after ${X402_PAYMENT_MAX_ATTEMPTS} attempts`
1118
- );
1140
+ }
1141
+ return {
1142
+ response,
1143
+ protocol: "x402",
1144
+ chain: "base",
1145
+ txHash,
1146
+ amount: capturedAmount,
1147
+ asset: "USDC"
1148
+ };
1149
+ };
1150
+ payMpp = async (url, request, _raw, maxPay, onProgress) => {
1151
+ if (!this.account) throw new Error("No wallet configured");
1152
+ let capturedAmount = "0";
1153
+ const mppx = Mppx.create({
1154
+ polyfill: false,
1155
+ methods: [
1156
+ tempo({
1157
+ account: this.account,
1158
+ maxDeposit: maxPay ?? DEFAULT_MAX_DEPOSIT
1159
+ })
1160
+ ],
1161
+ onChallenge: async (challenge) => {
1162
+ const challengeRequest = challenge.request;
1163
+ const intent = challenge.intent;
1164
+ let requiredRaw;
1165
+ if (intent === "session") {
1166
+ const suggestedDeposit = challengeRequest.suggestedDeposit;
1167
+ if (suggestedDeposit) {
1168
+ requiredRaw = BigInt(suggestedDeposit);
1169
+ } else {
1170
+ const depositStr = maxPay ?? DEFAULT_MAX_DEPOSIT;
1171
+ requiredRaw = BigInt(
1172
+ Math.floor(Number.parseFloat(depositStr) * 1e6)
1173
+ );
1174
+ }
1175
+ } else {
1176
+ requiredRaw = BigInt(challengeRequest.amount);
1119
1177
  }
1120
- const nextHeader = retryResponse.headers.get("payment-required") ?? retryResponse.headers.get("x-payment-required");
1121
- if (!nextHeader) {
1178
+ capturedAmount = formatUnits(requiredRaw, 6);
1179
+ if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
1122
1180
  throw new Error(
1123
- "x402 payment retry returned 402 without payment-required header"
1181
+ `Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
1124
1182
  );
1125
1183
  }
1126
- currentRaw = this.toX402RawChallenge(nextHeader);
1127
- await new Promise(
1128
- (resolve) => setTimeout(resolve, attempt * X402_PAYMENT_RETRY_DELAY_MS)
1129
- );
1130
- continue;
1131
- }
1132
- let txHash = null;
1133
- const paymentResponseHeader = retryResponse.headers.get("payment-response") ?? retryResponse.headers.get("x-payment-response");
1134
- if (paymentResponseHeader) {
1135
- try {
1136
- const settlement = decodePaymentResponseHeader(paymentResponseHeader);
1137
- txHash = settlement.transaction ?? null;
1138
- } catch {
1184
+ const methodDetails = challengeRequest.methodDetails;
1185
+ const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
1186
+ const isTestnet = challengeChainId === TEMPO_TESTNET_CHAIN_ID;
1187
+ const balanceChain = isTestnet ? "tempo-testnet" : "tempo";
1188
+ onProgress?.(`Checking Tempo balance...`);
1189
+ const tempoBalance = await this.getBalanceRaw(balanceChain);
1190
+ if (tempoBalance < requiredRaw) {
1191
+ if (isTestnet) {
1192
+ throw new Error(
1193
+ `Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
1194
+ );
1195
+ }
1196
+ await this.bridgeToTempo(requiredRaw, onProgress);
1139
1197
  }
1198
+ return void 0;
1140
1199
  }
1141
- return {
1142
- response: retryResponse,
1143
- protocol: "x402",
1144
- chain: "base",
1145
- txHash,
1146
- amount: amountUsdc,
1147
- asset: "USDC"
1148
- };
1149
- }
1150
- throw new Error("x402 payment failed");
1151
- };
1152
- payMpp = async (url, request, raw, maxPay) => {
1153
- const wwwAuth = raw?.["www-authenticate"] ?? "";
1154
- const challengeResponse = new Response("Payment Required", {
1155
- status: 402,
1156
- headers: { "www-authenticate": wwwAuth }
1157
1200
  });
1158
- const challenge = Challenge.fromResponse(challengeResponse);
1159
- const challengeRequest = challenge.request;
1160
- const amountRaw = challengeRequest.amount;
1161
- const amountUsdc = formatUnits(BigInt(amountRaw), 6);
1162
- if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
1163
- throw new Error(
1164
- `Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
1165
- );
1166
- }
1167
- const methodDetails = challengeRequest.methodDetails;
1168
- const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
1169
- const isTestnet = challengeChainId === TEMPO_TESTNET_CHAIN_ID;
1170
- const balanceChain = isTestnet ? "tempo-testnet" : "tempo";
1171
- const tempoBalance = await this.getBalanceRaw(balanceChain);
1172
- if (tempoBalance < BigInt(amountRaw)) {
1173
- if (isTestnet) {
1174
- throw new Error(
1175
- `Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${amountUsdc}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
1176
- );
1177
- }
1178
- await this.bridgeToTempo(BigInt(amountRaw));
1179
- }
1180
- const mppx = this.getMppxClient();
1181
- const credential = await mppx.createCredential(challengeResponse);
1182
- const retryResponse = await fetch(url, {
1201
+ const response = await mppx.fetch(url, {
1183
1202
  method: request.method,
1184
- headers: {
1185
- ...request.headers,
1186
- // biome-ignore lint/style/useNamingConvention: HTTP header name
1187
- Authorization: credential
1188
- },
1203
+ headers: request.headers,
1189
1204
  body: request.body
1190
1205
  });
1191
1206
  let txHash = null;
1192
1207
  try {
1193
- const receipt = Receipt.fromResponse(retryResponse);
1208
+ const receipt = Receipt.fromResponse(response);
1194
1209
  txHash = receipt.reference ?? null;
1195
1210
  } catch {
1196
1211
  }
1197
1212
  return {
1198
- response: retryResponse,
1213
+ response,
1199
1214
  protocol: "mpp",
1200
1215
  chain: "tempo",
1201
1216
  txHash,
1202
- amount: amountUsdc,
1217
+ amount: capturedAmount,
1203
1218
  asset: "USDC"
1204
1219
  };
1205
1220
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",
@@ -30,6 +30,8 @@
30
30
  "@relayprotocol/relay-sdk": "^5.2.1",
31
31
  "@x402/core": "^2.9.0",
32
32
  "@x402/evm": "^2.9.0",
33
+ "@x402/extensions": "^2.9.0",
34
+ "@x402/fetch": "^2.9.0",
33
35
  "commander": "^13.0.0",
34
36
  "mppx": "^0.5.9",
35
37
  "open": "^11.0.0",