@zeroxyz/cli 0.0.28 → 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 +382 -89
- 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,8 +1839,9 @@ import {
|
|
|
1614
1839
|
existsSync as existsSync2,
|
|
1615
1840
|
mkdirSync as mkdirSync2,
|
|
1616
1841
|
readdirSync,
|
|
1617
|
-
readFileSync as
|
|
1842
|
+
readFileSync as readFileSync4,
|
|
1618
1843
|
rmSync,
|
|
1844
|
+
statSync,
|
|
1619
1845
|
writeFileSync as writeFileSync2
|
|
1620
1846
|
} from "fs";
|
|
1621
1847
|
import { homedir as homedir2 } from "os";
|
|
@@ -1647,7 +1873,7 @@ var getPackageRoot = () => {
|
|
|
1647
1873
|
}
|
|
1648
1874
|
return dir;
|
|
1649
1875
|
};
|
|
1650
|
-
var sha256File = (filePath) => createHash3("sha256").update(
|
|
1876
|
+
var sha256File = (filePath) => createHash3("sha256").update(readFileSync4(filePath)).digest("hex");
|
|
1651
1877
|
var verifyFileCopy = (src, dest) => {
|
|
1652
1878
|
if (!existsSync2(dest)) return false;
|
|
1653
1879
|
return sha256File(src) === sha256File(dest);
|
|
@@ -1664,6 +1890,24 @@ var collectAllFiles = (dir) => {
|
|
|
1664
1890
|
}
|
|
1665
1891
|
return files;
|
|
1666
1892
|
};
|
|
1893
|
+
var copyDirRecursive = (src, dest) => {
|
|
1894
|
+
mkdirSync2(dest, { recursive: true });
|
|
1895
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
1896
|
+
const srcPath = join2(src, entry.name);
|
|
1897
|
+
const destPath = join2(dest, entry.name);
|
|
1898
|
+
if (entry.isDirectory()) {
|
|
1899
|
+
copyDirRecursive(srcPath, destPath);
|
|
1900
|
+
} else {
|
|
1901
|
+
const data = readFileSync4(srcPath);
|
|
1902
|
+
writeFileSync2(destPath, data);
|
|
1903
|
+
try {
|
|
1904
|
+
const mode = statSync(srcPath).mode;
|
|
1905
|
+
chmodSync(destPath, mode);
|
|
1906
|
+
} catch {
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1667
1911
|
var installHook = (home) => {
|
|
1668
1912
|
const claudeDir = join2(home, ".claude");
|
|
1669
1913
|
if (!existsSync2(claudeDir)) {
|
|
@@ -1689,7 +1933,7 @@ var installHook = (home) => {
|
|
|
1689
1933
|
let settings = {};
|
|
1690
1934
|
if (existsSync2(settingsPath)) {
|
|
1691
1935
|
try {
|
|
1692
|
-
settings = JSON.parse(
|
|
1936
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
1693
1937
|
} catch {
|
|
1694
1938
|
}
|
|
1695
1939
|
}
|
|
@@ -1800,30 +2044,38 @@ var installSkills = (home) => {
|
|
|
1800
2044
|
const skillsSourceDir = join2(getPackageRoot(), "skills");
|
|
1801
2045
|
const skillDirs = readdirSync(skillsSourceDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1802
2046
|
const installed = [];
|
|
2047
|
+
const errors = [];
|
|
1803
2048
|
for (const tool of AGENT_TOOLS) {
|
|
1804
2049
|
const toolDetectPath = join2(home, tool.detectDir);
|
|
1805
2050
|
if (!existsSync2(toolDetectPath)) {
|
|
1806
2051
|
continue;
|
|
1807
2052
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2053
|
+
try {
|
|
2054
|
+
const toolSkillsPath = join2(home, tool.skillsDir);
|
|
2055
|
+
mkdirSync2(toolSkillsPath, { recursive: true });
|
|
2056
|
+
for (const skillDir of skillDirs) {
|
|
2057
|
+
const src = join2(skillsSourceDir, skillDir);
|
|
2058
|
+
const dest = join2(toolSkillsPath, skillDir);
|
|
2059
|
+
copyDirRecursive(src, dest);
|
|
2060
|
+
for (const srcFile of collectAllFiles(src)) {
|
|
2061
|
+
const relPath = relative(src, srcFile);
|
|
2062
|
+
const destFile = join2(dest, relPath);
|
|
2063
|
+
if (!verifyFileCopy(srcFile, destFile)) {
|
|
2064
|
+
throw new Error(
|
|
2065
|
+
`Integrity check failed: ${destFile} does not match source`
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
1821
2068
|
}
|
|
2069
|
+
installed.push(`${tool.name}: ${dest}`);
|
|
1822
2070
|
}
|
|
1823
|
-
|
|
2071
|
+
} catch (err) {
|
|
2072
|
+
errors.push({
|
|
2073
|
+
tool: tool.name,
|
|
2074
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2075
|
+
});
|
|
1824
2076
|
}
|
|
1825
2077
|
}
|
|
1826
|
-
return installed;
|
|
2078
|
+
return { installed, errors };
|
|
1827
2079
|
};
|
|
1828
2080
|
var runInit = async (appContext, options = {}) => {
|
|
1829
2081
|
appContext.services.analyticsService.capture("init_started", {
|
|
@@ -1839,7 +2091,7 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1839
2091
|
const walletExists = (() => {
|
|
1840
2092
|
if (!existsSync2(configPath)) return false;
|
|
1841
2093
|
try {
|
|
1842
|
-
const existing = JSON.parse(
|
|
2094
|
+
const existing = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
1843
2095
|
return !!existing.privateKey;
|
|
1844
2096
|
} catch {
|
|
1845
2097
|
return false;
|
|
@@ -1849,7 +2101,7 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1849
2101
|
const privateKey = generatePrivateKey();
|
|
1850
2102
|
const account = privateKeyToAccount(privateKey);
|
|
1851
2103
|
mkdirSync2(zeroDir, { recursive: true });
|
|
1852
|
-
const existing = existsSync2(configPath) ? JSON.parse(
|
|
2104
|
+
const existing = existsSync2(configPath) ? JSON.parse(readFileSync4(configPath, "utf8")) : {};
|
|
1853
2105
|
writeFileSync2(
|
|
1854
2106
|
configPath,
|
|
1855
2107
|
JSON.stringify(
|
|
@@ -1863,7 +2115,7 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1863
2115
|
console.log(`Wallet address: ${account.address}`);
|
|
1864
2116
|
} else {
|
|
1865
2117
|
try {
|
|
1866
|
-
const existing = JSON.parse(
|
|
2118
|
+
const existing = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
1867
2119
|
const account = privateKeyToAccount(existing.privateKey);
|
|
1868
2120
|
walletAddress = account.address;
|
|
1869
2121
|
} catch {
|
|
@@ -1881,15 +2133,24 @@ var runInit = async (appContext, options = {}) => {
|
|
|
1881
2133
|
}
|
|
1882
2134
|
currentStep = "skills";
|
|
1883
2135
|
try {
|
|
1884
|
-
const installed = installSkills(home);
|
|
2136
|
+
const { installed, errors } = installSkills(home);
|
|
1885
2137
|
for (const entry of installed) {
|
|
1886
2138
|
const toolName = entry.split(":")[0];
|
|
1887
2139
|
if (toolName && !agentsWithSkills.includes(toolName)) {
|
|
1888
2140
|
agentsWithSkills.push(toolName);
|
|
1889
2141
|
}
|
|
1890
2142
|
}
|
|
2143
|
+
if (errors.length > 0) {
|
|
2144
|
+
skillsError = errors.map((e) => `${e.tool}: ${e.message}`).join("; ");
|
|
2145
|
+
for (const e of errors) {
|
|
2146
|
+
console.error(
|
|
2147
|
+
`Warning: failed to install skills for ${e.tool}: ${e.message}`
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
1891
2151
|
} catch (err) {
|
|
1892
2152
|
skillsError = err instanceof Error ? err.message : "unknown skills error";
|
|
2153
|
+
console.error(`Warning: skills install failed: ${skillsError}`);
|
|
1893
2154
|
}
|
|
1894
2155
|
currentStep = "hook";
|
|
1895
2156
|
try {
|
|
@@ -1971,7 +2232,7 @@ ${removedList}`);
|
|
|
1971
2232
|
);
|
|
1972
2233
|
|
|
1973
2234
|
// src/commands/review-command.ts
|
|
1974
|
-
import { readFileSync as
|
|
2235
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1975
2236
|
import { Command as Command6 } from "commander";
|
|
1976
2237
|
import { z as z3 } from "zod";
|
|
1977
2238
|
var bulkEntrySchema2 = z3.object({
|
|
@@ -2013,35 +2274,66 @@ Examples:
|
|
|
2013
2274
|
try {
|
|
2014
2275
|
const { analyticsService, apiService } = appContext.services;
|
|
2015
2276
|
if (options.fromFile) {
|
|
2016
|
-
const contents =
|
|
2277
|
+
const contents = readFileSync5(options.fromFile, "utf8");
|
|
2017
2278
|
const lines = contents.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
2018
|
-
|
|
2019
|
-
let
|
|
2279
|
+
const parsed = [];
|
|
2280
|
+
let parseFailures = 0;
|
|
2020
2281
|
for (const [idx, line] of lines.entries()) {
|
|
2021
2282
|
const lineNum = idx + 1;
|
|
2022
2283
|
try {
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
console.log(
|
|
2027
|
-
`[${lineNum}] ${parsed.runId} -> ${result2.reviewId}`
|
|
2028
|
-
);
|
|
2029
|
-
analyticsService.capture("review_submitted", {
|
|
2030
|
-
runId: parsed.runId,
|
|
2031
|
-
success: parsed.success,
|
|
2032
|
-
accuracy: parsed.accuracy,
|
|
2033
|
-
value: parsed.value,
|
|
2034
|
-
reliability: parsed.reliability,
|
|
2035
|
-
hasContent: !!parsed.content,
|
|
2036
|
-
bulk: true
|
|
2284
|
+
parsed.push({
|
|
2285
|
+
lineNum,
|
|
2286
|
+
entry: bulkEntrySchema2.parse(JSON.parse(line))
|
|
2037
2287
|
});
|
|
2038
2288
|
} catch (err) {
|
|
2039
|
-
|
|
2289
|
+
parseFailures += 1;
|
|
2040
2290
|
console.error(
|
|
2041
|
-
`[${lineNum}] FAILED: ${err instanceof Error ? err.message : String(err)}`
|
|
2291
|
+
`[${lineNum}] PARSE FAILED: ${err instanceof Error ? err.message : String(err)}`
|
|
2042
2292
|
);
|
|
2043
2293
|
}
|
|
2044
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
|
+
}
|
|
2045
2337
|
console.log(`
|
|
2046
2338
|
Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
2047
2339
|
if (failed > 0) process.exitCode = 1;
|
|
@@ -2080,7 +2372,7 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
|
2080
2372
|
}
|
|
2081
2373
|
if (list.runs.length > 1) {
|
|
2082
2374
|
console.error(
|
|
2083
|
-
`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>".`
|
|
2084
2376
|
);
|
|
2085
2377
|
process.exitCode = 1;
|
|
2086
2378
|
return;
|
|
@@ -2294,7 +2586,8 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2294
2586
|
capabilities: result.capabilities.map((c) => ({
|
|
2295
2587
|
position: c.position,
|
|
2296
2588
|
id: c.id,
|
|
2297
|
-
url: c.url
|
|
2589
|
+
url: c.url,
|
|
2590
|
+
displayCostAmount: c.cost.amount
|
|
2298
2591
|
}))
|
|
2299
2592
|
});
|
|
2300
2593
|
console.log(formatSearchResults(result.capabilities));
|
|
@@ -2337,7 +2630,7 @@ Read the full terms at: ${TERMS_URL}
|
|
|
2337
2630
|
});
|
|
2338
2631
|
|
|
2339
2632
|
// src/commands/wallet-command.ts
|
|
2340
|
-
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";
|
|
2341
2634
|
import { homedir as homedir3 } from "os";
|
|
2342
2635
|
import { join as join3 } from "path";
|
|
2343
2636
|
import { Command as Command10 } from "commander";
|
|
@@ -2432,7 +2725,7 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
|
|
|
2432
2725
|
const configPath = join3(zeroDir, "config.json");
|
|
2433
2726
|
if (!options.force && existsSync3(configPath)) {
|
|
2434
2727
|
try {
|
|
2435
|
-
const existing2 = JSON.parse(
|
|
2728
|
+
const existing2 = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2436
2729
|
if (existing2.privateKey) {
|
|
2437
2730
|
console.error(
|
|
2438
2731
|
"Wallet already configured. Use --force to overwrite."
|
|
@@ -2444,7 +2737,7 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
|
|
|
2444
2737
|
}
|
|
2445
2738
|
}
|
|
2446
2739
|
mkdirSync3(zeroDir, { recursive: true });
|
|
2447
|
-
const existing = existsSync3(configPath) ? JSON.parse(
|
|
2740
|
+
const existing = existsSync3(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
|
|
2448
2741
|
writeFileSync3(
|
|
2449
2742
|
configPath,
|
|
2450
2743
|
JSON.stringify(
|
|
@@ -2474,7 +2767,7 @@ var walletCommand = (appContext) => {
|
|
|
2474
2767
|
};
|
|
2475
2768
|
|
|
2476
2769
|
// src/commands/welcome-command.ts
|
|
2477
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2770
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
|
|
2478
2771
|
import { homedir as homedir4 } from "os";
|
|
2479
2772
|
import { join as join4 } from "path";
|
|
2480
2773
|
import { Command as Command11 } from "commander";
|
|
@@ -2485,7 +2778,7 @@ var readPrivateKey = () => {
|
|
|
2485
2778
|
const configPath = join4(homedir4(), ".zero", "config.json");
|
|
2486
2779
|
if (!existsSync4(configPath)) return null;
|
|
2487
2780
|
try {
|
|
2488
|
-
const config = JSON.parse(
|
|
2781
|
+
const config = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
2489
2782
|
if (typeof config.privateKey === "string") {
|
|
2490
2783
|
return config.privateKey;
|
|
2491
2784
|
}
|
|
@@ -2537,7 +2830,7 @@ If your browser didn't open, paste the URL above.`
|
|
|
2537
2830
|
// src/app.ts
|
|
2538
2831
|
var createApp = (appContext) => {
|
|
2539
2832
|
const { analyticsService } = appContext.services;
|
|
2540
|
-
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) => {
|
|
2541
2834
|
const agentFlag = actionCommand.opts().agent;
|
|
2542
2835
|
if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
|
|
2543
2836
|
analyticsService.setAgentHost(agentFlag.trim());
|
|
@@ -2584,14 +2877,14 @@ var getEnv = () => {
|
|
|
2584
2877
|
|
|
2585
2878
|
// src/app/app-services.ts
|
|
2586
2879
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2587
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2880
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
2588
2881
|
import { homedir as homedir5 } from "os";
|
|
2589
2882
|
import { join as join6 } from "path";
|
|
2590
2883
|
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
2591
2884
|
|
|
2592
2885
|
// src/services/analytics-service.ts
|
|
2593
2886
|
import { randomUUID } from "crypto";
|
|
2594
|
-
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";
|
|
2595
2888
|
import { dirname as dirname2 } from "path";
|
|
2596
2889
|
import { PostHog } from "posthog-node";
|
|
2597
2890
|
var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
|
|
@@ -2614,7 +2907,7 @@ var AnalyticsService = class {
|
|
|
2614
2907
|
let persistedAnonId;
|
|
2615
2908
|
try {
|
|
2616
2909
|
if (existsSync5(opts.configPath)) {
|
|
2617
|
-
const config = JSON.parse(
|
|
2910
|
+
const config = JSON.parse(readFileSync8(opts.configPath, "utf8"));
|
|
2618
2911
|
if (config.telemetry === false) {
|
|
2619
2912
|
telemetryEnabled = false;
|
|
2620
2913
|
}
|
|
@@ -2639,7 +2932,7 @@ var AnalyticsService = class {
|
|
|
2639
2932
|
try {
|
|
2640
2933
|
const dir = dirname2(opts.configPath);
|
|
2641
2934
|
mkdirSync4(dir, { recursive: true });
|
|
2642
|
-
const existing = existsSync5(opts.configPath) ? JSON.parse(
|
|
2935
|
+
const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync8(opts.configPath, "utf8")) : {};
|
|
2643
2936
|
writeFileSync4(
|
|
2644
2937
|
opts.configPath,
|
|
2645
2938
|
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
@@ -2675,7 +2968,7 @@ var AnalyticsService = class {
|
|
|
2675
2968
|
if (anonId === walletAddress) return;
|
|
2676
2969
|
let aliasedTo;
|
|
2677
2970
|
try {
|
|
2678
|
-
const config = JSON.parse(
|
|
2971
|
+
const config = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
2679
2972
|
if (typeof config.aliasedTo === "string") {
|
|
2680
2973
|
aliasedTo = config.aliasedTo;
|
|
2681
2974
|
}
|
|
@@ -2684,7 +2977,7 @@ var AnalyticsService = class {
|
|
|
2684
2977
|
if (aliasedTo === walletAddress) return;
|
|
2685
2978
|
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
2686
2979
|
try {
|
|
2687
|
-
const config = existsSync5(configPath) ? JSON.parse(
|
|
2980
|
+
const config = existsSync5(configPath) ? JSON.parse(readFileSync8(configPath, "utf8")) : {};
|
|
2688
2981
|
writeFileSync4(
|
|
2689
2982
|
configPath,
|
|
2690
2983
|
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
@@ -2727,7 +3020,7 @@ var AnalyticsService = class {
|
|
|
2727
3020
|
};
|
|
2728
3021
|
|
|
2729
3022
|
// src/services/state-service.ts
|
|
2730
|
-
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";
|
|
2731
3024
|
import { join as join5 } from "path";
|
|
2732
3025
|
var StateService = class {
|
|
2733
3026
|
constructor(zeroDir) {
|
|
@@ -2742,7 +3035,7 @@ var StateService = class {
|
|
|
2742
3035
|
loadLastSearch = () => {
|
|
2743
3036
|
try {
|
|
2744
3037
|
if (!existsSync6(this.lastSearchPath)) return null;
|
|
2745
|
-
const raw =
|
|
3038
|
+
const raw = readFileSync9(this.lastSearchPath, "utf8");
|
|
2746
3039
|
return JSON.parse(raw);
|
|
2747
3040
|
} catch {
|
|
2748
3041
|
return null;
|
|
@@ -2795,7 +3088,7 @@ var getServices = (env) => {
|
|
|
2795
3088
|
if (!privateKey) {
|
|
2796
3089
|
try {
|
|
2797
3090
|
if (existsSync7(configPath)) {
|
|
2798
|
-
const config = JSON.parse(
|
|
3091
|
+
const config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2799
3092
|
if (typeof config.privateKey === "string") {
|
|
2800
3093
|
privateKey = config.privateKey;
|
|
2801
3094
|
}
|
|
@@ -2807,7 +3100,7 @@ var getServices = (env) => {
|
|
|
2807
3100
|
let lowBalanceWarning = 1;
|
|
2808
3101
|
try {
|
|
2809
3102
|
if (existsSync7(configPath)) {
|
|
2810
|
-
const config = JSON.parse(
|
|
3103
|
+
const config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2811
3104
|
if (typeof config.lowBalanceWarning === "number") {
|
|
2812
3105
|
lowBalanceWarning = config.lowBalanceWarning;
|
|
2813
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
|
|