@zeroxyz/cli 0.0.25 → 0.0.26
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 +350 -120
- package/package.json +3 -3
- package/skills/zero/SKILL.md +10 -0
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command as Command11 } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@zeroxyz/cli",
|
|
9
|
-
version: "0.0.
|
|
9
|
+
version: "0.0.26",
|
|
10
10
|
type: "module",
|
|
11
11
|
bin: {
|
|
12
12
|
zero: "dist/index.js",
|
|
@@ -24,8 +24,8 @@ var package_default = {
|
|
|
24
24
|
build: "tsup src/index.ts --format esm --out-dir dist --clean",
|
|
25
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
26
|
prepublishOnly: "pnpm run build",
|
|
27
|
-
dev: "tsx src/index.ts",
|
|
28
|
-
cli: "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
27
|
+
dev: "ZERO_ENV=development tsx src/index.ts",
|
|
28
|
+
cli: "ZERO_ENV=development ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
29
29
|
"test:integration": "vitest run --project integration",
|
|
30
30
|
"test:online": "vitest run --project online",
|
|
31
31
|
"test:unit": "vitest run --project unit",
|
|
@@ -380,7 +380,10 @@ Categories the classifier picks from:
|
|
|
380
380
|
).option("--idempotency-key <key>", "Override the auto-generated dedup key").option(
|
|
381
381
|
"--from-file <path>",
|
|
382
382
|
"Submit bug reports in bulk from a JSONL file (one report per line)"
|
|
383
|
-
).option("--json", "Emit the API result as JSON on stdout (for batch use)").
|
|
383
|
+
).option("--json", "Emit the API result as JSON on stdout (for batch use)").option(
|
|
384
|
+
"--agent <name>",
|
|
385
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
386
|
+
).action(
|
|
384
387
|
async (description, options) => {
|
|
385
388
|
try {
|
|
386
389
|
const { analyticsService, apiService, stateService } = appContext.services;
|
|
@@ -421,7 +424,10 @@ Categories the classifier picks from:
|
|
|
421
424
|
category: result2.category,
|
|
422
425
|
severity: parsed.severity ?? 2,
|
|
423
426
|
bulk: true,
|
|
424
|
-
deduped: result2.deduped
|
|
427
|
+
deduped: result2.deduped,
|
|
428
|
+
hasCapability: !!result2.attached.capabilityId,
|
|
429
|
+
hasRun: !!result2.attached.runId,
|
|
430
|
+
hasSearch: !!result2.attached.searchId
|
|
425
431
|
});
|
|
426
432
|
} catch (err) {
|
|
427
433
|
failed += 1;
|
|
@@ -506,9 +512,15 @@ Bulk bug-report complete: ${ok} ok, ${failed} failed`
|
|
|
506
512
|
}
|
|
507
513
|
analyticsService.capture("bug_report_submitted", {
|
|
508
514
|
category: result.category,
|
|
515
|
+
categoryOverridden: !!options.category,
|
|
509
516
|
severity: options.severity ?? 2,
|
|
510
517
|
deduped: result.deduped,
|
|
511
|
-
autoContext: useAutoContext
|
|
518
|
+
autoContext: useAutoContext,
|
|
519
|
+
hasCapability: !!result.attached.capabilityId,
|
|
520
|
+
hasRun: !!result.attached.runId,
|
|
521
|
+
hasSearch: !!result.attached.searchId,
|
|
522
|
+
hasTitle: !!options.title,
|
|
523
|
+
hasReproduction: !!options.reproduction
|
|
512
524
|
});
|
|
513
525
|
} catch (err) {
|
|
514
526
|
console.error(
|
|
@@ -1078,6 +1090,18 @@ var PaymentService = class {
|
|
|
1078
1090
|
const raw = await this.getBalanceRaw(chain);
|
|
1079
1091
|
return { amount: formatUnits(raw, 6), asset: "USDC" };
|
|
1080
1092
|
};
|
|
1093
|
+
/**
|
|
1094
|
+
* Total spendable USDC across Base and Tempo. Users (and their agents)
|
|
1095
|
+
* shouldn't need to know which chain holds funds — the CLI bridges to
|
|
1096
|
+
* Tempo on demand, so the reported balance sums both sides.
|
|
1097
|
+
*/
|
|
1098
|
+
getTotalBalance = async () => {
|
|
1099
|
+
const [baseRaw, tempoRaw] = await Promise.all([
|
|
1100
|
+
this.getBalanceRaw("base"),
|
|
1101
|
+
this.getBalanceRaw("tempo")
|
|
1102
|
+
]);
|
|
1103
|
+
return { amount: formatUnits(baseRaw + tempoRaw, 6), asset: "USDC" };
|
|
1104
|
+
};
|
|
1081
1105
|
};
|
|
1082
1106
|
|
|
1083
1107
|
// src/util/infer-schema.ts
|
|
@@ -1116,7 +1140,33 @@ var tryParseJson = (text) => {
|
|
|
1116
1140
|
}
|
|
1117
1141
|
};
|
|
1118
1142
|
|
|
1143
|
+
// src/util/redact.ts
|
|
1144
|
+
var ERROR_MAX = 500;
|
|
1145
|
+
var QUERY_MAX = 200;
|
|
1146
|
+
var redactUrl = (raw) => {
|
|
1147
|
+
try {
|
|
1148
|
+
const parsed = new URL(raw);
|
|
1149
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
1150
|
+
} catch {
|
|
1151
|
+
return raw;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
var truncateQuery = (raw) => raw.length > QUERY_MAX ? `${raw.slice(0, QUERY_MAX)}\u2026` : raw;
|
|
1155
|
+
var truncateError = (raw) => raw.length > ERROR_MAX ? `${raw.slice(0, ERROR_MAX)}\u2026` : raw;
|
|
1156
|
+
|
|
1119
1157
|
// src/commands/fetch-command.ts
|
|
1158
|
+
var isTextContentType = (contentType) => {
|
|
1159
|
+
if (!contentType) return true;
|
|
1160
|
+
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
1161
|
+
if (ct.startsWith("text/")) return true;
|
|
1162
|
+
if (ct === "application/json" || ct.endsWith("+json")) return true;
|
|
1163
|
+
if (ct === "application/xml" || ct.endsWith("+xml")) return true;
|
|
1164
|
+
if (ct === "application/javascript" || ct === "application/ecmascript") {
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
if (ct === "application/x-www-form-urlencoded") return true;
|
|
1168
|
+
return false;
|
|
1169
|
+
};
|
|
1120
1170
|
var detectPaymentRequirement = (headers, status) => {
|
|
1121
1171
|
if (status !== 402) return null;
|
|
1122
1172
|
const x402Header = headers.get("payment-required") ?? headers.get("x-payment-required");
|
|
@@ -1145,6 +1195,9 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1145
1195
|
).option(
|
|
1146
1196
|
"--json",
|
|
1147
1197
|
"Emit {runId, status, latencyMs, payment, body} as JSON on stdout (for batch/non-TTY use)"
|
|
1198
|
+
).option(
|
|
1199
|
+
"--agent <name>",
|
|
1200
|
+
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
1148
1201
|
).action(
|
|
1149
1202
|
async (url, options) => {
|
|
1150
1203
|
try {
|
|
@@ -1196,7 +1249,9 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1196
1249
|
);
|
|
1197
1250
|
}
|
|
1198
1251
|
let finalResponse;
|
|
1252
|
+
let bodyBytes;
|
|
1199
1253
|
let body = "";
|
|
1254
|
+
let bodyIsBinary = false;
|
|
1200
1255
|
let paymentMeta;
|
|
1201
1256
|
let sessionMeta;
|
|
1202
1257
|
let fetchError;
|
|
@@ -1239,11 +1294,25 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1239
1294
|
} else {
|
|
1240
1295
|
finalResponse = response;
|
|
1241
1296
|
}
|
|
1242
|
-
|
|
1297
|
+
const buf = Buffer.from(await finalResponse.arrayBuffer());
|
|
1298
|
+
bodyBytes = buf;
|
|
1299
|
+
bodyIsBinary = !isTextContentType(
|
|
1300
|
+
finalResponse.headers.get("content-type")
|
|
1301
|
+
);
|
|
1302
|
+
body = bodyIsBinary ? "" : buf.toString("utf8");
|
|
1243
1303
|
} catch (err) {
|
|
1244
1304
|
if (err instanceof SessionCloseFailedError) {
|
|
1245
1305
|
finalResponse = err.response;
|
|
1246
|
-
|
|
1306
|
+
try {
|
|
1307
|
+
const buf = Buffer.from(await err.response.arrayBuffer());
|
|
1308
|
+
bodyBytes = buf;
|
|
1309
|
+
bodyIsBinary = !isTextContentType(
|
|
1310
|
+
err.response.headers.get("content-type")
|
|
1311
|
+
);
|
|
1312
|
+
body = bodyIsBinary ? "" : buf.toString("utf8");
|
|
1313
|
+
} catch {
|
|
1314
|
+
body = "";
|
|
1315
|
+
}
|
|
1247
1316
|
paymentMeta = {
|
|
1248
1317
|
protocol: "mpp",
|
|
1249
1318
|
chain: "tempo",
|
|
@@ -1271,16 +1340,12 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1271
1340
|
}
|
|
1272
1341
|
const latencyMs = Date.now() - startTime;
|
|
1273
1342
|
if (finalResponse && !options.json) {
|
|
1274
|
-
|
|
1343
|
+
if (bodyIsBinary && bodyBytes) {
|
|
1344
|
+
process.stdout.write(bodyBytes);
|
|
1345
|
+
} else {
|
|
1346
|
+
console.log(body);
|
|
1347
|
+
}
|
|
1275
1348
|
}
|
|
1276
|
-
analyticsService.capture("fetch_executed", {
|
|
1277
|
-
url,
|
|
1278
|
-
status: finalResponse?.status,
|
|
1279
|
-
hasPayment: !!paymentMeta,
|
|
1280
|
-
paymentProtocol: paymentMeta?.protocol,
|
|
1281
|
-
paymentAmount: paymentMeta?.amount,
|
|
1282
|
-
...fetchError && { error: fetchError.message }
|
|
1283
|
-
});
|
|
1284
1349
|
if (paymentMeta) {
|
|
1285
1350
|
try {
|
|
1286
1351
|
const balance = await walletService.getBalance();
|
|
@@ -1293,6 +1358,11 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1293
1358
|
Warning: Balance is $${balance.amount} \u2014 run \`zero wallet fund\` soon.
|
|
1294
1359
|
`
|
|
1295
1360
|
);
|
|
1361
|
+
analyticsService.capture("low_balance_warning_shown", {
|
|
1362
|
+
balance: balance.amount,
|
|
1363
|
+
threshold,
|
|
1364
|
+
paymentProtocol: paymentMeta.protocol
|
|
1365
|
+
});
|
|
1296
1366
|
}
|
|
1297
1367
|
}
|
|
1298
1368
|
} catch {
|
|
@@ -1338,17 +1408,35 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1338
1408
|
);
|
|
1339
1409
|
}
|
|
1340
1410
|
}
|
|
1411
|
+
const status = finalResponse?.status;
|
|
1412
|
+
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1413
|
+
analyticsService.capture("fetch_executed", {
|
|
1414
|
+
url: redactUrl(url),
|
|
1415
|
+
status,
|
|
1416
|
+
outcome,
|
|
1417
|
+
latencyMs,
|
|
1418
|
+
hasPayment: !!paymentMeta,
|
|
1419
|
+
paymentProtocol: paymentMeta?.protocol,
|
|
1420
|
+
paymentAmount: paymentMeta?.amount,
|
|
1421
|
+
capabilityId: capabilityId ?? void 0,
|
|
1422
|
+
searchId: searchId ?? void 0,
|
|
1423
|
+
runId: runId ?? void 0,
|
|
1424
|
+
runTracked: !!runId,
|
|
1425
|
+
...fetchError && { error: truncateError(fetchError.message) }
|
|
1426
|
+
});
|
|
1341
1427
|
if (fetchError && !options.json) {
|
|
1342
1428
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1343
1429
|
}
|
|
1344
1430
|
if (options.json) {
|
|
1431
|
+
const jsonBody = !finalResponse ? null : bodyIsBinary ? (bodyBytes ?? Buffer.alloc(0)).toString("base64") : body;
|
|
1345
1432
|
console.log(
|
|
1346
1433
|
JSON.stringify({
|
|
1347
1434
|
runId,
|
|
1348
1435
|
status: finalResponse?.status ?? null,
|
|
1349
1436
|
latencyMs,
|
|
1350
1437
|
payment: paymentMeta ?? null,
|
|
1351
|
-
body:
|
|
1438
|
+
body: jsonBody,
|
|
1439
|
+
...bodyIsBinary && { bodyEncoding: "base64" },
|
|
1352
1440
|
...fetchError && { error: fetchError.message },
|
|
1353
1441
|
...skipReasons.length > 0 && {
|
|
1354
1442
|
runTrackingSkipped: skipReasons
|
|
@@ -1453,7 +1541,10 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1453
1541
|
).argument(
|
|
1454
1542
|
"<identifier>",
|
|
1455
1543
|
"Position number from search results, or a capability slug"
|
|
1456
|
-
).option("--formatted", "Output formatted trust breakdown").
|
|
1544
|
+
).option("--formatted", "Output formatted trust breakdown").option(
|
|
1545
|
+
"--agent <name>",
|
|
1546
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1547
|
+
).action(async (identifier, options) => {
|
|
1457
1548
|
try {
|
|
1458
1549
|
const { analyticsService, apiService, stateService } = appContext.services;
|
|
1459
1550
|
const position = Number.parseInt(identifier, 10);
|
|
@@ -1493,7 +1584,9 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1493
1584
|
}
|
|
1494
1585
|
analyticsService.capture("capability_viewed", {
|
|
1495
1586
|
capabilityId,
|
|
1496
|
-
|
|
1587
|
+
fromLastSearch: isPosition,
|
|
1588
|
+
...isPosition ? { position } : {},
|
|
1589
|
+
...searchId ? { searchId } : {}
|
|
1497
1590
|
});
|
|
1498
1591
|
} catch (err) {
|
|
1499
1592
|
console.error(err instanceof Error ? err.message : "Get failed");
|
|
@@ -1717,107 +1810,126 @@ var installSkills = (home) => {
|
|
|
1717
1810
|
return installed;
|
|
1718
1811
|
};
|
|
1719
1812
|
var initCommand = (appContext) => new Command5("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").action(async (options) => {
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
let
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
return false;
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
const account = privateKeyToAccount(existing.privateKey);
|
|
1813
|
+
appContext.services.analyticsService.capture("init_started", {
|
|
1814
|
+
force: options.force ?? false
|
|
1815
|
+
});
|
|
1816
|
+
let currentStep = "wallet";
|
|
1817
|
+
try {
|
|
1818
|
+
const home = homedir2();
|
|
1819
|
+
const zeroDir = join2(home, ".zero");
|
|
1820
|
+
const configPath = join2(zeroDir, "config.json");
|
|
1821
|
+
let walletCreated = false;
|
|
1822
|
+
let walletAddress = null;
|
|
1823
|
+
const walletExists = (() => {
|
|
1824
|
+
if (!existsSync2(configPath)) return false;
|
|
1825
|
+
try {
|
|
1826
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1827
|
+
return !!existing.privateKey;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
})();
|
|
1832
|
+
if (!walletExists || options.force) {
|
|
1833
|
+
const privateKey = generatePrivateKey();
|
|
1834
|
+
const account = privateKeyToAccount(privateKey);
|
|
1835
|
+
mkdirSync2(zeroDir, { recursive: true });
|
|
1836
|
+
const existing = existsSync2(configPath) ? JSON.parse(readFileSync3(configPath, "utf8")) : {};
|
|
1837
|
+
writeFileSync2(
|
|
1838
|
+
configPath,
|
|
1839
|
+
JSON.stringify(
|
|
1840
|
+
{ ...existing, privateKey, lowBalanceWarning: 1 },
|
|
1841
|
+
null,
|
|
1842
|
+
2
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
walletCreated = true;
|
|
1754
1846
|
walletAddress = account.address;
|
|
1755
|
-
|
|
1847
|
+
console.log(`Wallet address: ${account.address}`);
|
|
1848
|
+
} else {
|
|
1849
|
+
try {
|
|
1850
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1851
|
+
const account = privateKeyToAccount(existing.privateKey);
|
|
1852
|
+
walletAddress = account.address;
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1756
1855
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1856
|
+
const agentsDetected = [];
|
|
1857
|
+
const agentsWithSkills = [];
|
|
1858
|
+
let skillsError = null;
|
|
1859
|
+
let hookInstalled = false;
|
|
1860
|
+
let hookError = null;
|
|
1861
|
+
for (const tool of AGENT_TOOLS) {
|
|
1862
|
+
if (existsSync2(join2(home, tool.configDir))) {
|
|
1863
|
+
agentsDetected.push(tool.name);
|
|
1864
|
+
}
|
|
1766
1865
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1866
|
+
currentStep = "skills";
|
|
1867
|
+
try {
|
|
1868
|
+
const installed = installSkills(home);
|
|
1869
|
+
for (const entry of installed) {
|
|
1870
|
+
const toolName = entry.split(":")[0];
|
|
1871
|
+
if (toolName && !agentsWithSkills.includes(toolName)) {
|
|
1872
|
+
agentsWithSkills.push(toolName);
|
|
1873
|
+
}
|
|
1774
1874
|
}
|
|
1875
|
+
} catch (err) {
|
|
1876
|
+
skillsError = err instanceof Error ? err.message : "unknown skills error";
|
|
1775
1877
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
`
|
|
1878
|
+
currentStep = "hook";
|
|
1879
|
+
try {
|
|
1880
|
+
hookInstalled = installHook(home);
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
hookError = err instanceof Error ? err.message : "unknown hook error";
|
|
1883
|
+
}
|
|
1884
|
+
currentStep = "cleanup_scan";
|
|
1885
|
+
const conflictingSkills = findConflictingSkills(home);
|
|
1886
|
+
if (conflictingSkills.length > 0) {
|
|
1887
|
+
const skillList = conflictingSkills.map((s) => ` - ${s.tool}: ${s.skillName}`).join("\n");
|
|
1888
|
+
console.error(
|
|
1889
|
+
`
|
|
1789
1890
|
Found deprecated skills that may conflict with Zero:
|
|
1790
1891
|
${skillList}
|
|
1791
1892
|
|
|
1792
1893
|
To remove them, run: zero init cleanup`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
console.error(
|
|
1897
|
+
'Zero is ready! Run `zero search` to find capabilities.\n\nBy using Zero, you agree to our Terms of Service:\n https://zero.xyz/terms-of-service\n\nRun `zero terms` to view the full terms.\n\nTry:\n zero search "translate text to Spanish"\n zero search "generate an image"\n zero search "weather forecast"'
|
|
1793
1898
|
);
|
|
1899
|
+
currentStep = "complete";
|
|
1900
|
+
appContext.services.analyticsService.capture("wallet_initialized", {
|
|
1901
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1902
|
+
wallet_created: walletCreated,
|
|
1903
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1904
|
+
wallet_address: walletAddress,
|
|
1905
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1906
|
+
agents_detected: agentsDetected,
|
|
1907
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1908
|
+
agents_detected_count: agentsDetected.length,
|
|
1909
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1910
|
+
skills_installed: agentsWithSkills.length > 0,
|
|
1911
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1912
|
+
skills_installed_for: agentsWithSkills,
|
|
1913
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1914
|
+
skills_error: skillsError,
|
|
1915
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1916
|
+
hook_installed: hookInstalled,
|
|
1917
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1918
|
+
hook_error: hookError,
|
|
1919
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1920
|
+
conflicting_skills_found: conflictingSkills.length,
|
|
1921
|
+
force: options.force ?? false
|
|
1922
|
+
});
|
|
1923
|
+
} catch (err) {
|
|
1924
|
+
appContext.services.analyticsService.capture("init_failed", {
|
|
1925
|
+
step: currentStep,
|
|
1926
|
+
error: truncateError(
|
|
1927
|
+
err instanceof Error ? err.message : String(err)
|
|
1928
|
+
),
|
|
1929
|
+
force: options.force ?? false
|
|
1930
|
+
});
|
|
1931
|
+
throw err;
|
|
1794
1932
|
}
|
|
1795
|
-
console.error(
|
|
1796
|
-
'Zero is ready! Run `zero search` to find capabilities.\n\nBy using Zero, you agree to our Terms of Service:\n https://zero.xyz/terms-of-service\n\nRun `zero terms` to view the full terms.\n\nTry:\n zero search "translate text to Spanish"\n zero search "generate an image"\n zero search "weather forecast"'
|
|
1797
|
-
);
|
|
1798
|
-
appContext.services.analyticsService.capture("wallet_initialized", {
|
|
1799
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1800
|
-
wallet_created: walletCreated,
|
|
1801
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1802
|
-
wallet_address: walletAddress,
|
|
1803
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1804
|
-
agents_detected: agentsDetected,
|
|
1805
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1806
|
-
agents_detected_count: agentsDetected.length,
|
|
1807
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1808
|
-
skills_installed: agentsWithSkills.length > 0,
|
|
1809
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1810
|
-
skills_installed_for: agentsWithSkills,
|
|
1811
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1812
|
-
skills_error: skillsError,
|
|
1813
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1814
|
-
hook_installed: hookInstalled,
|
|
1815
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1816
|
-
hook_error: hookError,
|
|
1817
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1818
|
-
conflicting_skills_found: conflictingSkills.length,
|
|
1819
|
-
force: options.force ?? false
|
|
1820
|
-
});
|
|
1821
1933
|
}).addCommand(
|
|
1822
1934
|
new Command5("cleanup").description(
|
|
1823
1935
|
"Remove deprecated skills (zam, tempo) that conflict with Zero"
|
|
@@ -1831,7 +1943,7 @@ To remove them, run: zero init cleanup`
|
|
|
1831
1943
|
const removedList = removed.map((s) => ` - ${s}`).join("\n");
|
|
1832
1944
|
console.error(`Removed deprecated skills:
|
|
1833
1945
|
${removedList}`);
|
|
1834
|
-
appContext.services.analyticsService.capture("
|
|
1946
|
+
appContext.services.analyticsService.capture("skills_cleaned_up", {
|
|
1835
1947
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1836
1948
|
skills_removed: removed,
|
|
1837
1949
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
@@ -1875,6 +1987,9 @@ Examples:
|
|
|
1875
1987
|
).option("--success", "The capability succeeded").option("--no-success", "The capability failed").option("--accuracy <n>", "Accuracy rating (1-5)", Number.parseInt).option("--value <n>", "Value rating (1-5)", Number.parseInt).option("--reliability <n>", "Reliability rating (1-5)", Number.parseInt).option("--content <text>", "Optional review text").option(
|
|
1876
1988
|
"--from-file <path>",
|
|
1877
1989
|
"Submit reviews in bulk from a JSONL file (one review object per line: {runId, success, accuracy, value, reliability, content?})"
|
|
1990
|
+
).option(
|
|
1991
|
+
"--agent <name>",
|
|
1992
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1878
1993
|
).action(
|
|
1879
1994
|
async (runId, options) => {
|
|
1880
1995
|
try {
|
|
@@ -1896,6 +2011,10 @@ Examples:
|
|
|
1896
2011
|
analyticsService.capture("review_submitted", {
|
|
1897
2012
|
runId: parsed.runId,
|
|
1898
2013
|
success: parsed.success,
|
|
2014
|
+
accuracy: parsed.accuracy,
|
|
2015
|
+
value: parsed.value,
|
|
2016
|
+
reliability: parsed.reliability,
|
|
2017
|
+
hasContent: !!parsed.content,
|
|
1899
2018
|
bulk: true
|
|
1900
2019
|
});
|
|
1901
2020
|
} catch (err) {
|
|
@@ -1978,7 +2097,12 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
|
1978
2097
|
console.log(`Review submitted: ${result.reviewId}`);
|
|
1979
2098
|
analyticsService.capture("review_submitted", {
|
|
1980
2099
|
runId,
|
|
1981
|
-
success: options.success
|
|
2100
|
+
success: options.success,
|
|
2101
|
+
accuracy,
|
|
2102
|
+
value,
|
|
2103
|
+
reliability,
|
|
2104
|
+
hasContent: !!options.content,
|
|
2105
|
+
resolvedByCapability: !!options.capability
|
|
1982
2106
|
});
|
|
1983
2107
|
} catch (err) {
|
|
1984
2108
|
console.error(err instanceof Error ? err.message : "Review failed");
|
|
@@ -2003,6 +2127,9 @@ Examples:
|
|
|
2003
2127
|
"--limit <n>",
|
|
2004
2128
|
"Max rows (1-100, default 25)",
|
|
2005
2129
|
(v) => Number.parseInt(v, 10)
|
|
2130
|
+
).option(
|
|
2131
|
+
"--agent <name>",
|
|
2132
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
2006
2133
|
).action(
|
|
2007
2134
|
async (options) => {
|
|
2008
2135
|
try {
|
|
@@ -2084,6 +2211,9 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2084
2211
|
).option(
|
|
2085
2212
|
"--exclude-source <source>",
|
|
2086
2213
|
"Exclude results from this crawl source"
|
|
2214
|
+
).option(
|
|
2215
|
+
"--agent <name>",
|
|
2216
|
+
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
2087
2217
|
).action(
|
|
2088
2218
|
async (query, options) => {
|
|
2089
2219
|
try {
|
|
@@ -2118,8 +2248,24 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2118
2248
|
excludeSource: options.excludeSource
|
|
2119
2249
|
});
|
|
2120
2250
|
analyticsService.capture("search_executed", {
|
|
2121
|
-
query,
|
|
2122
|
-
|
|
2251
|
+
query: truncateQuery(query),
|
|
2252
|
+
queryLength: query.length,
|
|
2253
|
+
resultCount: result.capabilities.length,
|
|
2254
|
+
searchId: result.searchId,
|
|
2255
|
+
total: result.total,
|
|
2256
|
+
hasMore: result.hasMore,
|
|
2257
|
+
offset: options.offset,
|
|
2258
|
+
limit: options.limit,
|
|
2259
|
+
freeOnly: options.free ?? false,
|
|
2260
|
+
maxCost: options.maxCost,
|
|
2261
|
+
minRating: options.minRating,
|
|
2262
|
+
protocol: options.protocol,
|
|
2263
|
+
minTrust: options.minTrust,
|
|
2264
|
+
availabilityStatus: options.status,
|
|
2265
|
+
includeAll: options.all ?? false,
|
|
2266
|
+
source: options.source,
|
|
2267
|
+
excludeSource: options.excludeSource,
|
|
2268
|
+
json: options.json ?? false
|
|
2123
2269
|
});
|
|
2124
2270
|
if (options.json) {
|
|
2125
2271
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -2313,6 +2459,14 @@ var walletCommand = (appContext) => {
|
|
|
2313
2459
|
var createApp = (appContext) => {
|
|
2314
2460
|
const { analyticsService } = appContext.services;
|
|
2315
2461
|
const program = new Command11().name("zero").description("Zero CLI \u2014 Search engine and payment platform for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
|
|
2462
|
+
const agentFlag = actionCommand.opts().agent;
|
|
2463
|
+
if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
|
|
2464
|
+
analyticsService.setAgentHost(agentFlag.trim());
|
|
2465
|
+
}
|
|
2466
|
+
appContext.invocation.current = {
|
|
2467
|
+
command: actionCommand.name(),
|
|
2468
|
+
startMs: Date.now()
|
|
2469
|
+
};
|
|
2316
2470
|
analyticsService.capture("command_executed", {
|
|
2317
2471
|
command: actionCommand.name()
|
|
2318
2472
|
});
|
|
@@ -2334,7 +2488,8 @@ var createApp = (appContext) => {
|
|
|
2334
2488
|
import z4 from "zod";
|
|
2335
2489
|
var envSchema = z4.object({
|
|
2336
2490
|
ZERO_API_URL: z4.string().default("https://api.zero.xyz"),
|
|
2337
|
-
ZERO_PRIVATE_KEY: z4.string().optional()
|
|
2491
|
+
ZERO_PRIVATE_KEY: z4.string().optional(),
|
|
2492
|
+
ZERO_ENV: z4.enum(["development", "production"]).default("production")
|
|
2338
2493
|
});
|
|
2339
2494
|
var getEnv = () => {
|
|
2340
2495
|
try {
|
|
@@ -2347,6 +2502,7 @@ var getEnv = () => {
|
|
|
2347
2502
|
};
|
|
2348
2503
|
|
|
2349
2504
|
// src/app/app-services.ts
|
|
2505
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2350
2506
|
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
2351
2507
|
import { homedir as homedir4 } from "os";
|
|
2352
2508
|
import { join as join5 } from "path";
|
|
@@ -2362,9 +2518,17 @@ var POSTHOG_HOST = "https://us.i.posthog.com";
|
|
|
2362
2518
|
var AnalyticsService = class {
|
|
2363
2519
|
posthog;
|
|
2364
2520
|
distinctId;
|
|
2521
|
+
walletAddress;
|
|
2365
2522
|
cliVersion;
|
|
2523
|
+
environment;
|
|
2524
|
+
requestId;
|
|
2525
|
+
agentHost;
|
|
2366
2526
|
constructor(opts) {
|
|
2367
2527
|
this.cliVersion = opts.cliVersion;
|
|
2528
|
+
this.environment = opts.environment;
|
|
2529
|
+
this.walletAddress = opts.walletAddress;
|
|
2530
|
+
this.requestId = opts.requestId;
|
|
2531
|
+
this.agentHost = opts.agentHost;
|
|
2368
2532
|
let telemetryEnabled = true;
|
|
2369
2533
|
let persistedAnonId;
|
|
2370
2534
|
try {
|
|
@@ -2417,6 +2581,40 @@ var AnalyticsService = class {
|
|
|
2417
2581
|
});
|
|
2418
2582
|
this.posthog.on("error", () => {
|
|
2419
2583
|
});
|
|
2584
|
+
if (opts.walletAddress && persistedAnonId) {
|
|
2585
|
+
this.maybeAliasAnonToWallet(
|
|
2586
|
+
opts.configPath,
|
|
2587
|
+
persistedAnonId,
|
|
2588
|
+
opts.walletAddress
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
maybeAliasAnonToWallet(configPath, anonId, walletAddress) {
|
|
2593
|
+
if (!this.posthog) return;
|
|
2594
|
+
if (anonId === walletAddress) return;
|
|
2595
|
+
let aliasedTo;
|
|
2596
|
+
try {
|
|
2597
|
+
const config = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2598
|
+
if (typeof config.aliasedTo === "string") {
|
|
2599
|
+
aliasedTo = config.aliasedTo;
|
|
2600
|
+
}
|
|
2601
|
+
} catch {
|
|
2602
|
+
}
|
|
2603
|
+
if (aliasedTo === walletAddress) return;
|
|
2604
|
+
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
2605
|
+
try {
|
|
2606
|
+
const config = existsSync4(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
|
|
2607
|
+
writeFileSync4(
|
|
2608
|
+
configPath,
|
|
2609
|
+
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
2610
|
+
);
|
|
2611
|
+
} catch {
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
// Per-invocation override applied by the preAction hook when `--agent`
|
|
2615
|
+
// is passed. Stateless — affects only this process.
|
|
2616
|
+
setAgentHost(next) {
|
|
2617
|
+
this.agentHost = next;
|
|
2420
2618
|
}
|
|
2421
2619
|
capture(event, properties) {
|
|
2422
2620
|
if (!this.posthog) return;
|
|
@@ -2427,6 +2625,13 @@ var AnalyticsService = class {
|
|
|
2427
2625
|
source: "cli",
|
|
2428
2626
|
// biome-ignore lint/style/useNamingConvention: snake_case is standard for analytics event properties
|
|
2429
2627
|
cli_version: this.cliVersion,
|
|
2628
|
+
environment: this.environment,
|
|
2629
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2630
|
+
wallet_address: this.walletAddress,
|
|
2631
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2632
|
+
request_id: this.requestId,
|
|
2633
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2634
|
+
agent_host: this.agentHost,
|
|
2430
2635
|
...properties
|
|
2431
2636
|
}
|
|
2432
2637
|
});
|
|
@@ -2476,7 +2681,7 @@ var WalletService = class {
|
|
|
2476
2681
|
if (!this.address) return null;
|
|
2477
2682
|
if (this.balanceGetter) {
|
|
2478
2683
|
try {
|
|
2479
|
-
const result = await this.balanceGetter(
|
|
2684
|
+
const result = await this.balanceGetter();
|
|
2480
2685
|
return { amount: result.amount, asset: result.asset };
|
|
2481
2686
|
} catch {
|
|
2482
2687
|
return {
|
|
@@ -2491,6 +2696,15 @@ var WalletService = class {
|
|
|
2491
2696
|
getLowBalanceWarning = () => this.config.lowBalanceWarning;
|
|
2492
2697
|
};
|
|
2493
2698
|
|
|
2699
|
+
// src/util/agent-host.ts
|
|
2700
|
+
var detectAgentHost = (env = process.env) => {
|
|
2701
|
+
if (env.ZERO_AGENT) return env.ZERO_AGENT;
|
|
2702
|
+
if (env.CLAUDECODE === "1") return "claude-code";
|
|
2703
|
+
if (env.CURSOR_TRACE_ID) return "cursor";
|
|
2704
|
+
if (env.TERM_PROGRAM === "vscode") return "vscode";
|
|
2705
|
+
return "unknown";
|
|
2706
|
+
};
|
|
2707
|
+
|
|
2494
2708
|
// src/app/app-services.ts
|
|
2495
2709
|
var CLI_VERSION = package_default.version;
|
|
2496
2710
|
var getServices = (env) => {
|
|
@@ -2527,12 +2741,15 @@ var getServices = (env) => {
|
|
|
2527
2741
|
{
|
|
2528
2742
|
lowBalanceWarning
|
|
2529
2743
|
},
|
|
2530
|
-
paymentService.
|
|
2744
|
+
paymentService.getTotalBalance
|
|
2531
2745
|
);
|
|
2532
2746
|
const analyticsService = new AnalyticsService({
|
|
2533
2747
|
walletAddress: account?.address ?? null,
|
|
2534
2748
|
configPath,
|
|
2535
|
-
cliVersion: CLI_VERSION
|
|
2749
|
+
cliVersion: CLI_VERSION,
|
|
2750
|
+
environment: env.ZERO_ENV,
|
|
2751
|
+
requestId: randomUUID2(),
|
|
2752
|
+
agentHost: detectAgentHost()
|
|
2536
2753
|
});
|
|
2537
2754
|
return {
|
|
2538
2755
|
analyticsService,
|
|
@@ -2551,7 +2768,8 @@ var createAppContext = () => {
|
|
|
2551
2768
|
}
|
|
2552
2769
|
return {
|
|
2553
2770
|
env,
|
|
2554
|
-
services: getServices(env)
|
|
2771
|
+
services: getServices(env),
|
|
2772
|
+
invocation: { current: null }
|
|
2555
2773
|
};
|
|
2556
2774
|
};
|
|
2557
2775
|
|
|
@@ -2563,15 +2781,27 @@ var main = async () => {
|
|
|
2563
2781
|
process.exit(1);
|
|
2564
2782
|
}
|
|
2565
2783
|
const app = createApp(appContext);
|
|
2784
|
+
let caughtError = null;
|
|
2566
2785
|
try {
|
|
2567
2786
|
await app.parseAsync(process.argv);
|
|
2568
2787
|
} catch (err) {
|
|
2569
2788
|
const isCommanderExit = err instanceof Error && "exitCode" in err && err.exitCode === 0;
|
|
2570
2789
|
if (!isCommanderExit) {
|
|
2571
|
-
|
|
2790
|
+
caughtError = err instanceof Error ? err : new Error(String(err));
|
|
2791
|
+
console.error(caughtError.message);
|
|
2572
2792
|
process.exitCode = 1;
|
|
2573
2793
|
}
|
|
2574
2794
|
} finally {
|
|
2795
|
+
const invocation = appContext.invocation.current;
|
|
2796
|
+
if (invocation) {
|
|
2797
|
+
appContext.services.analyticsService.capture("command_completed", {
|
|
2798
|
+
command: invocation.command,
|
|
2799
|
+
success: !caughtError && process.exitCode !== 1,
|
|
2800
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2801
|
+
duration_ms: Date.now() - invocation.startMs,
|
|
2802
|
+
...caughtError && { error: truncateError(caughtError.message) }
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2575
2805
|
await appContext.services.analyticsService.shutdown();
|
|
2576
2806
|
}
|
|
2577
2807
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxyz/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zero": "dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"build": "tsup src/index.ts --format esm --out-dir dist --clean",
|
|
19
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",
|
|
20
20
|
"prepublishOnly": "pnpm run build",
|
|
21
|
-
"dev": "tsx src/index.ts",
|
|
22
|
-
"cli": "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
21
|
+
"dev": "ZERO_ENV=development tsx src/index.ts",
|
|
22
|
+
"cli": "ZERO_ENV=development ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
23
23
|
"test:integration": "vitest run --project integration",
|
|
24
24
|
"test:online": "vitest run --project online",
|
|
25
25
|
"test:unit": "vitest run --project unit",
|
package/skills/zero/SKILL.md
CHANGED
|
@@ -43,6 +43,16 @@ Run these commands in order. Do not skip steps.
|
|
|
43
43
|
|
|
44
44
|
**Prefer `zero init` or `zero wallet set` for local/interactive use** so the wallet persists across shells. Use `ZERO_PRIVATE_KEY` only when persisting to disk is undesirable — CI jobs, containers, one-off scripts, or secrets-manager-injected environments.
|
|
45
45
|
|
|
46
|
+
**Identify yourself (if your platform isn't auto-detected).** Zero auto-detects Claude Code, Cursor, and VSCode via their session env vars. For platforms without a signature (Claude Web, Managed Agents, Codex, OpenCode, or anything else), pass your identity on every call — either flag or env var:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
zero search --agent claude-web "translate hello world"
|
|
50
|
+
# or
|
|
51
|
+
ZERO_AGENT=claude-web zero search "translate hello world"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Both are per-invocation and stateless — there's no persistent config to go stale if you move between sandboxes. Resolution order: `--agent` flag > `ZERO_AGENT` env var > host-specific env signals (`CLAUDECODE`, `CURSOR_TRACE_ID`, `TERM_PROGRAM=vscode`) > `unknown`. Canonical names: `claude-code`, `cursor`, `vscode`, `claude-web`, `codex`, `opencode`. Any string is accepted.
|
|
55
|
+
|
|
46
56
|
**Step 3 — Fund wallet:** `zero wallet fund` — opens browser to add USDC (Base). For manual transfer: `zero wallet fund --manual`.
|
|
47
57
|
|
|
48
58
|
**Step 4 — Confirm readiness:** `zero wallet balance`
|