@zeroxyz/cli 0.0.7 → 0.0.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.
Files changed (2) hide show
  1. package/dist/index.js +141 -91
  2. package/package.json +8 -2
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.7",
9
+ version: "0.0.9",
10
10
  type: "module",
11
11
  bin: {
12
12
  zero: "dist/index.js",
@@ -26,8 +26,9 @@ var package_default = {
26
26
  dev: "tsx src/index.ts",
27
27
  cli: "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
28
28
  "test:integration": "vitest run --project integration",
29
+ "test:online": "vitest run --project online",
29
30
  "test:unit": "vitest run --project unit",
30
- test: "pnpm run test:integration",
31
+ test: "pnpm run test:unit && pnpm run test:integration",
31
32
  typecheck: "tsc"
32
33
  },
33
34
  dependencies: {
@@ -35,6 +36,8 @@ var package_default = {
35
36
  "@relayprotocol/relay-sdk": "^5.2.1",
36
37
  "@x402/core": "^2.9.0",
37
38
  "@x402/evm": "^2.9.0",
39
+ "@x402/extensions": "^2.9.0",
40
+ "@x402/fetch": "^2.9.0",
38
41
  commander: "^13.0.0",
39
42
  mppx: "^0.5.9",
40
43
  open: "^11.0.0",
@@ -43,7 +46,10 @@ var package_default = {
43
46
  zod: "^4.3.5"
44
47
  },
45
48
  devDependencies: {
49
+ "@hono/node-server": "^1.19.13",
46
50
  "@types/node": "^25.0.7",
51
+ "@x402/hono": "^2.9.0",
52
+ hono: "^4.12.12",
47
53
  tsup: "^8.5.1",
48
54
  tsx: "^4.21.0",
49
55
  typescript: "^5.9.3",
@@ -105,7 +111,7 @@ var configCommand = (_appContext) => new Command("config").description("View or
105
111
  import { Command as Command2 } from "commander";
106
112
  var detectPaymentRequirement = (headers, status) => {
107
113
  if (status !== 402) return null;
108
- const x402Header = headers.get("x-payment-required");
114
+ const x402Header = headers.get("payment-required") ?? headers.get("x-payment-required");
109
115
  if (x402Header) {
110
116
  try {
111
117
  const decoded = JSON.parse(
@@ -218,10 +224,25 @@ var fetchCommand = (appContext2) => new Command2("fetch").description("Fetch a c
218
224
  paymentMode: "charge"
219
225
  }
220
226
  });
227
+ console.error(`
228
+ Run ID: ${runResult.runId}`);
221
229
  console.error(
222
- `
223
- Run \`zero review ${runResult.runId}\` to rate this capability.
224
- `
230
+ ` Leave a review to help other agents discover great capabilities:`
231
+ );
232
+ console.error(
233
+ ` zero review ${runResult.runId} --success --accuracy 5 --value 4 --reliability 5 --content "your feedback"`
234
+ );
235
+ console.error(
236
+ [
237
+ ``,
238
+ ` Tips for a great review:`,
239
+ ` --success / --no-success Did the API return the result you expected?`,
240
+ ` --accuracy 1-5 How correct was the response? (1 = wrong, 5 = perfect)`,
241
+ ` --value 1-5 Was it worth the price? (1 = overpriced, 5 = great deal)`,
242
+ ` --reliability 1-5 Did it respond quickly and without errors? (1 = flaky, 5 = rock solid)`,
243
+ ` --content Free-text is optional but helps other agents pick the best capability.`,
244
+ ``
245
+ ].join("\n")
225
246
  );
226
247
  } catch {
227
248
  }
@@ -498,7 +519,19 @@ var initCommand = (appContext2) => new Command4("init").description("Initialize
498
519
 
499
520
  // src/commands/review-command.ts
500
521
  import { Command as Command5 } from "commander";
501
- 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(
522
+ var reviewCommand = (appContext2) => new Command5("review").description("Submit a review for a capability run").addHelpText(
523
+ "after",
524
+ `
525
+ Tips for a great review:
526
+ --success / --no-success Did the API return the result you expected?
527
+ --accuracy 1-5 How correct was the response? (1 = wrong, 5 = perfect)
528
+ --value 1-5 Was it worth the price? (1 = overpriced, 5 = great deal)
529
+ --reliability 1-5 Did it respond quickly and without errors? (1 = flaky, 5 = rock solid)
530
+ --content Free-text is optional but helps other agents pick the best capability.
531
+
532
+ Example:
533
+ zero review run_abc123 --success --accuracy 5 --value 4 --reliability 5 --content "Fast, accurate translation"`
534
+ ).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(
502
535
  "--reliability <n>",
503
536
  "Reliability rating (1-5)",
504
537
  Number.parseInt
@@ -617,6 +650,7 @@ ${address}`);
617
650
  if (url) {
618
651
  await open(url);
619
652
  console.log("Opened funding page in your browser.");
653
+ console.log(`If it didn't open, visit: ${url}`);
620
654
  console.log(`Your wallet address: ${address}`);
621
655
  analyticsService.capture("wallet_funded", {
622
656
  method: "browser",
@@ -806,6 +840,7 @@ var capabilityResponseSchema = z2.object({
806
840
  method: z2.string(),
807
841
  headers: z2.record(z2.string(), z2.string()).nullable(),
808
842
  bodySchema: z2.record(z2.string(), z2.unknown()).nullable(),
843
+ responseSchema: z2.record(z2.string(), z2.unknown()).nullable(),
809
844
  example: z2.object({ request: z2.unknown(), response: z2.unknown() }).nullable(),
810
845
  tags: z2.array(z2.string()).nullable(),
811
846
  displayCostAmount: z2.string(),
@@ -923,13 +958,11 @@ import {
923
958
  MAINNET_RELAY_API
924
959
  } from "@relayprotocol/relay-sdk";
925
960
  import { x402Client as X402Client } from "@x402/core/client";
926
- import {
927
- decodePaymentRequiredHeader,
928
- decodePaymentResponseHeader,
929
- encodePaymentSignatureHeader
930
- } from "@x402/core/http";
961
+ import { decodePaymentResponseHeader, x402HTTPClient } from "@x402/core/http";
931
962
  import { ExactEvmScheme } from "@x402/evm/exact/client";
932
- import { Challenge } from "mppx";
963
+ import { createSIWxClientHook } from "@x402/extensions/sign-in-with-x";
964
+ import { wrapFetchWithPayment } from "@x402/fetch";
965
+ import { Receipt } from "mppx";
933
966
  import { Mppx, tempo } from "mppx/client";
934
967
  import {
935
968
  createPublicClient,
@@ -937,11 +970,14 @@ import {
937
970
  formatUnits,
938
971
  http
939
972
  } from "viem";
940
- import { base } from "viem/chains";
973
+ import { base, baseSepolia } from "viem/chains";
941
974
  var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
975
+ var USDC_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
942
976
  var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
977
+ var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
943
978
  var BASE_CHAIN_ID = 8453;
944
979
  var TEMPO_CHAIN_ID = 4217;
980
+ var TEMPO_TESTNET_CHAIN_ID = 42431;
945
981
  var calculateBuffer = (baseBalance) => {
946
982
  const twentyFivePercent = baseBalance / 4n;
947
983
  const twoDollars = 2000000n;
@@ -955,6 +991,14 @@ var tempoChain = {
955
991
  default: { http: ["https://rpc.tempo.xyz"] }
956
992
  }
957
993
  };
994
+ var tempoTestnetChain = {
995
+ id: TEMPO_TESTNET_CHAIN_ID,
996
+ name: "Tempo Testnet",
997
+ nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
998
+ rpcUrls: {
999
+ default: { http: ["https://rpc.moderato.tempo.xyz"] }
1000
+ }
1001
+ };
958
1002
  var ERC20_BALANCE_ABI = [
959
1003
  {
960
1004
  inputs: [{ name: "account", type: "address" }],
@@ -969,29 +1013,7 @@ var PaymentService = class {
969
1013
  this.account = account;
970
1014
  this.config = config;
971
1015
  }
972
- x402Client = null;
973
- mppxClient = null;
974
1016
  relayInitialized = false;
975
- getX402Client = () => {
976
- if (!this.x402Client) {
977
- if (!this.account) throw new Error("No wallet configured");
978
- this.x402Client = new X402Client().register(
979
- "eip155:*",
980
- new ExactEvmScheme(this.account)
981
- );
982
- }
983
- return this.x402Client;
984
- };
985
- getMppxClient = () => {
986
- if (!this.mppxClient) {
987
- if (!this.account) throw new Error("No wallet configured");
988
- this.mppxClient = Mppx.create({
989
- polyfill: false,
990
- methods: [tempo({ account: this.account })]
991
- });
992
- }
993
- return this.mppxClient;
994
- };
995
1017
  ensureRelayClient = () => {
996
1018
  if (!this.relayInitialized) {
997
1019
  createRelayClient({
@@ -1053,32 +1075,35 @@ var PaymentService = class {
1053
1075
  }
1054
1076
  throw new Error("Unrecognized 402 payment protocol");
1055
1077
  };
1056
- payX402 = async (url, request, raw, maxPay) => {
1057
- const headerValue = Buffer.from(JSON.stringify(raw)).toString("base64");
1058
- const paymentRequired = decodePaymentRequiredHeader(headerValue);
1059
- const requirement = paymentRequired.accepts[0];
1060
- if (!requirement) {
1061
- throw new Error("No accepted payment methods in x402 challenge");
1062
- }
1063
- const amountUsdc = formatUnits(BigInt(requirement.amount), 6);
1064
- if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
1065
- throw new Error(
1066
- `Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
1067
- );
1068
- }
1069
- const client = this.getX402Client();
1070
- const paymentPayload = await client.createPaymentPayload(paymentRequired);
1071
- const signatureHeader = encodePaymentSignatureHeader(paymentPayload);
1072
- const retryResponse = await fetch(url, {
1078
+ payX402 = async (url, request, _raw, maxPay) => {
1079
+ if (!this.account) throw new Error("No wallet configured");
1080
+ let capturedAmount = "0";
1081
+ const client = new X402Client().register(
1082
+ "eip155:*",
1083
+ new ExactEvmScheme(this.account)
1084
+ );
1085
+ client.onBeforePaymentCreation(async (context) => {
1086
+ const requirement = context.paymentRequired.accepts[0];
1087
+ if (!requirement) return;
1088
+ capturedAmount = formatUnits(BigInt(requirement.amount), 6);
1089
+ if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
1090
+ return {
1091
+ abort: true,
1092
+ reason: `Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
1093
+ };
1094
+ }
1095
+ });
1096
+ const httpClient = new x402HTTPClient(client).onPaymentRequired(
1097
+ createSIWxClientHook(this.account)
1098
+ );
1099
+ const wrappedFetch = wrapFetchWithPayment(fetch, httpClient);
1100
+ const response = await wrappedFetch(url, {
1073
1101
  method: request.method,
1074
- headers: {
1075
- ...request.headers,
1076
- "PAYMENT-SIGNATURE": signatureHeader
1077
- },
1102
+ headers: request.headers,
1078
1103
  body: request.body
1079
1104
  });
1080
1105
  let txHash = null;
1081
- const paymentResponseHeader = retryResponse.headers.get("payment-response");
1106
+ const paymentResponseHeader = response.headers.get("payment-response") ?? response.headers.get("x-payment-response");
1082
1107
  if (paymentResponseHeader) {
1083
1108
  try {
1084
1109
  const settlement = decodePaymentResponseHeader(paymentResponseHeader);
@@ -1087,61 +1112,86 @@ var PaymentService = class {
1087
1112
  }
1088
1113
  }
1089
1114
  return {
1090
- response: retryResponse,
1115
+ response,
1091
1116
  protocol: "x402",
1092
1117
  chain: "base",
1093
1118
  txHash,
1094
- amount: amountUsdc,
1119
+ amount: capturedAmount,
1095
1120
  asset: "USDC"
1096
1121
  };
1097
1122
  };
1098
- payMpp = async (url, request, raw, maxPay) => {
1099
- const wwwAuth = raw?.["www-authenticate"] ?? "";
1100
- const challengeResponse = new Response("Payment Required", {
1101
- status: 402,
1102
- headers: { "www-authenticate": wwwAuth }
1123
+ payMpp = async (url, request, _raw, maxPay) => {
1124
+ if (!this.account) throw new Error("No wallet configured");
1125
+ let capturedAmount = "0";
1126
+ const mppx = Mppx.create({
1127
+ polyfill: false,
1128
+ methods: [tempo({ account: this.account })],
1129
+ onChallenge: async (challenge) => {
1130
+ const challengeRequest = challenge.request;
1131
+ const amountRaw = challengeRequest.amount;
1132
+ capturedAmount = formatUnits(BigInt(amountRaw), 6);
1133
+ if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
1134
+ throw new Error(
1135
+ `Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
1136
+ );
1137
+ }
1138
+ const methodDetails = challengeRequest.methodDetails;
1139
+ const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
1140
+ const isTestnet = challengeChainId === TEMPO_TESTNET_CHAIN_ID;
1141
+ const balanceChain = isTestnet ? "tempo-testnet" : "tempo";
1142
+ const tempoBalance = await this.getBalanceRaw(balanceChain);
1143
+ if (tempoBalance < BigInt(amountRaw)) {
1144
+ if (isTestnet) {
1145
+ throw new Error(
1146
+ `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`
1147
+ );
1148
+ }
1149
+ await this.bridgeToTempo(BigInt(amountRaw));
1150
+ }
1151
+ return void 0;
1152
+ }
1103
1153
  });
1104
- const challenge = Challenge.fromResponse(challengeResponse);
1105
- const amountRaw = challenge.request.amount;
1106
- const amountUsdc = formatUnits(BigInt(amountRaw), 6);
1107
- if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
1108
- throw new Error(
1109
- `Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
1110
- );
1111
- }
1112
- const tempoBalance = await this.getBalanceRaw("tempo");
1113
- if (tempoBalance < BigInt(amountRaw)) {
1114
- await this.bridgeToTempo(BigInt(amountRaw));
1115
- }
1116
- const mppx = this.getMppxClient();
1117
- const credential = await mppx.createCredential(challengeResponse);
1118
- const retryResponse = await fetch(url, {
1154
+ const response = await mppx.fetch(url, {
1119
1155
  method: request.method,
1120
- headers: {
1121
- ...request.headers,
1122
- // biome-ignore lint/style/useNamingConvention: HTTP header name
1123
- Authorization: credential
1124
- },
1156
+ headers: request.headers,
1125
1157
  body: request.body
1126
1158
  });
1159
+ let txHash = null;
1160
+ try {
1161
+ const receipt = Receipt.fromResponse(response);
1162
+ txHash = receipt.reference ?? null;
1163
+ } catch {
1164
+ }
1127
1165
  return {
1128
- response: retryResponse,
1166
+ response,
1129
1167
  protocol: "mpp",
1130
1168
  chain: "tempo",
1131
- txHash: null,
1132
- // MPP pull mode — server broadcasts, tx hash not returned in response
1133
- amount: amountUsdc,
1169
+ txHash,
1170
+ amount: capturedAmount,
1134
1171
  asset: "USDC"
1135
1172
  };
1136
1173
  };
1174
+ resolveChainConfig = (chain) => {
1175
+ switch (chain) {
1176
+ case "base":
1177
+ return { viemChain: base, token: USDC_BASE };
1178
+ case "base-sepolia":
1179
+ return { viemChain: baseSepolia, token: USDC_BASE_SEPOLIA };
1180
+ case "tempo":
1181
+ return { viemChain: tempoChain, token: USDC_TEMPO };
1182
+ case "tempo-testnet":
1183
+ return { viemChain: tempoTestnetChain, token: PATHUSD_TEMPO };
1184
+ }
1185
+ };
1137
1186
  getBalanceRaw = async (chain) => {
1138
1187
  if (!this.account) return 0n;
1188
+ const { viemChain, token } = this.resolveChainConfig(chain);
1139
1189
  const client = createPublicClient({
1140
- chain: chain === "base" ? base : tempoChain,
1190
+ chain: viemChain,
1141
1191
  transport: http()
1142
1192
  });
1143
1193
  const balance = await client.readContract({
1144
- address: chain === "base" ? USDC_BASE : USDC_TEMPO,
1194
+ address: token,
1145
1195
  abi: ERC20_BALANCE_ABI,
1146
1196
  functionName: "balanceOf",
1147
1197
  args: [this.account.address]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",
@@ -20,8 +20,9 @@
20
20
  "dev": "tsx src/index.ts",
21
21
  "cli": "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
22
22
  "test:integration": "vitest run --project integration",
23
+ "test:online": "vitest run --project online",
23
24
  "test:unit": "vitest run --project unit",
24
- "test": "pnpm run test:integration",
25
+ "test": "pnpm run test:unit && pnpm run test:integration",
25
26
  "typecheck": "tsc"
26
27
  },
27
28
  "dependencies": {
@@ -29,6 +30,8 @@
29
30
  "@relayprotocol/relay-sdk": "^5.2.1",
30
31
  "@x402/core": "^2.9.0",
31
32
  "@x402/evm": "^2.9.0",
33
+ "@x402/extensions": "^2.9.0",
34
+ "@x402/fetch": "^2.9.0",
32
35
  "commander": "^13.0.0",
33
36
  "mppx": "^0.5.9",
34
37
  "open": "^11.0.0",
@@ -37,7 +40,10 @@
37
40
  "zod": "^4.3.5"
38
41
  },
39
42
  "devDependencies": {
43
+ "@hono/node-server": "^1.19.13",
40
44
  "@types/node": "^25.0.7",
45
+ "@x402/hono": "^2.9.0",
46
+ "hono": "^4.12.12",
41
47
  "tsup": "^8.5.1",
42
48
  "tsx": "^4.21.0",
43
49
  "typescript": "^5.9.3",