@zeroxyz/cli 0.0.33 → 0.0.36
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 +10 -0
- package/dist/index.js +516 -44
- package/package.json +1 -1
- package/skills/zero/SKILL.md +5 -2
package/README.md
CHANGED
|
@@ -48,6 +48,16 @@ zero search "image classification"
|
|
|
48
48
|
|
|
49
49
|
Results are numbered. Use `zero get <number>` to view details.
|
|
50
50
|
|
|
51
|
+
**Cost filtering.** By default, results are filtered to capabilities priced **≤ $30/call** as a wallet-safety cap. Override per-call:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
zero search "image classification" --max-cost 5 # cap at $5/call
|
|
55
|
+
zero search "expensive deep research" --max-cost 100 # raise the cap for hard tasks
|
|
56
|
+
zero search "image classification" --free # only free capabilities
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Other useful filters: `--min-rating <1-5>`, `--min-trust <0-100>`, `--protocol x402|mpp`, `--status healthy|degraded|down`, `--source <name>`, `--all` (no trust/health filtering).
|
|
60
|
+
|
|
51
61
|
### `zero get <position>`
|
|
52
62
|
|
|
53
63
|
Get full details for a capability from the last search results.
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
import {
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { homedir as homedir7 } from "os";
|
|
5
|
+
import { join as join8 } from "path";
|
|
5
6
|
|
|
6
7
|
// package.json
|
|
7
8
|
var package_default = {
|
|
8
9
|
name: "@zeroxyz/cli",
|
|
9
|
-
version: "0.0.
|
|
10
|
+
version: "0.0.36",
|
|
10
11
|
type: "module",
|
|
11
12
|
bin: {
|
|
12
13
|
zero: "dist/index.js",
|
|
@@ -59,6 +60,9 @@ var package_default = {
|
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
62
|
|
|
63
|
+
// src/app.ts
|
|
64
|
+
import { Command as Command12 } from "commander";
|
|
65
|
+
|
|
62
66
|
// src/commands/bug-report-command.ts
|
|
63
67
|
import { createHash as createHash2 } from "crypto";
|
|
64
68
|
import { readFileSync } from "fs";
|
|
@@ -77,6 +81,7 @@ var searchResultSchema = z.object({
|
|
|
77
81
|
description: z.string(),
|
|
78
82
|
whatItDoes: z.string().nullable().optional(),
|
|
79
83
|
url: z.string(),
|
|
84
|
+
urlTemplate: z.string().nullable().optional(),
|
|
80
85
|
cost: z.object({ amount: z.string(), asset: z.string() }),
|
|
81
86
|
rating: z.object({
|
|
82
87
|
score: z.string(),
|
|
@@ -103,6 +108,7 @@ var capabilityResponseSchema = z.object({
|
|
|
103
108
|
name: z.string(),
|
|
104
109
|
description: z.string(),
|
|
105
110
|
url: z.string(),
|
|
111
|
+
urlTemplate: z.string().nullable().optional(),
|
|
106
112
|
method: z.string(),
|
|
107
113
|
headers: z.record(z.string(), z.string()).nullable(),
|
|
108
114
|
bodySchema: z.record(z.string(), z.unknown()).nullable(),
|
|
@@ -146,7 +152,10 @@ var capabilityResponseSchema = z.object({
|
|
|
146
152
|
blockchainActivity: z.number().nullable(),
|
|
147
153
|
performance: z.number().nullable()
|
|
148
154
|
}).nullable().optional(),
|
|
149
|
-
availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional()
|
|
155
|
+
availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
|
|
156
|
+
activationCount: z.number().optional(),
|
|
157
|
+
lastUsedAt: z.string().nullable().optional(),
|
|
158
|
+
lastSuccessfullyRanAt: z.string().nullable().optional()
|
|
150
159
|
});
|
|
151
160
|
var createRunResponseSchema = z.object({
|
|
152
161
|
runId: z.string()
|
|
@@ -614,6 +623,7 @@ var configCommand = (_appContext) => new Command2("config").description("View or
|
|
|
614
623
|
});
|
|
615
624
|
|
|
616
625
|
// src/commands/fetch-command.ts
|
|
626
|
+
import { createHash as createHash3 } from "crypto";
|
|
617
627
|
import { readFileSync as readFileSync3 } from "fs";
|
|
618
628
|
import { resolve as resolvePath } from "path";
|
|
619
629
|
import { Command as Command3 } from "commander";
|
|
@@ -737,6 +747,7 @@ var pickSessionCloseAmount = (receipt, openTimeCumulative) => {
|
|
|
737
747
|
const accepted = BigInt(receipt.acceptedCumulative);
|
|
738
748
|
const spent = BigInt(receipt.spent);
|
|
739
749
|
const fromReceipt = accepted > spent ? accepted : spent;
|
|
750
|
+
if (receipt.metered === true) return fromReceipt;
|
|
740
751
|
return fromReceipt > openTimeCumulative ? fromReceipt : openTimeCumulative;
|
|
741
752
|
};
|
|
742
753
|
var PaymentService = class {
|
|
@@ -1203,6 +1214,22 @@ var truncateQuery = (raw) => raw.length > QUERY_MAX ? `${raw.slice(0, QUERY_MAX)
|
|
|
1203
1214
|
var truncateError = (raw) => raw.length > ERROR_MAX ? `${raw.slice(0, ERROR_MAX)}\u2026` : raw;
|
|
1204
1215
|
|
|
1205
1216
|
// src/commands/fetch-command.ts
|
|
1217
|
+
var sniffJsonShape = (buf) => {
|
|
1218
|
+
let i = 0;
|
|
1219
|
+
if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
|
|
1220
|
+
i = 3;
|
|
1221
|
+
}
|
|
1222
|
+
while (i < buf.length && (buf[i] === 32 || buf[i] === 9 || buf[i] === 10 || buf[i] === 13)) {
|
|
1223
|
+
i++;
|
|
1224
|
+
}
|
|
1225
|
+
if (i >= buf.length) return false;
|
|
1226
|
+
return buf[i] === 123 || buf[i] === 91;
|
|
1227
|
+
};
|
|
1228
|
+
var urlMatchesTemplate = (url, template) => {
|
|
1229
|
+
const escaped = template.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1230
|
+
const withCaptures = escaped.replace(/\\\{[^/\\}]+\\\}/g, "[^/]+");
|
|
1231
|
+
return new RegExp(`^${withCaptures}$`).test(url);
|
|
1232
|
+
};
|
|
1206
1233
|
var isTextContentType = (contentType) => {
|
|
1207
1234
|
if (!contentType) return true;
|
|
1208
1235
|
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
@@ -1285,7 +1312,10 @@ var detectPaymentRequirement = async (response) => {
|
|
|
1285
1312
|
};
|
|
1286
1313
|
var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
1287
1314
|
"Fetch a capability URL, handling 402 challenges automatically"
|
|
1288
|
-
).argument(
|
|
1315
|
+
).argument(
|
|
1316
|
+
"[url]",
|
|
1317
|
+
"URL to fetch. Optional when --capability is provided \u2014 the URL is resolved from the capability."
|
|
1318
|
+
).option(
|
|
1289
1319
|
"-X, --method <method>",
|
|
1290
1320
|
"HTTP method (GET, POST, PUT, PATCH, DELETE). Defaults to POST when -d is set, otherwise GET"
|
|
1291
1321
|
).option(
|
|
@@ -1317,6 +1347,73 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1317
1347
|
walletService
|
|
1318
1348
|
} = appContext.services;
|
|
1319
1349
|
const startTime = Date.now();
|
|
1350
|
+
let resolvedUrl;
|
|
1351
|
+
let resolvedMethodFromCapability;
|
|
1352
|
+
let resolvedCapabilityName;
|
|
1353
|
+
if (!url) {
|
|
1354
|
+
if (!options.capability) {
|
|
1355
|
+
console.error(
|
|
1356
|
+
"Missing URL. Pass a URL argument or --capability <uid|slug> so the URL can be resolved from the capability."
|
|
1357
|
+
);
|
|
1358
|
+
process.exitCode = 1;
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
try {
|
|
1362
|
+
const cap = await apiService.getCapability(options.capability);
|
|
1363
|
+
resolvedUrl = cap.url;
|
|
1364
|
+
resolvedMethodFromCapability = cap.method;
|
|
1365
|
+
resolvedCapabilityName = cap.name;
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
console.error(
|
|
1368
|
+
`Failed to resolve --capability ${options.capability}: ${err instanceof Error ? err.message : String(err)}`
|
|
1369
|
+
);
|
|
1370
|
+
process.exitCode = 1;
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
resolvedUrl = url;
|
|
1375
|
+
}
|
|
1376
|
+
if (!url && options.capability && !stateService.findSearchContextByCapability(options.capability)) {
|
|
1377
|
+
try {
|
|
1378
|
+
let capName = resolvedCapabilityName;
|
|
1379
|
+
if (capName === void 0) {
|
|
1380
|
+
const cap = await apiService.getCapability(options.capability);
|
|
1381
|
+
capName = cap.name;
|
|
1382
|
+
}
|
|
1383
|
+
const searchResult = await apiService.search({ query: capName });
|
|
1384
|
+
const slugFoundInResults = searchResult.capabilities.some(
|
|
1385
|
+
(c) => c.slug === options.capability || c.id === options.capability
|
|
1386
|
+
);
|
|
1387
|
+
stateService.saveLastSearch({
|
|
1388
|
+
searchId: searchResult.searchId,
|
|
1389
|
+
capabilities: searchResult.capabilities.map((c) => ({
|
|
1390
|
+
position: c.position,
|
|
1391
|
+
id: c.id,
|
|
1392
|
+
url: c.url,
|
|
1393
|
+
urlTemplate: c.urlTemplate ?? null,
|
|
1394
|
+
displayCostAmount: c.cost.amount
|
|
1395
|
+
}))
|
|
1396
|
+
});
|
|
1397
|
+
analyticsService.capture("search_executed", {
|
|
1398
|
+
query: truncateQuery(capName),
|
|
1399
|
+
queryLength: capName.length,
|
|
1400
|
+
resultCount: searchResult.capabilities.length,
|
|
1401
|
+
searchId: searchResult.searchId,
|
|
1402
|
+
total: searchResult.total,
|
|
1403
|
+
hasMore: searchResult.hasMore,
|
|
1404
|
+
json: false,
|
|
1405
|
+
triggeredBy: "slug_handoff",
|
|
1406
|
+
slugFoundInResults
|
|
1407
|
+
});
|
|
1408
|
+
analyticsService.capture("capability_viewed", {
|
|
1409
|
+
capabilityId: options.capability,
|
|
1410
|
+
fromLastSearch: false,
|
|
1411
|
+
searchId: searchResult.searchId,
|
|
1412
|
+
triggeredBy: "slug_handoff"
|
|
1413
|
+
});
|
|
1414
|
+
} catch {
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1320
1417
|
let resolvedBody;
|
|
1321
1418
|
try {
|
|
1322
1419
|
resolvedBody = resolveRequestBody(
|
|
@@ -1324,9 +1421,13 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1324
1421
|
options.dataStdin ?? false
|
|
1325
1422
|
);
|
|
1326
1423
|
} catch (err) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1424
|
+
const message = err instanceof Error ? err.message : "Failed to read request body";
|
|
1425
|
+
analyticsService.capture("fetch_error", {
|
|
1426
|
+
cliErrorClass: /exceeds the .* byte limit/.test(message) ? "payload_too_large" : "schema_validation_failed",
|
|
1427
|
+
url: redactUrl(resolvedUrl),
|
|
1428
|
+
error: truncateError(message)
|
|
1429
|
+
});
|
|
1430
|
+
console.error(message);
|
|
1330
1431
|
process.exitCode = 1;
|
|
1331
1432
|
return;
|
|
1332
1433
|
}
|
|
@@ -1343,21 +1444,30 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1343
1444
|
(k) => k.toLowerCase() === "content-type"
|
|
1344
1445
|
);
|
|
1345
1446
|
if (resolvedBody && !hasContentType) {
|
|
1346
|
-
headers["content-type"] = Buffer.isBuffer(resolvedBody) ? "application/octet-stream" : "application/json";
|
|
1447
|
+
headers["content-type"] = Buffer.isBuffer(resolvedBody) ? sniffJsonShape(resolvedBody) ? "application/json" : "application/octet-stream" : "application/json";
|
|
1347
1448
|
}
|
|
1348
1449
|
const log = (msg) => console.error(` ${msg}`);
|
|
1349
|
-
const method = options.method ? options.method.toUpperCase() : resolvedBody ? "POST" : "GET";
|
|
1450
|
+
const method = options.method ? options.method.toUpperCase() : resolvedMethodFromCapability && !resolvedBody ? resolvedMethodFromCapability.toUpperCase() : resolvedBody ? "POST" : "GET";
|
|
1350
1451
|
const requestInit = {
|
|
1351
1452
|
method,
|
|
1352
1453
|
headers,
|
|
1353
1454
|
body: resolvedBody
|
|
1354
1455
|
};
|
|
1355
|
-
const
|
|
1356
|
-
const
|
|
1357
|
-
(
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1456
|
+
const explicitCapabilityCtx = options.capability ? stateService.findSearchContextByCapability(options.capability) : null;
|
|
1457
|
+
const urlCtx = stateService.findSearchContextByUrl(resolvedUrl, {
|
|
1458
|
+
matchTemplate: (template) => {
|
|
1459
|
+
try {
|
|
1460
|
+
return urlMatchesTemplate(resolvedUrl, template);
|
|
1461
|
+
} catch {
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
const matchCtx = explicitCapabilityCtx ?? urlCtx;
|
|
1467
|
+
const capabilityId = options.capability ?? matchCtx?.capabilityId ?? null;
|
|
1468
|
+
const searchId = matchCtx?.searchId;
|
|
1469
|
+
const resultRank = matchCtx?.resultRank;
|
|
1470
|
+
const matchedDisplayCostAmount = matchCtx?.displayCostAmount;
|
|
1361
1471
|
const skipReasons = [];
|
|
1362
1472
|
if (!apiService.walletAddress) {
|
|
1363
1473
|
skipReasons.push(
|
|
@@ -1368,6 +1478,19 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1368
1478
|
skipReasons.push(
|
|
1369
1479
|
"no capability resolved \u2014 pass --capability <uid|slug> or run `zero search` first so the URL can be matched"
|
|
1370
1480
|
);
|
|
1481
|
+
if (!options.capability) {
|
|
1482
|
+
const lastSearch = stateService.loadLastSearch();
|
|
1483
|
+
const cached = lastSearch?.capabilities ?? [];
|
|
1484
|
+
analyticsService.capture("capability_resolution_missed", {
|
|
1485
|
+
url: redactUrl(resolvedUrl),
|
|
1486
|
+
method,
|
|
1487
|
+
hasLastSearch: lastSearch !== null,
|
|
1488
|
+
lastSearchSize: cached.length,
|
|
1489
|
+
lastSearchHasTemplates: cached.some(
|
|
1490
|
+
(c) => Boolean(c.urlTemplate)
|
|
1491
|
+
)
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1371
1494
|
}
|
|
1372
1495
|
let finalResponse;
|
|
1373
1496
|
let bodyBytes;
|
|
@@ -1377,20 +1500,20 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1377
1500
|
let sessionMeta;
|
|
1378
1501
|
let fetchError;
|
|
1379
1502
|
try {
|
|
1380
|
-
log(`Calling ${
|
|
1381
|
-
const response = await fetch(
|
|
1503
|
+
log(`Calling ${resolvedUrl}...`);
|
|
1504
|
+
const response = await fetch(resolvedUrl, requestInit);
|
|
1382
1505
|
const paymentReq = await detectPaymentRequirement(response);
|
|
1383
1506
|
if (paymentReq) {
|
|
1384
1507
|
log(
|
|
1385
1508
|
`Payment required (${paymentReq.protocol}) \u2014 preparing payment...`
|
|
1386
1509
|
);
|
|
1387
1510
|
const result = await paymentService.handlePayment(
|
|
1388
|
-
|
|
1511
|
+
resolvedUrl,
|
|
1389
1512
|
requestInit,
|
|
1390
1513
|
paymentReq,
|
|
1391
1514
|
options.maxPay,
|
|
1392
1515
|
log,
|
|
1393
|
-
|
|
1516
|
+
matchedDisplayCostAmount
|
|
1394
1517
|
);
|
|
1395
1518
|
finalResponse = result.response;
|
|
1396
1519
|
paymentMeta = {
|
|
@@ -1487,6 +1610,15 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1487
1610
|
} catch {
|
|
1488
1611
|
}
|
|
1489
1612
|
}
|
|
1613
|
+
const status = finalResponse?.status;
|
|
1614
|
+
const isFailure = !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
1615
|
+
let errorSnippetHash;
|
|
1616
|
+
let errorSnippetLength;
|
|
1617
|
+
if (isFailure && body && !bodyIsBinary) {
|
|
1618
|
+
const snippet = body.slice(0, 500);
|
|
1619
|
+
errorSnippetHash = createHash3("sha256").update(snippet).digest("hex");
|
|
1620
|
+
errorSnippetLength = snippet.length;
|
|
1621
|
+
}
|
|
1490
1622
|
let runId = null;
|
|
1491
1623
|
if (capabilityId && apiService.walletAddress) {
|
|
1492
1624
|
let requestSchema;
|
|
@@ -1511,6 +1643,8 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1511
1643
|
latencyMs,
|
|
1512
1644
|
requestSchema,
|
|
1513
1645
|
responseSchema,
|
|
1646
|
+
errorSnippetHash,
|
|
1647
|
+
errorSnippetLength,
|
|
1514
1648
|
...paymentMeta && {
|
|
1515
1649
|
costAmount: paymentMeta.amount,
|
|
1516
1650
|
costAsset: paymentMeta.asset,
|
|
@@ -1527,10 +1661,9 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1527
1661
|
);
|
|
1528
1662
|
}
|
|
1529
1663
|
}
|
|
1530
|
-
const status = finalResponse?.status;
|
|
1531
1664
|
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1532
1665
|
analyticsService.capture("fetch_executed", {
|
|
1533
|
-
url: redactUrl(
|
|
1666
|
+
url: redactUrl(resolvedUrl),
|
|
1534
1667
|
status,
|
|
1535
1668
|
outcome,
|
|
1536
1669
|
latencyMs,
|
|
@@ -1539,10 +1672,26 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1539
1672
|
paymentAmount: paymentMeta?.amount,
|
|
1540
1673
|
capabilityId: capabilityId ?? void 0,
|
|
1541
1674
|
searchId: searchId ?? void 0,
|
|
1675
|
+
resultRank: resultRank ?? void 0,
|
|
1542
1676
|
runId: runId ?? void 0,
|
|
1543
1677
|
runTracked: !!runId,
|
|
1544
1678
|
...fetchError && { error: truncateError(fetchError.message) }
|
|
1545
1679
|
});
|
|
1680
|
+
const isFetchFailure = Boolean(fetchError) || !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
1681
|
+
if (isFetchFailure) {
|
|
1682
|
+
const cliErrorClass = fetchError || !finalResponse ? "network" : !apiService.walletAddress ? "auth_missing" : "unknown";
|
|
1683
|
+
analyticsService.capture("fetch_error", {
|
|
1684
|
+
cliErrorClass,
|
|
1685
|
+
capabilityId: capabilityId ?? void 0,
|
|
1686
|
+
searchId: searchId ?? void 0,
|
|
1687
|
+
resultRank: resultRank ?? void 0,
|
|
1688
|
+
url: redactUrl(resolvedUrl),
|
|
1689
|
+
error: truncateError(
|
|
1690
|
+
fetchError?.message ?? skipReasons.join("; ")
|
|
1691
|
+
),
|
|
1692
|
+
skippedRun: !runId && skipReasons.length > 0
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1546
1695
|
if (fetchError && !options.json) {
|
|
1547
1696
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1548
1697
|
}
|
|
@@ -1609,7 +1758,16 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1609
1758
|
process.exitCode = 1;
|
|
1610
1759
|
}
|
|
1611
1760
|
} catch (err) {
|
|
1612
|
-
|
|
1761
|
+
const message = err instanceof Error ? err.message : "Fetch failed";
|
|
1762
|
+
try {
|
|
1763
|
+
appContext.services.analyticsService.capture("fetch_error", {
|
|
1764
|
+
cliErrorClass: "unknown",
|
|
1765
|
+
url: url ? redactUrl(url) : "unknown",
|
|
1766
|
+
error: truncateError(message)
|
|
1767
|
+
});
|
|
1768
|
+
} catch {
|
|
1769
|
+
}
|
|
1770
|
+
console.error(message);
|
|
1613
1771
|
process.exitCode = 1;
|
|
1614
1772
|
}
|
|
1615
1773
|
}
|
|
@@ -1621,6 +1779,22 @@ var formatReviewCount = (count) => {
|
|
|
1621
1779
|
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
1622
1780
|
return count.toString();
|
|
1623
1781
|
};
|
|
1782
|
+
var formatRelativeTimestamp = (iso) => {
|
|
1783
|
+
if (!iso) return "never";
|
|
1784
|
+
const then = new Date(iso).getTime();
|
|
1785
|
+
if (Number.isNaN(then)) return "never";
|
|
1786
|
+
const diffSec = Math.max(0, Math.round((Date.now() - then) / 1e3));
|
|
1787
|
+
if (diffSec < 60) return `${diffSec}s ago`;
|
|
1788
|
+
const diffMin = Math.round(diffSec / 60);
|
|
1789
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
1790
|
+
const diffHr = Math.round(diffMin / 60);
|
|
1791
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
1792
|
+
const diffDay = Math.round(diffHr / 24);
|
|
1793
|
+
if (diffDay < 30) return `${diffDay}d ago`;
|
|
1794
|
+
const diffMo = Math.round(diffDay / 30);
|
|
1795
|
+
if (diffMo < 12) return `${diffMo}mo ago`;
|
|
1796
|
+
return `${Math.round(diffMo / 12)}y ago`;
|
|
1797
|
+
};
|
|
1624
1798
|
var formatTrustScore = (capability) => {
|
|
1625
1799
|
if (capability.trustScore != null) {
|
|
1626
1800
|
return `Trust Score: ${capability.trustScore}/100`;
|
|
@@ -1705,11 +1879,11 @@ var buildTryItExample = (capability) => {
|
|
|
1705
1879
|
([k, schema]) => `${encodeURIComponent(k)}=${encodeURIComponent(placeholderFor(k, schema))}`
|
|
1706
1880
|
).join("&") : "";
|
|
1707
1881
|
const url = qs ? `${capability.url}?${qs}` : capability.url;
|
|
1708
|
-
const urlLine = ` zero fetch "${url}"`;
|
|
1882
|
+
const urlLine = ` zero fetch --capability ${capability.slug} "${url}"`;
|
|
1709
1883
|
if (headerFlags.length === 0) {
|
|
1710
1884
|
lines.push(urlLine);
|
|
1711
1885
|
} else {
|
|
1712
|
-
lines.push(` zero fetch \\`);
|
|
1886
|
+
lines.push(` zero fetch --capability ${capability.slug} \\`);
|
|
1713
1887
|
for (const h of headerFlags) lines.push(` ${h} \\`);
|
|
1714
1888
|
lines.push(` "${url}"`);
|
|
1715
1889
|
}
|
|
@@ -1727,11 +1901,10 @@ var buildTryItExample = (capability) => {
|
|
|
1727
1901
|
])
|
|
1728
1902
|
) : null;
|
|
1729
1903
|
const bodyJson = samplePayload ? JSON.stringify(samplePayload) : "<BODY_JSON>";
|
|
1730
|
-
lines.push(` zero fetch \\`);
|
|
1904
|
+
lines.push(` zero fetch --capability ${capability.slug} \\`);
|
|
1731
1905
|
if (method !== "POST") lines.push(` -X ${method} \\`);
|
|
1732
1906
|
for (const h of headerFlags) lines.push(` ${h} \\`);
|
|
1733
|
-
lines.push(` -d '${bodyJson}'
|
|
1734
|
-
lines.push(` ${capability.url}`);
|
|
1907
|
+
lines.push(` -d '${bodyJson}'`);
|
|
1735
1908
|
if (!body) {
|
|
1736
1909
|
lines.push(
|
|
1737
1910
|
" # bodySchema did not expose input.body \u2014 replace <BODY_JSON> with the exact shape shown above."
|
|
@@ -1768,6 +1941,9 @@ var formatCapability = (capability) => {
|
|
|
1768
1941
|
lines.push(...formatCost(capability));
|
|
1769
1942
|
lines.push(` URL: ${capability.url}`);
|
|
1770
1943
|
lines.push(` Method: ${capability.method}`);
|
|
1944
|
+
lines.push(
|
|
1945
|
+
` Last successful run: ${formatRelativeTimestamp(capability.lastSuccessfullyRanAt)}`
|
|
1946
|
+
);
|
|
1771
1947
|
lines.push(...buildTryItExample(capability));
|
|
1772
1948
|
return lines.join("\n");
|
|
1773
1949
|
};
|
|
@@ -1807,6 +1983,8 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1807
1983
|
searchId = lastSearch.searchId;
|
|
1808
1984
|
} else {
|
|
1809
1985
|
capabilityId = identifier;
|
|
1986
|
+
const ctx = stateService.findSearchContextByCapability(identifier);
|
|
1987
|
+
if (ctx) searchId = ctx.searchId;
|
|
1810
1988
|
}
|
|
1811
1989
|
const capability = await apiService.getCapability(
|
|
1812
1990
|
capabilityId,
|
|
@@ -1830,7 +2008,7 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1830
2008
|
});
|
|
1831
2009
|
|
|
1832
2010
|
// src/commands/init-command.ts
|
|
1833
|
-
import { createHash as
|
|
2011
|
+
import { createHash as createHash4 } from "crypto";
|
|
1834
2012
|
import {
|
|
1835
2013
|
chmodSync as chmodSync2,
|
|
1836
2014
|
existsSync as existsSync2,
|
|
@@ -1904,10 +2082,12 @@ var color = {
|
|
|
1904
2082
|
magenta: (s) => wrap("35", s),
|
|
1905
2083
|
green: (s) => wrap("32", s),
|
|
1906
2084
|
yellow: (s) => wrap("33", s),
|
|
2085
|
+
red: (s) => wrap("31", s),
|
|
1907
2086
|
gray: (s) => wrap("90", s),
|
|
1908
2087
|
boldCyan: (s) => wrap("1;36", s),
|
|
1909
2088
|
boldMagenta: (s) => wrap("1;35", s),
|
|
1910
|
-
boldGreen: (s) => wrap("1;32", s)
|
|
2089
|
+
boldGreen: (s) => wrap("1;32", s),
|
|
2090
|
+
boldRed: (s) => wrap("1;31", s)
|
|
1911
2091
|
};
|
|
1912
2092
|
var printZeroBanner = () => {
|
|
1913
2093
|
console.log("");
|
|
@@ -1949,12 +2129,17 @@ var sectionDivider = () => {
|
|
|
1949
2129
|
var printReadyFooter = () => {
|
|
1950
2130
|
const lines = [
|
|
1951
2131
|
"",
|
|
1952
|
-
` ${color.boldGreen("Zero is ready!")}
|
|
2132
|
+
` ${color.boldGreen("Zero is ready!")} Zero works best with an AI agent.`,
|
|
2133
|
+
` Open ${color.boldRed("Claude Code")}, Codex, Cursor, Blackbox, or your agent of choice`,
|
|
2134
|
+
" and try this prompt to get started:",
|
|
1953
2135
|
"",
|
|
1954
|
-
`
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2136
|
+
` ${color.cyan("What is zero and how do I use it?")}`,
|
|
2137
|
+
"",
|
|
2138
|
+
" You can also just tell your agent examples of what you are trying to do:",
|
|
2139
|
+
"",
|
|
2140
|
+
` ${color.cyan("Can you use zero to deploy an NYC weather website")}`,
|
|
2141
|
+
` ${color.cyan("Email me an image of a crystalline rocket ship with zero")}`,
|
|
2142
|
+
` ${color.cyan("Create a demo video with real voiceover for my project using zero")}`,
|
|
1958
2143
|
"",
|
|
1959
2144
|
` ${color.dim("By using Zero, you agree to our Terms of Service:")}`,
|
|
1960
2145
|
` ${color.dim("https://zero.xyz/terms-of-service")}`,
|
|
@@ -2010,7 +2195,7 @@ var getCliModuleDir = () => {
|
|
|
2010
2195
|
}
|
|
2011
2196
|
return __dirname;
|
|
2012
2197
|
};
|
|
2013
|
-
var sha256File = (filePath) =>
|
|
2198
|
+
var sha256File = (filePath) => createHash4("sha256").update(readFileSync4(filePath)).digest("hex");
|
|
2014
2199
|
var verifyFileCopy = (src, dest) => {
|
|
2015
2200
|
if (!existsSync2(dest)) return false;
|
|
2016
2201
|
return sha256File(src) === sha256File(dest);
|
|
@@ -2658,6 +2843,20 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
|
2658
2843
|
|
|
2659
2844
|
// src/commands/runs-command.ts
|
|
2660
2845
|
import { Command as Command7 } from "commander";
|
|
2846
|
+
var USD_ASSETS = /* @__PURE__ */ new Set(["USD", "USDC"]);
|
|
2847
|
+
var formatCost2 = (cost) => {
|
|
2848
|
+
if (!cost) return "free";
|
|
2849
|
+
const { amount, asset } = cost;
|
|
2850
|
+
const prefix = asset && USD_ASSETS.has(asset.toUpperCase()) ? "$" : "";
|
|
2851
|
+
const priced = asset ? `${prefix}${amount} ${asset}` : amount;
|
|
2852
|
+
return Number(amount) === 0 ? `free (${priced})` : priced;
|
|
2853
|
+
};
|
|
2854
|
+
var formatPayment = (payment) => {
|
|
2855
|
+
if (!payment) return "\u2014";
|
|
2856
|
+
const chain = payment.chain ? `:${payment.chain}` : "";
|
|
2857
|
+
const mode = payment.mode ? ` (${payment.mode})` : "";
|
|
2858
|
+
return `${payment.protocol}${chain}${mode}`;
|
|
2859
|
+
};
|
|
2661
2860
|
var runsCommand = (appContext) => new Command7("runs").description("List your recent capability runs").addHelpText(
|
|
2662
2861
|
"after",
|
|
2663
2862
|
`
|
|
@@ -2667,12 +2866,13 @@ A leading [\u2713] means the run already has a review; [ ] means it does not.
|
|
|
2667
2866
|
Examples:
|
|
2668
2867
|
zero runs # most recent runs
|
|
2669
2868
|
zero runs --unreviewed # filter to runs without a review
|
|
2670
|
-
zero runs --capability translate-en # filter to one capability (uid or slug)
|
|
2869
|
+
zero runs --capability translate-en # filter to one capability (uid or slug)
|
|
2870
|
+
zero runs --json # full structured output for scripts`
|
|
2671
2871
|
).option("--capability <id>", "Filter by capability uid or slug").option("--unreviewed", "Only show runs without a review").option(
|
|
2672
2872
|
"--limit <n>",
|
|
2673
2873
|
"Max rows (1-100, default 25)",
|
|
2674
2874
|
(v) => Number.parseInt(v, 10)
|
|
2675
|
-
).option(
|
|
2875
|
+
).option("--json", "Output raw JSON to stdout").option(
|
|
2676
2876
|
"--agent <name>",
|
|
2677
2877
|
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
2678
2878
|
).action(
|
|
@@ -2684,6 +2884,10 @@ Examples:
|
|
|
2684
2884
|
unreviewed: options.unreviewed,
|
|
2685
2885
|
limit: options.limit
|
|
2686
2886
|
});
|
|
2887
|
+
if (options.json) {
|
|
2888
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2687
2891
|
if (result.runs.length === 0) {
|
|
2688
2892
|
console.log("No runs found.");
|
|
2689
2893
|
return;
|
|
@@ -2693,8 +2897,10 @@ Examples:
|
|
|
2693
2897
|
const mark = r.reviewed ? "\u2713" : " ";
|
|
2694
2898
|
const status = r.status ?? "\u2014";
|
|
2695
2899
|
const latency = r.latencyMs != null ? `${r.latencyMs}ms` : "\u2014";
|
|
2900
|
+
const cost = formatCost2(r.cost);
|
|
2901
|
+
const payment = formatPayment(r.payment);
|
|
2696
2902
|
console.log(
|
|
2697
|
-
`[${mark}] ${r.uid} ${r.capabilitySlug} status=${status} ${latency} ${when}`
|
|
2903
|
+
`[${mark}] ${r.uid} ${r.capabilitySlug} status=${status} ${latency} ${cost} ${payment} ${when}`
|
|
2698
2904
|
);
|
|
2699
2905
|
}
|
|
2700
2906
|
if (result.nextCursor) {
|
|
@@ -2710,6 +2916,7 @@ Examples:
|
|
|
2710
2916
|
|
|
2711
2917
|
// src/commands/search-command.ts
|
|
2712
2918
|
import { Command as Command8 } from "commander";
|
|
2919
|
+
var DEFAULT_MAX_COST_USD = "30";
|
|
2713
2920
|
var formatReviewCount2 = (count) => {
|
|
2714
2921
|
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
2715
2922
|
return count.toString();
|
|
@@ -2747,7 +2954,10 @@ var formatSearchResults = (results) => {
|
|
|
2747
2954
|
"${displayDescription}"`;
|
|
2748
2955
|
}).join("\n");
|
|
2749
2956
|
};
|
|
2750
|
-
var searchCommand = (appContext) => new Command8("search").description("Search for capabilities").argument("<query>", "Search query").option("--json", "Output raw JSON to stdout").option("--offset <n>", "Pagination offset", Number).option("--limit <n>", "Results per page", Number).option("--free", "Only show free capabilities").option(
|
|
2957
|
+
var searchCommand = (appContext) => new Command8("search").description("Search for capabilities").argument("<query>", "Search query").option("--json", "Output raw JSON to stdout").option("--offset <n>", "Pagination offset", Number).option("--limit <n>", "Results per page", Number).option("--free", "Only show free capabilities").option(
|
|
2958
|
+
"--max-cost <amount>",
|
|
2959
|
+
`Maximum cost per call in USD (default: ${DEFAULT_MAX_COST_USD})`
|
|
2960
|
+
).option("--min-rating <stars>", "Minimum star rating (1-5)", Number).option("--protocol <protocol>", "Payment protocol (x402 or mpp)").option("--min-trust <n>", "Minimum trust score (0-100)", Number).option(
|
|
2751
2961
|
"--status <status>",
|
|
2752
2962
|
"Filter by availability (healthy, degraded, down)"
|
|
2753
2963
|
).option("--all", "Show all results (no trust or health filtering)").option(
|
|
@@ -2770,6 +2980,12 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2770
2980
|
process.exitCode = 1;
|
|
2771
2981
|
return;
|
|
2772
2982
|
}
|
|
2983
|
+
let effectiveMaxCost = options.maxCost;
|
|
2984
|
+
let appliedDefaultMaxCost = false;
|
|
2985
|
+
if (effectiveMaxCost === void 0 && !options.free) {
|
|
2986
|
+
effectiveMaxCost = DEFAULT_MAX_COST_USD;
|
|
2987
|
+
appliedDefaultMaxCost = true;
|
|
2988
|
+
}
|
|
2773
2989
|
const validStatuses = ["healthy", "degraded", "down"];
|
|
2774
2990
|
if (options.status && !validStatuses.includes(options.status)) {
|
|
2775
2991
|
console.error(
|
|
@@ -2783,7 +2999,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2783
2999
|
offset: options.offset,
|
|
2784
3000
|
limit: options.limit,
|
|
2785
3001
|
freeOnly: options.free,
|
|
2786
|
-
maxCost:
|
|
3002
|
+
maxCost: effectiveMaxCost,
|
|
2787
3003
|
minRating: options.minRating,
|
|
2788
3004
|
protocol: options.protocol,
|
|
2789
3005
|
minTrust: options.minTrust,
|
|
@@ -2802,7 +3018,8 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2802
3018
|
offset: options.offset,
|
|
2803
3019
|
limit: options.limit,
|
|
2804
3020
|
freeOnly: options.free ?? false,
|
|
2805
|
-
maxCost:
|
|
3021
|
+
maxCost: effectiveMaxCost,
|
|
3022
|
+
maxCostDefaulted: appliedDefaultMaxCost,
|
|
2806
3023
|
minRating: options.minRating,
|
|
2807
3024
|
protocol: options.protocol,
|
|
2808
3025
|
minTrust: options.minTrust,
|
|
@@ -2813,7 +3030,17 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2813
3030
|
json: options.json ?? false
|
|
2814
3031
|
});
|
|
2815
3032
|
if (options.json) {
|
|
2816
|
-
console.log(
|
|
3033
|
+
console.log(
|
|
3034
|
+
JSON.stringify(
|
|
3035
|
+
{
|
|
3036
|
+
...result,
|
|
3037
|
+
effectiveMaxCost: effectiveMaxCost ?? null,
|
|
3038
|
+
maxCostDefaulted: appliedDefaultMaxCost
|
|
3039
|
+
},
|
|
3040
|
+
null,
|
|
3041
|
+
2
|
|
3042
|
+
)
|
|
3043
|
+
);
|
|
2817
3044
|
return;
|
|
2818
3045
|
}
|
|
2819
3046
|
stateService.saveLastSearch({
|
|
@@ -2822,6 +3049,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2822
3049
|
position: c.position,
|
|
2823
3050
|
id: c.id,
|
|
2824
3051
|
url: c.url,
|
|
3052
|
+
urlTemplate: c.urlTemplate ?? null,
|
|
2825
3053
|
displayCostAmount: c.cost.amount
|
|
2826
3054
|
}))
|
|
2827
3055
|
});
|
|
@@ -3196,7 +3424,11 @@ var AnalyticsService = class {
|
|
|
3196
3424
|
this.posthog = new PostHog(POSTHOG_API_KEY, {
|
|
3197
3425
|
host: POSTHOG_HOST,
|
|
3198
3426
|
flushAt: 1,
|
|
3199
|
-
flushInterval: 0
|
|
3427
|
+
flushInterval: 0,
|
|
3428
|
+
// Vitest spins up many AnalyticsService instances per process; each
|
|
3429
|
+
// autocapture listener adds to process.{uncaughtException,unhandledRejection}
|
|
3430
|
+
// and trips Node's MaxListeners warning. Real CLI is one instance per process.
|
|
3431
|
+
enableExceptionAutocapture: !process.env.VITEST
|
|
3200
3432
|
});
|
|
3201
3433
|
this.posthog.on("error", () => {
|
|
3202
3434
|
});
|
|
@@ -3255,6 +3487,22 @@ var AnalyticsService = class {
|
|
|
3255
3487
|
}
|
|
3256
3488
|
});
|
|
3257
3489
|
}
|
|
3490
|
+
captureException(error, properties) {
|
|
3491
|
+
if (!this.posthog) return;
|
|
3492
|
+
this.posthog.captureException(error, this.distinctId, {
|
|
3493
|
+
source: "cli",
|
|
3494
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3495
|
+
cli_version: this.cliVersion,
|
|
3496
|
+
environment: this.environment,
|
|
3497
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3498
|
+
wallet_address: this.walletAddress,
|
|
3499
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3500
|
+
request_id: this.requestId,
|
|
3501
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3502
|
+
agent_host: this.agentHost,
|
|
3503
|
+
...properties
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3258
3506
|
async shutdown() {
|
|
3259
3507
|
if (!this.posthog) return;
|
|
3260
3508
|
try {
|
|
@@ -3267,15 +3515,27 @@ var AnalyticsService = class {
|
|
|
3267
3515
|
// src/services/state-service.ts
|
|
3268
3516
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
3269
3517
|
import { join as join5 } from "path";
|
|
3518
|
+
var RECENT_SEARCH_LIMIT = 10;
|
|
3270
3519
|
var StateService = class {
|
|
3271
3520
|
constructor(zeroDir) {
|
|
3272
3521
|
this.zeroDir = zeroDir;
|
|
3273
3522
|
this.lastSearchPath = join5(zeroDir, "last_search.json");
|
|
3523
|
+
this.recentSearchesPath = join5(zeroDir, "recent_searches.json");
|
|
3274
3524
|
}
|
|
3275
3525
|
lastSearchPath;
|
|
3526
|
+
recentSearchesPath;
|
|
3276
3527
|
saveLastSearch = (data) => {
|
|
3277
3528
|
mkdirSync5(this.zeroDir, { recursive: true });
|
|
3278
3529
|
writeFileSync5(this.lastSearchPath, JSON.stringify(data, null, 2));
|
|
3530
|
+
const recent = this.loadRecentSearches();
|
|
3531
|
+
const filtered = recent.searches.filter(
|
|
3532
|
+
(s) => s.searchId !== data.searchId
|
|
3533
|
+
);
|
|
3534
|
+
const next = [data, ...filtered].slice(0, RECENT_SEARCH_LIMIT);
|
|
3535
|
+
writeFileSync5(
|
|
3536
|
+
this.recentSearchesPath,
|
|
3537
|
+
JSON.stringify({ searches: next }, null, 2)
|
|
3538
|
+
);
|
|
3279
3539
|
};
|
|
3280
3540
|
loadLastSearch = () => {
|
|
3281
3541
|
try {
|
|
@@ -3286,6 +3546,63 @@ var StateService = class {
|
|
|
3286
3546
|
return null;
|
|
3287
3547
|
}
|
|
3288
3548
|
};
|
|
3549
|
+
loadRecentSearches = () => {
|
|
3550
|
+
try {
|
|
3551
|
+
if (!existsSync6(this.recentSearchesPath)) {
|
|
3552
|
+
const last = this.loadLastSearch();
|
|
3553
|
+
return { searches: last ? [last] : [] };
|
|
3554
|
+
}
|
|
3555
|
+
const raw = readFileSync9(this.recentSearchesPath, "utf8");
|
|
3556
|
+
const parsed = JSON.parse(raw);
|
|
3557
|
+
return { searches: parsed.searches ?? [] };
|
|
3558
|
+
} catch {
|
|
3559
|
+
return { searches: [] };
|
|
3560
|
+
}
|
|
3561
|
+
};
|
|
3562
|
+
// Walk recent searches newest-first, returning the first one whose
|
|
3563
|
+
// results contain `capabilityId` (uid or slug match). Preferred over
|
|
3564
|
+
// "loadLastSearch" for attributing rank — handles the parallel-search
|
|
3565
|
+
// case where the most recent search isn't the one being fetched.
|
|
3566
|
+
findSearchContextByCapability = (capabilityId) => {
|
|
3567
|
+
const recent = this.loadRecentSearches();
|
|
3568
|
+
for (const search of recent.searches) {
|
|
3569
|
+
const entry = search.capabilities.find((c) => c.id === capabilityId);
|
|
3570
|
+
if (entry) {
|
|
3571
|
+
return {
|
|
3572
|
+
searchId: search.searchId,
|
|
3573
|
+
resultRank: entry.position,
|
|
3574
|
+
capabilityId: entry.id,
|
|
3575
|
+
url: entry.url,
|
|
3576
|
+
displayCostAmount: entry.displayCostAmount
|
|
3577
|
+
};
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
return null;
|
|
3581
|
+
};
|
|
3582
|
+
// URL-based attribution for `zero fetch <url>`. Matches by URL prefix
|
|
3583
|
+
// (some capabilities accept query params or path tails the agent appends)
|
|
3584
|
+
// across the recent ring, newest match wins. Falls back to a caller-
|
|
3585
|
+
// supplied urlTemplate matcher (ADS-509 path-parameterized URLs).
|
|
3586
|
+
findSearchContextByUrl = (url, options) => {
|
|
3587
|
+
const recent = this.loadRecentSearches();
|
|
3588
|
+
for (const search of recent.searches) {
|
|
3589
|
+
const prefixHit = search.capabilities.find((c) => url.startsWith(c.url));
|
|
3590
|
+
const templateHit = prefixHit ?? search.capabilities.find(
|
|
3591
|
+
(c) => c.urlTemplate && options?.matchTemplate ? options.matchTemplate(c.urlTemplate) : false
|
|
3592
|
+
);
|
|
3593
|
+
const entry = templateHit;
|
|
3594
|
+
if (entry) {
|
|
3595
|
+
return {
|
|
3596
|
+
searchId: search.searchId,
|
|
3597
|
+
resultRank: entry.position,
|
|
3598
|
+
capabilityId: entry.id,
|
|
3599
|
+
url: entry.url,
|
|
3600
|
+
displayCostAmount: entry.displayCostAmount
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
return null;
|
|
3605
|
+
};
|
|
3289
3606
|
};
|
|
3290
3607
|
|
|
3291
3608
|
// src/services/wallet-service.ts
|
|
@@ -3392,6 +3709,155 @@ var createAppContext = () => {
|
|
|
3392
3709
|
};
|
|
3393
3710
|
};
|
|
3394
3711
|
|
|
3712
|
+
// src/util/update-check.ts
|
|
3713
|
+
import {
|
|
3714
|
+
existsSync as existsSync8,
|
|
3715
|
+
lstatSync,
|
|
3716
|
+
mkdirSync as mkdirSync6,
|
|
3717
|
+
readFileSync as readFileSync11,
|
|
3718
|
+
readlinkSync,
|
|
3719
|
+
writeFileSync as writeFileSync6
|
|
3720
|
+
} from "fs";
|
|
3721
|
+
import { homedir as homedir6 } from "os";
|
|
3722
|
+
import { dirname as dirname3, join as join7, resolve } from "path";
|
|
3723
|
+
var CACHE_FILENAME = "update_check.json";
|
|
3724
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@zeroxyz/cli/latest";
|
|
3725
|
+
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
3726
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
3727
|
+
var emptyCache = {
|
|
3728
|
+
lastCheckedMs: 0,
|
|
3729
|
+
latestVersion: null,
|
|
3730
|
+
lastShownMs: 0
|
|
3731
|
+
};
|
|
3732
|
+
var resolveExecPath = (execPath) => {
|
|
3733
|
+
try {
|
|
3734
|
+
const stat = lstatSync(execPath);
|
|
3735
|
+
if (stat.isSymbolicLink()) {
|
|
3736
|
+
const target = readlinkSync(execPath);
|
|
3737
|
+
return resolve(dirname3(execPath), target);
|
|
3738
|
+
}
|
|
3739
|
+
} catch {
|
|
3740
|
+
}
|
|
3741
|
+
return execPath;
|
|
3742
|
+
};
|
|
3743
|
+
var detectInstallMethod = (opts = {}) => {
|
|
3744
|
+
const execPath = opts.execPath ?? process.execPath;
|
|
3745
|
+
const pkg = opts.pkg ?? process.pkg;
|
|
3746
|
+
const home = opts.home ?? homedir6();
|
|
3747
|
+
if (pkg) return "binary";
|
|
3748
|
+
const resolved = resolveExecPath(execPath);
|
|
3749
|
+
const zeroBin = join7(home, ".zero", "bin");
|
|
3750
|
+
if (resolved.startsWith(zeroBin)) return "binary";
|
|
3751
|
+
return "npm";
|
|
3752
|
+
};
|
|
3753
|
+
var compareVersions = (a, b) => {
|
|
3754
|
+
const parse = (v) => {
|
|
3755
|
+
const dashIdx = v.indexOf("-");
|
|
3756
|
+
const base2 = dashIdx === -1 ? v : v.slice(0, dashIdx);
|
|
3757
|
+
const pre = dashIdx === -1 ? null : v.slice(dashIdx + 1);
|
|
3758
|
+
const nums = base2.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
3759
|
+
while (nums.length < 3) nums.push(0);
|
|
3760
|
+
return { nums, pre };
|
|
3761
|
+
};
|
|
3762
|
+
const pa = parse(a);
|
|
3763
|
+
const pb = parse(b);
|
|
3764
|
+
for (let i = 0; i < 3; i++) {
|
|
3765
|
+
const na = pa.nums[i] ?? 0;
|
|
3766
|
+
const nb = pb.nums[i] ?? 0;
|
|
3767
|
+
if (na !== nb) return na - nb;
|
|
3768
|
+
}
|
|
3769
|
+
if (pa.pre === pb.pre) return 0;
|
|
3770
|
+
if (pa.pre === null) return 1;
|
|
3771
|
+
if (pb.pre === null) return -1;
|
|
3772
|
+
return pa.pre < pb.pre ? -1 : 1;
|
|
3773
|
+
};
|
|
3774
|
+
var cachePath = (zeroDir) => join7(zeroDir, CACHE_FILENAME);
|
|
3775
|
+
var readCache = (zeroDir) => {
|
|
3776
|
+
try {
|
|
3777
|
+
const path = cachePath(zeroDir);
|
|
3778
|
+
if (!existsSync8(path)) return emptyCache;
|
|
3779
|
+
const raw = readFileSync11(path, "utf8");
|
|
3780
|
+
const parsed = JSON.parse(raw);
|
|
3781
|
+
return {
|
|
3782
|
+
lastCheckedMs: typeof parsed.lastCheckedMs === "number" ? parsed.lastCheckedMs : 0,
|
|
3783
|
+
latestVersion: typeof parsed.latestVersion === "string" ? parsed.latestVersion : null,
|
|
3784
|
+
lastShownMs: typeof parsed.lastShownMs === "number" ? parsed.lastShownMs : 0
|
|
3785
|
+
};
|
|
3786
|
+
} catch {
|
|
3787
|
+
return emptyCache;
|
|
3788
|
+
}
|
|
3789
|
+
};
|
|
3790
|
+
var writeCache = (zeroDir, cache) => {
|
|
3791
|
+
try {
|
|
3792
|
+
mkdirSync6(zeroDir, { recursive: true });
|
|
3793
|
+
writeFileSync6(cachePath(zeroDir), JSON.stringify(cache, null, 2));
|
|
3794
|
+
} catch {
|
|
3795
|
+
}
|
|
3796
|
+
};
|
|
3797
|
+
var fetchLatestVersion = async (url = NPM_REGISTRY_URL) => {
|
|
3798
|
+
try {
|
|
3799
|
+
const controller = new AbortController();
|
|
3800
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
3801
|
+
const response = await fetch(url, {
|
|
3802
|
+
signal: controller.signal,
|
|
3803
|
+
headers: { accept: "application/json" }
|
|
3804
|
+
});
|
|
3805
|
+
clearTimeout(timeout);
|
|
3806
|
+
if (!response.ok) return null;
|
|
3807
|
+
const json = await response.json();
|
|
3808
|
+
return typeof json.version === "string" ? json.version : null;
|
|
3809
|
+
} catch {
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
};
|
|
3813
|
+
var refreshUpdateCache = async (zeroDir, opts = {}) => {
|
|
3814
|
+
const now = opts.now ?? Date.now();
|
|
3815
|
+
const fetcher = opts.fetchLatest ?? fetchLatestVersion;
|
|
3816
|
+
const cache = readCache(zeroDir);
|
|
3817
|
+
if (cache.lastCheckedMs > 0 && now - cache.lastCheckedMs < CHECK_INTERVAL_MS) {
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
const latest = await fetcher();
|
|
3821
|
+
if (!latest) {
|
|
3822
|
+
writeCache(zeroDir, { ...cache, lastCheckedMs: now });
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
writeCache(zeroDir, {
|
|
3826
|
+
...cache,
|
|
3827
|
+
lastCheckedMs: now,
|
|
3828
|
+
latestVersion: latest
|
|
3829
|
+
});
|
|
3830
|
+
};
|
|
3831
|
+
var updateCommandFor = (method) => method === "binary" ? "curl -fsSL https://zero.xyz/install.sh | bash" : "npm install -g @zeroxyz/cli";
|
|
3832
|
+
var formatBanner = (currentVersion, latestVersion, method) => {
|
|
3833
|
+
const arrow = `${currentVersion} \u2192 ${latestVersion}`;
|
|
3834
|
+
const cmd = updateCommandFor(method);
|
|
3835
|
+
const lines = [
|
|
3836
|
+
"",
|
|
3837
|
+
` ${color.yellow("\u26A1 Update available")} ${color.dim(arrow)}`,
|
|
3838
|
+
` ${color.dim("Run:")} ${color.cyan(cmd)}`,
|
|
3839
|
+
""
|
|
3840
|
+
];
|
|
3841
|
+
return lines.join("\n");
|
|
3842
|
+
};
|
|
3843
|
+
var consumeBannerIfDue = (zeroDir, currentVersion, opts = {}) => {
|
|
3844
|
+
const now = opts.now ?? Date.now();
|
|
3845
|
+
const method = opts.method ?? detectInstallMethod();
|
|
3846
|
+
const cache = readCache(zeroDir);
|
|
3847
|
+
if (!cache.latestVersion) return null;
|
|
3848
|
+
if (compareVersions(currentVersion, cache.latestVersion) >= 0) return null;
|
|
3849
|
+
if (cache.lastShownMs > 0 && now - cache.lastShownMs < CHECK_INTERVAL_MS) {
|
|
3850
|
+
return null;
|
|
3851
|
+
}
|
|
3852
|
+
writeCache(zeroDir, { ...cache, lastShownMs: now });
|
|
3853
|
+
return formatBanner(currentVersion, cache.latestVersion, method);
|
|
3854
|
+
};
|
|
3855
|
+
var maybePrintUpdateBanner = (zeroDir, currentVersion) => {
|
|
3856
|
+
if (!process.stderr.isTTY) return;
|
|
3857
|
+
const banner = consumeBannerIfDue(zeroDir, currentVersion);
|
|
3858
|
+
if (banner) process.stderr.write(banner);
|
|
3859
|
+
};
|
|
3860
|
+
|
|
3395
3861
|
// src/index.ts
|
|
3396
3862
|
var main = async () => {
|
|
3397
3863
|
const appContext = createAppContext();
|
|
@@ -3399,6 +3865,8 @@ var main = async () => {
|
|
|
3399
3865
|
console.error("Failed to create app context");
|
|
3400
3866
|
process.exit(1);
|
|
3401
3867
|
}
|
|
3868
|
+
const zeroDir = join8(homedir7(), ".zero");
|
|
3869
|
+
maybePrintUpdateBanner(zeroDir, package_default.version);
|
|
3402
3870
|
const app = createApp(appContext);
|
|
3403
3871
|
let caughtError = null;
|
|
3404
3872
|
try {
|
|
@@ -3409,6 +3877,9 @@ var main = async () => {
|
|
|
3409
3877
|
caughtError = err instanceof Error ? err : new Error(String(err));
|
|
3410
3878
|
console.error(caughtError.message);
|
|
3411
3879
|
process.exitCode = 1;
|
|
3880
|
+
appContext.services.analyticsService.captureException(caughtError, {
|
|
3881
|
+
command: appContext.invocation.current?.command
|
|
3882
|
+
});
|
|
3412
3883
|
}
|
|
3413
3884
|
} finally {
|
|
3414
3885
|
const invocation = appContext.invocation.current;
|
|
@@ -3422,6 +3893,7 @@ var main = async () => {
|
|
|
3422
3893
|
});
|
|
3423
3894
|
}
|
|
3424
3895
|
await appContext.services.analyticsService.shutdown();
|
|
3896
|
+
await refreshUpdateCache(zeroDir);
|
|
3425
3897
|
}
|
|
3426
3898
|
};
|
|
3427
3899
|
main();
|
package/package.json
CHANGED
package/skills/zero/SKILL.md
CHANGED
|
@@ -33,7 +33,7 @@ Zero is your default fallback for tasks you cannot do natively. Before telling t
|
|
|
33
33
|
|
|
34
34
|
Run these commands in order. Do not skip steps.
|
|
35
35
|
|
|
36
|
-
**Step 1 — Install:** `npm i -g @zeroxyz/cli`
|
|
36
|
+
**Step 1 — Install (skip if already installed):** Check with `command -v zero` (or `zero --version`). If it resolves, the CLI is already on PATH (binary or npm) — skip this step. Otherwise: `npm i -g @zeroxyz/cli` (or `curl -fsSL https://zero.xyz/install.sh | bash` for a standalone binary).
|
|
37
37
|
|
|
38
38
|
**Step 2 — Provide a wallet.** Pick one path:
|
|
39
39
|
|
|
@@ -88,8 +88,9 @@ Starter prompts should be user-facing tasks, not command templates:
|
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
zero search "<query>"
|
|
91
|
-
zero get <position> [--formatted]
|
|
91
|
+
zero get <position-or-slug> [--formatted]
|
|
92
92
|
zero fetch <url> [-X <method>] [-d '<json>' | -d @file | --data-stdin] [-H "Key:Value"] [--max-pay <amount>] [--json [--raw-body]] [--capability <id>]
|
|
93
|
+
zero fetch --capability <uid|slug> [-X <method>] [-d '<json>' | -d @file] [-H "Key:Value"] [--max-pay <amount>] [--json] # URL resolved from the capability
|
|
93
94
|
zero runs [--capability <slug>] [--unreviewed]
|
|
94
95
|
zero review <runId> --accuracy <1-5> --value <1-5> --reliability <1-5> [--content "<notes>"]
|
|
95
96
|
zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --reliability <1-5> [--content "<notes>"]
|
|
@@ -98,6 +99,8 @@ zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --relia
|
|
|
98
99
|
### Workflow
|
|
99
100
|
|
|
100
101
|
1. **Search** — `zero search "weather forecast"` finds matching capabilities. Results show name, cost, rating, and success rate.
|
|
102
|
+
> **Detail-page → CLI bridge.** When you only have a capability slug (e.g. copied from a zero.xyz capability page), `zero get <slug>` and `zero fetch --capability <slug>` both work as drop-in replacements for the position-based forms. You don't need to run `zero search` first.
|
|
103
|
+
|
|
101
104
|
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.
|
|
102
105
|
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).
|
|
103
106
|
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, and **pass `--content "<notes>"` whenever you have something specific to say** — the content line lands on the capability's public detail page on zero.xyz, so it's what the next human buyer (and the next agent) reads when deciding whether to call this capability. See "Writing review content" below.
|