@zeroxyz/cli 0.0.25 → 0.0.27
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 +458 -151
- package/package.json +3 -3
- package/skills/zero/SKILL.md +10 -0
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/app.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command12 } from "commander";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@zeroxyz/cli",
|
|
9
|
-
version: "0.0.
|
|
9
|
+
version: "0.0.27",
|
|
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,10 +1140,41 @@ 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
|
|
1120
|
-
var
|
|
1121
|
-
if (
|
|
1122
|
-
const
|
|
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
|
+
};
|
|
1170
|
+
var looksLikeX402V1Body = (body) => {
|
|
1171
|
+
if (!body || typeof body !== "object") return false;
|
|
1172
|
+
const b = body;
|
|
1173
|
+
return b.x402Version === 1 && Array.isArray(b.accepts) && b.accepts.length > 0;
|
|
1174
|
+
};
|
|
1175
|
+
var detectPaymentRequirement = async (response) => {
|
|
1176
|
+
if (response.status !== 402) return null;
|
|
1177
|
+
const x402Header = response.headers.get("payment-required") ?? response.headers.get("x-payment-required");
|
|
1123
1178
|
if (x402Header) {
|
|
1124
1179
|
try {
|
|
1125
1180
|
const decoded = JSON.parse(
|
|
@@ -1130,10 +1185,20 @@ var detectPaymentRequirement = (headers, status) => {
|
|
|
1130
1185
|
return { protocol: "x402", raw: { encoded: x402Header } };
|
|
1131
1186
|
}
|
|
1132
1187
|
}
|
|
1133
|
-
const wwwAuth = headers.get("www-authenticate");
|
|
1188
|
+
const wwwAuth = response.headers.get("www-authenticate");
|
|
1134
1189
|
if (wwwAuth?.toLowerCase().includes("payment")) {
|
|
1135
1190
|
return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
|
|
1136
1191
|
}
|
|
1192
|
+
try {
|
|
1193
|
+
const text = await response.clone().text();
|
|
1194
|
+
if (text) {
|
|
1195
|
+
const parsed = JSON.parse(text);
|
|
1196
|
+
if (looksLikeX402V1Body(parsed)) {
|
|
1197
|
+
return { protocol: "x402", raw: parsed };
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
} catch {
|
|
1201
|
+
}
|
|
1137
1202
|
return { protocol: "unknown", raw: {} };
|
|
1138
1203
|
};
|
|
1139
1204
|
var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a capability URL with automatic payment handling").argument("<url>", "URL to fetch").option(
|
|
@@ -1145,6 +1210,9 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1145
1210
|
).option(
|
|
1146
1211
|
"--json",
|
|
1147
1212
|
"Emit {runId, status, latencyMs, payment, body} as JSON on stdout (for batch/non-TTY use)"
|
|
1213
|
+
).option(
|
|
1214
|
+
"--agent <name>",
|
|
1215
|
+
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
1148
1216
|
).action(
|
|
1149
1217
|
async (url, options) => {
|
|
1150
1218
|
try {
|
|
@@ -1196,17 +1264,16 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1196
1264
|
);
|
|
1197
1265
|
}
|
|
1198
1266
|
let finalResponse;
|
|
1267
|
+
let bodyBytes;
|
|
1199
1268
|
let body = "";
|
|
1269
|
+
let bodyIsBinary = false;
|
|
1200
1270
|
let paymentMeta;
|
|
1201
1271
|
let sessionMeta;
|
|
1202
1272
|
let fetchError;
|
|
1203
1273
|
try {
|
|
1204
1274
|
log(`Calling ${url}...`);
|
|
1205
1275
|
const response = await fetch(url, requestInit);
|
|
1206
|
-
const paymentReq = detectPaymentRequirement(
|
|
1207
|
-
response.headers,
|
|
1208
|
-
response.status
|
|
1209
|
-
);
|
|
1276
|
+
const paymentReq = await detectPaymentRequirement(response);
|
|
1210
1277
|
if (paymentReq) {
|
|
1211
1278
|
log(
|
|
1212
1279
|
`Payment required (${paymentReq.protocol}) \u2014 preparing payment...`
|
|
@@ -1239,11 +1306,25 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1239
1306
|
} else {
|
|
1240
1307
|
finalResponse = response;
|
|
1241
1308
|
}
|
|
1242
|
-
|
|
1309
|
+
const buf = Buffer.from(await finalResponse.arrayBuffer());
|
|
1310
|
+
bodyBytes = buf;
|
|
1311
|
+
bodyIsBinary = !isTextContentType(
|
|
1312
|
+
finalResponse.headers.get("content-type")
|
|
1313
|
+
);
|
|
1314
|
+
body = bodyIsBinary ? "" : buf.toString("utf8");
|
|
1243
1315
|
} catch (err) {
|
|
1244
1316
|
if (err instanceof SessionCloseFailedError) {
|
|
1245
1317
|
finalResponse = err.response;
|
|
1246
|
-
|
|
1318
|
+
try {
|
|
1319
|
+
const buf = Buffer.from(await err.response.arrayBuffer());
|
|
1320
|
+
bodyBytes = buf;
|
|
1321
|
+
bodyIsBinary = !isTextContentType(
|
|
1322
|
+
err.response.headers.get("content-type")
|
|
1323
|
+
);
|
|
1324
|
+
body = bodyIsBinary ? "" : buf.toString("utf8");
|
|
1325
|
+
} catch {
|
|
1326
|
+
body = "";
|
|
1327
|
+
}
|
|
1247
1328
|
paymentMeta = {
|
|
1248
1329
|
protocol: "mpp",
|
|
1249
1330
|
chain: "tempo",
|
|
@@ -1271,16 +1352,12 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1271
1352
|
}
|
|
1272
1353
|
const latencyMs = Date.now() - startTime;
|
|
1273
1354
|
if (finalResponse && !options.json) {
|
|
1274
|
-
|
|
1355
|
+
if (bodyIsBinary && bodyBytes) {
|
|
1356
|
+
process.stdout.write(bodyBytes);
|
|
1357
|
+
} else {
|
|
1358
|
+
console.log(body);
|
|
1359
|
+
}
|
|
1275
1360
|
}
|
|
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
1361
|
if (paymentMeta) {
|
|
1285
1362
|
try {
|
|
1286
1363
|
const balance = await walletService.getBalance();
|
|
@@ -1293,6 +1370,11 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1293
1370
|
Warning: Balance is $${balance.amount} \u2014 run \`zero wallet fund\` soon.
|
|
1294
1371
|
`
|
|
1295
1372
|
);
|
|
1373
|
+
analyticsService.capture("low_balance_warning_shown", {
|
|
1374
|
+
balance: balance.amount,
|
|
1375
|
+
threshold,
|
|
1376
|
+
paymentProtocol: paymentMeta.protocol
|
|
1377
|
+
});
|
|
1296
1378
|
}
|
|
1297
1379
|
}
|
|
1298
1380
|
} catch {
|
|
@@ -1338,17 +1420,35 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
1338
1420
|
);
|
|
1339
1421
|
}
|
|
1340
1422
|
}
|
|
1423
|
+
const status = finalResponse?.status;
|
|
1424
|
+
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1425
|
+
analyticsService.capture("fetch_executed", {
|
|
1426
|
+
url: redactUrl(url),
|
|
1427
|
+
status,
|
|
1428
|
+
outcome,
|
|
1429
|
+
latencyMs,
|
|
1430
|
+
hasPayment: !!paymentMeta,
|
|
1431
|
+
paymentProtocol: paymentMeta?.protocol,
|
|
1432
|
+
paymentAmount: paymentMeta?.amount,
|
|
1433
|
+
capabilityId: capabilityId ?? void 0,
|
|
1434
|
+
searchId: searchId ?? void 0,
|
|
1435
|
+
runId: runId ?? void 0,
|
|
1436
|
+
runTracked: !!runId,
|
|
1437
|
+
...fetchError && { error: truncateError(fetchError.message) }
|
|
1438
|
+
});
|
|
1341
1439
|
if (fetchError && !options.json) {
|
|
1342
1440
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1343
1441
|
}
|
|
1344
1442
|
if (options.json) {
|
|
1443
|
+
const jsonBody = !finalResponse ? null : bodyIsBinary ? (bodyBytes ?? Buffer.alloc(0)).toString("base64") : body;
|
|
1345
1444
|
console.log(
|
|
1346
1445
|
JSON.stringify({
|
|
1347
1446
|
runId,
|
|
1348
1447
|
status: finalResponse?.status ?? null,
|
|
1349
1448
|
latencyMs,
|
|
1350
1449
|
payment: paymentMeta ?? null,
|
|
1351
|
-
body:
|
|
1450
|
+
body: jsonBody,
|
|
1451
|
+
...bodyIsBinary && { bodyEncoding: "base64" },
|
|
1352
1452
|
...fetchError && { error: fetchError.message },
|
|
1353
1453
|
...skipReasons.length > 0 && {
|
|
1354
1454
|
runTrackingSkipped: skipReasons
|
|
@@ -1453,7 +1553,10 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1453
1553
|
).argument(
|
|
1454
1554
|
"<identifier>",
|
|
1455
1555
|
"Position number from search results, or a capability slug"
|
|
1456
|
-
).option("--formatted", "Output formatted trust breakdown").
|
|
1556
|
+
).option("--formatted", "Output formatted trust breakdown").option(
|
|
1557
|
+
"--agent <name>",
|
|
1558
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1559
|
+
).action(async (identifier, options) => {
|
|
1457
1560
|
try {
|
|
1458
1561
|
const { analyticsService, apiService, stateService } = appContext.services;
|
|
1459
1562
|
const position = Number.parseInt(identifier, 10);
|
|
@@ -1493,7 +1596,9 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1493
1596
|
}
|
|
1494
1597
|
analyticsService.capture("capability_viewed", {
|
|
1495
1598
|
capabilityId,
|
|
1496
|
-
|
|
1599
|
+
fromLastSearch: isPosition,
|
|
1600
|
+
...isPosition ? { position } : {},
|
|
1601
|
+
...searchId ? { searchId } : {}
|
|
1497
1602
|
});
|
|
1498
1603
|
} catch (err) {
|
|
1499
1604
|
console.error(err instanceof Error ? err.message : "Get failed");
|
|
@@ -1716,108 +1821,129 @@ var installSkills = (home) => {
|
|
|
1716
1821
|
}
|
|
1717
1822
|
return installed;
|
|
1718
1823
|
};
|
|
1719
|
-
var
|
|
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);
|
|
1824
|
+
var runInit = async (appContext, options = {}) => {
|
|
1825
|
+
appContext.services.analyticsService.capture("init_started", {
|
|
1826
|
+
force: options.force ?? false
|
|
1827
|
+
});
|
|
1828
|
+
let currentStep = "wallet";
|
|
1829
|
+
try {
|
|
1830
|
+
const home = homedir2();
|
|
1831
|
+
const zeroDir = join2(home, ".zero");
|
|
1832
|
+
const configPath = join2(zeroDir, "config.json");
|
|
1833
|
+
let walletCreated = false;
|
|
1834
|
+
let walletAddress = null;
|
|
1835
|
+
const walletExists = (() => {
|
|
1836
|
+
if (!existsSync2(configPath)) return false;
|
|
1837
|
+
try {
|
|
1838
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1839
|
+
return !!existing.privateKey;
|
|
1840
|
+
} catch {
|
|
1841
|
+
return false;
|
|
1842
|
+
}
|
|
1843
|
+
})();
|
|
1844
|
+
if (!walletExists || options.force) {
|
|
1845
|
+
const privateKey = generatePrivateKey();
|
|
1846
|
+
const account = privateKeyToAccount(privateKey);
|
|
1847
|
+
mkdirSync2(zeroDir, { recursive: true });
|
|
1848
|
+
const existing = existsSync2(configPath) ? JSON.parse(readFileSync3(configPath, "utf8")) : {};
|
|
1849
|
+
writeFileSync2(
|
|
1850
|
+
configPath,
|
|
1851
|
+
JSON.stringify(
|
|
1852
|
+
{ ...existing, privateKey, lowBalanceWarning: 1 },
|
|
1853
|
+
null,
|
|
1854
|
+
2
|
|
1855
|
+
)
|
|
1856
|
+
);
|
|
1857
|
+
walletCreated = true;
|
|
1754
1858
|
walletAddress = account.address;
|
|
1755
|
-
|
|
1859
|
+
console.log(`Wallet address: ${account.address}`);
|
|
1860
|
+
} else {
|
|
1861
|
+
try {
|
|
1862
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1863
|
+
const account = privateKeyToAccount(existing.privateKey);
|
|
1864
|
+
walletAddress = account.address;
|
|
1865
|
+
} catch {
|
|
1866
|
+
}
|
|
1756
1867
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1868
|
+
const agentsDetected = [];
|
|
1869
|
+
const agentsWithSkills = [];
|
|
1870
|
+
let skillsError = null;
|
|
1871
|
+
let hookInstalled = false;
|
|
1872
|
+
let hookError = null;
|
|
1873
|
+
for (const tool of AGENT_TOOLS) {
|
|
1874
|
+
if (existsSync2(join2(home, tool.configDir))) {
|
|
1875
|
+
agentsDetected.push(tool.name);
|
|
1876
|
+
}
|
|
1766
1877
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1878
|
+
currentStep = "skills";
|
|
1879
|
+
try {
|
|
1880
|
+
const installed = installSkills(home);
|
|
1881
|
+
for (const entry of installed) {
|
|
1882
|
+
const toolName = entry.split(":")[0];
|
|
1883
|
+
if (toolName && !agentsWithSkills.includes(toolName)) {
|
|
1884
|
+
agentsWithSkills.push(toolName);
|
|
1885
|
+
}
|
|
1774
1886
|
}
|
|
1887
|
+
} catch (err) {
|
|
1888
|
+
skillsError = err instanceof Error ? err.message : "unknown skills error";
|
|
1775
1889
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
`
|
|
1890
|
+
currentStep = "hook";
|
|
1891
|
+
try {
|
|
1892
|
+
hookInstalled = installHook(home);
|
|
1893
|
+
} catch (err) {
|
|
1894
|
+
hookError = err instanceof Error ? err.message : "unknown hook error";
|
|
1895
|
+
}
|
|
1896
|
+
currentStep = "cleanup_scan";
|
|
1897
|
+
const conflictingSkills = findConflictingSkills(home);
|
|
1898
|
+
if (conflictingSkills.length > 0) {
|
|
1899
|
+
const skillList = conflictingSkills.map((s) => ` - ${s.tool}: ${s.skillName}`).join("\n");
|
|
1900
|
+
console.error(
|
|
1901
|
+
`
|
|
1789
1902
|
Found deprecated skills that may conflict with Zero:
|
|
1790
1903
|
${skillList}
|
|
1791
1904
|
|
|
1792
1905
|
To remove them, run: zero init cleanup`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
console.error(
|
|
1909
|
+
'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
1910
|
);
|
|
1911
|
+
currentStep = "complete";
|
|
1912
|
+
appContext.services.analyticsService.capture("wallet_initialized", {
|
|
1913
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1914
|
+
wallet_created: walletCreated,
|
|
1915
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1916
|
+
wallet_address: walletAddress,
|
|
1917
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1918
|
+
agents_detected: agentsDetected,
|
|
1919
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1920
|
+
agents_detected_count: agentsDetected.length,
|
|
1921
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1922
|
+
skills_installed: agentsWithSkills.length > 0,
|
|
1923
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1924
|
+
skills_installed_for: agentsWithSkills,
|
|
1925
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1926
|
+
skills_error: skillsError,
|
|
1927
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1928
|
+
hook_installed: hookInstalled,
|
|
1929
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1930
|
+
hook_error: hookError,
|
|
1931
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1932
|
+
conflicting_skills_found: conflictingSkills.length,
|
|
1933
|
+
force: options.force ?? false
|
|
1934
|
+
});
|
|
1935
|
+
return { walletAddress, walletCreated };
|
|
1936
|
+
} catch (err) {
|
|
1937
|
+
appContext.services.analyticsService.capture("init_failed", {
|
|
1938
|
+
step: currentStep,
|
|
1939
|
+
error: truncateError(err instanceof Error ? err.message : String(err)),
|
|
1940
|
+
force: options.force ?? false
|
|
1941
|
+
});
|
|
1942
|
+
throw err;
|
|
1794
1943
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
-
});
|
|
1944
|
+
};
|
|
1945
|
+
var initCommand = (appContext) => new Command5("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").action(async (options) => {
|
|
1946
|
+
await runInit(appContext, options);
|
|
1821
1947
|
}).addCommand(
|
|
1822
1948
|
new Command5("cleanup").description(
|
|
1823
1949
|
"Remove deprecated skills (zam, tempo) that conflict with Zero"
|
|
@@ -1831,7 +1957,7 @@ To remove them, run: zero init cleanup`
|
|
|
1831
1957
|
const removedList = removed.map((s) => ` - ${s}`).join("\n");
|
|
1832
1958
|
console.error(`Removed deprecated skills:
|
|
1833
1959
|
${removedList}`);
|
|
1834
|
-
appContext.services.analyticsService.capture("
|
|
1960
|
+
appContext.services.analyticsService.capture("skills_cleaned_up", {
|
|
1835
1961
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1836
1962
|
skills_removed: removed,
|
|
1837
1963
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
@@ -1875,6 +2001,9 @@ Examples:
|
|
|
1875
2001
|
).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
2002
|
"--from-file <path>",
|
|
1877
2003
|
"Submit reviews in bulk from a JSONL file (one review object per line: {runId, success, accuracy, value, reliability, content?})"
|
|
2004
|
+
).option(
|
|
2005
|
+
"--agent <name>",
|
|
2006
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1878
2007
|
).action(
|
|
1879
2008
|
async (runId, options) => {
|
|
1880
2009
|
try {
|
|
@@ -1896,6 +2025,10 @@ Examples:
|
|
|
1896
2025
|
analyticsService.capture("review_submitted", {
|
|
1897
2026
|
runId: parsed.runId,
|
|
1898
2027
|
success: parsed.success,
|
|
2028
|
+
accuracy: parsed.accuracy,
|
|
2029
|
+
value: parsed.value,
|
|
2030
|
+
reliability: parsed.reliability,
|
|
2031
|
+
hasContent: !!parsed.content,
|
|
1899
2032
|
bulk: true
|
|
1900
2033
|
});
|
|
1901
2034
|
} catch (err) {
|
|
@@ -1978,7 +2111,12 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
|
1978
2111
|
console.log(`Review submitted: ${result.reviewId}`);
|
|
1979
2112
|
analyticsService.capture("review_submitted", {
|
|
1980
2113
|
runId,
|
|
1981
|
-
success: options.success
|
|
2114
|
+
success: options.success,
|
|
2115
|
+
accuracy,
|
|
2116
|
+
value,
|
|
2117
|
+
reliability,
|
|
2118
|
+
hasContent: !!options.content,
|
|
2119
|
+
resolvedByCapability: !!options.capability
|
|
1982
2120
|
});
|
|
1983
2121
|
} catch (err) {
|
|
1984
2122
|
console.error(err instanceof Error ? err.message : "Review failed");
|
|
@@ -2003,6 +2141,9 @@ Examples:
|
|
|
2003
2141
|
"--limit <n>",
|
|
2004
2142
|
"Max rows (1-100, default 25)",
|
|
2005
2143
|
(v) => Number.parseInt(v, 10)
|
|
2144
|
+
).option(
|
|
2145
|
+
"--agent <name>",
|
|
2146
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
2006
2147
|
).action(
|
|
2007
2148
|
async (options) => {
|
|
2008
2149
|
try {
|
|
@@ -2084,6 +2225,9 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2084
2225
|
).option(
|
|
2085
2226
|
"--exclude-source <source>",
|
|
2086
2227
|
"Exclude results from this crawl source"
|
|
2228
|
+
).option(
|
|
2229
|
+
"--agent <name>",
|
|
2230
|
+
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
2087
2231
|
).action(
|
|
2088
2232
|
async (query, options) => {
|
|
2089
2233
|
try {
|
|
@@ -2118,8 +2262,24 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2118
2262
|
excludeSource: options.excludeSource
|
|
2119
2263
|
});
|
|
2120
2264
|
analyticsService.capture("search_executed", {
|
|
2121
|
-
query,
|
|
2122
|
-
|
|
2265
|
+
query: truncateQuery(query),
|
|
2266
|
+
queryLength: query.length,
|
|
2267
|
+
resultCount: result.capabilities.length,
|
|
2268
|
+
searchId: result.searchId,
|
|
2269
|
+
total: result.total,
|
|
2270
|
+
hasMore: result.hasMore,
|
|
2271
|
+
offset: options.offset,
|
|
2272
|
+
limit: options.limit,
|
|
2273
|
+
freeOnly: options.free ?? false,
|
|
2274
|
+
maxCost: options.maxCost,
|
|
2275
|
+
minRating: options.minRating,
|
|
2276
|
+
protocol: options.protocol,
|
|
2277
|
+
minTrust: options.minTrust,
|
|
2278
|
+
availabilityStatus: options.status,
|
|
2279
|
+
includeAll: options.all ?? false,
|
|
2280
|
+
source: options.source,
|
|
2281
|
+
excludeSource: options.excludeSource,
|
|
2282
|
+
json: options.json ?? false
|
|
2123
2283
|
});
|
|
2124
2284
|
if (options.json) {
|
|
2125
2285
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -2309,10 +2469,79 @@ var walletCommand = (appContext) => {
|
|
|
2309
2469
|
return cmd;
|
|
2310
2470
|
};
|
|
2311
2471
|
|
|
2472
|
+
// src/commands/welcome-command.ts
|
|
2473
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
2474
|
+
import { homedir as homedir4 } from "os";
|
|
2475
|
+
import { join as join4 } from "path";
|
|
2476
|
+
import { Command as Command11 } from "commander";
|
|
2477
|
+
import open2 from "open";
|
|
2478
|
+
import { getAddress } from "viem";
|
|
2479
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
2480
|
+
var readPrivateKey = () => {
|
|
2481
|
+
const configPath = join4(homedir4(), ".zero", "config.json");
|
|
2482
|
+
if (!existsSync4(configPath)) return null;
|
|
2483
|
+
try {
|
|
2484
|
+
const config = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2485
|
+
if (typeof config.privateKey === "string") {
|
|
2486
|
+
return config.privateKey;
|
|
2487
|
+
}
|
|
2488
|
+
} catch {
|
|
2489
|
+
return null;
|
|
2490
|
+
}
|
|
2491
|
+
return null;
|
|
2492
|
+
};
|
|
2493
|
+
var welcomeCommand = (appContext) => new Command11("welcome").description("Claim your $5 welcome bonus.").action(async () => {
|
|
2494
|
+
const { analyticsService } = appContext.services;
|
|
2495
|
+
analyticsService.capture("welcome_started", {});
|
|
2496
|
+
try {
|
|
2497
|
+
let privateKey = readPrivateKey();
|
|
2498
|
+
if (!privateKey) {
|
|
2499
|
+
await runInit(appContext);
|
|
2500
|
+
privateKey = readPrivateKey();
|
|
2501
|
+
if (!privateKey) {
|
|
2502
|
+
throw new Error("Wallet initialization failed");
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
const account = privateKeyToAccount3(privateKey);
|
|
2506
|
+
const walletAddress = getAddress(account.address);
|
|
2507
|
+
const walletSignature = await account.signMessage({
|
|
2508
|
+
message: walletAddress
|
|
2509
|
+
});
|
|
2510
|
+
const url = new URL("/welcome", appContext.env.ZERO_WEB_URL);
|
|
2511
|
+
url.searchParams.set("wallet", walletAddress);
|
|
2512
|
+
url.searchParams.set("walletSignature", walletSignature);
|
|
2513
|
+
console.log(
|
|
2514
|
+
`Opening ${url.toString()}
|
|
2515
|
+
|
|
2516
|
+
If your browser didn't open, paste the URL above.`
|
|
2517
|
+
);
|
|
2518
|
+
await open2(url.toString());
|
|
2519
|
+
analyticsService.capture("welcome_link_opened", {
|
|
2520
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2521
|
+
wallet_address: walletAddress
|
|
2522
|
+
});
|
|
2523
|
+
} catch (err) {
|
|
2524
|
+
analyticsService.capture("welcome_failed", {
|
|
2525
|
+
error: truncateError(
|
|
2526
|
+
err instanceof Error ? err.message : String(err)
|
|
2527
|
+
)
|
|
2528
|
+
});
|
|
2529
|
+
throw err;
|
|
2530
|
+
}
|
|
2531
|
+
});
|
|
2532
|
+
|
|
2312
2533
|
// src/app.ts
|
|
2313
2534
|
var createApp = (appContext) => {
|
|
2314
2535
|
const { analyticsService } = appContext.services;
|
|
2315
|
-
const program = new
|
|
2536
|
+
const program = new Command12().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) => {
|
|
2537
|
+
const agentFlag = actionCommand.opts().agent;
|
|
2538
|
+
if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
|
|
2539
|
+
analyticsService.setAgentHost(agentFlag.trim());
|
|
2540
|
+
}
|
|
2541
|
+
appContext.invocation.current = {
|
|
2542
|
+
command: actionCommand.name(),
|
|
2543
|
+
startMs: Date.now()
|
|
2544
|
+
};
|
|
2316
2545
|
analyticsService.capture("command_executed", {
|
|
2317
2546
|
command: actionCommand.name()
|
|
2318
2547
|
});
|
|
@@ -2327,6 +2556,7 @@ var createApp = (appContext) => {
|
|
|
2327
2556
|
program.addCommand(walletCommand(appContext));
|
|
2328
2557
|
program.addCommand(configCommand(appContext));
|
|
2329
2558
|
program.addCommand(termsCommand(appContext));
|
|
2559
|
+
program.addCommand(welcomeCommand(appContext));
|
|
2330
2560
|
return program;
|
|
2331
2561
|
};
|
|
2332
2562
|
|
|
@@ -2334,7 +2564,9 @@ var createApp = (appContext) => {
|
|
|
2334
2564
|
import z4 from "zod";
|
|
2335
2565
|
var envSchema = z4.object({
|
|
2336
2566
|
ZERO_API_URL: z4.string().default("https://api.zero.xyz"),
|
|
2337
|
-
|
|
2567
|
+
ZERO_WEB_URL: z4.string().default("https://zero.xyz"),
|
|
2568
|
+
ZERO_PRIVATE_KEY: z4.string().optional(),
|
|
2569
|
+
ZERO_ENV: z4.enum(["development", "production"]).default("production")
|
|
2338
2570
|
});
|
|
2339
2571
|
var getEnv = () => {
|
|
2340
2572
|
try {
|
|
@@ -2347,14 +2579,15 @@ var getEnv = () => {
|
|
|
2347
2579
|
};
|
|
2348
2580
|
|
|
2349
2581
|
// src/app/app-services.ts
|
|
2350
|
-
import {
|
|
2351
|
-
import {
|
|
2352
|
-
import {
|
|
2353
|
-
import {
|
|
2582
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2583
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
2584
|
+
import { homedir as homedir5 } from "os";
|
|
2585
|
+
import { join as join6 } from "path";
|
|
2586
|
+
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
2354
2587
|
|
|
2355
2588
|
// src/services/analytics-service.ts
|
|
2356
2589
|
import { randomUUID } from "crypto";
|
|
2357
|
-
import { existsSync as
|
|
2590
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2358
2591
|
import { dirname as dirname2 } from "path";
|
|
2359
2592
|
import { PostHog } from "posthog-node";
|
|
2360
2593
|
var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
|
|
@@ -2362,14 +2595,22 @@ var POSTHOG_HOST = "https://us.i.posthog.com";
|
|
|
2362
2595
|
var AnalyticsService = class {
|
|
2363
2596
|
posthog;
|
|
2364
2597
|
distinctId;
|
|
2598
|
+
walletAddress;
|
|
2365
2599
|
cliVersion;
|
|
2600
|
+
environment;
|
|
2601
|
+
requestId;
|
|
2602
|
+
agentHost;
|
|
2366
2603
|
constructor(opts) {
|
|
2367
2604
|
this.cliVersion = opts.cliVersion;
|
|
2605
|
+
this.environment = opts.environment;
|
|
2606
|
+
this.walletAddress = opts.walletAddress;
|
|
2607
|
+
this.requestId = opts.requestId;
|
|
2608
|
+
this.agentHost = opts.agentHost;
|
|
2368
2609
|
let telemetryEnabled = true;
|
|
2369
2610
|
let persistedAnonId;
|
|
2370
2611
|
try {
|
|
2371
|
-
if (
|
|
2372
|
-
const config = JSON.parse(
|
|
2612
|
+
if (existsSync5(opts.configPath)) {
|
|
2613
|
+
const config = JSON.parse(readFileSync7(opts.configPath, "utf8"));
|
|
2373
2614
|
if (config.telemetry === false) {
|
|
2374
2615
|
telemetryEnabled = false;
|
|
2375
2616
|
}
|
|
@@ -2394,7 +2635,7 @@ var AnalyticsService = class {
|
|
|
2394
2635
|
try {
|
|
2395
2636
|
const dir = dirname2(opts.configPath);
|
|
2396
2637
|
mkdirSync4(dir, { recursive: true });
|
|
2397
|
-
const existing =
|
|
2638
|
+
const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync7(opts.configPath, "utf8")) : {};
|
|
2398
2639
|
writeFileSync4(
|
|
2399
2640
|
opts.configPath,
|
|
2400
2641
|
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
@@ -2417,6 +2658,40 @@ var AnalyticsService = class {
|
|
|
2417
2658
|
});
|
|
2418
2659
|
this.posthog.on("error", () => {
|
|
2419
2660
|
});
|
|
2661
|
+
if (opts.walletAddress && persistedAnonId) {
|
|
2662
|
+
this.maybeAliasAnonToWallet(
|
|
2663
|
+
opts.configPath,
|
|
2664
|
+
persistedAnonId,
|
|
2665
|
+
opts.walletAddress
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
maybeAliasAnonToWallet(configPath, anonId, walletAddress) {
|
|
2670
|
+
if (!this.posthog) return;
|
|
2671
|
+
if (anonId === walletAddress) return;
|
|
2672
|
+
let aliasedTo;
|
|
2673
|
+
try {
|
|
2674
|
+
const config = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
2675
|
+
if (typeof config.aliasedTo === "string") {
|
|
2676
|
+
aliasedTo = config.aliasedTo;
|
|
2677
|
+
}
|
|
2678
|
+
} catch {
|
|
2679
|
+
}
|
|
2680
|
+
if (aliasedTo === walletAddress) return;
|
|
2681
|
+
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
2682
|
+
try {
|
|
2683
|
+
const config = existsSync5(configPath) ? JSON.parse(readFileSync7(configPath, "utf8")) : {};
|
|
2684
|
+
writeFileSync4(
|
|
2685
|
+
configPath,
|
|
2686
|
+
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
2687
|
+
);
|
|
2688
|
+
} catch {
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
// Per-invocation override applied by the preAction hook when `--agent`
|
|
2692
|
+
// is passed. Stateless — affects only this process.
|
|
2693
|
+
setAgentHost(next) {
|
|
2694
|
+
this.agentHost = next;
|
|
2420
2695
|
}
|
|
2421
2696
|
capture(event, properties) {
|
|
2422
2697
|
if (!this.posthog) return;
|
|
@@ -2427,6 +2702,13 @@ var AnalyticsService = class {
|
|
|
2427
2702
|
source: "cli",
|
|
2428
2703
|
// biome-ignore lint/style/useNamingConvention: snake_case is standard for analytics event properties
|
|
2429
2704
|
cli_version: this.cliVersion,
|
|
2705
|
+
environment: this.environment,
|
|
2706
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2707
|
+
wallet_address: this.walletAddress,
|
|
2708
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2709
|
+
request_id: this.requestId,
|
|
2710
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2711
|
+
agent_host: this.agentHost,
|
|
2430
2712
|
...properties
|
|
2431
2713
|
}
|
|
2432
2714
|
});
|
|
@@ -2441,12 +2723,12 @@ var AnalyticsService = class {
|
|
|
2441
2723
|
};
|
|
2442
2724
|
|
|
2443
2725
|
// src/services/state-service.ts
|
|
2444
|
-
import { existsSync as
|
|
2445
|
-
import { join as
|
|
2726
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
2727
|
+
import { join as join5 } from "path";
|
|
2446
2728
|
var StateService = class {
|
|
2447
2729
|
constructor(zeroDir) {
|
|
2448
2730
|
this.zeroDir = zeroDir;
|
|
2449
|
-
this.lastSearchPath =
|
|
2731
|
+
this.lastSearchPath = join5(zeroDir, "last_search.json");
|
|
2450
2732
|
}
|
|
2451
2733
|
lastSearchPath;
|
|
2452
2734
|
saveLastSearch = (data) => {
|
|
@@ -2455,8 +2737,8 @@ var StateService = class {
|
|
|
2455
2737
|
};
|
|
2456
2738
|
loadLastSearch = () => {
|
|
2457
2739
|
try {
|
|
2458
|
-
if (!
|
|
2459
|
-
const raw =
|
|
2740
|
+
if (!existsSync6(this.lastSearchPath)) return null;
|
|
2741
|
+
const raw = readFileSync8(this.lastSearchPath, "utf8");
|
|
2460
2742
|
return JSON.parse(raw);
|
|
2461
2743
|
} catch {
|
|
2462
2744
|
return null;
|
|
@@ -2476,7 +2758,7 @@ var WalletService = class {
|
|
|
2476
2758
|
if (!this.address) return null;
|
|
2477
2759
|
if (this.balanceGetter) {
|
|
2478
2760
|
try {
|
|
2479
|
-
const result = await this.balanceGetter(
|
|
2761
|
+
const result = await this.balanceGetter();
|
|
2480
2762
|
return { amount: result.amount, asset: result.asset };
|
|
2481
2763
|
} catch {
|
|
2482
2764
|
return {
|
|
@@ -2491,16 +2773,25 @@ var WalletService = class {
|
|
|
2491
2773
|
getLowBalanceWarning = () => this.config.lowBalanceWarning;
|
|
2492
2774
|
};
|
|
2493
2775
|
|
|
2776
|
+
// src/util/agent-host.ts
|
|
2777
|
+
var detectAgentHost = (env = process.env) => {
|
|
2778
|
+
if (env.ZERO_AGENT) return env.ZERO_AGENT;
|
|
2779
|
+
if (env.CLAUDECODE === "1") return "claude-code";
|
|
2780
|
+
if (env.CURSOR_TRACE_ID) return "cursor";
|
|
2781
|
+
if (env.TERM_PROGRAM === "vscode") return "vscode";
|
|
2782
|
+
return "unknown";
|
|
2783
|
+
};
|
|
2784
|
+
|
|
2494
2785
|
// src/app/app-services.ts
|
|
2495
2786
|
var CLI_VERSION = package_default.version;
|
|
2496
2787
|
var getServices = (env) => {
|
|
2497
2788
|
let privateKey = env.ZERO_PRIVATE_KEY ? env.ZERO_PRIVATE_KEY : null;
|
|
2498
|
-
const zeroDir =
|
|
2499
|
-
const configPath =
|
|
2789
|
+
const zeroDir = join6(homedir5(), ".zero");
|
|
2790
|
+
const configPath = join6(zeroDir, "config.json");
|
|
2500
2791
|
if (!privateKey) {
|
|
2501
2792
|
try {
|
|
2502
|
-
if (
|
|
2503
|
-
const config = JSON.parse(
|
|
2793
|
+
if (existsSync7(configPath)) {
|
|
2794
|
+
const config = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
2504
2795
|
if (typeof config.privateKey === "string") {
|
|
2505
2796
|
privateKey = config.privateKey;
|
|
2506
2797
|
}
|
|
@@ -2508,11 +2799,11 @@ var getServices = (env) => {
|
|
|
2508
2799
|
} catch {
|
|
2509
2800
|
}
|
|
2510
2801
|
}
|
|
2511
|
-
const account = privateKey ?
|
|
2802
|
+
const account = privateKey ? privateKeyToAccount4(privateKey) : null;
|
|
2512
2803
|
let lowBalanceWarning = 1;
|
|
2513
2804
|
try {
|
|
2514
|
-
if (
|
|
2515
|
-
const config = JSON.parse(
|
|
2805
|
+
if (existsSync7(configPath)) {
|
|
2806
|
+
const config = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
2516
2807
|
if (typeof config.lowBalanceWarning === "number") {
|
|
2517
2808
|
lowBalanceWarning = config.lowBalanceWarning;
|
|
2518
2809
|
}
|
|
@@ -2527,12 +2818,15 @@ var getServices = (env) => {
|
|
|
2527
2818
|
{
|
|
2528
2819
|
lowBalanceWarning
|
|
2529
2820
|
},
|
|
2530
|
-
paymentService.
|
|
2821
|
+
paymentService.getTotalBalance
|
|
2531
2822
|
);
|
|
2532
2823
|
const analyticsService = new AnalyticsService({
|
|
2533
2824
|
walletAddress: account?.address ?? null,
|
|
2534
2825
|
configPath,
|
|
2535
|
-
cliVersion: CLI_VERSION
|
|
2826
|
+
cliVersion: CLI_VERSION,
|
|
2827
|
+
environment: env.ZERO_ENV,
|
|
2828
|
+
requestId: randomUUID2(),
|
|
2829
|
+
agentHost: detectAgentHost()
|
|
2536
2830
|
});
|
|
2537
2831
|
return {
|
|
2538
2832
|
analyticsService,
|
|
@@ -2551,7 +2845,8 @@ var createAppContext = () => {
|
|
|
2551
2845
|
}
|
|
2552
2846
|
return {
|
|
2553
2847
|
env,
|
|
2554
|
-
services: getServices(env)
|
|
2848
|
+
services: getServices(env),
|
|
2849
|
+
invocation: { current: null }
|
|
2555
2850
|
};
|
|
2556
2851
|
};
|
|
2557
2852
|
|
|
@@ -2563,15 +2858,27 @@ var main = async () => {
|
|
|
2563
2858
|
process.exit(1);
|
|
2564
2859
|
}
|
|
2565
2860
|
const app = createApp(appContext);
|
|
2861
|
+
let caughtError = null;
|
|
2566
2862
|
try {
|
|
2567
2863
|
await app.parseAsync(process.argv);
|
|
2568
2864
|
} catch (err) {
|
|
2569
2865
|
const isCommanderExit = err instanceof Error && "exitCode" in err && err.exitCode === 0;
|
|
2570
2866
|
if (!isCommanderExit) {
|
|
2571
|
-
|
|
2867
|
+
caughtError = err instanceof Error ? err : new Error(String(err));
|
|
2868
|
+
console.error(caughtError.message);
|
|
2572
2869
|
process.exitCode = 1;
|
|
2573
2870
|
}
|
|
2574
2871
|
} finally {
|
|
2872
|
+
const invocation = appContext.invocation.current;
|
|
2873
|
+
if (invocation) {
|
|
2874
|
+
appContext.services.analyticsService.capture("command_completed", {
|
|
2875
|
+
command: invocation.command,
|
|
2876
|
+
success: !caughtError && process.exitCode !== 1,
|
|
2877
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2878
|
+
duration_ms: Date.now() - invocation.startMs,
|
|
2879
|
+
...caughtError && { error: truncateError(caughtError.message) }
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2575
2882
|
await appContext.services.analyticsService.shutdown();
|
|
2576
2883
|
}
|
|
2577
2884
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxyz/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
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`
|