@zeroxyz/cli 0.0.29 → 0.0.30
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/README.md +2 -2
- package/dist/index.js +331 -74
- package/package.json +1 -1
- package/skills/zero/SKILL.md +99 -12
package/README.md
CHANGED
|
@@ -56,11 +56,11 @@ Get full details for a capability from the last search results.
|
|
|
56
56
|
zero get 1
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Outputs the capability as JSON (URL, method, headers, body schema, cost,
|
|
59
|
+
Outputs the capability as JSON (URL, method, headers, body schema, cost, supported protocols, etc.).
|
|
60
60
|
|
|
61
61
|
### `zero fetch <url>`
|
|
62
62
|
|
|
63
|
-
Fetch a capability URL. Automatically detects `402 Payment Required` responses and reports the
|
|
63
|
+
Fetch a capability URL. Automatically detects `402 Payment Required` responses and reports the protocol in use (x402, MPP, etc.).
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
# GET request
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command as Command12 } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@zeroxyz/cli",
|
|
9
|
-
version: "0.0.
|
|
9
|
+
version: "0.0.30",
|
|
10
10
|
type: "module",
|
|
11
11
|
bin: {
|
|
12
12
|
zero: "dist/index.js",
|
|
@@ -119,10 +119,14 @@ var capabilityResponseSchema = z.object({
|
|
|
119
119
|
state: z.enum(["unrated", "rated"]).optional()
|
|
120
120
|
}),
|
|
121
121
|
priceObserved: z.object({
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
minCents: z.string().nullable(),
|
|
123
|
+
medianCents: z.string().nullable(),
|
|
124
|
+
maxCents: z.string().nullable(),
|
|
125
|
+
// Deprecated alias for maxCents; kept for backward compat.
|
|
126
|
+
p95Cents: z.string().nullable(),
|
|
124
127
|
sampleCount: z.number(),
|
|
125
|
-
varies: z.boolean()
|
|
128
|
+
varies: z.boolean(),
|
|
129
|
+
failureChargeRate: z.number().nullable()
|
|
126
130
|
}).nullable().optional(),
|
|
127
131
|
paymentMethods: z.array(
|
|
128
132
|
z.object({
|
|
@@ -149,7 +153,31 @@ var createRunResponseSchema = z.object({
|
|
|
149
153
|
});
|
|
150
154
|
var createReviewResponseSchema = z.object({
|
|
151
155
|
reviewId: z.string(),
|
|
152
|
-
recorded: z.boolean()
|
|
156
|
+
recorded: z.boolean(),
|
|
157
|
+
updated: z.boolean().optional()
|
|
158
|
+
});
|
|
159
|
+
var batchReviewResponseSchema = z.object({
|
|
160
|
+
results: z.array(
|
|
161
|
+
z.union([
|
|
162
|
+
z.object({
|
|
163
|
+
runId: z.string(),
|
|
164
|
+
ok: z.literal(true),
|
|
165
|
+
reviewId: z.string(),
|
|
166
|
+
updated: z.boolean()
|
|
167
|
+
}),
|
|
168
|
+
z.object({
|
|
169
|
+
runId: z.string(),
|
|
170
|
+
ok: z.literal(false),
|
|
171
|
+
status: z.number(),
|
|
172
|
+
error: z.string()
|
|
173
|
+
})
|
|
174
|
+
])
|
|
175
|
+
),
|
|
176
|
+
summary: z.object({
|
|
177
|
+
total: z.number(),
|
|
178
|
+
ok: z.number(),
|
|
179
|
+
failed: z.number()
|
|
180
|
+
})
|
|
153
181
|
});
|
|
154
182
|
var BUG_REPORT_CATEGORIES = [
|
|
155
183
|
"search_relevance",
|
|
@@ -275,6 +303,10 @@ var ApiService = class {
|
|
|
275
303
|
const json = await this.request("POST", "/v1/reviews", data);
|
|
276
304
|
return createReviewResponseSchema.parse(json);
|
|
277
305
|
};
|
|
306
|
+
createReviewsBatch = async (reviews) => {
|
|
307
|
+
const json = await this.request("POST", "/v1/reviews/batch", { reviews });
|
|
308
|
+
return batchReviewResponseSchema.parse(json);
|
|
309
|
+
};
|
|
278
310
|
createBugReport = async (data) => {
|
|
279
311
|
const json = await this.request("POST", "/v1/bug-reports", data);
|
|
280
312
|
return createBugReportResponseSchema.parse(json);
|
|
@@ -582,6 +614,8 @@ var configCommand = (_appContext) => new Command2("config").description("View or
|
|
|
582
614
|
});
|
|
583
615
|
|
|
584
616
|
// src/commands/fetch-command.ts
|
|
617
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
618
|
+
import { resolve as resolvePath } from "path";
|
|
585
619
|
import { Command as Command3 } from "commander";
|
|
586
620
|
import { formatUnits as formatUnits2 } from "viem";
|
|
587
621
|
|
|
@@ -626,7 +660,9 @@ var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
|
|
|
626
660
|
var BASE_CHAIN_ID = 8453;
|
|
627
661
|
var TEMPO_CHAIN_ID = 4217;
|
|
628
662
|
var TEMPO_TESTNET_CHAIN_ID = 42431;
|
|
629
|
-
var
|
|
663
|
+
var DEFAULT_PREFUND_NO_COST_INFO = "1";
|
|
664
|
+
var ADAPTIVE_PREFUND_MULTIPLIER = 10;
|
|
665
|
+
var ADAPTIVE_PREFUND_CEILING = "100";
|
|
630
666
|
var KNOWN_EIP712_DOMAINS = {
|
|
631
667
|
// USDC on Base
|
|
632
668
|
[USDC_BASE.toLowerCase()]: { name: "USDC", version: "2" },
|
|
@@ -643,6 +679,16 @@ var calculateBuffer = (baseBalance) => {
|
|
|
643
679
|
const twoDollars = 2000000n;
|
|
644
680
|
return twentyFivePercent < twoDollars ? twentyFivePercent : twoDollars;
|
|
645
681
|
};
|
|
682
|
+
var resolveDefaultPrefund = (displayCostAmount) => {
|
|
683
|
+
const floor = Number.parseFloat(DEFAULT_PREFUND_NO_COST_INFO);
|
|
684
|
+
const ceiling = Number.parseFloat(ADAPTIVE_PREFUND_CEILING);
|
|
685
|
+
if (!displayCostAmount) return DEFAULT_PREFUND_NO_COST_INFO;
|
|
686
|
+
const cost = Number.parseFloat(displayCostAmount);
|
|
687
|
+
if (!Number.isFinite(cost) || cost <= 0) return DEFAULT_PREFUND_NO_COST_INFO;
|
|
688
|
+
const scaled = cost * ADAPTIVE_PREFUND_MULTIPLIER;
|
|
689
|
+
const bounded = scaled < floor ? floor : scaled > ceiling ? ceiling : scaled;
|
|
690
|
+
return bounded.toFixed(6).replace(/\.?0+$/, "");
|
|
691
|
+
};
|
|
646
692
|
var tempoChain = {
|
|
647
693
|
id: TEMPO_CHAIN_ID,
|
|
648
694
|
name: "Tempo",
|
|
@@ -723,7 +769,7 @@ var PaymentService = class {
|
|
|
723
769
|
const bridgeAmount = requiredAmount + buffer;
|
|
724
770
|
if (baseBalance < bridgeAmount) {
|
|
725
771
|
throw new Error(
|
|
726
|
-
`Insufficient Base USDC to bridge: have ${formatUnits(baseBalance, 6)}, need ${formatUnits(bridgeAmount, 6)} (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer)
|
|
772
|
+
`Insufficient Base USDC to bridge: have ${formatUnits(baseBalance, 6)}, need ${formatUnits(bridgeAmount, 6)} (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer). This gateway uses an MPP payment channel, which is pre-funded up front. Either fund your wallet on Base, or pass \`--max-pay <amount>\` to use a smaller channel (e.g. \`--max-pay 0.05\` for a ~5-cent channel sized for a single cheap call).`
|
|
727
773
|
);
|
|
728
774
|
}
|
|
729
775
|
onProgress?.(
|
|
@@ -759,7 +805,7 @@ var PaymentService = class {
|
|
|
759
805
|
});
|
|
760
806
|
return bridgeTxHash;
|
|
761
807
|
};
|
|
762
|
-
handlePayment = async (url, request, paymentRequirement, maxPay, onProgress) => {
|
|
808
|
+
handlePayment = async (url, request, paymentRequirement, maxPay, onProgress, displayCostAmount) => {
|
|
763
809
|
if (!this.account) {
|
|
764
810
|
throw new Error(
|
|
765
811
|
"No wallet configured \u2014 run `zero init` or set ZERO_PRIVATE_KEY"
|
|
@@ -776,7 +822,8 @@ var PaymentService = class {
|
|
|
776
822
|
request,
|
|
777
823
|
paymentRequirement.raw,
|
|
778
824
|
maxPay,
|
|
779
|
-
onProgress
|
|
825
|
+
onProgress,
|
|
826
|
+
displayCostAmount
|
|
780
827
|
);
|
|
781
828
|
}
|
|
782
829
|
throw new Error("Unrecognized 402 payment protocol");
|
|
@@ -840,15 +887,14 @@ var PaymentService = class {
|
|
|
840
887
|
* BEFORE mppx signs a credential — so balance/bridge logic runs in-band
|
|
841
888
|
* without adding a pre-probe round-trip.
|
|
842
889
|
*/
|
|
843
|
-
prepareTempoFunds = async (challenge, maxPay, onProgress) => {
|
|
890
|
+
prepareTempoFunds = async (challenge, maxPay, onProgress, displayCostAmount) => {
|
|
844
891
|
const challengeRequest = challenge.request;
|
|
845
892
|
let requiredRaw;
|
|
846
893
|
if (challenge.intent === "session") {
|
|
847
894
|
const suggestedDeposit = challengeRequest.suggestedDeposit;
|
|
895
|
+
const defaultPrefund = resolveDefaultPrefund(displayCostAmount);
|
|
848
896
|
requiredRaw = suggestedDeposit ? BigInt(suggestedDeposit) : BigInt(
|
|
849
|
-
Math.floor(
|
|
850
|
-
Number.parseFloat(maxPay ?? DEFAULT_MAX_DEPOSIT) * 1e6
|
|
851
|
-
)
|
|
897
|
+
Math.floor(Number.parseFloat(maxPay ?? defaultPrefund) * 1e6)
|
|
852
898
|
);
|
|
853
899
|
} else {
|
|
854
900
|
requiredRaw = BigInt(challengeRequest.amount);
|
|
@@ -889,7 +935,7 @@ var PaymentService = class {
|
|
|
889
935
|
* the extra state and return `410 "channel not found"` on the close.
|
|
890
936
|
* This mirrors the working production v0.0.21 single-fetch flow.
|
|
891
937
|
*/
|
|
892
|
-
payMpp = async (url, request, _raw, maxPay, onProgress) => {
|
|
938
|
+
payMpp = async (url, request, _raw, maxPay, onProgress, displayCostAmount) => {
|
|
893
939
|
const account = this.account;
|
|
894
940
|
if (!account) throw new Error("No wallet configured");
|
|
895
941
|
let capturedAmount = "0";
|
|
@@ -901,7 +947,7 @@ var PaymentService = class {
|
|
|
901
947
|
methods: [
|
|
902
948
|
tempo({
|
|
903
949
|
account,
|
|
904
|
-
maxDeposit: maxPay ??
|
|
950
|
+
maxDeposit: maxPay ?? resolveDefaultPrefund(displayCostAmount),
|
|
905
951
|
onChannelUpdate: (entry) => {
|
|
906
952
|
channelEntry = {
|
|
907
953
|
channelId: entry.channelId,
|
|
@@ -917,7 +963,8 @@ var PaymentService = class {
|
|
|
917
963
|
capturedAmount = await this.prepareTempoFunds(
|
|
918
964
|
challenge,
|
|
919
965
|
maxPay,
|
|
920
|
-
onProgress
|
|
966
|
+
onProgress,
|
|
967
|
+
displayCostAmount
|
|
921
968
|
);
|
|
922
969
|
return void 0;
|
|
923
970
|
}
|
|
@@ -1167,6 +1214,43 @@ var isTextContentType = (contentType) => {
|
|
|
1167
1214
|
if (ct === "application/x-www-form-urlencoded") return true;
|
|
1168
1215
|
return false;
|
|
1169
1216
|
};
|
|
1217
|
+
var isJsonContentType = (contentType) => {
|
|
1218
|
+
if (!contentType) return false;
|
|
1219
|
+
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
1220
|
+
return ct === "application/json" || ct.endsWith("+json");
|
|
1221
|
+
};
|
|
1222
|
+
var MAX_REQUEST_BODY_BYTES = 10 * 1024 * 1024;
|
|
1223
|
+
var resolveRequestBody = (rawData, readStdin) => {
|
|
1224
|
+
const fromFile = (spec) => {
|
|
1225
|
+
if (spec === "@-") {
|
|
1226
|
+
return readFileSync3(0, "utf8");
|
|
1227
|
+
}
|
|
1228
|
+
const path = resolvePath(spec.slice(1));
|
|
1229
|
+
return readFileSync3(path, "utf8");
|
|
1230
|
+
};
|
|
1231
|
+
let body;
|
|
1232
|
+
if (readStdin && rawData !== void 0) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
"Conflicting body sources: use either --data-stdin or -d, not both."
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
if (readStdin) {
|
|
1238
|
+
body = readFileSync3(0, "utf8");
|
|
1239
|
+
} else if (rawData?.startsWith("@")) {
|
|
1240
|
+
body = fromFile(rawData);
|
|
1241
|
+
} else {
|
|
1242
|
+
body = rawData;
|
|
1243
|
+
}
|
|
1244
|
+
if (body !== void 0) {
|
|
1245
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
1246
|
+
if (bytes > MAX_REQUEST_BODY_BYTES) {
|
|
1247
|
+
throw new Error(
|
|
1248
|
+
`Request body is ${bytes} bytes \u2014 exceeds the ${MAX_REQUEST_BODY_BYTES} byte limit. Split the payload, compress it, or contact the capability owner about raising the cap.`
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return body;
|
|
1253
|
+
};
|
|
1170
1254
|
var looksLikeX402V1Body = (body) => {
|
|
1171
1255
|
if (!body || typeof body !== "object") return false;
|
|
1172
1256
|
const b = body;
|
|
@@ -1201,15 +1285,26 @@ var detectPaymentRequirement = async (response) => {
|
|
|
1201
1285
|
}
|
|
1202
1286
|
return { protocol: "unknown", raw: {} };
|
|
1203
1287
|
};
|
|
1204
|
-
var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
1288
|
+
var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
1289
|
+
"Fetch a capability URL, handling 402 challenges automatically"
|
|
1290
|
+
).argument("<url>", "URL to fetch").option(
|
|
1205
1291
|
"-X, --method <method>",
|
|
1206
1292
|
"HTTP method (GET, POST, PUT, PATCH, DELETE). Defaults to POST when -d is set, otherwise GET"
|
|
1207
|
-
).option(
|
|
1293
|
+
).option(
|
|
1294
|
+
"-d, --data <body>",
|
|
1295
|
+
"Request body. Pass a literal JSON string, or `@path/to/file` to read from a file, or `@-` to read from stdin."
|
|
1296
|
+
).option(
|
|
1297
|
+
"--data-stdin",
|
|
1298
|
+
"Read the request body from stdin (equivalent to `-d @-`)."
|
|
1299
|
+
).option("-H, --header <header...>", "Headers in Key:Value format").option("--max-pay <amount>", "Maximum per-call spend limit (USDC)").option(
|
|
1208
1300
|
"--capability <id>",
|
|
1209
1301
|
"Bind this fetch to a capability (uid or slug) so a reviewable run is recorded even without a prior `zero search`"
|
|
1210
1302
|
).option(
|
|
1211
1303
|
"--json",
|
|
1212
|
-
"Emit {runId, status, latencyMs, payment, body} as JSON on stdout (for batch/non-TTY use)"
|
|
1304
|
+
"Emit {runId, ok, status, latencyMs, payment, body, bodyRaw} as JSON on stdout (for batch/non-TTY use)"
|
|
1305
|
+
).option(
|
|
1306
|
+
"--raw-body",
|
|
1307
|
+
"With --json: keep `body` as the raw response string instead of parsing JSON. `bodyRaw` still reflects the raw text."
|
|
1213
1308
|
).option(
|
|
1214
1309
|
"--agent <name>",
|
|
1215
1310
|
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
@@ -1224,6 +1319,19 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1224
1319
|
walletService
|
|
1225
1320
|
} = appContext.services;
|
|
1226
1321
|
const startTime = Date.now();
|
|
1322
|
+
let resolvedBody;
|
|
1323
|
+
try {
|
|
1324
|
+
resolvedBody = resolveRequestBody(
|
|
1325
|
+
options.data,
|
|
1326
|
+
options.dataStdin ?? false
|
|
1327
|
+
);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
console.error(
|
|
1330
|
+
err instanceof Error ? err.message : "Failed to read request body"
|
|
1331
|
+
);
|
|
1332
|
+
process.exitCode = 1;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1227
1335
|
const headers = {};
|
|
1228
1336
|
if (options.header) {
|
|
1229
1337
|
for (const h of options.header) {
|
|
@@ -1236,15 +1344,15 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1236
1344
|
const hasContentType = Object.keys(headers).some(
|
|
1237
1345
|
(k) => k.toLowerCase() === "content-type"
|
|
1238
1346
|
);
|
|
1239
|
-
if (
|
|
1347
|
+
if (resolvedBody && !hasContentType) {
|
|
1240
1348
|
headers["content-type"] = "application/json";
|
|
1241
1349
|
}
|
|
1242
1350
|
const log = (msg) => console.error(` ${msg}`);
|
|
1243
|
-
const method = options.method ? options.method.toUpperCase() :
|
|
1351
|
+
const method = options.method ? options.method.toUpperCase() : resolvedBody ? "POST" : "GET";
|
|
1244
1352
|
const requestInit = {
|
|
1245
1353
|
method,
|
|
1246
1354
|
headers,
|
|
1247
|
-
body:
|
|
1355
|
+
body: resolvedBody
|
|
1248
1356
|
};
|
|
1249
1357
|
const lastSearch = stateService.loadLastSearch();
|
|
1250
1358
|
const matchedCapability = lastSearch?.capabilities.find(
|
|
@@ -1283,7 +1391,8 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1283
1391
|
requestInit,
|
|
1284
1392
|
paymentReq,
|
|
1285
1393
|
options.maxPay,
|
|
1286
|
-
log
|
|
1394
|
+
log,
|
|
1395
|
+
matchedCapability?.displayCostAmount
|
|
1287
1396
|
);
|
|
1288
1397
|
finalResponse = result.response;
|
|
1289
1398
|
paymentMeta = {
|
|
@@ -1384,8 +1493,8 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1384
1493
|
if (capabilityId && apiService.walletAddress) {
|
|
1385
1494
|
let requestSchema;
|
|
1386
1495
|
let responseSchema;
|
|
1387
|
-
if (
|
|
1388
|
-
const parsedReq = tryParseJson(
|
|
1496
|
+
if (resolvedBody) {
|
|
1497
|
+
const parsedReq = tryParseJson(resolvedBody);
|
|
1389
1498
|
if (parsedReq !== null && typeof parsedReq === "object") {
|
|
1390
1499
|
requestSchema = inferSchema(parsedReq);
|
|
1391
1500
|
}
|
|
@@ -1440,14 +1549,30 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1440
1549
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1441
1550
|
}
|
|
1442
1551
|
if (options.json) {
|
|
1443
|
-
const
|
|
1552
|
+
const responseStatus = finalResponse?.status ?? null;
|
|
1553
|
+
const ok = responseStatus !== null && responseStatus >= 200 && responseStatus < 300;
|
|
1554
|
+
const responseCt = finalResponse?.headers.get("content-type") ?? null;
|
|
1555
|
+
const bodyString = bodyIsBinary ? (bodyBytes ?? Buffer.alloc(0)).toString("base64") : body;
|
|
1556
|
+
let parsedBody = null;
|
|
1557
|
+
if (finalResponse && !bodyIsBinary && !options.rawBody) {
|
|
1558
|
+
if (isJsonContentType(responseCt)) {
|
|
1559
|
+
const parsed = tryParseJson(body);
|
|
1560
|
+
parsedBody = parsed === null ? body : parsed;
|
|
1561
|
+
} else {
|
|
1562
|
+
parsedBody = body;
|
|
1563
|
+
}
|
|
1564
|
+
} else if (finalResponse) {
|
|
1565
|
+
parsedBody = bodyString;
|
|
1566
|
+
}
|
|
1444
1567
|
console.log(
|
|
1445
1568
|
JSON.stringify({
|
|
1446
1569
|
runId,
|
|
1447
|
-
|
|
1570
|
+
ok,
|
|
1571
|
+
status: responseStatus,
|
|
1448
1572
|
latencyMs,
|
|
1449
1573
|
payment: paymentMeta ?? null,
|
|
1450
|
-
body:
|
|
1574
|
+
body: parsedBody,
|
|
1575
|
+
bodyRaw: finalResponse ? bodyString : null,
|
|
1451
1576
|
...bodyIsBinary && { bodyEncoding: "base64" },
|
|
1452
1577
|
...fetchError && { error: fetchError.message },
|
|
1453
1578
|
...skipReasons.length > 0 && {
|
|
@@ -1508,6 +1633,30 @@ var formatTrustComponent = (label, value) => {
|
|
|
1508
1633
|
const display = value != null ? `${value}/100` : "--";
|
|
1509
1634
|
return ` ${label.padEnd(22)}${display}`;
|
|
1510
1635
|
};
|
|
1636
|
+
var centsToDollars = (cents) => {
|
|
1637
|
+
const value = Number.parseFloat(cents) / 100;
|
|
1638
|
+
if (value < 0.01) return value.toFixed(4);
|
|
1639
|
+
if (value < 1) return value.toFixed(3);
|
|
1640
|
+
return value.toFixed(2);
|
|
1641
|
+
};
|
|
1642
|
+
var formatCost = (capability) => {
|
|
1643
|
+
const lines = [];
|
|
1644
|
+
const observed = capability.priceObserved;
|
|
1645
|
+
if (observed && observed.sampleCount > 0 && observed.varies && observed.minCents && observed.maxCents) {
|
|
1646
|
+
const min = centsToDollars(observed.minCents);
|
|
1647
|
+
const max = centsToDollars(observed.maxCents);
|
|
1648
|
+
const median = observed.medianCents ? centsToDollars(observed.medianCents) : null;
|
|
1649
|
+
const detail = median ? `median $${median}, n=${observed.sampleCount}` : `n=${observed.sampleCount}`;
|
|
1650
|
+
lines.push(` Cost: $${min}\u2013$${max}/call (${detail})`);
|
|
1651
|
+
} else {
|
|
1652
|
+
lines.push(` Cost: $${capability.displayCostAmount}/call`);
|
|
1653
|
+
}
|
|
1654
|
+
if (observed?.failureChargeRate != null && observed.failureChargeRate > 0) {
|
|
1655
|
+
const pct = Math.round(observed.failureChargeRate * 100);
|
|
1656
|
+
lines.push(` \u26A0 ~${pct}% of failed runs still charged the wallet`);
|
|
1657
|
+
}
|
|
1658
|
+
return lines;
|
|
1659
|
+
};
|
|
1511
1660
|
var formatRating = (rating) => {
|
|
1512
1661
|
if (rating.state === "unrated") return "unrated";
|
|
1513
1662
|
const successPct = `${Math.round(Number.parseFloat(rating.successRate) * 100)}%`;
|
|
@@ -1517,6 +1666,81 @@ var formatRating = (rating) => {
|
|
|
1517
1666
|
}
|
|
1518
1667
|
return `${successPct} success, ${reviews} reviews`;
|
|
1519
1668
|
};
|
|
1669
|
+
var asNode = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1670
|
+
var placeholderFor = (fieldName, propSchema) => {
|
|
1671
|
+
const node = asNode(propSchema);
|
|
1672
|
+
const example = node?.example ?? node?.default;
|
|
1673
|
+
if (typeof example === "string") return example;
|
|
1674
|
+
if (typeof example === "number" || typeof example === "boolean") {
|
|
1675
|
+
return String(example);
|
|
1676
|
+
}
|
|
1677
|
+
return `<${fieldName.toUpperCase()}>`;
|
|
1678
|
+
};
|
|
1679
|
+
var extractInputEnvelope = (bodySchema, method) => {
|
|
1680
|
+
if (!bodySchema) return {};
|
|
1681
|
+
const props = asNode(bodySchema.properties);
|
|
1682
|
+
if (!props) return {};
|
|
1683
|
+
const inputProps = asNode(asNode(props.input)?.properties);
|
|
1684
|
+
if (inputProps) {
|
|
1685
|
+
return {
|
|
1686
|
+
queryParams: asNode(asNode(inputProps.queryParams)?.properties) ?? void 0,
|
|
1687
|
+
body: asNode(asNode(inputProps.body)?.properties) ?? void 0
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
const upperMethod = method.toUpperCase();
|
|
1691
|
+
const isGetLike = upperMethod === "GET" || upperMethod === "DELETE";
|
|
1692
|
+
return isGetLike ? { queryParams: props } : { body: props };
|
|
1693
|
+
};
|
|
1694
|
+
var buildTryItExample = (capability) => {
|
|
1695
|
+
const method = capability.method.toUpperCase();
|
|
1696
|
+
const lines = ["", "Try it:"];
|
|
1697
|
+
const { queryParams, body } = extractInputEnvelope(
|
|
1698
|
+
capability.bodySchema,
|
|
1699
|
+
capability.method
|
|
1700
|
+
);
|
|
1701
|
+
const callerHeaders = Object.entries(capability.headers ?? {});
|
|
1702
|
+
const headerFlags = callerHeaders.map(
|
|
1703
|
+
([k, v]) => `-H "${k}: ${v}" # [caller-provided]`
|
|
1704
|
+
);
|
|
1705
|
+
if (method === "GET" || queryParams && !body) {
|
|
1706
|
+
const qs = queryParams ? Object.entries(queryParams).map(
|
|
1707
|
+
([k, schema]) => `${encodeURIComponent(k)}=${encodeURIComponent(placeholderFor(k, schema))}`
|
|
1708
|
+
).join("&") : "";
|
|
1709
|
+
const url = qs ? `${capability.url}?${qs}` : capability.url;
|
|
1710
|
+
const urlLine = ` zero fetch "${url}"`;
|
|
1711
|
+
if (headerFlags.length === 0) {
|
|
1712
|
+
lines.push(urlLine);
|
|
1713
|
+
} else {
|
|
1714
|
+
lines.push(` zero fetch \\`);
|
|
1715
|
+
for (const h of headerFlags) lines.push(` ${h} \\`);
|
|
1716
|
+
lines.push(` "${url}"`);
|
|
1717
|
+
}
|
|
1718
|
+
if (!queryParams && method === "GET") {
|
|
1719
|
+
lines.push(
|
|
1720
|
+
" # bodySchema did not expose queryParams \u2014 call the URL as-is or inspect the raw schema above."
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
return lines;
|
|
1724
|
+
}
|
|
1725
|
+
const samplePayload = body ? Object.fromEntries(
|
|
1726
|
+
Object.entries(body).map(([k, schema]) => [
|
|
1727
|
+
k,
|
|
1728
|
+
placeholderFor(k, schema)
|
|
1729
|
+
])
|
|
1730
|
+
) : null;
|
|
1731
|
+
const bodyJson = samplePayload ? JSON.stringify(samplePayload) : "<BODY_JSON>";
|
|
1732
|
+
lines.push(` zero fetch \\`);
|
|
1733
|
+
if (method !== "POST") lines.push(` -X ${method} \\`);
|
|
1734
|
+
for (const h of headerFlags) lines.push(` ${h} \\`);
|
|
1735
|
+
lines.push(` -d '${bodyJson}' \\`);
|
|
1736
|
+
lines.push(` ${capability.url}`);
|
|
1737
|
+
if (!body) {
|
|
1738
|
+
lines.push(
|
|
1739
|
+
" # bodySchema did not expose input.body \u2014 replace <BODY_JSON> with the exact shape shown above."
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
return lines;
|
|
1743
|
+
};
|
|
1520
1744
|
var formatCapability = (capability) => {
|
|
1521
1745
|
const lines = [];
|
|
1522
1746
|
lines.push(capability.name);
|
|
@@ -1543,9 +1767,10 @@ var formatCapability = (capability) => {
|
|
|
1543
1767
|
}
|
|
1544
1768
|
lines.push(` Rating: ${formatRating(capability.rating)}`);
|
|
1545
1769
|
lines.push(` Status: ${capability.availabilityStatus ?? "unknown"}`);
|
|
1546
|
-
lines.push(
|
|
1770
|
+
lines.push(...formatCost(capability));
|
|
1547
1771
|
lines.push(` URL: ${capability.url}`);
|
|
1548
1772
|
lines.push(` Method: ${capability.method}`);
|
|
1773
|
+
lines.push(...buildTryItExample(capability));
|
|
1549
1774
|
return lines.join("\n");
|
|
1550
1775
|
};
|
|
1551
1776
|
var getCommand = (appContext) => new Command4("get").description(
|
|
@@ -1614,7 +1839,7 @@ import {
|
|
|
1614
1839
|
existsSync as existsSync2,
|
|
1615
1840
|
mkdirSync as mkdirSync2,
|
|
1616
1841
|
readdirSync,
|
|
1617
|
-
readFileSync as
|
|
1842
|
+
readFileSync as readFileSync4,
|
|
1618
1843
|
rmSync,
|
|
1619
1844
|
statSync,
|
|
1620
1845
|
writeFileSync as writeFileSync2
|
|
@@ -1648,7 +1873,7 @@ var getPackageRoot = () => {
|
|
|
1648
1873
|
}
|
|
1649
1874
|
return dir;
|
|
1650
1875
|
};
|
|
1651
|
-
var sha256File = (filePath) => createHash3("sha256").update(
|
|
1876
|
+
var sha256File = (filePath) => createHash3("sha256").update(readFileSync4(filePath)).digest("hex");
|
|
1652
1877
|
var verifyFileCopy = (src, dest) => {
|
|
1653
1878
|
if (!existsSync2(dest)) return false;
|
|
1654
1879
|
return sha256File(src) === sha256File(dest);
|
|
@@ -1673,7 +1898,7 @@ var copyDirRecursive = (src, dest) => {
|
|
|
1673
1898
|
if (entry.isDirectory()) {
|
|
1674
1899
|
copyDirRecursive(srcPath, destPath);
|
|
1675
1900
|
} else {
|
|
1676
|
-
const data =
|
|
1901
|
+
const data = readFileSync4(srcPath);
|
|
1677
1902
|
writeFileSync2(destPath, data);
|
|
1678
1903
|
try {
|
|
1679
1904
|
const mode = statSync(srcPath).mode;
|
|
@@ -1708,7 +1933,7 @@ var installHook = (home) => {
|
|
|
1708
1933
|
let settings = {};
|
|
1709
1934
|
if (existsSync2(settingsPath)) {
|
|
1710
1935
|
try {
|
|
1711
|
-
settings = JSON.parse(
|
|
1936
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
1712
1937
|
} catch {
|
|
1713
1938
|
}
|
|
1714
1939
|
}
|
|
@@ -1866,7 +2091,7 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1866
2091
|
const walletExists = (() => {
|
|
1867
2092
|
if (!existsSync2(configPath)) return false;
|
|
1868
2093
|
try {
|
|
1869
|
-
const existing = JSON.parse(
|
|
2094
|
+
const existing = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
1870
2095
|
return !!existing.privateKey;
|
|
1871
2096
|
} catch {
|
|
1872
2097
|
return false;
|
|
@@ -1876,7 +2101,7 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1876
2101
|
const privateKey = generatePrivateKey();
|
|
1877
2102
|
const account = privateKeyToAccount(privateKey);
|
|
1878
2103
|
mkdirSync2(zeroDir, { recursive: true });
|
|
1879
|
-
const existing = existsSync2(configPath) ? JSON.parse(
|
|
2104
|
+
const existing = existsSync2(configPath) ? JSON.parse(readFileSync4(configPath, "utf8")) : {};
|
|
1880
2105
|
writeFileSync2(
|
|
1881
2106
|
configPath,
|
|
1882
2107
|
JSON.stringify(
|
|
@@ -1890,7 +2115,7 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1890
2115
|
console.log(`Wallet address: ${account.address}`);
|
|
1891
2116
|
} else {
|
|
1892
2117
|
try {
|
|
1893
|
-
const existing = JSON.parse(
|
|
2118
|
+
const existing = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
1894
2119
|
const account = privateKeyToAccount(existing.privateKey);
|
|
1895
2120
|
walletAddress = account.address;
|
|
1896
2121
|
} catch {
|
|
@@ -2007,7 +2232,7 @@ ${removedList}`);
|
|
|
2007
2232
|
);
|
|
2008
2233
|
|
|
2009
2234
|
// src/commands/review-command.ts
|
|
2010
|
-
import { readFileSync as
|
|
2235
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2011
2236
|
import { Command as Command6 } from "commander";
|
|
2012
2237
|
import { z as z3 } from "zod";
|
|
2013
2238
|
var bulkEntrySchema2 = z3.object({
|
|
@@ -2049,35 +2274,66 @@ Examples:
|
|
|
2049
2274
|
try {
|
|
2050
2275
|
const { analyticsService, apiService } = appContext.services;
|
|
2051
2276
|
if (options.fromFile) {
|
|
2052
|
-
const contents =
|
|
2277
|
+
const contents = readFileSync5(options.fromFile, "utf8");
|
|
2053
2278
|
const lines = contents.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
2054
|
-
|
|
2055
|
-
let
|
|
2279
|
+
const parsed = [];
|
|
2280
|
+
let parseFailures = 0;
|
|
2056
2281
|
for (const [idx, line] of lines.entries()) {
|
|
2057
2282
|
const lineNum = idx + 1;
|
|
2058
2283
|
try {
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
console.log(
|
|
2063
|
-
`[${lineNum}] ${parsed.runId} -> ${result2.reviewId}`
|
|
2064
|
-
);
|
|
2065
|
-
analyticsService.capture("review_submitted", {
|
|
2066
|
-
runId: parsed.runId,
|
|
2067
|
-
success: parsed.success,
|
|
2068
|
-
accuracy: parsed.accuracy,
|
|
2069
|
-
value: parsed.value,
|
|
2070
|
-
reliability: parsed.reliability,
|
|
2071
|
-
hasContent: !!parsed.content,
|
|
2072
|
-
bulk: true
|
|
2284
|
+
parsed.push({
|
|
2285
|
+
lineNum,
|
|
2286
|
+
entry: bulkEntrySchema2.parse(JSON.parse(line))
|
|
2073
2287
|
});
|
|
2074
2288
|
} catch (err) {
|
|
2075
|
-
|
|
2289
|
+
parseFailures += 1;
|
|
2076
2290
|
console.error(
|
|
2077
|
-
`[${lineNum}] FAILED: ${err instanceof Error ? err.message : String(err)}`
|
|
2291
|
+
`[${lineNum}] PARSE FAILED: ${err instanceof Error ? err.message : String(err)}`
|
|
2078
2292
|
);
|
|
2079
2293
|
}
|
|
2080
2294
|
}
|
|
2295
|
+
if (parsed.length === 0) {
|
|
2296
|
+
console.error("No valid review lines found in file.");
|
|
2297
|
+
process.exitCode = 1;
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
const Chunk = 100;
|
|
2301
|
+
let ok = 0;
|
|
2302
|
+
let failed = parseFailures;
|
|
2303
|
+
for (let i = 0; i < parsed.length; i += Chunk) {
|
|
2304
|
+
const chunk = parsed.slice(i, i + Chunk);
|
|
2305
|
+
const response = await apiService.createReviewsBatch(
|
|
2306
|
+
chunk.map((p) => p.entry)
|
|
2307
|
+
);
|
|
2308
|
+
for (const [chunkIdx, item] of response.results.entries()) {
|
|
2309
|
+
const lineNum = chunk[chunkIdx]?.lineNum ?? i + chunkIdx + 1;
|
|
2310
|
+
if (item.ok) {
|
|
2311
|
+
ok += 1;
|
|
2312
|
+
const suffix = item.updated ? " (updated)" : "";
|
|
2313
|
+
console.log(
|
|
2314
|
+
`[${lineNum}] ${item.runId} -> ${item.reviewId}${suffix}`
|
|
2315
|
+
);
|
|
2316
|
+
const entry = chunk[chunkIdx]?.entry;
|
|
2317
|
+
if (entry) {
|
|
2318
|
+
analyticsService.capture("review_submitted", {
|
|
2319
|
+
runId: entry.runId,
|
|
2320
|
+
success: entry.success,
|
|
2321
|
+
accuracy: entry.accuracy,
|
|
2322
|
+
value: entry.value,
|
|
2323
|
+
reliability: entry.reliability,
|
|
2324
|
+
hasContent: !!entry.content,
|
|
2325
|
+
bulk: true,
|
|
2326
|
+
updated: item.updated
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
} else {
|
|
2330
|
+
failed += 1;
|
|
2331
|
+
console.error(
|
|
2332
|
+
`[${lineNum}] FAILED (${item.status}): ${item.error}`
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2081
2337
|
console.log(`
|
|
2082
2338
|
Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
2083
2339
|
if (failed > 0) process.exitCode = 1;
|
|
@@ -2116,7 +2372,7 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
|
2116
2372
|
}
|
|
2117
2373
|
if (list.runs.length > 1) {
|
|
2118
2374
|
console.error(
|
|
2119
|
-
`Multiple un-reviewed runs for "${options.capability}".
|
|
2375
|
+
`Multiple un-reviewed runs for "${options.capability}". Either pass a runId explicitly (see "zero runs --capability ${options.capability} --unreviewed"), or review them in one call via "zero review --from-file <reviews.jsonl>".`
|
|
2120
2376
|
);
|
|
2121
2377
|
process.exitCode = 1;
|
|
2122
2378
|
return;
|
|
@@ -2330,7 +2586,8 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2330
2586
|
capabilities: result.capabilities.map((c) => ({
|
|
2331
2587
|
position: c.position,
|
|
2332
2588
|
id: c.id,
|
|
2333
|
-
url: c.url
|
|
2589
|
+
url: c.url,
|
|
2590
|
+
displayCostAmount: c.cost.amount
|
|
2334
2591
|
}))
|
|
2335
2592
|
});
|
|
2336
2593
|
console.log(formatSearchResults(result.capabilities));
|
|
@@ -2373,7 +2630,7 @@ Read the full terms at: ${TERMS_URL}
|
|
|
2373
2630
|
});
|
|
2374
2631
|
|
|
2375
2632
|
// src/commands/wallet-command.ts
|
|
2376
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
2633
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
2377
2634
|
import { homedir as homedir3 } from "os";
|
|
2378
2635
|
import { join as join3 } from "path";
|
|
2379
2636
|
import { Command as Command10 } from "commander";
|
|
@@ -2468,7 +2725,7 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
|
|
|
2468
2725
|
const configPath = join3(zeroDir, "config.json");
|
|
2469
2726
|
if (!options.force && existsSync3(configPath)) {
|
|
2470
2727
|
try {
|
|
2471
|
-
const existing2 = JSON.parse(
|
|
2728
|
+
const existing2 = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2472
2729
|
if (existing2.privateKey) {
|
|
2473
2730
|
console.error(
|
|
2474
2731
|
"Wallet already configured. Use --force to overwrite."
|
|
@@ -2480,7 +2737,7 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
|
|
|
2480
2737
|
}
|
|
2481
2738
|
}
|
|
2482
2739
|
mkdirSync3(zeroDir, { recursive: true });
|
|
2483
|
-
const existing = existsSync3(configPath) ? JSON.parse(
|
|
2740
|
+
const existing = existsSync3(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
|
|
2484
2741
|
writeFileSync3(
|
|
2485
2742
|
configPath,
|
|
2486
2743
|
JSON.stringify(
|
|
@@ -2510,7 +2767,7 @@ var walletCommand = (appContext) => {
|
|
|
2510
2767
|
};
|
|
2511
2768
|
|
|
2512
2769
|
// src/commands/welcome-command.ts
|
|
2513
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2770
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
|
|
2514
2771
|
import { homedir as homedir4 } from "os";
|
|
2515
2772
|
import { join as join4 } from "path";
|
|
2516
2773
|
import { Command as Command11 } from "commander";
|
|
@@ -2521,7 +2778,7 @@ var readPrivateKey = () => {
|
|
|
2521
2778
|
const configPath = join4(homedir4(), ".zero", "config.json");
|
|
2522
2779
|
if (!existsSync4(configPath)) return null;
|
|
2523
2780
|
try {
|
|
2524
|
-
const config = JSON.parse(
|
|
2781
|
+
const config = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
2525
2782
|
if (typeof config.privateKey === "string") {
|
|
2526
2783
|
return config.privateKey;
|
|
2527
2784
|
}
|
|
@@ -2573,7 +2830,7 @@ If your browser didn't open, paste the URL above.`
|
|
|
2573
2830
|
// src/app.ts
|
|
2574
2831
|
var createApp = (appContext) => {
|
|
2575
2832
|
const { analyticsService } = appContext.services;
|
|
2576
|
-
const program = new Command12().name("zero").description("Zero CLI \u2014 Search engine
|
|
2833
|
+
const program = new Command12().name("zero").description("Zero CLI \u2014 Search engine for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
|
|
2577
2834
|
const agentFlag = actionCommand.opts().agent;
|
|
2578
2835
|
if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
|
|
2579
2836
|
analyticsService.setAgentHost(agentFlag.trim());
|
|
@@ -2620,14 +2877,14 @@ var getEnv = () => {
|
|
|
2620
2877
|
|
|
2621
2878
|
// src/app/app-services.ts
|
|
2622
2879
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2623
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2880
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
2624
2881
|
import { homedir as homedir5 } from "os";
|
|
2625
2882
|
import { join as join6 } from "path";
|
|
2626
2883
|
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
2627
2884
|
|
|
2628
2885
|
// src/services/analytics-service.ts
|
|
2629
2886
|
import { randomUUID } from "crypto";
|
|
2630
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as
|
|
2887
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
2631
2888
|
import { dirname as dirname2 } from "path";
|
|
2632
2889
|
import { PostHog } from "posthog-node";
|
|
2633
2890
|
var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
|
|
@@ -2650,7 +2907,7 @@ var AnalyticsService = class {
|
|
|
2650
2907
|
let persistedAnonId;
|
|
2651
2908
|
try {
|
|
2652
2909
|
if (existsSync5(opts.configPath)) {
|
|
2653
|
-
const config = JSON.parse(
|
|
2910
|
+
const config = JSON.parse(readFileSync8(opts.configPath, "utf8"));
|
|
2654
2911
|
if (config.telemetry === false) {
|
|
2655
2912
|
telemetryEnabled = false;
|
|
2656
2913
|
}
|
|
@@ -2675,7 +2932,7 @@ var AnalyticsService = class {
|
|
|
2675
2932
|
try {
|
|
2676
2933
|
const dir = dirname2(opts.configPath);
|
|
2677
2934
|
mkdirSync4(dir, { recursive: true });
|
|
2678
|
-
const existing = existsSync5(opts.configPath) ? JSON.parse(
|
|
2935
|
+
const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync8(opts.configPath, "utf8")) : {};
|
|
2679
2936
|
writeFileSync4(
|
|
2680
2937
|
opts.configPath,
|
|
2681
2938
|
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
@@ -2711,7 +2968,7 @@ var AnalyticsService = class {
|
|
|
2711
2968
|
if (anonId === walletAddress) return;
|
|
2712
2969
|
let aliasedTo;
|
|
2713
2970
|
try {
|
|
2714
|
-
const config = JSON.parse(
|
|
2971
|
+
const config = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
2715
2972
|
if (typeof config.aliasedTo === "string") {
|
|
2716
2973
|
aliasedTo = config.aliasedTo;
|
|
2717
2974
|
}
|
|
@@ -2720,7 +2977,7 @@ var AnalyticsService = class {
|
|
|
2720
2977
|
if (aliasedTo === walletAddress) return;
|
|
2721
2978
|
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
2722
2979
|
try {
|
|
2723
|
-
const config = existsSync5(configPath) ? JSON.parse(
|
|
2980
|
+
const config = existsSync5(configPath) ? JSON.parse(readFileSync8(configPath, "utf8")) : {};
|
|
2724
2981
|
writeFileSync4(
|
|
2725
2982
|
configPath,
|
|
2726
2983
|
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
@@ -2763,7 +3020,7 @@ var AnalyticsService = class {
|
|
|
2763
3020
|
};
|
|
2764
3021
|
|
|
2765
3022
|
// src/services/state-service.ts
|
|
2766
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as
|
|
3023
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
2767
3024
|
import { join as join5 } from "path";
|
|
2768
3025
|
var StateService = class {
|
|
2769
3026
|
constructor(zeroDir) {
|
|
@@ -2778,7 +3035,7 @@ var StateService = class {
|
|
|
2778
3035
|
loadLastSearch = () => {
|
|
2779
3036
|
try {
|
|
2780
3037
|
if (!existsSync6(this.lastSearchPath)) return null;
|
|
2781
|
-
const raw =
|
|
3038
|
+
const raw = readFileSync9(this.lastSearchPath, "utf8");
|
|
2782
3039
|
return JSON.parse(raw);
|
|
2783
3040
|
} catch {
|
|
2784
3041
|
return null;
|
|
@@ -2831,7 +3088,7 @@ var getServices = (env) => {
|
|
|
2831
3088
|
if (!privateKey) {
|
|
2832
3089
|
try {
|
|
2833
3090
|
if (existsSync7(configPath)) {
|
|
2834
|
-
const config = JSON.parse(
|
|
3091
|
+
const config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2835
3092
|
if (typeof config.privateKey === "string") {
|
|
2836
3093
|
privateKey = config.privateKey;
|
|
2837
3094
|
}
|
|
@@ -2843,7 +3100,7 @@ var getServices = (env) => {
|
|
|
2843
3100
|
let lowBalanceWarning = 1;
|
|
2844
3101
|
try {
|
|
2845
3102
|
if (existsSync7(configPath)) {
|
|
2846
|
-
const config = JSON.parse(
|
|
3103
|
+
const config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2847
3104
|
if (typeof config.lowBalanceWarning === "number") {
|
|
2848
3105
|
lowBalanceWarning = config.lowBalanceWarning;
|
|
2849
3106
|
}
|
package/package.json
CHANGED
package/skills/zero/SKILL.md
CHANGED
|
@@ -85,8 +85,8 @@ Starter prompts should be user-facing tasks, not command templates:
|
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
zero search "<query>"
|
|
88
|
-
zero get <position>
|
|
89
|
-
zero fetch <url> [-d '<json>'] [-H "Key:Value"] [--max-pay <amount>]
|
|
88
|
+
zero get <position> [--formatted]
|
|
89
|
+
zero fetch <url> [-X <method>] [-d '<json>' | -d @file | --data-stdin] [-H "Key:Value"] [--max-pay <amount>] [--json [--raw-body]] [--capability <id>]
|
|
90
90
|
zero runs [--capability <slug>] [--unreviewed]
|
|
91
91
|
zero review <runId> --accuracy <1-5> --value <1-5> --reliability <1-5>
|
|
92
92
|
zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --reliability <1-5>
|
|
@@ -95,32 +95,118 @@ zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --relia
|
|
|
95
95
|
### Workflow
|
|
96
96
|
|
|
97
97
|
1. **Search** — `zero search "weather forecast"` finds matching capabilities. Results show name, cost, rating, and success rate.
|
|
98
|
-
2. **Inspect** — `zero get 1`
|
|
99
|
-
3. **Call** — `zero fetch <url>` makes the request. If the server returns 402, payment is handled automatically (x402 and MPP
|
|
100
|
-
4. **Review** — `zero review <runId>` submits a quality review. Run IDs are printed
|
|
101
|
-
5. **Retroactive review** — if you lost a runId, run `zero runs --unreviewed` (or `zero runs --capability <slug> --unreviewed`)
|
|
98
|
+
2. **Inspect** — `zero get 1 --formatted` prints a human summary **and a copy-pasteable `Try it:` command** wired to the capability's schema. Plain `zero get 1` returns full JSON (URL, method, `bodySchema`, examples, pricing) for `jq` pipelines. **If `bodySchema` is `null`**, the capability hasn't been schema-indexed yet — skip it and `zero get 2`, don't invent field names.
|
|
99
|
+
3. **Call** — `zero fetch <url>` makes the request. If the server returns 402, payment is handled automatically (x402 and MPP, including cross-chain bridging from Base to Tempo).
|
|
100
|
+
4. **Review** — `zero review <runId>` submits a quality review. Run IDs are printed to **stderr** after a successful fetch (or returned on stdout in `--json` mode). Always review after a paid call.
|
|
101
|
+
5. **Retroactive review** — if you lost a runId, run `zero runs --unreviewed` (or `zero runs --capability <slug> --unreviewed`). `zero review --capability <slug> ...` auto-resolves to your most recent un-reviewed run for that capability.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
**Fastest path:** `zero search "..." → zero get <n> --formatted → copy the `Try it:` line → edit placeholders → run it`. The `Try it` block already knows whether to use querystring vs `-d`, and labels every header as `[caller-provided]` so you know which `-H` flags to fill in yourself.
|
|
104
|
+
|
|
105
|
+
### Request Shape Cheatsheet
|
|
106
|
+
|
|
107
|
+
Read `bodySchema` from `zero get <n>` first. The schema describes an `input` envelope with `type: "http"`, a `method`, and either `queryParams` (GET) or `body` (POST). Translate the envelope into a real HTTP call — do **not** send the envelope as the request body.
|
|
108
|
+
|
|
109
|
+
**GET capabilities — put `queryParams` in the URL, not a body:**
|
|
104
110
|
|
|
105
111
|
```bash
|
|
106
|
-
# GET
|
|
107
|
-
zero fetch https://api.example.com/
|
|
112
|
+
# bodySchema declares: input.method = "GET", input.queryParams = { ip }
|
|
113
|
+
zero fetch "https://api.example.com/ip-geo/locate?ip=8.8.8.8"
|
|
114
|
+
```
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
**POST capabilities — send `input.body` as JSON:**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# bodySchema declares: input.method = "POST", input.body = { text, to }
|
|
110
120
|
zero fetch https://api.example.com/translate \
|
|
111
121
|
-d '{"text":"hello","to":"es"}' \
|
|
112
122
|
-H "Content-Type:application/json"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Cap spend:**
|
|
113
126
|
|
|
114
|
-
|
|
127
|
+
```bash
|
|
115
128
|
zero fetch https://api.example.com/expensive --max-pay 0.50
|
|
116
129
|
```
|
|
117
130
|
|
|
131
|
+
### Flag Reference (`zero fetch`)
|
|
132
|
+
|
|
133
|
+
| Flag | When to use |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `-X, --method <verb>` | Force HTTP method. Defaults to `POST` when `-d` is set, otherwise `GET`. |
|
|
136
|
+
| `-d, --data <body>` | Request body. Three shapes: a literal JSON string (`-d '{"k":"v"}'`), a file reference (`-d @./payload.json`), or stdin (`-d @-`). Implies POST and auto-sets `Content-Type: application/json` if you didn't pass `-H`. |
|
|
137
|
+
| `--data-stdin` | Alias for `-d @-`. Read the request body from stdin. Mutually exclusive with `-d`. |
|
|
138
|
+
| `-H, --header <k:v>` | Add a header. Repeatable. Use for caller-provided auth/API keys the capability requires. |
|
|
139
|
+
| `--json` | Emit `{runId, ok, status, latencyMs, payment, body, bodyRaw}` as JSON on stdout. `body` is **parsed** when the response is JSON (opt out with `--raw-body`); `bodyRaw` is always the exact text. `ok` is `true` iff `status` is 2xx. |
|
|
140
|
+
| `--raw-body` | With `--json`: keep `body` as the raw response string instead of parsing JSON. |
|
|
141
|
+
| `--max-pay <usdc>` | Refuse to pay more than this per call. |
|
|
142
|
+
| `--capability <uid\|slug>` | Bind this fetch to a capability when you didn't just `zero search` — required to record a reviewable run in batch contexts. |
|
|
143
|
+
|
|
144
|
+
**Body size cap:** `-d` (inline, file, or stdin) rejects bodies over 10 MB with a clear error. For truly large payloads, split, compress, or contact the capability owner.
|
|
145
|
+
|
|
146
|
+
### Output Handling
|
|
147
|
+
|
|
148
|
+
`zero fetch` separates streams so piping works:
|
|
149
|
+
|
|
150
|
+
- **stdout** — response body only (text) or raw bytes (binary). In `--json` mode, a single JSON envelope instead.
|
|
151
|
+
- **stderr** — progress logs, payment info, the `Run ID: ...` line, review tips, warnings.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Default mode — body is clean on stdout
|
|
155
|
+
zero fetch "https://api.example.com/ip-geo/locate?ip=8.8.8.8" | jq .country
|
|
156
|
+
|
|
157
|
+
# --json mode — body is already parsed (ok flag + structured body)
|
|
158
|
+
zero fetch --json "https://api.example.com/ip-geo/locate?ip=8.8.8.8" \
|
|
159
|
+
| jq 'select(.ok) | {runId, country: .body.country}'
|
|
160
|
+
|
|
161
|
+
# Opt out of parsing if you need the literal bytes
|
|
162
|
+
zero fetch --json --raw-body "<url>" | jq '.bodyRaw'
|
|
163
|
+
|
|
164
|
+
# Suppress progress entirely
|
|
165
|
+
zero fetch "<url>" 2>/dev/null | jq .
|
|
166
|
+
|
|
167
|
+
# Binary (image/audio/pdf): redirect stdout to a file
|
|
168
|
+
zero fetch "<image-url>" > out.png
|
|
169
|
+
|
|
170
|
+
# Large payloads: use a file or stdin to avoid arg-size limits
|
|
171
|
+
zero fetch https://upload.example.com -d @./big-image.b64
|
|
172
|
+
cat payload.json | zero fetch https://api.example.com --data-stdin
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**`--json` envelope fields:**
|
|
176
|
+
|
|
177
|
+
| Field | Type | Notes |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| `runId` | `string\|null` | Zero-side run ID for `zero review`. `null` when the run wasn't recorded (missing wallet or capability). |
|
|
180
|
+
| `ok` | `boolean` | `true` iff `status` is in the 200–299 range. Use this, not `status`, for success checks. |
|
|
181
|
+
| `status` | `number\|null` | Upstream HTTP status code. |
|
|
182
|
+
| `latencyMs` | `number` | End-to-end call latency. |
|
|
183
|
+
| `payment` | `object\|null` | `{protocol, chain, txHash, amount, asset}` when a payment was made; `null` for free calls. |
|
|
184
|
+
| `body` | `any` | Parsed JSON for `application/json` responses, or the string for other text, or base64 string for binary. Pass `--raw-body` to always keep it as the raw string. |
|
|
185
|
+
| `bodyRaw` | `string\|null` | The response as text (or base64 when binary). Always present for forwarding / hashing. |
|
|
186
|
+
| `bodyEncoding` | `"base64"` | Only set when the response was binary. |
|
|
187
|
+
| `error` | `string` | Only set when the fetch or session-close failed. |
|
|
188
|
+
|
|
189
|
+
**When to reach for `--json`:** batch/agent pipelines where you need `runId`, `ok`, or `payment` programmatically. Default text mode is fine for human-directed one-offs.
|
|
190
|
+
|
|
191
|
+
**Reviewing programmatically:** capture `runId` from the envelope, then call `zero review <runId> --success --accuracy N --value N --reliability N` (use `--no-success` if `ok` was `false`).
|
|
192
|
+
|
|
118
193
|
### Response Handling
|
|
119
194
|
|
|
120
195
|
- Return the response payload to the user directly.
|
|
121
|
-
- If response contains a file URL, download it
|
|
196
|
+
- If response contains a file URL, download it: `curl -fsSL "<url>" -o <filename>`.
|
|
122
197
|
- After multi-request workflows, check remaining balance with `zero wallet balance`.
|
|
123
198
|
|
|
199
|
+
### Gotchas
|
|
200
|
+
|
|
201
|
+
- **Don't POST a GET envelope.** If `bodySchema` says `method: "GET"` with `queryParams`, encode those as URL query string. POSTing `{"input":{"queryParams":{...}}}` to a GET endpoint will 4xx.
|
|
202
|
+
- **Don't guess field names when `bodySchema` is `null`.** Skip to the next search result. The POST example above (`{text, to}`) is illustrative — real request bodies must match whatever the capability's own `bodySchema` declares (e.g. `{text, target_language}`), not the example's field names.
|
|
203
|
+
- **Large bodies go through `-d @file` or `--data-stdin`.** Inline `-d '<long-string>'` can run past shell arg limits (~1 MB) and fail silently. Anything bigger than a few KB is safer through a file or stdin.
|
|
204
|
+
- **`--json`'s `body` is already parsed for JSON responses.** No more `fromjson`/`JSON.parse` on the body field. If you want the literal bytes (e.g. to hash or forward), use `bodyRaw` or pass `--raw-body`.
|
|
205
|
+
- **Check `ok`, not `status`, for success.** `ok` is a pre-computed 2xx boolean; `status` is the raw HTTP code (useful for distinguishing 404 from 500 but not a success flag).
|
|
206
|
+
- **`--max-pay` is your cost guard.** Always set it before calling an unfamiliar capability or one with per-call pricing you haven't verified.
|
|
207
|
+
- **Capability must be resolvable.** If you skip `zero search` and call `zero fetch <url>` directly, pass `--capability <uid|slug>` so the run is recorded for review.
|
|
208
|
+
- **Review failures too, when they're the capability's fault.** A 4xx/5xx from the upstream API counts as a real result — submit `zero review <runId> --no-success` so future agents see the failure. Do **not** review failures caused by CLI-internal bugs (see Common Issues).
|
|
209
|
+
|
|
124
210
|
### Rules
|
|
125
211
|
|
|
126
212
|
- **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.
|
|
@@ -218,6 +304,7 @@ zero wallet balance
|
|
|
218
304
|
| No search results | Query too narrow | Broaden search terms: `zero search "<broader query>"`. |
|
|
219
305
|
| Wrong request schema (4xx error) | Incorrect body or headers | Run `zero get <position>` to check the exact schema, method, and required headers. |
|
|
220
306
|
| Cross-chain bridge delay | Bridging USDC from Base to Tempo | Automatic — the CLI bridges with a 25% buffer. Wait for confirmation and retry if needed. |
|
|
307
|
+
| `No client registered for x402 version: N` | CLI-internal payment bug — not a capability problem | Skip to the next search result. Optionally `zero bug-report "x402 version N unsupported on <slug>"`. Do not `zero review --no-success` (not the capability's fault). |
|
|
221
308
|
|
|
222
309
|
## Try These
|
|
223
310
|
|