@zeroxyz/cli 0.0.34 → 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/dist/index.js +406 -25
- package/package.json +1 -1
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(),
|
|
@@ -617,6 +623,7 @@ var configCommand = (_appContext) => new Command2("config").description("View or
|
|
|
617
623
|
});
|
|
618
624
|
|
|
619
625
|
// src/commands/fetch-command.ts
|
|
626
|
+
import { createHash as createHash3 } from "crypto";
|
|
620
627
|
import { readFileSync as readFileSync3 } from "fs";
|
|
621
628
|
import { resolve as resolvePath } from "path";
|
|
622
629
|
import { Command as Command3 } from "commander";
|
|
@@ -740,6 +747,7 @@ var pickSessionCloseAmount = (receipt, openTimeCumulative) => {
|
|
|
740
747
|
const accepted = BigInt(receipt.acceptedCumulative);
|
|
741
748
|
const spent = BigInt(receipt.spent);
|
|
742
749
|
const fromReceipt = accepted > spent ? accepted : spent;
|
|
750
|
+
if (receipt.metered === true) return fromReceipt;
|
|
743
751
|
return fromReceipt > openTimeCumulative ? fromReceipt : openTimeCumulative;
|
|
744
752
|
};
|
|
745
753
|
var PaymentService = class {
|
|
@@ -1206,6 +1214,22 @@ var truncateQuery = (raw) => raw.length > QUERY_MAX ? `${raw.slice(0, QUERY_MAX)
|
|
|
1206
1214
|
var truncateError = (raw) => raw.length > ERROR_MAX ? `${raw.slice(0, ERROR_MAX)}\u2026` : raw;
|
|
1207
1215
|
|
|
1208
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
|
+
};
|
|
1209
1233
|
var isTextContentType = (contentType) => {
|
|
1210
1234
|
if (!contentType) return true;
|
|
1211
1235
|
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
@@ -1325,6 +1349,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1325
1349
|
const startTime = Date.now();
|
|
1326
1350
|
let resolvedUrl;
|
|
1327
1351
|
let resolvedMethodFromCapability;
|
|
1352
|
+
let resolvedCapabilityName;
|
|
1328
1353
|
if (!url) {
|
|
1329
1354
|
if (!options.capability) {
|
|
1330
1355
|
console.error(
|
|
@@ -1337,6 +1362,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1337
1362
|
const cap = await apiService.getCapability(options.capability);
|
|
1338
1363
|
resolvedUrl = cap.url;
|
|
1339
1364
|
resolvedMethodFromCapability = cap.method;
|
|
1365
|
+
resolvedCapabilityName = cap.name;
|
|
1340
1366
|
} catch (err) {
|
|
1341
1367
|
console.error(
|
|
1342
1368
|
`Failed to resolve --capability ${options.capability}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1347,6 +1373,47 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1347
1373
|
} else {
|
|
1348
1374
|
resolvedUrl = url;
|
|
1349
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
|
+
}
|
|
1350
1417
|
let resolvedBody;
|
|
1351
1418
|
try {
|
|
1352
1419
|
resolvedBody = resolveRequestBody(
|
|
@@ -1354,9 +1421,13 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1354
1421
|
options.dataStdin ?? false
|
|
1355
1422
|
);
|
|
1356
1423
|
} catch (err) {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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);
|
|
1360
1431
|
process.exitCode = 1;
|
|
1361
1432
|
return;
|
|
1362
1433
|
}
|
|
@@ -1373,7 +1444,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1373
1444
|
(k) => k.toLowerCase() === "content-type"
|
|
1374
1445
|
);
|
|
1375
1446
|
if (resolvedBody && !hasContentType) {
|
|
1376
|
-
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";
|
|
1377
1448
|
}
|
|
1378
1449
|
const log = (msg) => console.error(` ${msg}`);
|
|
1379
1450
|
const method = options.method ? options.method.toUpperCase() : resolvedMethodFromCapability && !resolvedBody ? resolvedMethodFromCapability.toUpperCase() : resolvedBody ? "POST" : "GET";
|
|
@@ -1382,12 +1453,21 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1382
1453
|
headers,
|
|
1383
1454
|
body: resolvedBody
|
|
1384
1455
|
};
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1387
|
-
(
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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;
|
|
1391
1471
|
const skipReasons = [];
|
|
1392
1472
|
if (!apiService.walletAddress) {
|
|
1393
1473
|
skipReasons.push(
|
|
@@ -1398,6 +1478,19 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1398
1478
|
skipReasons.push(
|
|
1399
1479
|
"no capability resolved \u2014 pass --capability <uid|slug> or run `zero search` first so the URL can be matched"
|
|
1400
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
|
+
}
|
|
1401
1494
|
}
|
|
1402
1495
|
let finalResponse;
|
|
1403
1496
|
let bodyBytes;
|
|
@@ -1420,7 +1513,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1420
1513
|
paymentReq,
|
|
1421
1514
|
options.maxPay,
|
|
1422
1515
|
log,
|
|
1423
|
-
|
|
1516
|
+
matchedDisplayCostAmount
|
|
1424
1517
|
);
|
|
1425
1518
|
finalResponse = result.response;
|
|
1426
1519
|
paymentMeta = {
|
|
@@ -1517,6 +1610,15 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1517
1610
|
} catch {
|
|
1518
1611
|
}
|
|
1519
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
|
+
}
|
|
1520
1622
|
let runId = null;
|
|
1521
1623
|
if (capabilityId && apiService.walletAddress) {
|
|
1522
1624
|
let requestSchema;
|
|
@@ -1541,6 +1643,8 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1541
1643
|
latencyMs,
|
|
1542
1644
|
requestSchema,
|
|
1543
1645
|
responseSchema,
|
|
1646
|
+
errorSnippetHash,
|
|
1647
|
+
errorSnippetLength,
|
|
1544
1648
|
...paymentMeta && {
|
|
1545
1649
|
costAmount: paymentMeta.amount,
|
|
1546
1650
|
costAsset: paymentMeta.asset,
|
|
@@ -1557,7 +1661,6 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1557
1661
|
);
|
|
1558
1662
|
}
|
|
1559
1663
|
}
|
|
1560
|
-
const status = finalResponse?.status;
|
|
1561
1664
|
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1562
1665
|
analyticsService.capture("fetch_executed", {
|
|
1563
1666
|
url: redactUrl(resolvedUrl),
|
|
@@ -1569,10 +1672,26 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1569
1672
|
paymentAmount: paymentMeta?.amount,
|
|
1570
1673
|
capabilityId: capabilityId ?? void 0,
|
|
1571
1674
|
searchId: searchId ?? void 0,
|
|
1675
|
+
resultRank: resultRank ?? void 0,
|
|
1572
1676
|
runId: runId ?? void 0,
|
|
1573
1677
|
runTracked: !!runId,
|
|
1574
1678
|
...fetchError && { error: truncateError(fetchError.message) }
|
|
1575
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
|
+
}
|
|
1576
1695
|
if (fetchError && !options.json) {
|
|
1577
1696
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1578
1697
|
}
|
|
@@ -1639,7 +1758,16 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1639
1758
|
process.exitCode = 1;
|
|
1640
1759
|
}
|
|
1641
1760
|
} catch (err) {
|
|
1642
|
-
|
|
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);
|
|
1643
1771
|
process.exitCode = 1;
|
|
1644
1772
|
}
|
|
1645
1773
|
}
|
|
@@ -1855,6 +1983,8 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1855
1983
|
searchId = lastSearch.searchId;
|
|
1856
1984
|
} else {
|
|
1857
1985
|
capabilityId = identifier;
|
|
1986
|
+
const ctx = stateService.findSearchContextByCapability(identifier);
|
|
1987
|
+
if (ctx) searchId = ctx.searchId;
|
|
1858
1988
|
}
|
|
1859
1989
|
const capability = await apiService.getCapability(
|
|
1860
1990
|
capabilityId,
|
|
@@ -1878,7 +2008,7 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1878
2008
|
});
|
|
1879
2009
|
|
|
1880
2010
|
// src/commands/init-command.ts
|
|
1881
|
-
import { createHash as
|
|
2011
|
+
import { createHash as createHash4 } from "crypto";
|
|
1882
2012
|
import {
|
|
1883
2013
|
chmodSync as chmodSync2,
|
|
1884
2014
|
existsSync as existsSync2,
|
|
@@ -1952,10 +2082,12 @@ var color = {
|
|
|
1952
2082
|
magenta: (s) => wrap("35", s),
|
|
1953
2083
|
green: (s) => wrap("32", s),
|
|
1954
2084
|
yellow: (s) => wrap("33", s),
|
|
2085
|
+
red: (s) => wrap("31", s),
|
|
1955
2086
|
gray: (s) => wrap("90", s),
|
|
1956
2087
|
boldCyan: (s) => wrap("1;36", s),
|
|
1957
2088
|
boldMagenta: (s) => wrap("1;35", s),
|
|
1958
|
-
boldGreen: (s) => wrap("1;32", s)
|
|
2089
|
+
boldGreen: (s) => wrap("1;32", s),
|
|
2090
|
+
boldRed: (s) => wrap("1;31", s)
|
|
1959
2091
|
};
|
|
1960
2092
|
var printZeroBanner = () => {
|
|
1961
2093
|
console.log("");
|
|
@@ -1998,12 +2130,16 @@ var printReadyFooter = () => {
|
|
|
1998
2130
|
const lines = [
|
|
1999
2131
|
"",
|
|
2000
2132
|
` ${color.boldGreen("Zero is ready!")} Zero works best with an AI agent.`,
|
|
2001
|
-
|
|
2002
|
-
" and try
|
|
2133
|
+
` Open ${color.boldRed("Claude Code")}, Codex, Cursor, Blackbox, or your agent of choice`,
|
|
2134
|
+
" and try this prompt to get started:",
|
|
2003
2135
|
"",
|
|
2004
|
-
` ${color.cyan(
|
|
2005
|
-
|
|
2006
|
-
|
|
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")}`,
|
|
2007
2143
|
"",
|
|
2008
2144
|
` ${color.dim("By using Zero, you agree to our Terms of Service:")}`,
|
|
2009
2145
|
` ${color.dim("https://zero.xyz/terms-of-service")}`,
|
|
@@ -2059,7 +2195,7 @@ var getCliModuleDir = () => {
|
|
|
2059
2195
|
}
|
|
2060
2196
|
return __dirname;
|
|
2061
2197
|
};
|
|
2062
|
-
var sha256File = (filePath) =>
|
|
2198
|
+
var sha256File = (filePath) => createHash4("sha256").update(readFileSync4(filePath)).digest("hex");
|
|
2063
2199
|
var verifyFileCopy = (src, dest) => {
|
|
2064
2200
|
if (!existsSync2(dest)) return false;
|
|
2065
2201
|
return sha256File(src) === sha256File(dest);
|
|
@@ -2913,6 +3049,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2913
3049
|
position: c.position,
|
|
2914
3050
|
id: c.id,
|
|
2915
3051
|
url: c.url,
|
|
3052
|
+
urlTemplate: c.urlTemplate ?? null,
|
|
2916
3053
|
displayCostAmount: c.cost.amount
|
|
2917
3054
|
}))
|
|
2918
3055
|
});
|
|
@@ -3287,7 +3424,11 @@ var AnalyticsService = class {
|
|
|
3287
3424
|
this.posthog = new PostHog(POSTHOG_API_KEY, {
|
|
3288
3425
|
host: POSTHOG_HOST,
|
|
3289
3426
|
flushAt: 1,
|
|
3290
|
-
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
|
|
3291
3432
|
});
|
|
3292
3433
|
this.posthog.on("error", () => {
|
|
3293
3434
|
});
|
|
@@ -3346,6 +3487,22 @@ var AnalyticsService = class {
|
|
|
3346
3487
|
}
|
|
3347
3488
|
});
|
|
3348
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
|
+
}
|
|
3349
3506
|
async shutdown() {
|
|
3350
3507
|
if (!this.posthog) return;
|
|
3351
3508
|
try {
|
|
@@ -3358,15 +3515,27 @@ var AnalyticsService = class {
|
|
|
3358
3515
|
// src/services/state-service.ts
|
|
3359
3516
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
3360
3517
|
import { join as join5 } from "path";
|
|
3518
|
+
var RECENT_SEARCH_LIMIT = 10;
|
|
3361
3519
|
var StateService = class {
|
|
3362
3520
|
constructor(zeroDir) {
|
|
3363
3521
|
this.zeroDir = zeroDir;
|
|
3364
3522
|
this.lastSearchPath = join5(zeroDir, "last_search.json");
|
|
3523
|
+
this.recentSearchesPath = join5(zeroDir, "recent_searches.json");
|
|
3365
3524
|
}
|
|
3366
3525
|
lastSearchPath;
|
|
3526
|
+
recentSearchesPath;
|
|
3367
3527
|
saveLastSearch = (data) => {
|
|
3368
3528
|
mkdirSync5(this.zeroDir, { recursive: true });
|
|
3369
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
|
+
);
|
|
3370
3539
|
};
|
|
3371
3540
|
loadLastSearch = () => {
|
|
3372
3541
|
try {
|
|
@@ -3377,6 +3546,63 @@ var StateService = class {
|
|
|
3377
3546
|
return null;
|
|
3378
3547
|
}
|
|
3379
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
|
+
};
|
|
3380
3606
|
};
|
|
3381
3607
|
|
|
3382
3608
|
// src/services/wallet-service.ts
|
|
@@ -3483,6 +3709,155 @@ var createAppContext = () => {
|
|
|
3483
3709
|
};
|
|
3484
3710
|
};
|
|
3485
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
|
+
|
|
3486
3861
|
// src/index.ts
|
|
3487
3862
|
var main = async () => {
|
|
3488
3863
|
const appContext = createAppContext();
|
|
@@ -3490,6 +3865,8 @@ var main = async () => {
|
|
|
3490
3865
|
console.error("Failed to create app context");
|
|
3491
3866
|
process.exit(1);
|
|
3492
3867
|
}
|
|
3868
|
+
const zeroDir = join8(homedir7(), ".zero");
|
|
3869
|
+
maybePrintUpdateBanner(zeroDir, package_default.version);
|
|
3493
3870
|
const app = createApp(appContext);
|
|
3494
3871
|
let caughtError = null;
|
|
3495
3872
|
try {
|
|
@@ -3500,6 +3877,9 @@ var main = async () => {
|
|
|
3500
3877
|
caughtError = err instanceof Error ? err : new Error(String(err));
|
|
3501
3878
|
console.error(caughtError.message);
|
|
3502
3879
|
process.exitCode = 1;
|
|
3880
|
+
appContext.services.analyticsService.captureException(caughtError, {
|
|
3881
|
+
command: appContext.invocation.current?.command
|
|
3882
|
+
});
|
|
3503
3883
|
}
|
|
3504
3884
|
} finally {
|
|
3505
3885
|
const invocation = appContext.invocation.current;
|
|
@@ -3513,6 +3893,7 @@ var main = async () => {
|
|
|
3513
3893
|
});
|
|
3514
3894
|
}
|
|
3515
3895
|
await appContext.services.analyticsService.shutdown();
|
|
3896
|
+
await refreshUpdateCache(zeroDir);
|
|
3516
3897
|
}
|
|
3517
3898
|
};
|
|
3518
3899
|
main();
|