@zeroxyz/cli 0.0.34 → 0.0.37
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 +433 -105
- package/package.json +3 -3
- package/skills/zero/SKILL.md +9 -2
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Search for capabilities by free-text query.
|
|
|
46
46
|
zero search "image classification"
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Results are numbered. Use `zero get <number>` to view details.
|
|
49
|
+
Results are numbered and include public rating/review context. Use `zero get <number>` to view details.
|
|
50
50
|
|
|
51
51
|
**Cost filtering.** By default, results are filtered to capabilities priced **≤ $30/call** as a wallet-safety cap. Override per-call:
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ zero search "expensive deep research" --max-cost 100 # raise the cap for hard ta
|
|
|
56
56
|
zero search "image classification" --free # only free capabilities
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Other useful filters: `--
|
|
59
|
+
Other useful filters: `--protocol x402|mpp`, `--status healthy|degraded|down`, `--source <name>`, `--all` (disables default quality filtering).
|
|
60
60
|
|
|
61
61
|
### `zero get <position>`
|
|
62
62
|
|
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.37",
|
|
10
11
|
type: "module",
|
|
11
12
|
bin: {
|
|
12
13
|
zero: "dist/index.js",
|
|
@@ -22,7 +23,7 @@ var package_default = {
|
|
|
22
23
|
},
|
|
23
24
|
scripts: {
|
|
24
25
|
build: "tsup src/index.ts --format esm --out-dir dist --clean",
|
|
25
|
-
"build:binary": "tsup --config tsup.binary.ts && cp -r skills hooks dist/pkg/ && pnpm exec pkg dist/pkg/index.cjs --config pkg.json --targets node24-macos-arm64,node24-macos-x64,node24-linux-x64 --output dist/bin/zero",
|
|
26
|
+
"build:binary": "tsup --config tsup.binary.ts && cp -r skills hooks dist/pkg/ && pnpm exec pkg dist/pkg/index.cjs --config pkg.json --targets node24-macos-arm64,node24-macos-x64,node24-linux-x64,node24-linux-arm64 --output dist/bin/zero",
|
|
26
27
|
prepublishOnly: "pnpm run build",
|
|
27
28
|
dev: "ZERO_ENV=development tsx src/index.ts",
|
|
28
29
|
cli: "ZERO_ENV=development ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
@@ -40,7 +41,7 @@ var package_default = {
|
|
|
40
41
|
"@x402/extensions": "^2.9.0",
|
|
41
42
|
"@x402/fetch": "^2.9.0",
|
|
42
43
|
commander: "^13.0.0",
|
|
43
|
-
mppx: "^0.
|
|
44
|
+
mppx: "^0.6.9",
|
|
44
45
|
open: "^11.0.0",
|
|
45
46
|
"posthog-node": "^5.29.2",
|
|
46
47
|
viem: "^2.47.10",
|
|
@@ -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";
|
|
@@ -68,6 +72,13 @@ import { z as z2 } from "zod";
|
|
|
68
72
|
// src/services/api-service.ts
|
|
69
73
|
import { createHash } from "crypto";
|
|
70
74
|
import z from "zod";
|
|
75
|
+
var ratingSchema = z.object({
|
|
76
|
+
score: z.string(),
|
|
77
|
+
successRate: z.string(),
|
|
78
|
+
reviews: z.number(),
|
|
79
|
+
stars: z.string().nullable().optional(),
|
|
80
|
+
state: z.enum(["unrated", "rated"]).optional()
|
|
81
|
+
});
|
|
71
82
|
var searchResultSchema = z.object({
|
|
72
83
|
id: z.string(),
|
|
73
84
|
position: z.number(),
|
|
@@ -77,18 +88,12 @@ var searchResultSchema = z.object({
|
|
|
77
88
|
description: z.string(),
|
|
78
89
|
whatItDoes: z.string().nullable().optional(),
|
|
79
90
|
url: z.string(),
|
|
91
|
+
urlTemplate: z.string().nullable().optional(),
|
|
80
92
|
cost: z.object({ amount: z.string(), asset: z.string() }),
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
successRate: z.string(),
|
|
84
|
-
reviews: z.number(),
|
|
85
|
-
stars: z.string().nullable().optional(),
|
|
86
|
-
state: z.enum(["unrated", "rated"]).optional()
|
|
87
|
-
}),
|
|
88
|
-
trustScore: z.number().nullable().optional(),
|
|
89
|
-
trustSignalCount: z.number().optional(),
|
|
93
|
+
reviewCount: z.number().optional(),
|
|
94
|
+
rating: ratingSchema,
|
|
90
95
|
availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
|
|
91
|
-
|
|
96
|
+
displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional()
|
|
92
97
|
});
|
|
93
98
|
var searchResponseSchema = z.object({
|
|
94
99
|
searchId: 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(),
|
|
@@ -111,13 +117,8 @@ var capabilityResponseSchema = z.object({
|
|
|
111
117
|
tags: z.array(z.string()).nullable(),
|
|
112
118
|
displayCostAmount: z.string(),
|
|
113
119
|
displayCostAsset: z.string(),
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
successRate: z.string(),
|
|
117
|
-
reviews: z.number(),
|
|
118
|
-
stars: z.string().nullable().optional(),
|
|
119
|
-
state: z.enum(["unrated", "rated"]).optional()
|
|
120
|
-
}),
|
|
120
|
+
reviewCount: z.number(),
|
|
121
|
+
rating: ratingSchema,
|
|
121
122
|
priceObserved: z.object({
|
|
122
123
|
minCents: z.string().nullable(),
|
|
123
124
|
medianCents: z.string().nullable(),
|
|
@@ -140,13 +141,8 @@ var capabilityResponseSchema = z.object({
|
|
|
140
141
|
priority: z.number()
|
|
141
142
|
})
|
|
142
143
|
).nullable(),
|
|
143
|
-
trustScore: z.number().nullable().optional(),
|
|
144
|
-
trustComponents: z.object({
|
|
145
|
-
apiQuality: z.number().nullable(),
|
|
146
|
-
blockchainActivity: z.number().nullable(),
|
|
147
|
-
performance: z.number().nullable()
|
|
148
|
-
}).nullable().optional(),
|
|
149
144
|
availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
|
|
145
|
+
displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
|
|
150
146
|
activationCount: z.number().optional(),
|
|
151
147
|
lastUsedAt: z.string().nullable().optional(),
|
|
152
148
|
lastSuccessfullyRanAt: z.string().nullable().optional()
|
|
@@ -617,6 +613,7 @@ var configCommand = (_appContext) => new Command2("config").description("View or
|
|
|
617
613
|
});
|
|
618
614
|
|
|
619
615
|
// src/commands/fetch-command.ts
|
|
616
|
+
import { createHash as createHash3 } from "crypto";
|
|
620
617
|
import { readFileSync as readFileSync3 } from "fs";
|
|
621
618
|
import { resolve as resolvePath } from "path";
|
|
622
619
|
import { Command as Command3 } from "commander";
|
|
@@ -740,6 +737,7 @@ var pickSessionCloseAmount = (receipt, openTimeCumulative) => {
|
|
|
740
737
|
const accepted = BigInt(receipt.acceptedCumulative);
|
|
741
738
|
const spent = BigInt(receipt.spent);
|
|
742
739
|
const fromReceipt = accepted > spent ? accepted : spent;
|
|
740
|
+
if (receipt.metered === true) return fromReceipt;
|
|
743
741
|
return fromReceipt > openTimeCumulative ? fromReceipt : openTimeCumulative;
|
|
744
742
|
};
|
|
745
743
|
var PaymentService = class {
|
|
@@ -1206,6 +1204,22 @@ var truncateQuery = (raw) => raw.length > QUERY_MAX ? `${raw.slice(0, QUERY_MAX)
|
|
|
1206
1204
|
var truncateError = (raw) => raw.length > ERROR_MAX ? `${raw.slice(0, ERROR_MAX)}\u2026` : raw;
|
|
1207
1205
|
|
|
1208
1206
|
// src/commands/fetch-command.ts
|
|
1207
|
+
var sniffJsonShape = (buf) => {
|
|
1208
|
+
let i = 0;
|
|
1209
|
+
if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
|
|
1210
|
+
i = 3;
|
|
1211
|
+
}
|
|
1212
|
+
while (i < buf.length && (buf[i] === 32 || buf[i] === 9 || buf[i] === 10 || buf[i] === 13)) {
|
|
1213
|
+
i++;
|
|
1214
|
+
}
|
|
1215
|
+
if (i >= buf.length) return false;
|
|
1216
|
+
return buf[i] === 123 || buf[i] === 91;
|
|
1217
|
+
};
|
|
1218
|
+
var urlMatchesTemplate = (url, template) => {
|
|
1219
|
+
const escaped = template.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1220
|
+
const withCaptures = escaped.replace(/\\\{[^/\\}]+\\\}/g, "[^/]+");
|
|
1221
|
+
return new RegExp(`^${withCaptures}$`).test(url);
|
|
1222
|
+
};
|
|
1209
1223
|
var isTextContentType = (contentType) => {
|
|
1210
1224
|
if (!contentType) return true;
|
|
1211
1225
|
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
@@ -1325,6 +1339,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1325
1339
|
const startTime = Date.now();
|
|
1326
1340
|
let resolvedUrl;
|
|
1327
1341
|
let resolvedMethodFromCapability;
|
|
1342
|
+
let resolvedCapabilityName;
|
|
1328
1343
|
if (!url) {
|
|
1329
1344
|
if (!options.capability) {
|
|
1330
1345
|
console.error(
|
|
@@ -1337,6 +1352,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1337
1352
|
const cap = await apiService.getCapability(options.capability);
|
|
1338
1353
|
resolvedUrl = cap.url;
|
|
1339
1354
|
resolvedMethodFromCapability = cap.method;
|
|
1355
|
+
resolvedCapabilityName = cap.name;
|
|
1340
1356
|
} catch (err) {
|
|
1341
1357
|
console.error(
|
|
1342
1358
|
`Failed to resolve --capability ${options.capability}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1347,6 +1363,47 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1347
1363
|
} else {
|
|
1348
1364
|
resolvedUrl = url;
|
|
1349
1365
|
}
|
|
1366
|
+
if (!url && options.capability && !stateService.findSearchContextByCapability(options.capability)) {
|
|
1367
|
+
try {
|
|
1368
|
+
let capName = resolvedCapabilityName;
|
|
1369
|
+
if (capName === void 0) {
|
|
1370
|
+
const cap = await apiService.getCapability(options.capability);
|
|
1371
|
+
capName = cap.name;
|
|
1372
|
+
}
|
|
1373
|
+
const searchResult = await apiService.search({ query: capName });
|
|
1374
|
+
const slugFoundInResults = searchResult.capabilities.some(
|
|
1375
|
+
(c) => c.slug === options.capability || c.id === options.capability
|
|
1376
|
+
);
|
|
1377
|
+
stateService.saveLastSearch({
|
|
1378
|
+
searchId: searchResult.searchId,
|
|
1379
|
+
capabilities: searchResult.capabilities.map((c) => ({
|
|
1380
|
+
position: c.position,
|
|
1381
|
+
id: c.id,
|
|
1382
|
+
url: c.url,
|
|
1383
|
+
urlTemplate: c.urlTemplate ?? null,
|
|
1384
|
+
displayCostAmount: c.cost.amount
|
|
1385
|
+
}))
|
|
1386
|
+
});
|
|
1387
|
+
analyticsService.capture("search_executed", {
|
|
1388
|
+
query: truncateQuery(capName),
|
|
1389
|
+
queryLength: capName.length,
|
|
1390
|
+
resultCount: searchResult.capabilities.length,
|
|
1391
|
+
searchId: searchResult.searchId,
|
|
1392
|
+
total: searchResult.total,
|
|
1393
|
+
hasMore: searchResult.hasMore,
|
|
1394
|
+
json: false,
|
|
1395
|
+
triggeredBy: "slug_handoff",
|
|
1396
|
+
slugFoundInResults
|
|
1397
|
+
});
|
|
1398
|
+
analyticsService.capture("capability_viewed", {
|
|
1399
|
+
capabilityId: options.capability,
|
|
1400
|
+
fromLastSearch: false,
|
|
1401
|
+
searchId: searchResult.searchId,
|
|
1402
|
+
triggeredBy: "slug_handoff"
|
|
1403
|
+
});
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1350
1407
|
let resolvedBody;
|
|
1351
1408
|
try {
|
|
1352
1409
|
resolvedBody = resolveRequestBody(
|
|
@@ -1354,9 +1411,13 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1354
1411
|
options.dataStdin ?? false
|
|
1355
1412
|
);
|
|
1356
1413
|
} catch (err) {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1414
|
+
const message = err instanceof Error ? err.message : "Failed to read request body";
|
|
1415
|
+
analyticsService.capture("fetch_error", {
|
|
1416
|
+
cliErrorClass: /exceeds the .* byte limit/.test(message) ? "payload_too_large" : "schema_validation_failed",
|
|
1417
|
+
url: redactUrl(resolvedUrl),
|
|
1418
|
+
error: truncateError(message)
|
|
1419
|
+
});
|
|
1420
|
+
console.error(message);
|
|
1360
1421
|
process.exitCode = 1;
|
|
1361
1422
|
return;
|
|
1362
1423
|
}
|
|
@@ -1373,7 +1434,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1373
1434
|
(k) => k.toLowerCase() === "content-type"
|
|
1374
1435
|
);
|
|
1375
1436
|
if (resolvedBody && !hasContentType) {
|
|
1376
|
-
headers["content-type"] = Buffer.isBuffer(resolvedBody) ? "application/octet-stream" : "application/json";
|
|
1437
|
+
headers["content-type"] = Buffer.isBuffer(resolvedBody) ? sniffJsonShape(resolvedBody) ? "application/json" : "application/octet-stream" : "application/json";
|
|
1377
1438
|
}
|
|
1378
1439
|
const log = (msg) => console.error(` ${msg}`);
|
|
1379
1440
|
const method = options.method ? options.method.toUpperCase() : resolvedMethodFromCapability && !resolvedBody ? resolvedMethodFromCapability.toUpperCase() : resolvedBody ? "POST" : "GET";
|
|
@@ -1382,12 +1443,21 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1382
1443
|
headers,
|
|
1383
1444
|
body: resolvedBody
|
|
1384
1445
|
};
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1387
|
-
(
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1446
|
+
const explicitCapabilityCtx = options.capability ? stateService.findSearchContextByCapability(options.capability) : null;
|
|
1447
|
+
const urlCtx = stateService.findSearchContextByUrl(resolvedUrl, {
|
|
1448
|
+
matchTemplate: (template) => {
|
|
1449
|
+
try {
|
|
1450
|
+
return urlMatchesTemplate(resolvedUrl, template);
|
|
1451
|
+
} catch {
|
|
1452
|
+
return false;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
const matchCtx = explicitCapabilityCtx ?? urlCtx;
|
|
1457
|
+
const capabilityId = options.capability ?? matchCtx?.capabilityId ?? null;
|
|
1458
|
+
const searchId = matchCtx?.searchId;
|
|
1459
|
+
const resultRank = matchCtx?.resultRank;
|
|
1460
|
+
const matchedDisplayCostAmount = matchCtx?.displayCostAmount;
|
|
1391
1461
|
const skipReasons = [];
|
|
1392
1462
|
if (!apiService.walletAddress) {
|
|
1393
1463
|
skipReasons.push(
|
|
@@ -1398,6 +1468,19 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1398
1468
|
skipReasons.push(
|
|
1399
1469
|
"no capability resolved \u2014 pass --capability <uid|slug> or run `zero search` first so the URL can be matched"
|
|
1400
1470
|
);
|
|
1471
|
+
if (!options.capability) {
|
|
1472
|
+
const lastSearch = stateService.loadLastSearch();
|
|
1473
|
+
const cached = lastSearch?.capabilities ?? [];
|
|
1474
|
+
analyticsService.capture("capability_resolution_missed", {
|
|
1475
|
+
url: redactUrl(resolvedUrl),
|
|
1476
|
+
method,
|
|
1477
|
+
hasLastSearch: lastSearch !== null,
|
|
1478
|
+
lastSearchSize: cached.length,
|
|
1479
|
+
lastSearchHasTemplates: cached.some(
|
|
1480
|
+
(c) => Boolean(c.urlTemplate)
|
|
1481
|
+
)
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1401
1484
|
}
|
|
1402
1485
|
let finalResponse;
|
|
1403
1486
|
let bodyBytes;
|
|
@@ -1420,7 +1503,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1420
1503
|
paymentReq,
|
|
1421
1504
|
options.maxPay,
|
|
1422
1505
|
log,
|
|
1423
|
-
|
|
1506
|
+
matchedDisplayCostAmount
|
|
1424
1507
|
);
|
|
1425
1508
|
finalResponse = result.response;
|
|
1426
1509
|
paymentMeta = {
|
|
@@ -1517,6 +1600,15 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1517
1600
|
} catch {
|
|
1518
1601
|
}
|
|
1519
1602
|
}
|
|
1603
|
+
const status = finalResponse?.status;
|
|
1604
|
+
const isFailure = !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
1605
|
+
let errorSnippetHash;
|
|
1606
|
+
let errorSnippetLength;
|
|
1607
|
+
if (isFailure && body && !bodyIsBinary) {
|
|
1608
|
+
const snippet = body.slice(0, 500);
|
|
1609
|
+
errorSnippetHash = createHash3("sha256").update(snippet).digest("hex");
|
|
1610
|
+
errorSnippetLength = snippet.length;
|
|
1611
|
+
}
|
|
1520
1612
|
let runId = null;
|
|
1521
1613
|
if (capabilityId && apiService.walletAddress) {
|
|
1522
1614
|
let requestSchema;
|
|
@@ -1541,6 +1633,8 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1541
1633
|
latencyMs,
|
|
1542
1634
|
requestSchema,
|
|
1543
1635
|
responseSchema,
|
|
1636
|
+
errorSnippetHash,
|
|
1637
|
+
errorSnippetLength,
|
|
1544
1638
|
...paymentMeta && {
|
|
1545
1639
|
costAmount: paymentMeta.amount,
|
|
1546
1640
|
costAsset: paymentMeta.asset,
|
|
@@ -1557,7 +1651,6 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1557
1651
|
);
|
|
1558
1652
|
}
|
|
1559
1653
|
}
|
|
1560
|
-
const status = finalResponse?.status;
|
|
1561
1654
|
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1562
1655
|
analyticsService.capture("fetch_executed", {
|
|
1563
1656
|
url: redactUrl(resolvedUrl),
|
|
@@ -1569,10 +1662,26 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1569
1662
|
paymentAmount: paymentMeta?.amount,
|
|
1570
1663
|
capabilityId: capabilityId ?? void 0,
|
|
1571
1664
|
searchId: searchId ?? void 0,
|
|
1665
|
+
resultRank: resultRank ?? void 0,
|
|
1572
1666
|
runId: runId ?? void 0,
|
|
1573
1667
|
runTracked: !!runId,
|
|
1574
1668
|
...fetchError && { error: truncateError(fetchError.message) }
|
|
1575
1669
|
});
|
|
1670
|
+
const isFetchFailure = Boolean(fetchError) || !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
1671
|
+
if (isFetchFailure) {
|
|
1672
|
+
const cliErrorClass = fetchError || !finalResponse ? "network" : !apiService.walletAddress ? "auth_missing" : "unknown";
|
|
1673
|
+
analyticsService.capture("fetch_error", {
|
|
1674
|
+
cliErrorClass,
|
|
1675
|
+
capabilityId: capabilityId ?? void 0,
|
|
1676
|
+
searchId: searchId ?? void 0,
|
|
1677
|
+
resultRank: resultRank ?? void 0,
|
|
1678
|
+
url: redactUrl(resolvedUrl),
|
|
1679
|
+
error: truncateError(
|
|
1680
|
+
fetchError?.message ?? skipReasons.join("; ")
|
|
1681
|
+
),
|
|
1682
|
+
skippedRun: !runId && skipReasons.length > 0
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1576
1685
|
if (fetchError && !options.json) {
|
|
1577
1686
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1578
1687
|
}
|
|
@@ -1639,7 +1748,16 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1639
1748
|
process.exitCode = 1;
|
|
1640
1749
|
}
|
|
1641
1750
|
} catch (err) {
|
|
1642
|
-
|
|
1751
|
+
const message = err instanceof Error ? err.message : "Fetch failed";
|
|
1752
|
+
try {
|
|
1753
|
+
appContext.services.analyticsService.capture("fetch_error", {
|
|
1754
|
+
cliErrorClass: "unknown",
|
|
1755
|
+
url: url ? redactUrl(url) : "unknown",
|
|
1756
|
+
error: truncateError(message)
|
|
1757
|
+
});
|
|
1758
|
+
} catch {
|
|
1759
|
+
}
|
|
1760
|
+
console.error(message);
|
|
1643
1761
|
process.exitCode = 1;
|
|
1644
1762
|
}
|
|
1645
1763
|
}
|
|
@@ -1647,10 +1765,6 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1647
1765
|
|
|
1648
1766
|
// src/commands/get-command.ts
|
|
1649
1767
|
import { Command as Command4 } from "commander";
|
|
1650
|
-
var formatReviewCount = (count) => {
|
|
1651
|
-
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
1652
|
-
return count.toString();
|
|
1653
|
-
};
|
|
1654
1768
|
var formatRelativeTimestamp = (iso) => {
|
|
1655
1769
|
if (!iso) return "never";
|
|
1656
1770
|
const then = new Date(iso).getTime();
|
|
@@ -1667,16 +1781,6 @@ var formatRelativeTimestamp = (iso) => {
|
|
|
1667
1781
|
if (diffMo < 12) return `${diffMo}mo ago`;
|
|
1668
1782
|
return `${Math.round(diffMo / 12)}y ago`;
|
|
1669
1783
|
};
|
|
1670
|
-
var formatTrustScore = (capability) => {
|
|
1671
|
-
if (capability.trustScore != null) {
|
|
1672
|
-
return `Trust Score: ${capability.trustScore}/100`;
|
|
1673
|
-
}
|
|
1674
|
-
return "Trust Score: --";
|
|
1675
|
-
};
|
|
1676
|
-
var formatTrustComponent = (label, value) => {
|
|
1677
|
-
const display = value != null ? `${value}/100` : "--";
|
|
1678
|
-
return ` ${label.padEnd(22)}${display}`;
|
|
1679
|
-
};
|
|
1680
1784
|
var centsToDollars = (cents) => {
|
|
1681
1785
|
const value = Number.parseFloat(cents) / 100;
|
|
1682
1786
|
if (value < 0.01) return value.toFixed(4);
|
|
@@ -1701,6 +1805,10 @@ var formatCost = (capability) => {
|
|
|
1701
1805
|
}
|
|
1702
1806
|
return lines;
|
|
1703
1807
|
};
|
|
1808
|
+
var formatReviewCount = (count) => {
|
|
1809
|
+
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
1810
|
+
return count.toString();
|
|
1811
|
+
};
|
|
1704
1812
|
var formatRating = (rating) => {
|
|
1705
1813
|
if (rating.state === "unrated") return "unrated";
|
|
1706
1814
|
const successPct = `${Math.round(Number.parseFloat(rating.successRate) * 100)}%`;
|
|
@@ -1787,29 +1895,8 @@ var buildTryItExample = (capability) => {
|
|
|
1787
1895
|
var formatCapability = (capability) => {
|
|
1788
1896
|
const lines = [];
|
|
1789
1897
|
lines.push(capability.name);
|
|
1790
|
-
lines.push(` ${formatTrustScore(capability)}`);
|
|
1791
|
-
if (capability.trustComponents) {
|
|
1792
|
-
lines.push(
|
|
1793
|
-
formatTrustComponent(
|
|
1794
|
-
"API Quality:",
|
|
1795
|
-
capability.trustComponents.apiQuality
|
|
1796
|
-
)
|
|
1797
|
-
);
|
|
1798
|
-
lines.push(
|
|
1799
|
-
formatTrustComponent(
|
|
1800
|
-
"Blockchain Activity:",
|
|
1801
|
-
capability.trustComponents.blockchainActivity
|
|
1802
|
-
)
|
|
1803
|
-
);
|
|
1804
|
-
lines.push(
|
|
1805
|
-
formatTrustComponent(
|
|
1806
|
-
"Performance:",
|
|
1807
|
-
capability.trustComponents.performance
|
|
1808
|
-
)
|
|
1809
|
-
);
|
|
1810
|
-
}
|
|
1811
1898
|
lines.push(` Rating: ${formatRating(capability.rating)}`);
|
|
1812
|
-
lines.push(` Status: ${capability.
|
|
1899
|
+
lines.push(` Status: ${capability.displayStatus ?? "unknown"}`);
|
|
1813
1900
|
lines.push(...formatCost(capability));
|
|
1814
1901
|
lines.push(` URL: ${capability.url}`);
|
|
1815
1902
|
lines.push(` Method: ${capability.method}`);
|
|
@@ -1824,7 +1911,7 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1824
1911
|
).argument(
|
|
1825
1912
|
"<identifier>",
|
|
1826
1913
|
"Position number from search results, or a capability slug"
|
|
1827
|
-
).option("--formatted", "Output formatted
|
|
1914
|
+
).option("--formatted", "Output formatted capability details").option(
|
|
1828
1915
|
"--agent <name>",
|
|
1829
1916
|
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1830
1917
|
).action(async (identifier, options) => {
|
|
@@ -1855,6 +1942,8 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1855
1942
|
searchId = lastSearch.searchId;
|
|
1856
1943
|
} else {
|
|
1857
1944
|
capabilityId = identifier;
|
|
1945
|
+
const ctx = stateService.findSearchContextByCapability(identifier);
|
|
1946
|
+
if (ctx) searchId = ctx.searchId;
|
|
1858
1947
|
}
|
|
1859
1948
|
const capability = await apiService.getCapability(
|
|
1860
1949
|
capabilityId,
|
|
@@ -1878,7 +1967,7 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1878
1967
|
});
|
|
1879
1968
|
|
|
1880
1969
|
// src/commands/init-command.ts
|
|
1881
|
-
import { createHash as
|
|
1970
|
+
import { createHash as createHash4 } from "crypto";
|
|
1882
1971
|
import {
|
|
1883
1972
|
chmodSync as chmodSync2,
|
|
1884
1973
|
existsSync as existsSync2,
|
|
@@ -1952,10 +2041,12 @@ var color = {
|
|
|
1952
2041
|
magenta: (s) => wrap("35", s),
|
|
1953
2042
|
green: (s) => wrap("32", s),
|
|
1954
2043
|
yellow: (s) => wrap("33", s),
|
|
2044
|
+
red: (s) => wrap("31", s),
|
|
1955
2045
|
gray: (s) => wrap("90", s),
|
|
1956
2046
|
boldCyan: (s) => wrap("1;36", s),
|
|
1957
2047
|
boldMagenta: (s) => wrap("1;35", s),
|
|
1958
|
-
boldGreen: (s) => wrap("1;32", s)
|
|
2048
|
+
boldGreen: (s) => wrap("1;32", s),
|
|
2049
|
+
boldRed: (s) => wrap("1;31", s)
|
|
1959
2050
|
};
|
|
1960
2051
|
var printZeroBanner = () => {
|
|
1961
2052
|
console.log("");
|
|
@@ -1998,12 +2089,16 @@ var printReadyFooter = () => {
|
|
|
1998
2089
|
const lines = [
|
|
1999
2090
|
"",
|
|
2000
2091
|
` ${color.boldGreen("Zero is ready!")} Zero works best with an AI agent.`,
|
|
2001
|
-
|
|
2002
|
-
" and try
|
|
2092
|
+
` Open ${color.boldRed("Claude Code")}, Codex, Cursor, Blackbox, or your agent of choice`,
|
|
2093
|
+
" and try this prompt to get started:",
|
|
2003
2094
|
"",
|
|
2004
|
-
` ${color.cyan(
|
|
2005
|
-
|
|
2006
|
-
|
|
2095
|
+
` ${color.cyan("What is zero and how do I use it?")}`,
|
|
2096
|
+
"",
|
|
2097
|
+
" You can also just tell your agent examples of what you are trying to do:",
|
|
2098
|
+
"",
|
|
2099
|
+
` ${color.cyan("Can you use zero to deploy an NYC weather website")}`,
|
|
2100
|
+
` ${color.cyan("Email me an image of a crystalline rocket ship with zero")}`,
|
|
2101
|
+
` ${color.cyan("Create a demo video with real voiceover for my project using zero")}`,
|
|
2007
2102
|
"",
|
|
2008
2103
|
` ${color.dim("By using Zero, you agree to our Terms of Service:")}`,
|
|
2009
2104
|
` ${color.dim("https://zero.xyz/terms-of-service")}`,
|
|
@@ -2059,7 +2154,7 @@ var getCliModuleDir = () => {
|
|
|
2059
2154
|
}
|
|
2060
2155
|
return __dirname;
|
|
2061
2156
|
};
|
|
2062
|
-
var sha256File = (filePath) =>
|
|
2157
|
+
var sha256File = (filePath) => createHash4("sha256").update(readFileSync4(filePath)).digest("hex");
|
|
2063
2158
|
var verifyFileCopy = (src, dest) => {
|
|
2064
2159
|
if (!existsSync2(dest)) return false;
|
|
2065
2160
|
return sha256File(src) === sha256File(dest);
|
|
@@ -2786,7 +2881,7 @@ var formatReviewCount2 = (count) => {
|
|
|
2786
2881
|
return count.toString();
|
|
2787
2882
|
};
|
|
2788
2883
|
var formatRatingBadge = (rating) => {
|
|
2789
|
-
if (rating.state === "unrated") return "
|
|
2884
|
+
if (rating.state === "unrated") return "unrated";
|
|
2790
2885
|
const successPct = `${Math.round(Number.parseFloat(rating.successRate) * 100)}%`;
|
|
2791
2886
|
const reviews = formatReviewCount2(rating.reviews);
|
|
2792
2887
|
if (rating.stars) {
|
|
@@ -2794,13 +2889,7 @@ var formatRatingBadge = (rating) => {
|
|
|
2794
2889
|
}
|
|
2795
2890
|
return `${successPct} success \xB7 ${reviews} reviews`;
|
|
2796
2891
|
};
|
|
2797
|
-
var
|
|
2798
|
-
if (item.trustScore != null) {
|
|
2799
|
-
return `Trust: ${item.trustScore}`;
|
|
2800
|
-
}
|
|
2801
|
-
return "Trust: --";
|
|
2802
|
-
};
|
|
2803
|
-
var formatHealthBadge = (status) => {
|
|
2892
|
+
var formatStatusBadge = (status) => {
|
|
2804
2893
|
if (!status || status === "unknown") return "";
|
|
2805
2894
|
return ` \u2014 ${status}`;
|
|
2806
2895
|
};
|
|
@@ -2810,21 +2899,19 @@ var formatSearchResults = (results) => {
|
|
|
2810
2899
|
const baseName = r.canonicalName ?? r.name;
|
|
2811
2900
|
const displayName = r.brandName ? `${r.brandName} ${baseName}` : baseName;
|
|
2812
2901
|
const displayDescription = r.whatItDoes ?? r.description;
|
|
2813
|
-
const trustBadge = formatTrustBadge(r);
|
|
2814
2902
|
const ratingBadge = formatRatingBadge(r.rating);
|
|
2815
|
-
const
|
|
2816
|
-
|
|
2817
|
-
return ` ${r.position}. ${displayName} \u2014 $${r.cost.amount}/call \u2014 ${trustBadge} \u2014 ${ratingBadge}${healthBadge}${relevance}
|
|
2903
|
+
const statusBadge = formatStatusBadge(r.displayStatus);
|
|
2904
|
+
return ` ${r.position}. ${displayName} \u2014 $${r.cost.amount}/call \u2014 ${ratingBadge}${statusBadge}
|
|
2818
2905
|
"${displayDescription}"`;
|
|
2819
2906
|
}).join("\n");
|
|
2820
2907
|
};
|
|
2821
2908
|
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(
|
|
2822
2909
|
"--max-cost <amount>",
|
|
2823
2910
|
`Maximum cost per call in USD (default: ${DEFAULT_MAX_COST_USD})`
|
|
2824
|
-
).option("--
|
|
2911
|
+
).option("--protocol <protocol>", "Payment protocol (x402 or mpp)").option(
|
|
2825
2912
|
"--status <status>",
|
|
2826
2913
|
"Filter by availability (healthy, degraded, down)"
|
|
2827
|
-
).option("--all", "
|
|
2914
|
+
).option("--all", "Disable default quality filtering").option(
|
|
2828
2915
|
"--source <source>",
|
|
2829
2916
|
"Only show results from this crawl source (e.g. mpp, bazaar)"
|
|
2830
2917
|
).option(
|
|
@@ -2864,9 +2951,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2864
2951
|
limit: options.limit,
|
|
2865
2952
|
freeOnly: options.free,
|
|
2866
2953
|
maxCost: effectiveMaxCost,
|
|
2867
|
-
minRating: options.minRating,
|
|
2868
2954
|
protocol: options.protocol,
|
|
2869
|
-
minTrust: options.minTrust,
|
|
2870
2955
|
availabilityStatus: options.status,
|
|
2871
2956
|
includeAll: options.all,
|
|
2872
2957
|
source: options.source,
|
|
@@ -2884,9 +2969,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2884
2969
|
freeOnly: options.free ?? false,
|
|
2885
2970
|
maxCost: effectiveMaxCost,
|
|
2886
2971
|
maxCostDefaulted: appliedDefaultMaxCost,
|
|
2887
|
-
minRating: options.minRating,
|
|
2888
2972
|
protocol: options.protocol,
|
|
2889
|
-
minTrust: options.minTrust,
|
|
2890
2973
|
availabilityStatus: options.status,
|
|
2891
2974
|
includeAll: options.all ?? false,
|
|
2892
2975
|
source: options.source,
|
|
@@ -2913,6 +2996,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2913
2996
|
position: c.position,
|
|
2914
2997
|
id: c.id,
|
|
2915
2998
|
url: c.url,
|
|
2999
|
+
urlTemplate: c.urlTemplate ?? null,
|
|
2916
3000
|
displayCostAmount: c.cost.amount
|
|
2917
3001
|
}))
|
|
2918
3002
|
});
|
|
@@ -3287,7 +3371,11 @@ var AnalyticsService = class {
|
|
|
3287
3371
|
this.posthog = new PostHog(POSTHOG_API_KEY, {
|
|
3288
3372
|
host: POSTHOG_HOST,
|
|
3289
3373
|
flushAt: 1,
|
|
3290
|
-
flushInterval: 0
|
|
3374
|
+
flushInterval: 0,
|
|
3375
|
+
// Vitest spins up many AnalyticsService instances per process; each
|
|
3376
|
+
// autocapture listener adds to process.{uncaughtException,unhandledRejection}
|
|
3377
|
+
// and trips Node's MaxListeners warning. Real CLI is one instance per process.
|
|
3378
|
+
enableExceptionAutocapture: !process.env.VITEST
|
|
3291
3379
|
});
|
|
3292
3380
|
this.posthog.on("error", () => {
|
|
3293
3381
|
});
|
|
@@ -3346,6 +3434,22 @@ var AnalyticsService = class {
|
|
|
3346
3434
|
}
|
|
3347
3435
|
});
|
|
3348
3436
|
}
|
|
3437
|
+
captureException(error, properties) {
|
|
3438
|
+
if (!this.posthog) return;
|
|
3439
|
+
this.posthog.captureException(error, this.distinctId, {
|
|
3440
|
+
source: "cli",
|
|
3441
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3442
|
+
cli_version: this.cliVersion,
|
|
3443
|
+
environment: this.environment,
|
|
3444
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3445
|
+
wallet_address: this.walletAddress,
|
|
3446
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3447
|
+
request_id: this.requestId,
|
|
3448
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3449
|
+
agent_host: this.agentHost,
|
|
3450
|
+
...properties
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3349
3453
|
async shutdown() {
|
|
3350
3454
|
if (!this.posthog) return;
|
|
3351
3455
|
try {
|
|
@@ -3358,15 +3462,27 @@ var AnalyticsService = class {
|
|
|
3358
3462
|
// src/services/state-service.ts
|
|
3359
3463
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
3360
3464
|
import { join as join5 } from "path";
|
|
3465
|
+
var RECENT_SEARCH_LIMIT = 10;
|
|
3361
3466
|
var StateService = class {
|
|
3362
3467
|
constructor(zeroDir) {
|
|
3363
3468
|
this.zeroDir = zeroDir;
|
|
3364
3469
|
this.lastSearchPath = join5(zeroDir, "last_search.json");
|
|
3470
|
+
this.recentSearchesPath = join5(zeroDir, "recent_searches.json");
|
|
3365
3471
|
}
|
|
3366
3472
|
lastSearchPath;
|
|
3473
|
+
recentSearchesPath;
|
|
3367
3474
|
saveLastSearch = (data) => {
|
|
3368
3475
|
mkdirSync5(this.zeroDir, { recursive: true });
|
|
3369
3476
|
writeFileSync5(this.lastSearchPath, JSON.stringify(data, null, 2));
|
|
3477
|
+
const recent = this.loadRecentSearches();
|
|
3478
|
+
const filtered = recent.searches.filter(
|
|
3479
|
+
(s) => s.searchId !== data.searchId
|
|
3480
|
+
);
|
|
3481
|
+
const next = [data, ...filtered].slice(0, RECENT_SEARCH_LIMIT);
|
|
3482
|
+
writeFileSync5(
|
|
3483
|
+
this.recentSearchesPath,
|
|
3484
|
+
JSON.stringify({ searches: next }, null, 2)
|
|
3485
|
+
);
|
|
3370
3486
|
};
|
|
3371
3487
|
loadLastSearch = () => {
|
|
3372
3488
|
try {
|
|
@@ -3377,6 +3493,63 @@ var StateService = class {
|
|
|
3377
3493
|
return null;
|
|
3378
3494
|
}
|
|
3379
3495
|
};
|
|
3496
|
+
loadRecentSearches = () => {
|
|
3497
|
+
try {
|
|
3498
|
+
if (!existsSync6(this.recentSearchesPath)) {
|
|
3499
|
+
const last = this.loadLastSearch();
|
|
3500
|
+
return { searches: last ? [last] : [] };
|
|
3501
|
+
}
|
|
3502
|
+
const raw = readFileSync9(this.recentSearchesPath, "utf8");
|
|
3503
|
+
const parsed = JSON.parse(raw);
|
|
3504
|
+
return { searches: parsed.searches ?? [] };
|
|
3505
|
+
} catch {
|
|
3506
|
+
return { searches: [] };
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
// Walk recent searches newest-first, returning the first one whose
|
|
3510
|
+
// results contain `capabilityId` (uid or slug match). Preferred over
|
|
3511
|
+
// "loadLastSearch" for attributing rank — handles the parallel-search
|
|
3512
|
+
// case where the most recent search isn't the one being fetched.
|
|
3513
|
+
findSearchContextByCapability = (capabilityId) => {
|
|
3514
|
+
const recent = this.loadRecentSearches();
|
|
3515
|
+
for (const search of recent.searches) {
|
|
3516
|
+
const entry = search.capabilities.find((c) => c.id === capabilityId);
|
|
3517
|
+
if (entry) {
|
|
3518
|
+
return {
|
|
3519
|
+
searchId: search.searchId,
|
|
3520
|
+
resultRank: entry.position,
|
|
3521
|
+
capabilityId: entry.id,
|
|
3522
|
+
url: entry.url,
|
|
3523
|
+
displayCostAmount: entry.displayCostAmount
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
return null;
|
|
3528
|
+
};
|
|
3529
|
+
// URL-based attribution for `zero fetch <url>`. Matches by URL prefix
|
|
3530
|
+
// (some capabilities accept query params or path tails the agent appends)
|
|
3531
|
+
// across the recent ring, newest match wins. Falls back to a caller-
|
|
3532
|
+
// supplied urlTemplate matcher (ADS-509 path-parameterized URLs).
|
|
3533
|
+
findSearchContextByUrl = (url, options) => {
|
|
3534
|
+
const recent = this.loadRecentSearches();
|
|
3535
|
+
for (const search of recent.searches) {
|
|
3536
|
+
const prefixHit = search.capabilities.find((c) => url.startsWith(c.url));
|
|
3537
|
+
const templateHit = prefixHit ?? search.capabilities.find(
|
|
3538
|
+
(c) => c.urlTemplate && options?.matchTemplate ? options.matchTemplate(c.urlTemplate) : false
|
|
3539
|
+
);
|
|
3540
|
+
const entry = templateHit;
|
|
3541
|
+
if (entry) {
|
|
3542
|
+
return {
|
|
3543
|
+
searchId: search.searchId,
|
|
3544
|
+
resultRank: entry.position,
|
|
3545
|
+
capabilityId: entry.id,
|
|
3546
|
+
url: entry.url,
|
|
3547
|
+
displayCostAmount: entry.displayCostAmount
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
return null;
|
|
3552
|
+
};
|
|
3380
3553
|
};
|
|
3381
3554
|
|
|
3382
3555
|
// src/services/wallet-service.ts
|
|
@@ -3483,6 +3656,155 @@ var createAppContext = () => {
|
|
|
3483
3656
|
};
|
|
3484
3657
|
};
|
|
3485
3658
|
|
|
3659
|
+
// src/util/update-check.ts
|
|
3660
|
+
import {
|
|
3661
|
+
existsSync as existsSync8,
|
|
3662
|
+
lstatSync,
|
|
3663
|
+
mkdirSync as mkdirSync6,
|
|
3664
|
+
readFileSync as readFileSync11,
|
|
3665
|
+
readlinkSync,
|
|
3666
|
+
writeFileSync as writeFileSync6
|
|
3667
|
+
} from "fs";
|
|
3668
|
+
import { homedir as homedir6 } from "os";
|
|
3669
|
+
import { dirname as dirname3, join as join7, resolve } from "path";
|
|
3670
|
+
var CACHE_FILENAME = "update_check.json";
|
|
3671
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@zeroxyz/cli/latest";
|
|
3672
|
+
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
3673
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
3674
|
+
var emptyCache = {
|
|
3675
|
+
lastCheckedMs: 0,
|
|
3676
|
+
latestVersion: null,
|
|
3677
|
+
lastShownMs: 0
|
|
3678
|
+
};
|
|
3679
|
+
var resolveExecPath = (execPath) => {
|
|
3680
|
+
try {
|
|
3681
|
+
const stat = lstatSync(execPath);
|
|
3682
|
+
if (stat.isSymbolicLink()) {
|
|
3683
|
+
const target = readlinkSync(execPath);
|
|
3684
|
+
return resolve(dirname3(execPath), target);
|
|
3685
|
+
}
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3688
|
+
return execPath;
|
|
3689
|
+
};
|
|
3690
|
+
var detectInstallMethod = (opts = {}) => {
|
|
3691
|
+
const execPath = opts.execPath ?? process.execPath;
|
|
3692
|
+
const pkg = opts.pkg ?? process.pkg;
|
|
3693
|
+
const home = opts.home ?? homedir6();
|
|
3694
|
+
if (pkg) return "binary";
|
|
3695
|
+
const resolved = resolveExecPath(execPath);
|
|
3696
|
+
const zeroBin = join7(home, ".zero", "bin");
|
|
3697
|
+
if (resolved.startsWith(zeroBin)) return "binary";
|
|
3698
|
+
return "npm";
|
|
3699
|
+
};
|
|
3700
|
+
var compareVersions = (a, b) => {
|
|
3701
|
+
const parse = (v) => {
|
|
3702
|
+
const dashIdx = v.indexOf("-");
|
|
3703
|
+
const base2 = dashIdx === -1 ? v : v.slice(0, dashIdx);
|
|
3704
|
+
const pre = dashIdx === -1 ? null : v.slice(dashIdx + 1);
|
|
3705
|
+
const nums = base2.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
3706
|
+
while (nums.length < 3) nums.push(0);
|
|
3707
|
+
return { nums, pre };
|
|
3708
|
+
};
|
|
3709
|
+
const pa = parse(a);
|
|
3710
|
+
const pb = parse(b);
|
|
3711
|
+
for (let i = 0; i < 3; i++) {
|
|
3712
|
+
const na = pa.nums[i] ?? 0;
|
|
3713
|
+
const nb = pb.nums[i] ?? 0;
|
|
3714
|
+
if (na !== nb) return na - nb;
|
|
3715
|
+
}
|
|
3716
|
+
if (pa.pre === pb.pre) return 0;
|
|
3717
|
+
if (pa.pre === null) return 1;
|
|
3718
|
+
if (pb.pre === null) return -1;
|
|
3719
|
+
return pa.pre < pb.pre ? -1 : 1;
|
|
3720
|
+
};
|
|
3721
|
+
var cachePath = (zeroDir) => join7(zeroDir, CACHE_FILENAME);
|
|
3722
|
+
var readCache = (zeroDir) => {
|
|
3723
|
+
try {
|
|
3724
|
+
const path = cachePath(zeroDir);
|
|
3725
|
+
if (!existsSync8(path)) return emptyCache;
|
|
3726
|
+
const raw = readFileSync11(path, "utf8");
|
|
3727
|
+
const parsed = JSON.parse(raw);
|
|
3728
|
+
return {
|
|
3729
|
+
lastCheckedMs: typeof parsed.lastCheckedMs === "number" ? parsed.lastCheckedMs : 0,
|
|
3730
|
+
latestVersion: typeof parsed.latestVersion === "string" ? parsed.latestVersion : null,
|
|
3731
|
+
lastShownMs: typeof parsed.lastShownMs === "number" ? parsed.lastShownMs : 0
|
|
3732
|
+
};
|
|
3733
|
+
} catch {
|
|
3734
|
+
return emptyCache;
|
|
3735
|
+
}
|
|
3736
|
+
};
|
|
3737
|
+
var writeCache = (zeroDir, cache) => {
|
|
3738
|
+
try {
|
|
3739
|
+
mkdirSync6(zeroDir, { recursive: true });
|
|
3740
|
+
writeFileSync6(cachePath(zeroDir), JSON.stringify(cache, null, 2));
|
|
3741
|
+
} catch {
|
|
3742
|
+
}
|
|
3743
|
+
};
|
|
3744
|
+
var fetchLatestVersion = async (url = NPM_REGISTRY_URL) => {
|
|
3745
|
+
try {
|
|
3746
|
+
const controller = new AbortController();
|
|
3747
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
3748
|
+
const response = await fetch(url, {
|
|
3749
|
+
signal: controller.signal,
|
|
3750
|
+
headers: { accept: "application/json" }
|
|
3751
|
+
});
|
|
3752
|
+
clearTimeout(timeout);
|
|
3753
|
+
if (!response.ok) return null;
|
|
3754
|
+
const json = await response.json();
|
|
3755
|
+
return typeof json.version === "string" ? json.version : null;
|
|
3756
|
+
} catch {
|
|
3757
|
+
return null;
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
var refreshUpdateCache = async (zeroDir, opts = {}) => {
|
|
3761
|
+
const now = opts.now ?? Date.now();
|
|
3762
|
+
const fetcher = opts.fetchLatest ?? fetchLatestVersion;
|
|
3763
|
+
const cache = readCache(zeroDir);
|
|
3764
|
+
if (cache.lastCheckedMs > 0 && now - cache.lastCheckedMs < CHECK_INTERVAL_MS) {
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
const latest = await fetcher();
|
|
3768
|
+
if (!latest) {
|
|
3769
|
+
writeCache(zeroDir, { ...cache, lastCheckedMs: now });
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
writeCache(zeroDir, {
|
|
3773
|
+
...cache,
|
|
3774
|
+
lastCheckedMs: now,
|
|
3775
|
+
latestVersion: latest
|
|
3776
|
+
});
|
|
3777
|
+
};
|
|
3778
|
+
var updateCommandFor = (method) => method === "binary" ? "curl -fsSL https://zero.xyz/install.sh | bash" : "npm install -g @zeroxyz/cli";
|
|
3779
|
+
var formatBanner = (currentVersion, latestVersion, method) => {
|
|
3780
|
+
const arrow = `${currentVersion} \u2192 ${latestVersion}`;
|
|
3781
|
+
const cmd = updateCommandFor(method);
|
|
3782
|
+
const lines = [
|
|
3783
|
+
"",
|
|
3784
|
+
` ${color.yellow("\u26A1 Update available")} ${color.dim(arrow)}`,
|
|
3785
|
+
` ${color.dim("Run:")} ${color.cyan(cmd)}`,
|
|
3786
|
+
""
|
|
3787
|
+
];
|
|
3788
|
+
return lines.join("\n");
|
|
3789
|
+
};
|
|
3790
|
+
var consumeBannerIfDue = (zeroDir, currentVersion, opts = {}) => {
|
|
3791
|
+
const now = opts.now ?? Date.now();
|
|
3792
|
+
const method = opts.method ?? detectInstallMethod();
|
|
3793
|
+
const cache = readCache(zeroDir);
|
|
3794
|
+
if (!cache.latestVersion) return null;
|
|
3795
|
+
if (compareVersions(currentVersion, cache.latestVersion) >= 0) return null;
|
|
3796
|
+
if (cache.lastShownMs > 0 && now - cache.lastShownMs < CHECK_INTERVAL_MS) {
|
|
3797
|
+
return null;
|
|
3798
|
+
}
|
|
3799
|
+
writeCache(zeroDir, { ...cache, lastShownMs: now });
|
|
3800
|
+
return formatBanner(currentVersion, cache.latestVersion, method);
|
|
3801
|
+
};
|
|
3802
|
+
var maybePrintUpdateBanner = (zeroDir, currentVersion) => {
|
|
3803
|
+
if (!process.stderr.isTTY) return;
|
|
3804
|
+
const banner = consumeBannerIfDue(zeroDir, currentVersion);
|
|
3805
|
+
if (banner) process.stderr.write(banner);
|
|
3806
|
+
};
|
|
3807
|
+
|
|
3486
3808
|
// src/index.ts
|
|
3487
3809
|
var main = async () => {
|
|
3488
3810
|
const appContext = createAppContext();
|
|
@@ -3490,6 +3812,8 @@ var main = async () => {
|
|
|
3490
3812
|
console.error("Failed to create app context");
|
|
3491
3813
|
process.exit(1);
|
|
3492
3814
|
}
|
|
3815
|
+
const zeroDir = join8(homedir7(), ".zero");
|
|
3816
|
+
maybePrintUpdateBanner(zeroDir, package_default.version);
|
|
3493
3817
|
const app = createApp(appContext);
|
|
3494
3818
|
let caughtError = null;
|
|
3495
3819
|
try {
|
|
@@ -3500,6 +3824,9 @@ var main = async () => {
|
|
|
3500
3824
|
caughtError = err instanceof Error ? err : new Error(String(err));
|
|
3501
3825
|
console.error(caughtError.message);
|
|
3502
3826
|
process.exitCode = 1;
|
|
3827
|
+
appContext.services.analyticsService.captureException(caughtError, {
|
|
3828
|
+
command: appContext.invocation.current?.command
|
|
3829
|
+
});
|
|
3503
3830
|
}
|
|
3504
3831
|
} finally {
|
|
3505
3832
|
const invocation = appContext.invocation.current;
|
|
@@ -3513,6 +3840,7 @@ var main = async () => {
|
|
|
3513
3840
|
});
|
|
3514
3841
|
}
|
|
3515
3842
|
await appContext.services.analyticsService.shutdown();
|
|
3843
|
+
await refreshUpdateCache(zeroDir);
|
|
3516
3844
|
}
|
|
3517
3845
|
};
|
|
3518
3846
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxyz/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zero": "dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsup src/index.ts --format esm --out-dir dist --clean",
|
|
19
|
-
"build:binary": "tsup --config tsup.binary.ts && cp -r skills hooks dist/pkg/ && pnpm exec pkg dist/pkg/index.cjs --config pkg.json --targets node24-macos-arm64,node24-macos-x64,node24-linux-x64 --output dist/bin/zero",
|
|
19
|
+
"build:binary": "tsup --config tsup.binary.ts && cp -r skills hooks dist/pkg/ && pnpm exec pkg dist/pkg/index.cjs --config pkg.json --targets node24-macos-arm64,node24-macos-x64,node24-linux-x64,node24-linux-arm64 --output dist/bin/zero",
|
|
20
20
|
"prepublishOnly": "pnpm run build",
|
|
21
21
|
"dev": "ZERO_ENV=development tsx src/index.ts",
|
|
22
22
|
"cli": "ZERO_ENV=development ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@x402/extensions": "^2.9.0",
|
|
35
35
|
"@x402/fetch": "^2.9.0",
|
|
36
36
|
"commander": "^13.0.0",
|
|
37
|
-
"mppx": "^0.
|
|
37
|
+
"mppx": "^0.6.9",
|
|
38
38
|
"open": "^11.0.0",
|
|
39
39
|
"posthog-node": "^5.29.2",
|
|
40
40
|
"viem": "^2.47.10",
|
package/skills/zero/SKILL.md
CHANGED
|
@@ -98,13 +98,16 @@ zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --relia
|
|
|
98
98
|
|
|
99
99
|
### Workflow
|
|
100
100
|
|
|
101
|
-
1. **Search** — `zero search "weather forecast"` finds matching capabilities. Results show name, cost,
|
|
101
|
+
1. **Search** — `zero search "weather forecast"` finds matching capabilities. Results show ranked capabilities with name, cost, availability, and a short description.
|
|
102
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
103
|
|
|
104
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.
|
|
105
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).
|
|
106
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.
|
|
107
107
|
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.
|
|
108
|
+
6. **Revise a review** — re-running `zero review <runId> ...` for a run you've already reviewed **overwrites** your prior ratings and content (same wallet only). Use this when a retry succeeded, a result that looked good failed downstream, or you want to expand the notes after more usage. There's no separate edit command — same call, same runId.
|
|
109
|
+
|
|
110
|
+
> **Only your latest review per capability is shown publicly.** When you've reviewed a capability across multiple runs, both the public detail page and the search-ranking math dedupe to your most recent review per capability — older reviews stay in the DB as a version trail but don't display and don't count toward the rating. So if your judgment has changed, submit a fresh review on a recent run (or edit the existing one via the same-`runId` upsert above) — don't assume the old review still represents you.
|
|
108
111
|
|
|
109
112
|
### Writing review content
|
|
110
113
|
|
|
@@ -118,6 +121,10 @@ zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --relia
|
|
|
118
121
|
|
|
119
122
|
Each names the task attempted, what the output actually was, and a specific observation (latency, a gotcha, a fit/misfit note). That's the kind of line a human buyer trusts and another agent can learn from.
|
|
120
123
|
|
|
124
|
+
**Name the use case.** State the general type of work you were using the capability for (no private/proprietary detail) so other readers can tell whether their task fits. Examples: *"Used this to generate stock-style hero photography for a vibe-coded landing page"*, *"Called from a daily ETL to enrich new signups with company metadata"*, *"Translating short product blurbs (~80 chars) for a Spanish-language store"*. This is what makes a review useful for someone deciding whether to call the capability for *their* task — not just whether it worked for yours.
|
|
125
|
+
|
|
126
|
+
**Two-part structure works well** when you have enough to say: lead with the human-facing half (use case, what you got, fit/misfit), then a second half with agent-facing technical notes (exact field names, gotchas, retry behavior, schema quirks). Each half is independently useful — humans skim the top, agents grep the bottom.
|
|
127
|
+
|
|
121
128
|
**Review failures with content too.** Failure notes are arguably more valuable — they warn the next caller. Example: *"FLUX Schnell returned HTTP 500 Internal Server Error — paid 0.003 USDC via MPP but got no image."* Pair with `--no-success`.
|
|
122
129
|
|
|
123
130
|
**Skip `--content` rather than write filler.** "Worked great", "Fast response", or test strings like "trial 1" add noise, pollute the capability's public page, and dilute the signal agents rely on. If you don't have a specific observation, just submit the numeric ratings.
|
|
@@ -231,7 +238,7 @@ cat payload.json | zero fetch https://api.example.com --data-stdin
|
|
|
231
238
|
|
|
232
239
|
### Rules
|
|
233
240
|
|
|
234
|
-
- **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
|
|
241
|
+
- **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 ranking and availability.
|
|
235
242
|
- **Always `zero get` before `zero fetch`.** Even if you "know" the URL, re-fetch the full details to confirm the URL, method, required headers, body schema, and current price. Do not reconstruct a fetch call from memory.
|
|
236
243
|
- Never guess endpoint URLs or schemas.
|
|
237
244
|
- Use `--max-pay` before potentially expensive requests.
|