@zeroxyz/cli 0.0.21 → 0.0.23

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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command as Command9 } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@zeroxyz/cli",
9
- version: "0.0.21",
9
+ version: "0.0.23",
10
10
  type: "module",
11
11
  bin: {
12
12
  zero: "dist/index.js",
@@ -111,6 +111,44 @@ var configCommand = (_appContext) => new Command("config").description("View or
111
111
 
112
112
  // src/commands/fetch-command.ts
113
113
  import { Command as Command2 } from "commander";
114
+
115
+ // src/util/infer-schema.ts
116
+ var inferSchema = (value, depth = 0) => {
117
+ if (depth > 6) return { type: typeOf(value) };
118
+ if (value === null) return { type: "null" };
119
+ if (Array.isArray(value)) {
120
+ const itemSchemas = value.slice(0, 3).map((v) => inferSchema(v, depth + 1));
121
+ return {
122
+ type: "array",
123
+ items: itemSchemas[0] ?? { type: "unknown" }
124
+ };
125
+ }
126
+ if (typeof value === "object") {
127
+ const obj = value;
128
+ const properties = {};
129
+ const required = [];
130
+ for (const [k, v] of Object.entries(obj)) {
131
+ properties[k] = inferSchema(v, depth + 1);
132
+ required.push(k);
133
+ }
134
+ return { type: "object", properties, required };
135
+ }
136
+ return { type: typeOf(value) };
137
+ };
138
+ var typeOf = (v) => {
139
+ if (v === null) return "null";
140
+ if (Array.isArray(v)) return "array";
141
+ return typeof v;
142
+ };
143
+ var tryParseJson = (text) => {
144
+ try {
145
+ return JSON.parse(text);
146
+ } catch {
147
+ return null;
148
+ }
149
+ };
150
+
151
+ // src/commands/fetch-command.ts
114
152
  var detectPaymentRequirement = (headers, status) => {
115
153
  if (status !== 402) return null;
116
154
  const x402Header = headers.get("payment-required") ?? headers.get("x-payment-required");
@@ -130,7 +168,10 @@ var detectPaymentRequirement = (headers, status) => {
130
168
  }
131
169
  return { protocol: "unknown", raw: {} };
132
170
  };
133
- var fetchCommand = (appContext) => new Command2("fetch").description("Fetch a capability URL with automatic payment handling").argument("<url>", "URL to fetch").option("-d, --data <body>", "Request body (JSON string)").option("-H, --header <header...>", "Headers in Key:Value format").option("--max-pay <amount>", "Maximum amount willing to pay (USDC)").option(
171
+ var fetchCommand = (appContext) => new Command2("fetch").description("Fetch a capability URL with automatic payment handling").argument("<url>", "URL to fetch").option(
172
+ "-X, --method <method>",
173
+ "HTTP method (GET, POST, PUT, PATCH, DELETE). Defaults to POST when -d is set, otherwise GET"
174
+ ).option("-d, --data <body>", "Request body (JSON string)").option("-H, --header <header...>", "Headers in Key:Value format").option("--max-pay <amount>", "Maximum amount willing to pay (USDC)").option(
134
175
  "--capability <id>",
135
176
  "Bind this fetch to a capability (uid or slug) so a reviewable run is recorded even without a prior `zero search`"
136
177
  ).option(
@@ -163,55 +204,80 @@ var fetchCommand = (appContext) => new Command2("fetch").description("Fetch a ca
163
204
  headers["content-type"] = "application/json";
164
205
  }
165
206
  const log = (msg) => console.error(` ${msg}`);
207
+ const method = options.method ? options.method.toUpperCase() : options.data ? "POST" : "GET";
166
208
  const requestInit = {
167
- method: options.data ? "POST" : "GET",
209
+ method,
168
210
  headers,
169
211
  body: options.data
170
212
  };
171
- log(`Calling ${url}...`);
172
- const response = await fetch(url, requestInit);
173
- const paymentReq = detectPaymentRequirement(
174
- response.headers,
175
- response.status
213
+ const lastSearch = stateService.loadLastSearch();
214
+ const matchedCapability = lastSearch?.capabilities.find(
215
+ (c) => url.startsWith(c.url)
176
216
  );
177
- let finalResponse;
178
- let paymentMeta;
179
- if (paymentReq) {
180
- log(
181
- `Payment required (${paymentReq.protocol}) \u2014 preparing payment...`
217
+ const capabilityId = options.capability ?? matchedCapability?.id ?? null;
218
+ const searchId = matchedCapability ? lastSearch?.searchId : void 0;
219
+ const skipReasons = [];
220
+ if (!apiService.walletAddress) {
221
+ skipReasons.push(
222
+ "no wallet configured (run `zero wallet import` / set ZERO_PRIVATE_KEY)"
182
223
  );
183
- const result = await paymentService.handlePayment(
184
- url,
185
- requestInit,
186
- paymentReq,
187
- options.maxPay,
188
- log
224
+ }
225
+ if (!capabilityId) {
226
+ skipReasons.push(
227
+ "no capability resolved \u2014 pass --capability <uid|slug> or run `zero search` first so the URL can be matched"
189
228
  );
190
- finalResponse = result.response;
191
- paymentMeta = {
192
- protocol: result.protocol,
193
- chain: result.chain,
194
- txHash: result.txHash,
195
- amount: result.amount,
196
- asset: result.asset
197
- };
198
- log(
199
- `Paid ${result.amount} ${result.asset} via ${result.protocol} on ${result.chain}`
229
+ }
230
+ let finalResponse;
231
+ let body = "";
232
+ let paymentMeta;
233
+ let fetchError;
234
+ try {
235
+ log(`Calling ${url}...`);
236
+ const response = await fetch(url, requestInit);
237
+ const paymentReq = detectPaymentRequirement(
238
+ response.headers,
239
+ response.status
200
240
  );
201
- } else {
202
- finalResponse = response;
241
+ if (paymentReq) {
242
+ log(
243
+ `Payment required (${paymentReq.protocol}) \u2014 preparing payment...`
244
+ );
245
+ const result = await paymentService.handlePayment(
246
+ url,
247
+ requestInit,
248
+ paymentReq,
249
+ options.maxPay,
250
+ log
251
+ );
252
+ finalResponse = result.response;
253
+ paymentMeta = {
254
+ protocol: result.protocol,
255
+ chain: result.chain,
256
+ txHash: result.txHash,
257
+ amount: result.amount,
258
+ asset: result.asset
259
+ };
260
+ log(
261
+ `Paid ${result.amount} ${result.asset} via ${result.protocol} on ${result.chain}`
262
+ );
263
+ } else {
264
+ finalResponse = response;
265
+ }
266
+ body = await finalResponse.text();
267
+ } catch (err) {
268
+ fetchError = err instanceof Error ? err : new Error(String(err));
203
269
  }
204
270
  const latencyMs = Date.now() - startTime;
205
- const body = await finalResponse.text();
206
- if (!options.json) {
271
+ if (finalResponse && !options.json) {
207
272
  console.log(body);
208
273
  }
209
274
  analyticsService.capture("fetch_executed", {
210
275
  url,
211
- status: finalResponse.status,
276
+ status: finalResponse?.status,
212
277
  hasPayment: !!paymentMeta,
213
278
  paymentProtocol: paymentMeta?.protocol,
214
- paymentAmount: paymentMeta?.amount
279
+ paymentAmount: paymentMeta?.amount,
280
+ ...fetchError && { error: fetchError.message }
215
281
  });
216
282
  if (paymentMeta) {
217
283
  try {
@@ -230,31 +296,30 @@ var fetchCommand = (appContext) => new Command2("fetch").description("Fetch a ca
230
296
  } catch {
231
297
  }
232
298
  }
233
- const lastSearch = stateService.loadLastSearch();
234
- const matchedCapability = lastSearch?.capabilities.find(
235
- (c) => url.startsWith(c.url)
236
- );
237
- const capabilityId = options.capability ?? matchedCapability?.id ?? null;
238
- const searchId = matchedCapability ? lastSearch?.searchId : void 0;
239
299
  let runId = null;
240
- const skipReasons = [];
241
- if (!apiService.walletAddress) {
242
- skipReasons.push(
243
- "no wallet configured (run `zero wallet import` / set ZERO_PRIVATE_KEY)"
244
- );
245
- }
246
- if (!capabilityId) {
247
- skipReasons.push(
248
- "no capability resolved \u2014 pass --capability <uid|slug> or run `zero search` first so the URL can be matched"
249
- );
250
- }
251
300
  if (capabilityId && apiService.walletAddress) {
301
+ let requestSchema;
302
+ let responseSchema;
303
+ if (options.data) {
304
+ const parsedReq = tryParseJson(options.data);
305
+ if (parsedReq !== null && typeof parsedReq === "object") {
306
+ requestSchema = inferSchema(parsedReq);
307
+ }
308
+ }
309
+ if (body) {
310
+ const parsedResp = tryParseJson(body);
311
+ if (parsedResp !== null && typeof parsedResp === "object") {
312
+ responseSchema = inferSchema(parsedResp);
313
+ }
314
+ }
252
315
  try {
253
316
  const runResult = await apiService.createRun({
254
317
  capabilityId,
255
318
  searchId,
256
- status: finalResponse.status,
319
+ status: finalResponse?.status,
257
320
  latencyMs,
321
+ requestSchema,
322
+ responseSchema,
258
323
  ...paymentMeta && {
259
324
  costAmount: paymentMeta.amount,
260
325
  costAsset: paymentMeta.asset,
@@ -271,14 +336,18 @@ var fetchCommand = (appContext) => new Command2("fetch").description("Fetch a ca
271
336
  );
272
337
  }
273
338
  }
339
+ if (fetchError && !options.json) {
340
+ console.error(` Fetch failed: ${fetchError.message}`);
341
+ }
274
342
  if (options.json) {
275
343
  console.log(
276
344
  JSON.stringify({
277
345
  runId,
278
- status: finalResponse.status,
346
+ status: finalResponse?.status ?? null,
279
347
  latencyMs,
280
348
  payment: paymentMeta ?? null,
281
- body,
349
+ body: finalResponse ? body : null,
350
+ ...fetchError && { error: fetchError.message },
282
351
  ...skipReasons.length > 0 && {
283
352
  runTrackingSkipped: skipReasons
284
353
  }
@@ -311,6 +380,9 @@ var fetchCommand = (appContext) => new Command2("fetch").description("Fetch a ca
311
380
  Note: this run was NOT recorded for review \u2014 ${skipReasons.join("; ")}.`
312
381
  );
313
382
  }
383
+ if (fetchError) {
384
+ process.exitCode = 1;
385
+ }
314
386
  } catch (err) {
315
387
  console.error(err instanceof Error ? err.message : "Fetch failed");
316
388
  process.exitCode = 1;
@@ -810,7 +882,13 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
810
882
  process.exitCode = 1;
811
883
  return;
812
884
  }
813
- runId = list.runs[0].uid;
885
+ const [only] = list.runs;
886
+ if (!only) {
887
+ console.error("No run found for capability");
888
+ process.exitCode = 1;
889
+ return;
890
+ }
891
+ runId = only.uid;
814
892
  console.log(`Resolved to run ${runId}`);
815
893
  }
816
894
  const accuracy = requireRating("accuracy", options.accuracy);
@@ -934,7 +1012,13 @@ var formatSearchResults = (results) => {
934
1012
  var searchCommand = (appContext) => new Command7("search").description("Search for capabilities").argument("<query>", "Search query").option("--json", "Output raw JSON to stdout").option("--offset <n>", "Pagination offset", Number).option("--limit <n>", "Results per page", Number).option("--free", "Only show free capabilities").option("--max-cost <amount>", "Maximum cost per call").option("--min-rating <stars>", "Minimum star rating (1-5)", Number).option("--protocol <protocol>", "Payment protocol (x402 or mpp)").option("--min-trust <n>", "Minimum trust score (0-100)", Number).option(
935
1013
  "--status <status>",
936
1014
  "Filter by availability (healthy, degraded, down)"
937
- ).option("--all", "Show all results (no trust or health filtering)").action(
1015
+ ).option("--all", "Show all results (no trust or health filtering)").option(
1016
+ "--source <source>",
1017
+ "Only show results from this crawl source (e.g. mpp, bazaar)"
1018
+ ).option(
1019
+ "--exclude-source <source>",
1020
+ "Exclude results from this crawl source"
1021
+ ).action(
938
1022
  async (query, options) => {
939
1023
  try {
940
1024
  const { analyticsService, apiService, stateService } = appContext.services;
@@ -963,7 +1047,9 @@ var searchCommand = (appContext) => new Command7("search").description("Search f
963
1047
  protocol: options.protocol,
964
1048
  minTrust: options.minTrust,
965
1049
  availabilityStatus: options.status,
966
- includeAll: options.all
1050
+ includeAll: options.all,
1051
+ source: options.source,
1052
+ excludeSource: options.excludeSource
967
1053
  });
968
1054
  analyticsService.capture("search_executed", {
969
1055
  query,
@@ -1311,6 +1397,12 @@ var capabilityResponseSchema = z3.object({
1311
1397
  stars: z3.string().nullable().optional(),
1312
1398
  state: z3.enum(["unrated", "rated"]).optional()
1313
1399
  }),
1400
+ priceObserved: z3.object({
1401
+ medianCents: z3.string(),
1402
+ p95Cents: z3.string(),
1403
+ sampleCount: z3.number(),
1404
+ varies: z3.boolean()
1405
+ }).nullable().optional(),
1314
1406
  paymentMethods: z3.array(
1315
1407
  z3.object({
1316
1408
  uid: z3.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",
@@ -33,7 +33,13 @@ Run these commands in order. Do not skip steps.
33
33
 
34
34
  **Step 1 — Install:** `npm i -g @zeroxyz/cli`
35
35
 
36
- **Step 2 — Initialize wallet:** `zero init` — generates a new wallet. Save the printed address. Or, to use an existing wallet: `zero wallet set <privateKey>` (0x-prefixed hex private key).
36
+ **Step 2 — Provide a wallet.** Pick one path:
37
+
38
+ - **New wallet (persistent):** `zero init` — generates a wallet and writes it to `~/.zero/config.json`. Save the printed address.
39
+ - **Existing wallet (persistent):** `zero wallet set <privateKey>` — 0x-prefixed hex. Writes to `~/.zero/config.json`. Refuses to overwrite an existing wallet without `--force`.
40
+ - **Existing wallet (ephemeral, e.g. CI):** set `ZERO_PRIVATE_KEY=0x...` in the environment. The CLI reads it at runtime and writes nothing to disk.
41
+
42
+ **Prefer `zero init` or `zero wallet set` for local/interactive use** so the wallet persists across shells. Use `ZERO_PRIVATE_KEY` only when persisting to disk is undesirable — CI jobs, containers, one-off scripts, or secrets-manager-injected environments.
37
43
 
38
44
  **Step 3 — Fund wallet:** `zero wallet fund` — opens browser to add USDC (Base). For manual transfer: `zero wallet fund --manual`.
39
45
 
@@ -41,7 +47,7 @@ Run these commands in order. Do not skip steps.
41
47
 
42
48
  ### Setup Rules
43
49
 
44
- - If `ZERO_PRIVATE_KEY` is set in the environment, the CLI uses that key instead of the one from `zero init`.
50
+ - Precedence: `ZERO_PRIVATE_KEY` (env) > `~/.zero/config.json`. If the env var is set, it wins even when a config file exists useful for overriding on a single invocation (`ZERO_PRIVATE_KEY=0x... zero wallet balance`).
45
51
  - Wallet must be funded with USDC on Base before calling paid capabilities.
46
52
 
47
53
  ## After Setup
@@ -105,7 +111,9 @@ zero fetch https://api.example.com/expensive --max-pay 0.50
105
111
 
106
112
  ### Rules
107
113
 
108
- - Always discover capabilities with `zero search` + `zero get` before calling; never guess endpoint URLs or schemas.
114
+ - **Always `zero search` fresh, every time.** Never reuse a capability URL, slug, schema, or price from an earlier turn, prior conversation, training data, or memory. Capabilities churn constantly — endpoints go offline, prices change, schemas evolve, and rankings shift as reviews accumulate. A capability that worked yesterday may be dead, repriced, or outranked today. Searching again costs nothing and is the only way to get current trust scores and availability.
115
+ - **Always `zero get` before `zero fetch`.** Even if you "know" the URL, re-fetch the full details to confirm the URL, method, required headers, body schema, and current price. Do not reconstruct a fetch call from memory.
116
+ - Never guess endpoint URLs or schemas.
109
117
  - Use `--max-pay` before potentially expensive requests.
110
118
  - Review capabilities after every paid call. The review signal feeds search ranking — skipping reviews means your next search is less informed.
111
119
  - Before ending a multi-call task, run `zero runs --unreviewed` and review anything you missed.