@zeroxyz/cli 0.0.37 → 0.0.38
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 +408 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { join as join8 } from "path";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@zeroxyz/cli",
|
|
10
|
-
version: "0.0.
|
|
10
|
+
version: "0.0.38",
|
|
11
11
|
type: "module",
|
|
12
12
|
bin: {
|
|
13
13
|
zero: "dist/index.js",
|
|
@@ -115,6 +115,7 @@ var capabilityResponseSchema = z.object({
|
|
|
115
115
|
responseSchema: z.record(z.string(), z.unknown()).nullable(),
|
|
116
116
|
example: z.object({ request: z.unknown(), response: z.unknown() }).nullable(),
|
|
117
117
|
tags: z.array(z.string()).nullable(),
|
|
118
|
+
exampleAgentPrompt: z.string().nullable().optional(),
|
|
118
119
|
displayCostAmount: z.string(),
|
|
119
120
|
displayCostAsset: z.string(),
|
|
120
121
|
reviewCount: z.number(),
|
|
@@ -228,7 +229,7 @@ var buildCanonicalMessage = (method, path, body, timestamp, nonce) => {
|
|
|
228
229
|
const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
|
|
229
230
|
return `${method}:${path}:${bodyHash}:${timestamp}:${nonce}`;
|
|
230
231
|
};
|
|
231
|
-
var ApiService = class {
|
|
232
|
+
var ApiService = class _ApiService {
|
|
232
233
|
constructor(baseUrl, account) {
|
|
233
234
|
this.baseUrl = baseUrl;
|
|
234
235
|
this.account = account;
|
|
@@ -236,6 +237,7 @@ var ApiService = class {
|
|
|
236
237
|
}
|
|
237
238
|
walletAddress;
|
|
238
239
|
account;
|
|
240
|
+
withAccount = (account) => new _ApiService(this.baseUrl, account);
|
|
239
241
|
signRequest = async (method, path, body) => {
|
|
240
242
|
if (!this.account) throw new Error("No private key configured");
|
|
241
243
|
const timestamp = Math.floor(Date.now() / 1e3).toString();
|
|
@@ -1120,8 +1122,9 @@ var PaymentService = class {
|
|
|
1120
1122
|
return { viemChain: tempoTestnetChain, token: PATHUSD_TEMPO };
|
|
1121
1123
|
}
|
|
1122
1124
|
};
|
|
1123
|
-
getBalanceRaw = async (chain) => {
|
|
1124
|
-
|
|
1125
|
+
getBalanceRaw = async (chain, address) => {
|
|
1126
|
+
const target = address ?? this.account?.address;
|
|
1127
|
+
if (!target) return 0n;
|
|
1125
1128
|
const { viemChain, token } = this.resolveChainConfig(chain);
|
|
1126
1129
|
const client = createPublicClient({
|
|
1127
1130
|
chain: viemChain,
|
|
@@ -1131,7 +1134,7 @@ var PaymentService = class {
|
|
|
1131
1134
|
address: token,
|
|
1132
1135
|
abi: ERC20_BALANCE_ABI,
|
|
1133
1136
|
functionName: "balanceOf",
|
|
1134
|
-
args: [
|
|
1137
|
+
args: [target]
|
|
1135
1138
|
});
|
|
1136
1139
|
return balance;
|
|
1137
1140
|
};
|
|
@@ -1151,6 +1154,13 @@ var PaymentService = class {
|
|
|
1151
1154
|
]);
|
|
1152
1155
|
return { amount: formatUnits(baseRaw + tempoRaw, 6), asset: "USDC" };
|
|
1153
1156
|
};
|
|
1157
|
+
getTotalBalanceForAddress = async (address) => {
|
|
1158
|
+
const [baseRaw, tempoRaw] = await Promise.all([
|
|
1159
|
+
this.getBalanceRaw("base", address),
|
|
1160
|
+
this.getBalanceRaw("tempo", address)
|
|
1161
|
+
]);
|
|
1162
|
+
return { amount: formatUnits(baseRaw + tempoRaw, 6), asset: "USDC" };
|
|
1163
|
+
};
|
|
1154
1164
|
};
|
|
1155
1165
|
|
|
1156
1166
|
// src/util/infer-schema.ts
|
|
@@ -1237,30 +1247,69 @@ var isJsonContentType = (contentType) => {
|
|
|
1237
1247
|
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
1238
1248
|
return ct === "application/json" || ct.endsWith("+json");
|
|
1239
1249
|
};
|
|
1240
|
-
var
|
|
1241
|
-
var
|
|
1250
|
+
var MAX_INLINE_REQUEST_BODY_BYTES = 10 * 1024 * 1024;
|
|
1251
|
+
var MAX_FILE_REQUEST_BODY_BYTES = 500 * 1024 * 1024;
|
|
1252
|
+
var UPSTREAM_ERROR_FIELDS = [
|
|
1253
|
+
"error",
|
|
1254
|
+
"message",
|
|
1255
|
+
"detail",
|
|
1256
|
+
"reason",
|
|
1257
|
+
"error_description"
|
|
1258
|
+
];
|
|
1259
|
+
var UPSTREAM_ERROR_MAX_LEN = 200;
|
|
1260
|
+
var clampUpstreamMessage = (value) => {
|
|
1261
|
+
const trimmed = value.trim();
|
|
1262
|
+
if (!trimmed) return "";
|
|
1263
|
+
return trimmed.length > UPSTREAM_ERROR_MAX_LEN ? `${trimmed.slice(0, UPSTREAM_ERROR_MAX_LEN)}\u2026` : trimmed;
|
|
1264
|
+
};
|
|
1265
|
+
var extractUpstreamErrorMessage = (body) => {
|
|
1266
|
+
const parsed = tryParseJson(body);
|
|
1267
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
1268
|
+
const clipped = clampUpstreamMessage(body);
|
|
1269
|
+
return clipped || void 0;
|
|
1270
|
+
}
|
|
1271
|
+
const record = parsed;
|
|
1272
|
+
for (const field of UPSTREAM_ERROR_FIELDS) {
|
|
1273
|
+
const value = record[field];
|
|
1274
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1275
|
+
return clampUpstreamMessage(value);
|
|
1276
|
+
}
|
|
1277
|
+
if (value && typeof value === "object") {
|
|
1278
|
+
const nested = value.message;
|
|
1279
|
+
if (typeof nested === "string" && nested.length > 0) {
|
|
1280
|
+
return clampUpstreamMessage(nested);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return void 0;
|
|
1285
|
+
};
|
|
1286
|
+
var resolveRequestBody = (rawData, readStdin2) => {
|
|
1242
1287
|
const fromFile = (spec) => {
|
|
1243
1288
|
if (spec === "@-") return readFileSync3(0);
|
|
1244
1289
|
return readFileSync3(resolvePath(spec.slice(1)));
|
|
1245
1290
|
};
|
|
1246
|
-
if (
|
|
1291
|
+
if (readStdin2 && rawData !== void 0) {
|
|
1247
1292
|
throw new Error(
|
|
1248
1293
|
"Conflicting body sources: use either --data-stdin or -d, not both."
|
|
1249
1294
|
);
|
|
1250
1295
|
}
|
|
1251
1296
|
let body;
|
|
1252
|
-
|
|
1297
|
+
let cap;
|
|
1298
|
+
if (readStdin2) {
|
|
1253
1299
|
body = readFileSync3(0);
|
|
1300
|
+
cap = MAX_FILE_REQUEST_BODY_BYTES;
|
|
1254
1301
|
} else if (rawData?.startsWith("@")) {
|
|
1255
1302
|
body = fromFile(rawData);
|
|
1303
|
+
cap = MAX_FILE_REQUEST_BODY_BYTES;
|
|
1256
1304
|
} else {
|
|
1257
1305
|
body = rawData;
|
|
1306
|
+
cap = MAX_INLINE_REQUEST_BODY_BYTES;
|
|
1258
1307
|
}
|
|
1259
1308
|
if (body !== void 0) {
|
|
1260
1309
|
const bytes = Buffer.isBuffer(body) ? body.length : Buffer.byteLength(body, "utf8");
|
|
1261
|
-
if (bytes >
|
|
1310
|
+
if (bytes > cap) {
|
|
1262
1311
|
throw new Error(
|
|
1263
|
-
`Request body is ${bytes} bytes \u2014 exceeds the ${
|
|
1312
|
+
`Request body is ${bytes} bytes \u2014 exceeds the ${cap} byte local cap. This is a CLI-side guard, not a protocol limit. Compress the payload or contact us if you need it raised.`
|
|
1264
1313
|
);
|
|
1265
1314
|
}
|
|
1266
1315
|
}
|
|
@@ -1371,14 +1420,16 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1371
1420
|
capName = cap.name;
|
|
1372
1421
|
}
|
|
1373
1422
|
const searchResult = await apiService.search({ query: capName });
|
|
1374
|
-
const
|
|
1423
|
+
const matchedEntry = searchResult.capabilities.find(
|
|
1375
1424
|
(c) => c.slug === options.capability || c.id === options.capability
|
|
1376
1425
|
);
|
|
1426
|
+
const slugFoundInResults = Boolean(matchedEntry);
|
|
1377
1427
|
stateService.saveLastSearch({
|
|
1378
1428
|
searchId: searchResult.searchId,
|
|
1379
1429
|
capabilities: searchResult.capabilities.map((c) => ({
|
|
1380
1430
|
position: c.position,
|
|
1381
1431
|
id: c.id,
|
|
1432
|
+
slug: c.slug,
|
|
1382
1433
|
url: c.url,
|
|
1383
1434
|
urlTemplate: c.urlTemplate ?? null,
|
|
1384
1435
|
displayCostAmount: c.cost.amount
|
|
@@ -1396,7 +1447,13 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1396
1447
|
slugFoundInResults
|
|
1397
1448
|
});
|
|
1398
1449
|
analyticsService.capture("capability_viewed", {
|
|
1450
|
+
// Existing field preserved as the raw --capability
|
|
1451
|
+
// input for back-compat.
|
|
1399
1452
|
capabilityId: options.capability,
|
|
1453
|
+
// New canonical identifier fields hydrated from the
|
|
1454
|
+
// resolved search result (when the slug was found).
|
|
1455
|
+
capabilityUid: matchedEntry?.id,
|
|
1456
|
+
capabilitySlug: matchedEntry?.slug,
|
|
1400
1457
|
fromLastSearch: false,
|
|
1401
1458
|
searchId: searchResult.searchId,
|
|
1402
1459
|
triggeredBy: "slug_handoff"
|
|
@@ -1413,7 +1470,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1413
1470
|
} catch (err) {
|
|
1414
1471
|
const message = err instanceof Error ? err.message : "Failed to read request body";
|
|
1415
1472
|
analyticsService.capture("fetch_error", {
|
|
1416
|
-
cliErrorClass: /exceeds the .* byte
|
|
1473
|
+
cliErrorClass: /exceeds the .* byte local cap/.test(message) ? "payload_too_large" : "schema_validation_failed",
|
|
1417
1474
|
url: redactUrl(resolvedUrl),
|
|
1418
1475
|
error: truncateError(message)
|
|
1419
1476
|
});
|
|
@@ -1455,6 +1512,8 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1455
1512
|
});
|
|
1456
1513
|
const matchCtx = explicitCapabilityCtx ?? urlCtx;
|
|
1457
1514
|
const capabilityId = options.capability ?? matchCtx?.capabilityId ?? null;
|
|
1515
|
+
const capabilityUid = matchCtx?.capabilityUid ?? null;
|
|
1516
|
+
const capabilitySlug = matchCtx?.capabilitySlug ?? null;
|
|
1458
1517
|
const searchId = matchCtx?.searchId;
|
|
1459
1518
|
const resultRank = matchCtx?.resultRank;
|
|
1460
1519
|
const matchedDisplayCostAmount = matchCtx?.displayCostAmount;
|
|
@@ -1604,10 +1663,12 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1604
1663
|
const isFailure = !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
1605
1664
|
let errorSnippetHash;
|
|
1606
1665
|
let errorSnippetLength;
|
|
1666
|
+
let upstreamErrorMessage;
|
|
1607
1667
|
if (isFailure && body && !bodyIsBinary) {
|
|
1608
1668
|
const snippet = body.slice(0, 500);
|
|
1609
1669
|
errorSnippetHash = createHash3("sha256").update(snippet).digest("hex");
|
|
1610
1670
|
errorSnippetLength = snippet.length;
|
|
1671
|
+
upstreamErrorMessage = extractUpstreamErrorMessage(body);
|
|
1611
1672
|
}
|
|
1612
1673
|
let runId = null;
|
|
1613
1674
|
if (capabilityId && apiService.walletAddress) {
|
|
@@ -1651,7 +1712,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1651
1712
|
);
|
|
1652
1713
|
}
|
|
1653
1714
|
}
|
|
1654
|
-
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1715
|
+
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status === 402 && paymentMeta ? "payment_rejected" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1655
1716
|
analyticsService.capture("fetch_executed", {
|
|
1656
1717
|
url: redactUrl(resolvedUrl),
|
|
1657
1718
|
status,
|
|
@@ -1660,6 +1721,13 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1660
1721
|
hasPayment: !!paymentMeta,
|
|
1661
1722
|
paymentProtocol: paymentMeta?.protocol,
|
|
1662
1723
|
paymentAmount: paymentMeta?.amount,
|
|
1724
|
+
// Canonical join keys — capabilitySlug is the dashboard
|
|
1725
|
+
// breakdown dimension; capabilityUid is the stable join
|
|
1726
|
+
// key with the capabilities table. capabilityId is the
|
|
1727
|
+
// back-compat alias (same value as capabilityUid when
|
|
1728
|
+
// resolved, else the raw --capability input).
|
|
1729
|
+
capabilityUid: capabilityUid ?? void 0,
|
|
1730
|
+
capabilitySlug: capabilitySlug ?? void 0,
|
|
1663
1731
|
capabilityId: capabilityId ?? void 0,
|
|
1664
1732
|
searchId: searchId ?? void 0,
|
|
1665
1733
|
resultRank: resultRank ?? void 0,
|
|
@@ -1669,15 +1737,21 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1669
1737
|
});
|
|
1670
1738
|
const isFetchFailure = Boolean(fetchError) || !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
1671
1739
|
if (isFetchFailure) {
|
|
1672
|
-
const cliErrorClass = fetchError || !finalResponse ? "network" : !apiService.walletAddress ? "auth_missing" : "unknown";
|
|
1740
|
+
const cliErrorClass = fetchError || !finalResponse ? "network" : !apiService.walletAddress ? "auth_missing" : status === 402 && paymentMeta ? "payment_rejected" : typeof status === "number" && status >= 500 ? "upstream_5xx" : typeof status === "number" && status >= 400 ? "upstream_4xx" : "unknown";
|
|
1673
1741
|
analyticsService.capture("fetch_error", {
|
|
1674
1742
|
cliErrorClass,
|
|
1743
|
+
capabilityUid: capabilityUid ?? void 0,
|
|
1744
|
+
capabilitySlug: capabilitySlug ?? void 0,
|
|
1675
1745
|
capabilityId: capabilityId ?? void 0,
|
|
1676
1746
|
searchId: searchId ?? void 0,
|
|
1677
1747
|
resultRank: resultRank ?? void 0,
|
|
1678
1748
|
url: redactUrl(resolvedUrl),
|
|
1749
|
+
status: typeof status === "number" ? status : void 0,
|
|
1750
|
+
upstreamErrorMessage,
|
|
1751
|
+
errorSnippetHash,
|
|
1752
|
+
errorSnippetLength,
|
|
1679
1753
|
error: truncateError(
|
|
1680
|
-
fetchError?.message ?? skipReasons.join("; ")
|
|
1754
|
+
fetchError?.message ?? upstreamErrorMessage ?? skipReasons.join("; ")
|
|
1681
1755
|
),
|
|
1682
1756
|
skippedRun: !runId && skipReasons.length > 0
|
|
1683
1757
|
});
|
|
@@ -1685,6 +1759,16 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1685
1759
|
if (fetchError && !options.json) {
|
|
1686
1760
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
1687
1761
|
}
|
|
1762
|
+
if (finalResponse && !fetchError && !options.json && typeof status === "number" && (status < 200 || status >= 300)) {
|
|
1763
|
+
const messageSuffix = upstreamErrorMessage ? ` \u2014 ${upstreamErrorMessage}` : "";
|
|
1764
|
+
if (status === 402 && paymentMeta) {
|
|
1765
|
+
console.error(
|
|
1766
|
+
` Upstream returned 402 after payment was sent \u2014 the seller's facilitator rejected the credential${messageSuffix}`
|
|
1767
|
+
);
|
|
1768
|
+
} else {
|
|
1769
|
+
console.error(` Upstream returned ${status}${messageSuffix}`);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1688
1772
|
if (options.json) {
|
|
1689
1773
|
const responseStatus = finalResponse?.status ?? null;
|
|
1690
1774
|
const ok = responseStatus !== null && responseStatus >= 200 && responseStatus < 300;
|
|
@@ -1765,6 +1849,19 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
|
|
|
1765
1849
|
|
|
1766
1850
|
// src/commands/get-command.ts
|
|
1767
1851
|
import { Command as Command4 } from "commander";
|
|
1852
|
+
|
|
1853
|
+
// src/util/format-price.ts
|
|
1854
|
+
var centsToDollars = (cents) => {
|
|
1855
|
+
const value = Number.parseFloat(cents) / 100;
|
|
1856
|
+
if (!Number.isFinite(value) || value === 0) return "0.00";
|
|
1857
|
+
if (value < 1e-4) return value.toFixed(6);
|
|
1858
|
+
if (value < 1e-3) return value.toFixed(5);
|
|
1859
|
+
if (value < 0.01) return value.toFixed(4);
|
|
1860
|
+
if (value < 1) return value.toFixed(3);
|
|
1861
|
+
return value.toFixed(2);
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
// src/commands/get-command.ts
|
|
1768
1865
|
var formatRelativeTimestamp = (iso) => {
|
|
1769
1866
|
if (!iso) return "never";
|
|
1770
1867
|
const then = new Date(iso).getTime();
|
|
@@ -1781,12 +1878,6 @@ var formatRelativeTimestamp = (iso) => {
|
|
|
1781
1878
|
if (diffMo < 12) return `${diffMo}mo ago`;
|
|
1782
1879
|
return `${Math.round(diffMo / 12)}y ago`;
|
|
1783
1880
|
};
|
|
1784
|
-
var centsToDollars = (cents) => {
|
|
1785
|
-
const value = Number.parseFloat(cents) / 100;
|
|
1786
|
-
if (value < 0.01) return value.toFixed(4);
|
|
1787
|
-
if (value < 1) return value.toFixed(3);
|
|
1788
|
-
return value.toFixed(2);
|
|
1789
|
-
};
|
|
1790
1881
|
var formatCost = (capability) => {
|
|
1791
1882
|
const lines = [];
|
|
1792
1883
|
const observed = capability.priceObserved;
|
|
@@ -1796,6 +1887,10 @@ var formatCost = (capability) => {
|
|
|
1796
1887
|
const median = observed.medianCents ? centsToDollars(observed.medianCents) : null;
|
|
1797
1888
|
const detail = median ? `median $${median}, n=${observed.sampleCount}` : `n=${observed.sampleCount}`;
|
|
1798
1889
|
lines.push(` Cost: $${min}\u2013$${max}/call (${detail})`);
|
|
1890
|
+
} else if (capability.displayCostAmount === "0") {
|
|
1891
|
+
lines.push(` Cost: Free`);
|
|
1892
|
+
} else if (capability.displayCostAmount === "unknown") {
|
|
1893
|
+
lines.push(` Cost: variable pricing`);
|
|
1799
1894
|
} else {
|
|
1800
1895
|
lines.push(` Cost: $${capability.displayCostAmount}/call`);
|
|
1801
1896
|
}
|
|
@@ -1903,6 +1998,12 @@ var formatCapability = (capability) => {
|
|
|
1903
1998
|
lines.push(
|
|
1904
1999
|
` Last successful run: ${formatRelativeTimestamp(capability.lastSuccessfullyRanAt)}`
|
|
1905
2000
|
);
|
|
2001
|
+
if (capability.exampleAgentPrompt?.trim()) {
|
|
2002
|
+
lines.push(" Example prompt:");
|
|
2003
|
+
for (const promptLine of capability.exampleAgentPrompt.split("\n")) {
|
|
2004
|
+
lines.push(` ${promptLine}`);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
1906
2007
|
lines.push(...buildTryItExample(capability));
|
|
1907
2008
|
return lines.join("\n");
|
|
1908
2009
|
};
|
|
@@ -1955,7 +2056,15 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
1955
2056
|
console.log(JSON.stringify(capability, null, 2));
|
|
1956
2057
|
}
|
|
1957
2058
|
analyticsService.capture("capability_viewed", {
|
|
2059
|
+
// Existing field — raw user input (uid, slug, or position
|
|
2060
|
+
// resolved to uid via the last-search cache). Preserved
|
|
2061
|
+
// for back-compat with existing dashboards.
|
|
1958
2062
|
capabilityId,
|
|
2063
|
+
// New canonical identifier fields read off the API response,
|
|
2064
|
+
// so they are always populated regardless of how the user
|
|
2065
|
+
// referenced the capability.
|
|
2066
|
+
capabilityUid: capability.uid,
|
|
2067
|
+
capabilitySlug: capability.slug,
|
|
1959
2068
|
fromLastSearch: isPosition,
|
|
1960
2069
|
...isPosition ? { position } : {},
|
|
1961
2070
|
...searchId ? { searchId } : {}
|
|
@@ -2089,7 +2198,12 @@ var printReadyFooter = () => {
|
|
|
2089
2198
|
const lines = [
|
|
2090
2199
|
"",
|
|
2091
2200
|
` ${color.boldGreen("Zero is ready!")} Zero works best with an AI agent.`,
|
|
2092
|
-
|
|
2201
|
+
"",
|
|
2202
|
+
" First, claim your $5 welcome bonus \u2014 run this now, before opening your agent:",
|
|
2203
|
+
"",
|
|
2204
|
+
` ${color.cyan("zero welcome")}`,
|
|
2205
|
+
"",
|
|
2206
|
+
` Then open ${color.boldRed("Claude Code")}, Codex, Cursor, Blackbox, or your agent of choice`,
|
|
2093
2207
|
" and try this prompt to get started:",
|
|
2094
2208
|
"",
|
|
2095
2209
|
` ${color.cyan("What is zero and how do I use it?")}`,
|
|
@@ -2552,12 +2666,13 @@ To remove them, run: ${color.cyan("zero init cleanup")}`
|
|
|
2552
2666
|
sectionDivider();
|
|
2553
2667
|
console.error(printReadyFooter());
|
|
2554
2668
|
currentStep = "complete";
|
|
2669
|
+
if (walletAddress) {
|
|
2670
|
+
appContext.services.analyticsService.setWalletAddress(walletAddress);
|
|
2671
|
+
}
|
|
2555
2672
|
appContext.services.analyticsService.capture("wallet_initialized", {
|
|
2556
2673
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2557
2674
|
wallet_created: walletCreated,
|
|
2558
2675
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2559
|
-
wallet_address: walletAddress,
|
|
2560
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2561
2676
|
agents_detected: agentsDetected,
|
|
2562
2677
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2563
2678
|
agents_detected_count: agentsDetected.length,
|
|
@@ -2901,7 +3016,8 @@ var formatSearchResults = (results) => {
|
|
|
2901
3016
|
const displayDescription = r.whatItDoes ?? r.description;
|
|
2902
3017
|
const ratingBadge = formatRatingBadge(r.rating);
|
|
2903
3018
|
const statusBadge = formatStatusBadge(r.displayStatus);
|
|
2904
|
-
|
|
3019
|
+
const costLabel = r.cost.amount === "0" ? "Free" : r.cost.amount === "unknown" ? "variable pricing" : `$${r.cost.amount}/call`;
|
|
3020
|
+
return ` ${r.position}. ${displayName} \u2014 ${costLabel} \u2014 ${ratingBadge}${statusBadge}
|
|
2905
3021
|
"${displayDescription}"`;
|
|
2906
3022
|
}).join("\n");
|
|
2907
3023
|
};
|
|
@@ -2972,8 +3088,8 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2972
3088
|
protocol: options.protocol,
|
|
2973
3089
|
availabilityStatus: options.status,
|
|
2974
3090
|
includeAll: options.all ?? false,
|
|
2975
|
-
|
|
2976
|
-
|
|
3091
|
+
listingSource: options.source,
|
|
3092
|
+
excludeListingSource: options.excludeSource,
|
|
2977
3093
|
json: options.json ?? false
|
|
2978
3094
|
});
|
|
2979
3095
|
if (options.json) {
|
|
@@ -2995,6 +3111,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
2995
3111
|
capabilities: result.capabilities.map((c) => ({
|
|
2996
3112
|
position: c.position,
|
|
2997
3113
|
id: c.id,
|
|
3114
|
+
slug: c.slug,
|
|
2998
3115
|
url: c.url,
|
|
2999
3116
|
urlTemplate: c.urlTemplate ?? null,
|
|
3000
3117
|
displayCostAmount: c.cost.amount
|
|
@@ -3045,9 +3162,43 @@ import { homedir as homedir3 } from "os";
|
|
|
3045
3162
|
import { join as join3 } from "path";
|
|
3046
3163
|
import { Command as Command10 } from "commander";
|
|
3047
3164
|
import open from "open";
|
|
3048
|
-
import {
|
|
3049
|
-
|
|
3050
|
-
|
|
3165
|
+
import { isAddress } from "viem";
|
|
3166
|
+
import { generatePrivateKey as generatePrivateKey2, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
3167
|
+
|
|
3168
|
+
// src/util/stdin.ts
|
|
3169
|
+
var readStdin = async () => {
|
|
3170
|
+
const chunks = [];
|
|
3171
|
+
for await (const chunk of process.stdin) {
|
|
3172
|
+
chunks.push(chunk);
|
|
3173
|
+
}
|
|
3174
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3175
|
+
};
|
|
3176
|
+
|
|
3177
|
+
// src/commands/wallet-command.ts
|
|
3178
|
+
var PRIVATE_KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
|
|
3179
|
+
var parseProvider = (raw) => raw === "stripe" ? "stripe" : "coinbase";
|
|
3180
|
+
var walletBalanceCommand = (appContext) => new Command10("balance").description("Show wallet balance").option(
|
|
3181
|
+
"--address <address>",
|
|
3182
|
+
"Check balance for an arbitrary address (no key needed). Useful with `zero wallet generate` \u2014 verify funds arrived without setting the wallet as your default."
|
|
3183
|
+
).action(async (options) => {
|
|
3184
|
+
const { walletService, paymentService } = appContext.services;
|
|
3185
|
+
if (options.address) {
|
|
3186
|
+
if (!isAddress(options.address)) {
|
|
3187
|
+
console.error("Invalid address (expected 0x + 40 hex chars).");
|
|
3188
|
+
process.exitCode = 1;
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
try {
|
|
3192
|
+
const balance2 = await paymentService.getTotalBalanceForAddress(
|
|
3193
|
+
options.address
|
|
3194
|
+
);
|
|
3195
|
+
console.log(`${balance2.amount} ${balance2.asset}`);
|
|
3196
|
+
} catch {
|
|
3197
|
+
console.error("Failed to fetch balance");
|
|
3198
|
+
process.exitCode = 1;
|
|
3199
|
+
}
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3051
3202
|
const balance = await walletService.getBalance();
|
|
3052
3203
|
if (balance === null) {
|
|
3053
3204
|
console.error("No wallet configured. Run `zero init` first.");
|
|
@@ -3061,6 +3212,20 @@ var walletBalanceCommand = (appContext) => new Command10("balance").description(
|
|
|
3061
3212
|
}
|
|
3062
3213
|
console.log(`${balance.amount} ${balance.asset}`);
|
|
3063
3214
|
});
|
|
3215
|
+
var readPrivateKeyFromStdin = async () => {
|
|
3216
|
+
if (process.stdin.isTTY) {
|
|
3217
|
+
console.error(
|
|
3218
|
+
"Expected a private key on stdin. Example:\n zero wallet generate --json | jq -r .privateKey | zero wallet fund --key-stdin"
|
|
3219
|
+
);
|
|
3220
|
+
return null;
|
|
3221
|
+
}
|
|
3222
|
+
const raw = (await readStdin()).trim();
|
|
3223
|
+
if (!PRIVATE_KEY_PATTERN.test(raw)) {
|
|
3224
|
+
console.error("Invalid private key on stdin (expected 0x + 64 hex chars).");
|
|
3225
|
+
return null;
|
|
3226
|
+
}
|
|
3227
|
+
return raw;
|
|
3228
|
+
};
|
|
3064
3229
|
var walletFundCommand = (appContext) => new Command10("fund").description("Fund your wallet").argument("[amount]", "Amount to fund in USDC").option("--manual", "Show wallet address for manual transfer").option(
|
|
3065
3230
|
"--no-open",
|
|
3066
3231
|
"Print the funding URL instead of opening a browser (for agents \u2014 funding links are one-time use, hand the URL to the user)"
|
|
@@ -3068,9 +3233,55 @@ var walletFundCommand = (appContext) => new Command10("fund").description("Fund
|
|
|
3068
3233
|
"--use <provider>",
|
|
3069
3234
|
"Onramp provider: coinbase or stripe",
|
|
3070
3235
|
"coinbase"
|
|
3236
|
+
).option(
|
|
3237
|
+
"--key-stdin",
|
|
3238
|
+
"Read a private key from stdin and mint a funding URL for that wallet instead of your configured one. Useful with `zero wallet generate`. Does not modify your config."
|
|
3071
3239
|
).action(
|
|
3072
3240
|
async (amount, options) => {
|
|
3073
|
-
const { analyticsService, walletService } = appContext.services;
|
|
3241
|
+
const { analyticsService, apiService, walletService } = appContext.services;
|
|
3242
|
+
const provider = parseProvider(options.use);
|
|
3243
|
+
if (options.keyStdin) {
|
|
3244
|
+
const key = await readPrivateKeyFromStdin();
|
|
3245
|
+
if (!key) {
|
|
3246
|
+
process.exitCode = 1;
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
const altAccount = privateKeyToAccount2(key);
|
|
3250
|
+
const altApi = apiService.withAccount(altAccount);
|
|
3251
|
+
if (options.manual) {
|
|
3252
|
+
console.log(`Send USDC (Base) to:
|
|
3253
|
+
${altAccount.address}`);
|
|
3254
|
+
analyticsService.capture("wallet_funded", {
|
|
3255
|
+
method: "manual",
|
|
3256
|
+
amount,
|
|
3257
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3258
|
+
alt_wallet: true
|
|
3259
|
+
});
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
const url2 = await altApi.getFundingUrl(amount, provider);
|
|
3263
|
+
if (!url2) {
|
|
3264
|
+
console.log(`Send USDC (Base) to:
|
|
3265
|
+
${altAccount.address}`);
|
|
3266
|
+
console.error(
|
|
3267
|
+
"Could not get funding URL. Send USDC (Base) manually."
|
|
3268
|
+
);
|
|
3269
|
+
process.exitCode = 1;
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
console.log(
|
|
3273
|
+
"Funding URL (one-time use \u2014 open it in a browser to fund):"
|
|
3274
|
+
);
|
|
3275
|
+
console.log(url2);
|
|
3276
|
+
console.log(`Wallet address: ${altAccount.address}`);
|
|
3277
|
+
analyticsService.capture("wallet_funded", {
|
|
3278
|
+
method: "url",
|
|
3279
|
+
amount,
|
|
3280
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3281
|
+
alt_wallet: true
|
|
3282
|
+
});
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3074
3285
|
const address = walletService.getAddress();
|
|
3075
3286
|
if (!address) {
|
|
3076
3287
|
console.error("No wallet configured. Run `zero init` first.");
|
|
@@ -3086,11 +3297,7 @@ ${address}`);
|
|
|
3086
3297
|
});
|
|
3087
3298
|
return;
|
|
3088
3299
|
}
|
|
3089
|
-
const
|
|
3090
|
-
const url = await appContext.services.apiService.getFundingUrl(
|
|
3091
|
-
amount,
|
|
3092
|
-
provider
|
|
3093
|
-
);
|
|
3300
|
+
const url = await apiService.getFundingUrl(amount, provider);
|
|
3094
3301
|
if (url) {
|
|
3095
3302
|
if (options.open) {
|
|
3096
3303
|
await open(url);
|
|
@@ -3171,18 +3378,95 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
|
|
|
3171
3378
|
)
|
|
3172
3379
|
);
|
|
3173
3380
|
console.log(`Wallet set: ${account.address}`);
|
|
3381
|
+
analyticsService.setWalletAddress(account.address);
|
|
3174
3382
|
analyticsService.capture("wallet_set", {
|
|
3175
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3176
|
-
wallet_address: account.address,
|
|
3177
3383
|
force: options.force ?? false
|
|
3178
3384
|
});
|
|
3179
3385
|
});
|
|
3386
|
+
var walletGenerateCommand = (appContext) => new Command10("generate").description(
|
|
3387
|
+
"Generate a fresh wallet (address + private key) without touching your configured wallet"
|
|
3388
|
+
).option("--json", "Emit { address, privateKey } as JSON").option(
|
|
3389
|
+
"--fund",
|
|
3390
|
+
"Also mint a one-time onramp URL for the new wallet (does not auto-open)"
|
|
3391
|
+
).option("--amount <amount>", "With --fund: USDC amount to pre-fill").option(
|
|
3392
|
+
"--use <provider>",
|
|
3393
|
+
"With --fund: onramp provider (coinbase | stripe)",
|
|
3394
|
+
"coinbase"
|
|
3395
|
+
).action(
|
|
3396
|
+
async (options) => {
|
|
3397
|
+
const { analyticsService, apiService } = appContext.services;
|
|
3398
|
+
const privateKey = generatePrivateKey2();
|
|
3399
|
+
const account = privateKeyToAccount2(privateKey);
|
|
3400
|
+
let fundingUrl = null;
|
|
3401
|
+
if (options.fund) {
|
|
3402
|
+
const provider = parseProvider(options.use);
|
|
3403
|
+
fundingUrl = await apiService.withAccount(account).getFundingUrl(options.amount, provider);
|
|
3404
|
+
}
|
|
3405
|
+
if (options.json) {
|
|
3406
|
+
const payload = {
|
|
3407
|
+
address: account.address,
|
|
3408
|
+
privateKey
|
|
3409
|
+
};
|
|
3410
|
+
if (options.fund) {
|
|
3411
|
+
payload.fundingUrl = fundingUrl;
|
|
3412
|
+
}
|
|
3413
|
+
console.log(JSON.stringify(payload));
|
|
3414
|
+
} else {
|
|
3415
|
+
console.log("");
|
|
3416
|
+
console.log(
|
|
3417
|
+
"New wallet generated. Save these \u2014 they are NOT stored anywhere."
|
|
3418
|
+
);
|
|
3419
|
+
console.log("");
|
|
3420
|
+
console.log(
|
|
3421
|
+
" WARNING: Anyone with this private key can spend any funds sent"
|
|
3422
|
+
);
|
|
3423
|
+
console.log(
|
|
3424
|
+
" to the address. Don't paste it into chat, commit it to git, or"
|
|
3425
|
+
);
|
|
3426
|
+
console.log(" share your screen with it visible.");
|
|
3427
|
+
console.log("");
|
|
3428
|
+
console.log(` Address: ${account.address}`);
|
|
3429
|
+
console.log(` Private key: ${privateKey}`);
|
|
3430
|
+
console.log("");
|
|
3431
|
+
if (options.fund) {
|
|
3432
|
+
if (fundingUrl) {
|
|
3433
|
+
console.log("Funding URL (one-time use):");
|
|
3434
|
+
console.log(` ${fundingUrl}`);
|
|
3435
|
+
console.log("");
|
|
3436
|
+
} else {
|
|
3437
|
+
console.log(
|
|
3438
|
+
"Could not mint a funding URL \u2014 fund the address manually with USDC (Base)."
|
|
3439
|
+
);
|
|
3440
|
+
console.log("");
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
console.log("Next steps:");
|
|
3444
|
+
if (!options.fund) {
|
|
3445
|
+
console.log(" - Fund the address with USDC (Base) before use.");
|
|
3446
|
+
}
|
|
3447
|
+
console.log(
|
|
3448
|
+
` - Use it for a single command: ZERO_PRIVATE_KEY=${privateKey} zero <command>`
|
|
3449
|
+
);
|
|
3450
|
+
console.log(
|
|
3451
|
+
" - Or make it your default wallet: zero wallet set <privateKey> --force"
|
|
3452
|
+
);
|
|
3453
|
+
console.log("");
|
|
3454
|
+
}
|
|
3455
|
+
analyticsService.capture("wallet_generated", {
|
|
3456
|
+
format: options.json ? "json" : "text",
|
|
3457
|
+
funded: options.fund ?? false,
|
|
3458
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3459
|
+
funding_url_minted: options.fund ? fundingUrl !== null : false
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
);
|
|
3180
3463
|
var walletCommand = (appContext) => {
|
|
3181
3464
|
const cmd = new Command10("wallet").description("Manage your wallet");
|
|
3182
3465
|
cmd.addCommand(walletBalanceCommand(appContext));
|
|
3183
3466
|
cmd.addCommand(walletFundCommand(appContext));
|
|
3184
3467
|
cmd.addCommand(walletAddressCommand(appContext));
|
|
3185
3468
|
cmd.addCommand(walletSetCommand(appContext));
|
|
3469
|
+
cmd.addCommand(walletGenerateCommand(appContext));
|
|
3186
3470
|
return cmd;
|
|
3187
3471
|
};
|
|
3188
3472
|
|
|
@@ -3207,9 +3491,37 @@ var readPrivateKey = () => {
|
|
|
3207
3491
|
}
|
|
3208
3492
|
return null;
|
|
3209
3493
|
};
|
|
3494
|
+
var linuxInstallHint = () => {
|
|
3495
|
+
return [
|
|
3496
|
+
" On Debian/Ubuntu: sudo apt install xdg-utils",
|
|
3497
|
+
" On RHEL/Fedora: sudo dnf install xdg-utils"
|
|
3498
|
+
].join("\n");
|
|
3499
|
+
};
|
|
3500
|
+
var printManualFallback = (url) => {
|
|
3501
|
+
const lines = [
|
|
3502
|
+
"",
|
|
3503
|
+
"\u26A0\uFE0F Couldn't auto-open your browser (this is common on WSL, SSH, or",
|
|
3504
|
+
" containers \u2014 no xdg-open installed).",
|
|
3505
|
+
"",
|
|
3506
|
+
"Copy and paste this URL into your browser to claim:",
|
|
3507
|
+
"",
|
|
3508
|
+
` ${url}`,
|
|
3509
|
+
""
|
|
3510
|
+
];
|
|
3511
|
+
if (process.platform === "linux") {
|
|
3512
|
+
lines.push(
|
|
3513
|
+
"Tip: install xdg-utils so 'zero welcome' can open the browser for you:",
|
|
3514
|
+
linuxInstallHint(),
|
|
3515
|
+
""
|
|
3516
|
+
);
|
|
3517
|
+
}
|
|
3518
|
+
console.log(lines.join("\n"));
|
|
3519
|
+
};
|
|
3210
3520
|
var welcomeCommand = (appContext) => new Command11("welcome").description("Claim your $5 welcome bonus.").action(async () => {
|
|
3211
3521
|
const { analyticsService } = appContext.services;
|
|
3212
3522
|
analyticsService.capture("welcome_started", {});
|
|
3523
|
+
let walletAddress;
|
|
3524
|
+
let url;
|
|
3213
3525
|
try {
|
|
3214
3526
|
let privateKey = readPrivateKey();
|
|
3215
3527
|
if (!privateKey) {
|
|
@@ -3220,30 +3532,45 @@ var welcomeCommand = (appContext) => new Command11("welcome").description("Claim
|
|
|
3220
3532
|
}
|
|
3221
3533
|
}
|
|
3222
3534
|
const account = privateKeyToAccount3(privateKey);
|
|
3223
|
-
|
|
3535
|
+
walletAddress = getAddress(account.address);
|
|
3224
3536
|
const walletSignature = await account.signMessage({
|
|
3225
3537
|
message: walletAddress
|
|
3226
3538
|
});
|
|
3227
|
-
|
|
3539
|
+
url = new URL("/welcome", appContext.env.ZERO_WEB_URL);
|
|
3228
3540
|
url.searchParams.set("wallet", walletAddress);
|
|
3229
3541
|
url.searchParams.set("walletSignature", walletSignature);
|
|
3542
|
+
} catch (err) {
|
|
3543
|
+
analyticsService.capture("welcome_failed", {
|
|
3544
|
+
error: truncateError(
|
|
3545
|
+
err instanceof Error ? err.message : String(err)
|
|
3546
|
+
)
|
|
3547
|
+
});
|
|
3548
|
+
throw err;
|
|
3549
|
+
}
|
|
3550
|
+
const urlString = url.toString();
|
|
3551
|
+
analyticsService.setWalletAddress(walletAddress);
|
|
3552
|
+
try {
|
|
3553
|
+
await open2(urlString);
|
|
3230
3554
|
console.log(
|
|
3231
|
-
`Opening ${
|
|
3555
|
+
`Opening ${urlString}
|
|
3232
3556
|
|
|
3233
3557
|
If your browser didn't open, paste the URL above.`
|
|
3234
3558
|
);
|
|
3235
|
-
await open2(url.toString());
|
|
3236
3559
|
analyticsService.capture("welcome_link_opened", {
|
|
3237
3560
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3238
|
-
|
|
3561
|
+
open_method: "auto"
|
|
3239
3562
|
});
|
|
3240
3563
|
} catch (err) {
|
|
3241
|
-
|
|
3242
|
-
|
|
3564
|
+
printManualFallback(urlString);
|
|
3565
|
+
analyticsService.capture("welcome_link_opened", {
|
|
3566
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3567
|
+
open_method: "manual_copy",
|
|
3568
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3569
|
+
open_error: truncateError(
|
|
3243
3570
|
err instanceof Error ? err.message : String(err)
|
|
3244
|
-
)
|
|
3571
|
+
),
|
|
3572
|
+
platform: process.platform
|
|
3245
3573
|
});
|
|
3246
|
-
throw err;
|
|
3247
3574
|
}
|
|
3248
3575
|
});
|
|
3249
3576
|
|
|
@@ -3414,12 +3741,30 @@ var AnalyticsService = class {
|
|
|
3414
3741
|
setAgentHost(next) {
|
|
3415
3742
|
this.agentHost = next;
|
|
3416
3743
|
}
|
|
3744
|
+
// Per-invocation override for the `wallet_address` super-prop. Used by
|
|
3745
|
+
// commands that mint or load a wallet mid-process (init, wallet set,
|
|
3746
|
+
// welcome) so subsequent captures carry the now-known address without
|
|
3747
|
+
// needing to pass it as a per-event prop (which would collide with the
|
|
3748
|
+
// super-prop — see ZERO-43). distinctId is intentionally NOT changed:
|
|
3749
|
+
// the anon→wallet identity merge happens on the next CLI invocation
|
|
3750
|
+
// via PostHog alias, when AnalyticsService is constructed with both
|
|
3751
|
+
// the persisted anonId and the new walletAddress.
|
|
3752
|
+
setWalletAddress(walletAddress) {
|
|
3753
|
+
this.walletAddress = walletAddress;
|
|
3754
|
+
}
|
|
3417
3755
|
capture(event, properties) {
|
|
3418
3756
|
if (!this.posthog) return;
|
|
3419
3757
|
this.posthog.capture({
|
|
3420
3758
|
distinctId: this.distinctId,
|
|
3421
3759
|
event,
|
|
3760
|
+
// Super-props are spread LAST so they always win against a same-
|
|
3761
|
+
// named per-event prop (see ZERO-43: a `source: undefined` on
|
|
3762
|
+
// `search_executed` was silently clobbering the `cli` surface stamp).
|
|
3763
|
+
// The reserved super-prop names are enforced by the events registry
|
|
3764
|
+
// test in `analytics/__tests__/events.test.ts` — any new event prop
|
|
3765
|
+
// that collides will fail CI.
|
|
3422
3766
|
properties: {
|
|
3767
|
+
...properties,
|
|
3423
3768
|
source: "cli",
|
|
3424
3769
|
// biome-ignore lint/style/useNamingConvention: snake_case is standard for analytics event properties
|
|
3425
3770
|
cli_version: this.cliVersion,
|
|
@@ -3429,14 +3774,14 @@ var AnalyticsService = class {
|
|
|
3429
3774
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3430
3775
|
request_id: this.requestId,
|
|
3431
3776
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3432
|
-
agent_host: this.agentHost
|
|
3433
|
-
...properties
|
|
3777
|
+
agent_host: this.agentHost
|
|
3434
3778
|
}
|
|
3435
3779
|
});
|
|
3436
3780
|
}
|
|
3437
3781
|
captureException(error, properties) {
|
|
3438
3782
|
if (!this.posthog) return;
|
|
3439
3783
|
this.posthog.captureException(error, this.distinctId, {
|
|
3784
|
+
...properties,
|
|
3440
3785
|
source: "cli",
|
|
3441
3786
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3442
3787
|
cli_version: this.cliVersion,
|
|
@@ -3446,8 +3791,7 @@ var AnalyticsService = class {
|
|
|
3446
3791
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3447
3792
|
request_id: this.requestId,
|
|
3448
3793
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
3449
|
-
agent_host: this.agentHost
|
|
3450
|
-
...properties
|
|
3794
|
+
agent_host: this.agentHost
|
|
3451
3795
|
});
|
|
3452
3796
|
}
|
|
3453
3797
|
async shutdown() {
|
|
@@ -3507,18 +3851,23 @@ var StateService = class {
|
|
|
3507
3851
|
}
|
|
3508
3852
|
};
|
|
3509
3853
|
// Walk recent searches newest-first, returning the first one whose
|
|
3510
|
-
// results contain `
|
|
3511
|
-
// "loadLastSearch" for attributing rank — handles the
|
|
3512
|
-
// case where the most recent search isn't the one being
|
|
3513
|
-
|
|
3854
|
+
// results contain `capabilityRef` (matches against uid OR slug).
|
|
3855
|
+
// Preferred over "loadLastSearch" for attributing rank — handles the
|
|
3856
|
+
// parallel-search case where the most recent search isn't the one being
|
|
3857
|
+
// fetched.
|
|
3858
|
+
findSearchContextByCapability = (capabilityRef) => {
|
|
3514
3859
|
const recent = this.loadRecentSearches();
|
|
3515
3860
|
for (const search of recent.searches) {
|
|
3516
|
-
const entry = search.capabilities.find(
|
|
3861
|
+
const entry = search.capabilities.find(
|
|
3862
|
+
(c) => c.id === capabilityRef || c.slug === capabilityRef
|
|
3863
|
+
);
|
|
3517
3864
|
if (entry) {
|
|
3518
3865
|
return {
|
|
3519
3866
|
searchId: search.searchId,
|
|
3520
3867
|
resultRank: entry.position,
|
|
3521
3868
|
capabilityId: entry.id,
|
|
3869
|
+
capabilityUid: entry.id,
|
|
3870
|
+
capabilitySlug: entry.slug ?? null,
|
|
3522
3871
|
url: entry.url,
|
|
3523
3872
|
displayCostAmount: entry.displayCostAmount
|
|
3524
3873
|
};
|
|
@@ -3543,6 +3892,8 @@ var StateService = class {
|
|
|
3543
3892
|
searchId: search.searchId,
|
|
3544
3893
|
resultRank: entry.position,
|
|
3545
3894
|
capabilityId: entry.id,
|
|
3895
|
+
capabilityUid: entry.id,
|
|
3896
|
+
capabilitySlug: entry.slug ?? null,
|
|
3546
3897
|
url: entry.url,
|
|
3547
3898
|
displayCostAmount: entry.displayCostAmount
|
|
3548
3899
|
};
|