@zeroxyz/cli 0.0.39 → 0.0.41
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 +529 -135
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { homedir as homedir8 } from "os";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join10 } from "path";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@zeroxyz/cli",
|
|
10
|
-
version: "0.0.
|
|
10
|
+
version: "0.0.41",
|
|
11
11
|
type: "module",
|
|
12
12
|
bin: {
|
|
13
13
|
zero: "dist/index.js",
|
|
@@ -205,7 +205,11 @@ var searchResultSchema = z.object({
|
|
|
205
205
|
reviewCount: z.number().optional(),
|
|
206
206
|
rating: ratingSchema,
|
|
207
207
|
availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
|
|
208
|
-
displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional()
|
|
208
|
+
displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
|
|
209
|
+
// Pass-through: API stamps this on Zero-published / withzero.{ai,xyz}
|
|
210
|
+
// services so `zero search --json` consumers can render their own
|
|
211
|
+
// provenance UI. CLI doesn't render a badge today.
|
|
212
|
+
isFirstParty: z.boolean().optional().default(false)
|
|
209
213
|
});
|
|
210
214
|
var searchResponseSchema = z.object({
|
|
211
215
|
searchId: z.string(),
|
|
@@ -258,7 +262,9 @@ var capabilityResponseSchema = z.object({
|
|
|
258
262
|
displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
|
|
259
263
|
activationCount: z.number().optional(),
|
|
260
264
|
lastUsedAt: z.string().nullable().optional(),
|
|
261
|
-
lastSuccessfullyRanAt: z.string().nullable().optional()
|
|
265
|
+
lastSuccessfullyRanAt: z.string().nullable().optional(),
|
|
266
|
+
// Pass-through (see searchResultSchema). Available on `zero get --json`.
|
|
267
|
+
isFirstParty: z.boolean().optional().default(false)
|
|
262
268
|
});
|
|
263
269
|
var createRunResponseSchema = z.object({
|
|
264
270
|
runId: z.string()
|
|
@@ -344,6 +350,19 @@ var userDtoSchema = z.object({
|
|
|
344
350
|
createdAt: z.union([z.string(), z.date()]).optional(),
|
|
345
351
|
lastLoginAt: z.union([z.string(), z.date()]).nullable().optional()
|
|
346
352
|
});
|
|
353
|
+
var userWalletDtoSchema = z.object({
|
|
354
|
+
walletAddress: z.string(),
|
|
355
|
+
source: z.enum(["self_custody", "privy_embedded"]),
|
|
356
|
+
isPrimary: z.boolean(),
|
|
357
|
+
linkedAt: z.union([z.string(), z.date()])
|
|
358
|
+
});
|
|
359
|
+
var signResultSchema = z.object({
|
|
360
|
+
signature: z.string(),
|
|
361
|
+
walletAddress: z.string()
|
|
362
|
+
});
|
|
363
|
+
var migrateResultSchema = z.object({
|
|
364
|
+
transactionHash: z.string()
|
|
365
|
+
});
|
|
347
366
|
var deviceStartResultSchema = z.object({
|
|
348
367
|
deviceCode: z.string(),
|
|
349
368
|
userCode: z.string(),
|
|
@@ -361,6 +380,10 @@ var devicePollResponseSchema = z.union([
|
|
|
361
380
|
user: userDtoSchema
|
|
362
381
|
})
|
|
363
382
|
]);
|
|
383
|
+
var jsonStringifyBigintSafe = (value) => JSON.stringify(
|
|
384
|
+
value,
|
|
385
|
+
(_key, v) => typeof v === "bigint" ? v.toString() : v
|
|
386
|
+
);
|
|
364
387
|
var buildCanonicalMessage = (method, path, body, timestamp, nonce) => {
|
|
365
388
|
const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
|
|
366
389
|
return `${method}:${path}:${bodyHash}:${timestamp}:${nonce}`;
|
|
@@ -378,6 +401,9 @@ var ApiService = class _ApiService {
|
|
|
378
401
|
account;
|
|
379
402
|
credentials;
|
|
380
403
|
onSessionRefreshed;
|
|
404
|
+
setWalletAddress = (address) => {
|
|
405
|
+
this.walletAddress = address;
|
|
406
|
+
};
|
|
381
407
|
withAccount = (account) => new _ApiService(this.baseUrl, account);
|
|
382
408
|
signRequest = async (method, path, body) => {
|
|
383
409
|
if (!this.account) throw new Error("No private key configured");
|
|
@@ -392,13 +418,28 @@ var ApiService = class _ApiService {
|
|
|
392
418
|
"x-zero-signature": signature
|
|
393
419
|
};
|
|
394
420
|
};
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
|
|
421
|
+
// Auth modes per request. `this.account` is only ever the local private key
|
|
422
|
+
// (the managed Privy proxy lives on PaymentService, never here), so an
|
|
423
|
+
// account here means a BYO key is present.
|
|
424
|
+
// - "default": session JWT takes precedence over EIP-191. If signed in, the
|
|
425
|
+
// JWT is the identity the API trusts; otherwise fall back to a wallet
|
|
426
|
+
// signature, else anonymous. Used by session-scoped endpoints
|
|
427
|
+
// (/users/me, sign-*, wallets) and read endpoints.
|
|
428
|
+
// - "wallet-attributed": the action is attributed to a wallet (runs,
|
|
429
|
+
// reviews, bug reports). Prefer the local private key (EIP-191) so the run
|
|
430
|
+
// is attributed to the wallet that actually paid; when there's no local
|
|
431
|
+
// key (managed user) fall back to the session Bearer and let the server
|
|
432
|
+
// resolve the wallet from the authenticated user. We never send both — a
|
|
433
|
+
// present session would make the server skip EIP-191 verification, so a
|
|
434
|
+
// Bearer + wallet headers combo must never happen.
|
|
435
|
+
buildHeaders = async (method, path, bodyStr, auth) => {
|
|
399
436
|
const base2 = {
|
|
400
437
|
"content-type": "application/json"
|
|
401
438
|
};
|
|
439
|
+
if (auth === "wallet-attributed" && this.account) {
|
|
440
|
+
const walletHeaders = await this.signRequest(method, path, bodyStr);
|
|
441
|
+
return { ...base2, ...walletHeaders };
|
|
442
|
+
}
|
|
402
443
|
if (this.credentials.kind === "session") {
|
|
403
444
|
base2.authorization = `Bearer ${this.credentials.accessToken}`;
|
|
404
445
|
return base2;
|
|
@@ -430,12 +471,13 @@ var ApiService = class _ApiService {
|
|
|
430
471
|
await this.onSessionRefreshed(body);
|
|
431
472
|
return true;
|
|
432
473
|
};
|
|
433
|
-
request = async (method, path, body) => {
|
|
474
|
+
request = async (method, path, body, opts = {}) => {
|
|
434
475
|
const url = `${this.baseUrl}${path}`;
|
|
435
|
-
const bodyStr = body ?
|
|
476
|
+
const bodyStr = body ? jsonStringifyBigintSafe(body) : void 0;
|
|
477
|
+
const auth = opts.auth ?? "default";
|
|
436
478
|
const makeInit = async () => ({
|
|
437
479
|
method,
|
|
438
|
-
headers: await this.buildHeaders(method, path, bodyStr),
|
|
480
|
+
headers: await this.buildHeaders(method, path, bodyStr, auth),
|
|
439
481
|
body: bodyStr
|
|
440
482
|
});
|
|
441
483
|
let response = await fetch(url, await makeInit());
|
|
@@ -504,7 +546,9 @@ var ApiService = class _ApiService {
|
|
|
504
546
|
return capabilityResponseSchema.parse(json);
|
|
505
547
|
};
|
|
506
548
|
createRun = async (data) => {
|
|
507
|
-
const json = await this.request("POST", "/v1/runs", data
|
|
549
|
+
const json = await this.request("POST", "/v1/runs", data, {
|
|
550
|
+
auth: "wallet-attributed"
|
|
551
|
+
});
|
|
508
552
|
return createRunResponseSchema.parse(json);
|
|
509
553
|
};
|
|
510
554
|
listRuns = async (params = {}) => {
|
|
@@ -514,19 +558,32 @@ var ApiService = class _ApiService {
|
|
|
514
558
|
if (params.limit) qs.set("limit", String(params.limit));
|
|
515
559
|
if (params.cursor) qs.set("cursor", params.cursor);
|
|
516
560
|
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
517
|
-
const json = await this.request("GET", `/v1/runs${suffix}
|
|
561
|
+
const json = await this.request("GET", `/v1/runs${suffix}`, void 0, {
|
|
562
|
+
auth: "wallet-attributed"
|
|
563
|
+
});
|
|
518
564
|
return listRunsResponseSchema.parse(json);
|
|
519
565
|
};
|
|
520
566
|
createReview = async (data) => {
|
|
521
|
-
const json = await this.request("POST", "/v1/reviews", data
|
|
567
|
+
const json = await this.request("POST", "/v1/reviews", data, {
|
|
568
|
+
auth: "wallet-attributed"
|
|
569
|
+
});
|
|
522
570
|
return createReviewResponseSchema.parse(json);
|
|
523
571
|
};
|
|
524
572
|
createReviewsBatch = async (reviews) => {
|
|
525
|
-
const json = await this.request(
|
|
573
|
+
const json = await this.request(
|
|
574
|
+
"POST",
|
|
575
|
+
"/v1/reviews/batch",
|
|
576
|
+
{ reviews },
|
|
577
|
+
{
|
|
578
|
+
auth: "wallet-attributed"
|
|
579
|
+
}
|
|
580
|
+
);
|
|
526
581
|
return batchReviewResponseSchema.parse(json);
|
|
527
582
|
};
|
|
528
583
|
createBugReport = async (data) => {
|
|
529
|
-
const json = await this.request("POST", "/v1/bug-reports", data
|
|
584
|
+
const json = await this.request("POST", "/v1/bug-reports", data, {
|
|
585
|
+
auth: "wallet-attributed"
|
|
586
|
+
});
|
|
530
587
|
return createBugReportResponseSchema.parse(json);
|
|
531
588
|
};
|
|
532
589
|
getFundingUrl = async (amount, provider = "coinbase") => {
|
|
@@ -543,6 +600,53 @@ var ApiService = class _ApiService {
|
|
|
543
600
|
return null;
|
|
544
601
|
}
|
|
545
602
|
};
|
|
603
|
+
getWallets = async () => {
|
|
604
|
+
const json = await this.request("GET", "/v1/users/me/wallets");
|
|
605
|
+
return z.array(userWalletDtoSchema).parse(json);
|
|
606
|
+
};
|
|
607
|
+
provisionWallet = async () => {
|
|
608
|
+
const json = await this.request("POST", "/v1/users/me/wallets/provision");
|
|
609
|
+
return userWalletDtoSchema.parse(json);
|
|
610
|
+
};
|
|
611
|
+
// Hands a BYO-wallet-signed EIP-3009 authorization to the server, which
|
|
612
|
+
// broadcasts it on Base via the gas-paying relayer (sweeping USDC into the
|
|
613
|
+
// caller's provisioned wallet). Session-authed.
|
|
614
|
+
migrateWallet = async (authorization) => {
|
|
615
|
+
const json = await this.request("POST", "/v1/users/me/wallets/migrate", {
|
|
616
|
+
authorization
|
|
617
|
+
});
|
|
618
|
+
return migrateResultSchema.parse(json);
|
|
619
|
+
};
|
|
620
|
+
signTypedDataRemote = async (typedData) => {
|
|
621
|
+
const json = await this.request("POST", "/v1/users/me/sign-typed-data", {
|
|
622
|
+
typedData
|
|
623
|
+
});
|
|
624
|
+
const parsed = signResultSchema.parse(json);
|
|
625
|
+
return {
|
|
626
|
+
signature: parsed.signature,
|
|
627
|
+
walletAddress: parsed.walletAddress
|
|
628
|
+
};
|
|
629
|
+
};
|
|
630
|
+
signMessageRemote = async (message) => {
|
|
631
|
+
const json = await this.request("POST", "/v1/users/me/sign-message", {
|
|
632
|
+
message
|
|
633
|
+
});
|
|
634
|
+
const parsed = signResultSchema.parse(json);
|
|
635
|
+
return {
|
|
636
|
+
signature: parsed.signature,
|
|
637
|
+
walletAddress: parsed.walletAddress
|
|
638
|
+
};
|
|
639
|
+
};
|
|
640
|
+
signTransactionRemote = async (input) => {
|
|
641
|
+
const json = await this.request("POST", "/v1/users/me/sign-transaction", {
|
|
642
|
+
unsignedTransaction: input.unsignedTransaction
|
|
643
|
+
});
|
|
644
|
+
const parsed = signResultSchema.parse(json);
|
|
645
|
+
return {
|
|
646
|
+
signature: parsed.signature,
|
|
647
|
+
walletAddress: parsed.walletAddress
|
|
648
|
+
};
|
|
649
|
+
};
|
|
546
650
|
};
|
|
547
651
|
|
|
548
652
|
// src/commands/bug-report-command.ts
|
|
@@ -839,6 +943,7 @@ import { Command as Command4 } from "commander";
|
|
|
839
943
|
import { formatUnits as formatUnits2 } from "viem";
|
|
840
944
|
|
|
841
945
|
// src/services/payment-service.ts
|
|
946
|
+
import { randomBytes } from "crypto";
|
|
842
947
|
import {
|
|
843
948
|
adaptViemWallet,
|
|
844
949
|
convertViemChainToRelayChain,
|
|
@@ -859,7 +964,7 @@ import {
|
|
|
859
964
|
formatUnits,
|
|
860
965
|
http
|
|
861
966
|
} from "viem";
|
|
862
|
-
import { base, baseSepolia } from "viem/chains";
|
|
967
|
+
import { base, baseSepolia, tempo as viemTempoChain } from "viem/chains";
|
|
863
968
|
var SessionCloseFailedError = class extends Error {
|
|
864
969
|
session;
|
|
865
970
|
response;
|
|
@@ -933,6 +1038,32 @@ var ERC20_BALANCE_ABI = [
|
|
|
933
1038
|
type: "function"
|
|
934
1039
|
}
|
|
935
1040
|
];
|
|
1041
|
+
var ERC20_TRANSFER_ABI = [
|
|
1042
|
+
{
|
|
1043
|
+
inputs: [
|
|
1044
|
+
{ name: "to", type: "address" },
|
|
1045
|
+
{ name: "amount", type: "uint256" }
|
|
1046
|
+
],
|
|
1047
|
+
name: "transfer",
|
|
1048
|
+
outputs: [{ name: "", type: "bool" }],
|
|
1049
|
+
stateMutability: "nonpayable",
|
|
1050
|
+
type: "function"
|
|
1051
|
+
}
|
|
1052
|
+
];
|
|
1053
|
+
var USDC_BASE_SWEEP_DOMAIN = { name: "USD Coin", version: "2" };
|
|
1054
|
+
var SWEEP_AUTHORIZATION_TTL_S = 900;
|
|
1055
|
+
var TEMPO_FEE_RESERVE_RAW = 10000n;
|
|
1056
|
+
var EIP3009_TRANSFER_TYPES = {
|
|
1057
|
+
// biome-ignore lint/style/useNamingConvention: EIP-712 primaryType must match the on-chain type name
|
|
1058
|
+
TransferWithAuthorization: [
|
|
1059
|
+
{ name: "from", type: "address" },
|
|
1060
|
+
{ name: "to", type: "address" },
|
|
1061
|
+
{ name: "value", type: "uint256" },
|
|
1062
|
+
{ name: "validAfter", type: "uint256" },
|
|
1063
|
+
{ name: "validBefore", type: "uint256" },
|
|
1064
|
+
{ name: "nonce", type: "bytes32" }
|
|
1065
|
+
]
|
|
1066
|
+
};
|
|
936
1067
|
var decodeSessionReceiptHeader = (header) => {
|
|
937
1068
|
if (!header) return null;
|
|
938
1069
|
try {
|
|
@@ -1378,6 +1509,115 @@ var PaymentService = class {
|
|
|
1378
1509
|
]);
|
|
1379
1510
|
return { amount: formatUnits(baseRaw + tempoRaw, 6), asset: "USDC" };
|
|
1380
1511
|
};
|
|
1512
|
+
// Total USDC across both legs (Base + Tempo) for the configured wallet — what
|
|
1513
|
+
// the migrate confirmation prompt quotes. Best-effort: a chain whose RPC read
|
|
1514
|
+
// fails counts as 0 rather than blocking the other leg.
|
|
1515
|
+
getSweepableBalance = async () => {
|
|
1516
|
+
const [baseRaw, tempoRaw] = await Promise.all([
|
|
1517
|
+
this.getBalanceRaw("base").catch(() => 0n),
|
|
1518
|
+
this.getBalanceRaw("tempo").catch(() => 0n)
|
|
1519
|
+
]);
|
|
1520
|
+
const raw = baseRaw + tempoRaw;
|
|
1521
|
+
return { raw, usdc: formatUnits(raw, 6) };
|
|
1522
|
+
};
|
|
1523
|
+
// Sweeps all USDC from the configured wallet into `to` across Base and Tempo.
|
|
1524
|
+
// Each leg is independent and never throws — failures are captured in the
|
|
1525
|
+
// returned per-chain result so the caller can decide whether to retire the
|
|
1526
|
+
// source key.
|
|
1527
|
+
migrateAllUsdc = async (to, relayer) => {
|
|
1528
|
+
const baseResult = await this.sweepBaseUsdc(to, relayer);
|
|
1529
|
+
const tempoResult = await this.sweepTempoUsdc(to);
|
|
1530
|
+
return [baseResult, tempoResult];
|
|
1531
|
+
};
|
|
1532
|
+
// Base leg: sign an EIP-3009 transferWithAuthorization for the full balance
|
|
1533
|
+
// and hand it to the relayer, which broadcasts it gaslessly.
|
|
1534
|
+
sweepBaseUsdc = async (to, relayer) => {
|
|
1535
|
+
try {
|
|
1536
|
+
if (!this.account) throw new Error("No wallet configured");
|
|
1537
|
+
const balance = await this.getBalanceRaw("base");
|
|
1538
|
+
if (balance <= 0n) return { chain: "base", status: "skipped" };
|
|
1539
|
+
const nowS = Math.floor(Date.now() / 1e3);
|
|
1540
|
+
const message = {
|
|
1541
|
+
from: this.account.address,
|
|
1542
|
+
to,
|
|
1543
|
+
value: balance,
|
|
1544
|
+
validAfter: 0n,
|
|
1545
|
+
validBefore: BigInt(nowS + SWEEP_AUTHORIZATION_TTL_S),
|
|
1546
|
+
nonce: `0x${randomBytes(32).toString("hex")}`
|
|
1547
|
+
};
|
|
1548
|
+
const signature = await this.account.signTypedData({
|
|
1549
|
+
domain: {
|
|
1550
|
+
name: USDC_BASE_SWEEP_DOMAIN.name,
|
|
1551
|
+
version: USDC_BASE_SWEEP_DOMAIN.version,
|
|
1552
|
+
chainId: BASE_CHAIN_ID,
|
|
1553
|
+
verifyingContract: USDC_BASE
|
|
1554
|
+
},
|
|
1555
|
+
types: EIP3009_TRANSFER_TYPES,
|
|
1556
|
+
primaryType: "TransferWithAuthorization",
|
|
1557
|
+
message
|
|
1558
|
+
});
|
|
1559
|
+
const { transactionHash } = await relayer.migrateWallet({
|
|
1560
|
+
from: message.from,
|
|
1561
|
+
to: message.to,
|
|
1562
|
+
value: message.value.toString(),
|
|
1563
|
+
validAfter: message.validAfter.toString(),
|
|
1564
|
+
validBefore: message.validBefore.toString(),
|
|
1565
|
+
nonce: message.nonce,
|
|
1566
|
+
signature
|
|
1567
|
+
});
|
|
1568
|
+
return {
|
|
1569
|
+
chain: "base",
|
|
1570
|
+
status: "swept",
|
|
1571
|
+
amount: formatUnits(balance, 6),
|
|
1572
|
+
txHash: transactionHash
|
|
1573
|
+
};
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
return { chain: "base", status: "failed", error: err.message };
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
// Tempo leg: self-broadcast an ERC-20 transfer paying the fee in USDC via
|
|
1579
|
+
// `feeToken` (no native gas token needed). Uses viem's tempo chain for its
|
|
1580
|
+
// type-0x76 serializer; reserves a tiny amount to cover the fee.
|
|
1581
|
+
sweepTempoUsdc = async (to) => {
|
|
1582
|
+
try {
|
|
1583
|
+
if (!this.account) throw new Error("No wallet configured");
|
|
1584
|
+
const balance = await this.getBalanceRaw("tempo");
|
|
1585
|
+
if (balance <= TEMPO_FEE_RESERVE_RAW) {
|
|
1586
|
+
return { chain: "tempo", status: "skipped" };
|
|
1587
|
+
}
|
|
1588
|
+
const amount = balance - TEMPO_FEE_RESERVE_RAW;
|
|
1589
|
+
const wallet = createWalletClient({
|
|
1590
|
+
account: this.account,
|
|
1591
|
+
chain: viemTempoChain,
|
|
1592
|
+
transport: http()
|
|
1593
|
+
});
|
|
1594
|
+
const txHash = await wallet.writeContract({
|
|
1595
|
+
address: USDC_TEMPO,
|
|
1596
|
+
abi: ERC20_TRANSFER_ABI,
|
|
1597
|
+
functionName: "transfer",
|
|
1598
|
+
args: [to, amount],
|
|
1599
|
+
feeToken: USDC_TEMPO
|
|
1600
|
+
// biome-ignore lint/suspicious/noExplicitAny: tempo feeToken param
|
|
1601
|
+
});
|
|
1602
|
+
const publicClient = createPublicClient({
|
|
1603
|
+
chain: viemTempoChain,
|
|
1604
|
+
transport: http()
|
|
1605
|
+
});
|
|
1606
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
1607
|
+
return {
|
|
1608
|
+
chain: "tempo",
|
|
1609
|
+
status: "swept",
|
|
1610
|
+
amount: formatUnits(amount, 6),
|
|
1611
|
+
txHash
|
|
1612
|
+
};
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
return {
|
|
1615
|
+
chain: "tempo",
|
|
1616
|
+
status: "failed",
|
|
1617
|
+
error: err.message
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1381
1621
|
};
|
|
1382
1622
|
|
|
1383
1623
|
// src/util/infer-schema.ts
|
|
@@ -1629,55 +1869,6 @@ var fetchCommand = (appContext) => new Command4("fetch").description(
|
|
|
1629
1869
|
} else {
|
|
1630
1870
|
resolvedUrl = url;
|
|
1631
1871
|
}
|
|
1632
|
-
if (!url && options.capability && !stateService.findSearchContextByCapability(options.capability)) {
|
|
1633
|
-
try {
|
|
1634
|
-
let capName = resolvedCapabilityName;
|
|
1635
|
-
if (capName === void 0) {
|
|
1636
|
-
const cap = await apiService.getCapability(options.capability);
|
|
1637
|
-
capName = cap.name;
|
|
1638
|
-
}
|
|
1639
|
-
const searchResult = await apiService.search({ query: capName });
|
|
1640
|
-
const matchedEntry = searchResult.capabilities.find(
|
|
1641
|
-
(c) => c.slug === options.capability || c.id === options.capability
|
|
1642
|
-
);
|
|
1643
|
-
const slugFoundInResults = Boolean(matchedEntry);
|
|
1644
|
-
stateService.saveLastSearch({
|
|
1645
|
-
searchId: searchResult.searchId,
|
|
1646
|
-
capabilities: searchResult.capabilities.map((c) => ({
|
|
1647
|
-
position: c.position,
|
|
1648
|
-
id: c.id,
|
|
1649
|
-
slug: c.slug,
|
|
1650
|
-
url: c.url,
|
|
1651
|
-
urlTemplate: c.urlTemplate ?? null,
|
|
1652
|
-
displayCostAmount: c.cost.amount
|
|
1653
|
-
}))
|
|
1654
|
-
});
|
|
1655
|
-
analyticsService.capture("search_executed", {
|
|
1656
|
-
query: truncateQuery(capName),
|
|
1657
|
-
queryLength: capName.length,
|
|
1658
|
-
resultCount: searchResult.capabilities.length,
|
|
1659
|
-
searchId: searchResult.searchId,
|
|
1660
|
-
total: searchResult.total,
|
|
1661
|
-
hasMore: searchResult.hasMore,
|
|
1662
|
-
json: false,
|
|
1663
|
-
triggeredBy: "slug_handoff",
|
|
1664
|
-
slugFoundInResults
|
|
1665
|
-
});
|
|
1666
|
-
analyticsService.capture("capability_viewed", {
|
|
1667
|
-
// Existing field preserved as the raw --capability
|
|
1668
|
-
// input for back-compat.
|
|
1669
|
-
capabilityId: options.capability,
|
|
1670
|
-
// New canonical identifier fields hydrated from the
|
|
1671
|
-
// resolved search result (when the slug was found).
|
|
1672
|
-
capabilityUid: matchedEntry?.id,
|
|
1673
|
-
capabilitySlug: matchedEntry?.slug,
|
|
1674
|
-
fromLastSearch: false,
|
|
1675
|
-
searchId: searchResult.searchId,
|
|
1676
|
-
triggeredBy: "slug_handoff"
|
|
1677
|
-
});
|
|
1678
|
-
} catch {
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
1872
|
let resolvedBody;
|
|
1682
1873
|
try {
|
|
1683
1874
|
resolvedBody = resolveRequestBody(
|
|
@@ -1733,6 +1924,7 @@ var fetchCommand = (appContext) => new Command4("fetch").description(
|
|
|
1733
1924
|
const capabilitySlug = matchCtx?.capabilitySlug ?? null;
|
|
1734
1925
|
const searchId = matchCtx?.searchId;
|
|
1735
1926
|
const resultRank = matchCtx?.resultRank;
|
|
1927
|
+
const fetchOrigin = matchCtx ? "from_search" : options.capability ? "direct_slug" : "direct_url";
|
|
1736
1928
|
const matchedDisplayCostAmount = matchCtx?.displayCostAmount;
|
|
1737
1929
|
const skipReasons = [];
|
|
1738
1930
|
if (!apiService.walletAddress) {
|
|
@@ -1950,6 +2142,9 @@ var fetchCommand = (appContext) => new Command4("fetch").description(
|
|
|
1950
2142
|
resultRank: resultRank ?? void 0,
|
|
1951
2143
|
runId: runId ?? void 0,
|
|
1952
2144
|
runTracked: !!runId,
|
|
2145
|
+
// A NULL searchId on a direct_slug fetch is correct here,
|
|
2146
|
+
// not a funnel gap to paper over.
|
|
2147
|
+
fetchOrigin,
|
|
1953
2148
|
...fetchError && { error: truncateError(fetchError.message) }
|
|
1954
2149
|
});
|
|
1955
2150
|
const isFetchFailure = Boolean(fetchError) || !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
@@ -3368,14 +3563,30 @@ Read the full terms at: ${TERMS_URL}
|
|
|
3368
3563
|
});
|
|
3369
3564
|
|
|
3370
3565
|
// src/commands/wallet-command.ts
|
|
3371
|
-
import { existsSync as existsSync3, readFileSync as
|
|
3566
|
+
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
3372
3567
|
import { homedir as homedir4 } from "os";
|
|
3373
|
-
import { join as
|
|
3568
|
+
import { join as join5 } from "path";
|
|
3569
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
3374
3570
|
import { Command as Command11 } from "commander";
|
|
3375
3571
|
import open2 from "open";
|
|
3376
3572
|
import { isAddress } from "viem";
|
|
3377
3573
|
import { generatePrivateKey as generatePrivateKey2, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
3378
3574
|
|
|
3575
|
+
// src/util/migrate-config.ts
|
|
3576
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
3577
|
+
import { join as join4 } from "path";
|
|
3578
|
+
var backupAndStripPrivateKey = (zeroDir) => {
|
|
3579
|
+
const configPath = join4(zeroDir, "config.json");
|
|
3580
|
+
const backupPath = join4(zeroDir, "config.backup.json");
|
|
3581
|
+
const raw = readFileSync7(configPath, "utf8");
|
|
3582
|
+
ensureSecureDir(zeroDir);
|
|
3583
|
+
writeSecureFile(backupPath, raw);
|
|
3584
|
+
const parsed = JSON.parse(raw);
|
|
3585
|
+
delete parsed.privateKey;
|
|
3586
|
+
writeSecureFile(configPath, JSON.stringify(parsed, null, 2));
|
|
3587
|
+
return { backupPath, configPath };
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3379
3590
|
// src/util/stdin.ts
|
|
3380
3591
|
var readStdin = async () => {
|
|
3381
3592
|
const chunks = [];
|
|
@@ -3559,11 +3770,11 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
|
|
|
3559
3770
|
process.exitCode = 1;
|
|
3560
3771
|
return;
|
|
3561
3772
|
}
|
|
3562
|
-
const zeroDir =
|
|
3563
|
-
const configPath =
|
|
3773
|
+
const zeroDir = join5(homedir4(), ".zero");
|
|
3774
|
+
const configPath = join5(zeroDir, "config.json");
|
|
3564
3775
|
if (!options.force && existsSync3(configPath)) {
|
|
3565
3776
|
try {
|
|
3566
|
-
const existing2 = JSON.parse(
|
|
3777
|
+
const existing2 = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
3567
3778
|
if (existing2.privateKey) {
|
|
3568
3779
|
console.error(
|
|
3569
3780
|
"Wallet already configured. Use --force to overwrite."
|
|
@@ -3575,7 +3786,7 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
|
|
|
3575
3786
|
}
|
|
3576
3787
|
}
|
|
3577
3788
|
ensureSecureDir(zeroDir);
|
|
3578
|
-
const existing = existsSync3(configPath) ? JSON.parse(
|
|
3789
|
+
const existing = existsSync3(configPath) ? JSON.parse(readFileSync8(configPath, "utf8")) : {};
|
|
3579
3790
|
writeSecureFile(
|
|
3580
3791
|
configPath,
|
|
3581
3792
|
JSON.stringify(
|
|
@@ -3671,6 +3882,142 @@ var walletGenerateCommand = (appContext) => new Command11("generate").descriptio
|
|
|
3671
3882
|
});
|
|
3672
3883
|
}
|
|
3673
3884
|
);
|
|
3885
|
+
var resolveZeroWalletAddress = async (apiService) => {
|
|
3886
|
+
try {
|
|
3887
|
+
const wallets = await apiService.getWallets();
|
|
3888
|
+
const primary = wallets.find(
|
|
3889
|
+
(w) => w.source === "privy_embedded" && w.isPrimary
|
|
3890
|
+
);
|
|
3891
|
+
if (primary) return primary.walletAddress;
|
|
3892
|
+
const provisioned = await apiService.provisionWallet();
|
|
3893
|
+
return provisioned.walletAddress ?? null;
|
|
3894
|
+
} catch {
|
|
3895
|
+
return null;
|
|
3896
|
+
}
|
|
3897
|
+
};
|
|
3898
|
+
var walletMigrateCommand = (appContext) => new Command11("migrate").description(
|
|
3899
|
+
"Sweep all USDC from your private-key wallet into your Zero wallet"
|
|
3900
|
+
).option("--json", "Emit the migration result as JSON").option("-y, --yes", "Skip the confirmation prompt").action(async (options) => {
|
|
3901
|
+
const { apiService, paymentService } = appContext.services;
|
|
3902
|
+
const zeroDir = join5(homedir4(), ".zero");
|
|
3903
|
+
const configPath = join5(zeroDir, "config.json");
|
|
3904
|
+
const config = existsSync3(configPath) ? readConfig(configPath) : {};
|
|
3905
|
+
const privateKey = appContext.env.ZERO_PRIVATE_KEY ?? config.privateKey;
|
|
3906
|
+
if (!privateKey) {
|
|
3907
|
+
console.error(
|
|
3908
|
+
"No private-key wallet found. Run `zero wallet set <key>` first."
|
|
3909
|
+
);
|
|
3910
|
+
process.exitCode = 1;
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
const hasSession = Boolean(
|
|
3914
|
+
appContext.env.ZERO_SESSION_TOKEN || config.session
|
|
3915
|
+
);
|
|
3916
|
+
if (!hasSession) {
|
|
3917
|
+
console.error("Not logged in. Run `zero auth login` first.");
|
|
3918
|
+
process.exitCode = 1;
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
let source;
|
|
3922
|
+
try {
|
|
3923
|
+
source = privateKeyToAccount2(privateKey);
|
|
3924
|
+
} catch {
|
|
3925
|
+
console.error("Invalid private key in config.");
|
|
3926
|
+
process.exitCode = 1;
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
const to = await resolveZeroWalletAddress(apiService);
|
|
3930
|
+
if (!to) {
|
|
3931
|
+
console.error(
|
|
3932
|
+
"Could not resolve your Zero wallet. Run `zero auth login` and try again."
|
|
3933
|
+
);
|
|
3934
|
+
process.exitCode = 1;
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
if (to.toLowerCase() === source.address.toLowerCase()) {
|
|
3938
|
+
console.error(
|
|
3939
|
+
"Source and destination are the same wallet \u2014 nothing to migrate."
|
|
3940
|
+
);
|
|
3941
|
+
process.exitCode = 1;
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
const balance = await paymentService.getSweepableBalance();
|
|
3945
|
+
if (balance.raw <= 0n) {
|
|
3946
|
+
if (options.json) {
|
|
3947
|
+
console.log(
|
|
3948
|
+
JSON.stringify({ destination: to, legs: [], keyRemoved: false })
|
|
3949
|
+
);
|
|
3950
|
+
} else {
|
|
3951
|
+
console.log("Nothing to migrate (no USDC found).");
|
|
3952
|
+
}
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
if (!options.yes) {
|
|
3956
|
+
if (options.json || !process.stdin.isTTY) {
|
|
3957
|
+
console.error(
|
|
3958
|
+
"Refusing to migrate without confirmation. Re-run with --yes to proceed."
|
|
3959
|
+
);
|
|
3960
|
+
process.exitCode = 1;
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const proceed = await confirm({
|
|
3964
|
+
message: `Please confirm you would like to transfer $${balance.usdc} from your private wallet to your Zero managed wallet`
|
|
3965
|
+
});
|
|
3966
|
+
if (isCancel(proceed) || !proceed) {
|
|
3967
|
+
console.log("Migration cancelled.");
|
|
3968
|
+
return;
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
const results = await paymentService.migrateAllUsdc(to, apiService);
|
|
3972
|
+
const anyFailed = results.some((r) => r.status === "failed");
|
|
3973
|
+
const anySwept = results.some((r) => r.status === "swept");
|
|
3974
|
+
let backupPath = null;
|
|
3975
|
+
if (anySwept && !anyFailed && config.privateKey) {
|
|
3976
|
+
try {
|
|
3977
|
+
backupPath = backupAndStripPrivateKey(zeroDir).backupPath;
|
|
3978
|
+
} catch {
|
|
3979
|
+
backupPath = null;
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
if (options.json) {
|
|
3983
|
+
console.log(
|
|
3984
|
+
JSON.stringify({
|
|
3985
|
+
destination: to,
|
|
3986
|
+
legs: results,
|
|
3987
|
+
keyRemoved: backupPath !== null,
|
|
3988
|
+
backupPath
|
|
3989
|
+
})
|
|
3990
|
+
);
|
|
3991
|
+
} else {
|
|
3992
|
+
console.log(`Migrating USDC \u2192 ${to}`);
|
|
3993
|
+
for (const r of results) {
|
|
3994
|
+
if (r.status === "swept") {
|
|
3995
|
+
console.log(
|
|
3996
|
+
` ${r.chain}: swept ${r.amount} USDC (tx ${r.txHash})`
|
|
3997
|
+
);
|
|
3998
|
+
} else if (r.status === "skipped") {
|
|
3999
|
+
console.log(` ${r.chain}: skipped (no balance)`);
|
|
4000
|
+
} else {
|
|
4001
|
+
console.log(` ${r.chain}: FAILED \u2014 ${r.error}`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
if (backupPath) {
|
|
4005
|
+
console.log("");
|
|
4006
|
+
console.log(
|
|
4007
|
+
`Private key removed from config (backed up to ${backupPath}). Your Zero wallet is now primary.`
|
|
4008
|
+
);
|
|
4009
|
+
} else if (anyFailed) {
|
|
4010
|
+
console.log("");
|
|
4011
|
+
console.log(
|
|
4012
|
+
"A transfer failed \u2014 your private key was kept. Re-run `zero wallet migrate` to retry."
|
|
4013
|
+
);
|
|
4014
|
+
} else if (!anySwept) {
|
|
4015
|
+
console.log("");
|
|
4016
|
+
console.log("Nothing to migrate (no USDC found).");
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
if (anyFailed) process.exitCode = 1;
|
|
4020
|
+
});
|
|
3674
4021
|
var walletCommand = (appContext) => {
|
|
3675
4022
|
const cmd = new Command11("wallet").description("Manage your wallet");
|
|
3676
4023
|
cmd.addCommand(walletBalanceCommand(appContext));
|
|
@@ -3678,22 +4025,23 @@ var walletCommand = (appContext) => {
|
|
|
3678
4025
|
cmd.addCommand(walletAddressCommand(appContext));
|
|
3679
4026
|
cmd.addCommand(walletSetCommand(appContext));
|
|
3680
4027
|
cmd.addCommand(walletGenerateCommand(appContext));
|
|
4028
|
+
cmd.addCommand(walletMigrateCommand(appContext), { hidden: true });
|
|
3681
4029
|
return cmd;
|
|
3682
4030
|
};
|
|
3683
4031
|
|
|
3684
4032
|
// src/commands/welcome-command.ts
|
|
3685
|
-
import { existsSync as existsSync4, readFileSync as
|
|
4033
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
|
|
3686
4034
|
import { homedir as homedir5 } from "os";
|
|
3687
|
-
import { join as
|
|
4035
|
+
import { join as join6 } from "path";
|
|
3688
4036
|
import { Command as Command12 } from "commander";
|
|
3689
4037
|
import open3 from "open";
|
|
3690
4038
|
import { getAddress } from "viem";
|
|
3691
4039
|
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
3692
4040
|
var readPrivateKey = () => {
|
|
3693
|
-
const configPath =
|
|
4041
|
+
const configPath = join6(homedir5(), ".zero", "config.json");
|
|
3694
4042
|
if (!existsSync4(configPath)) return null;
|
|
3695
4043
|
try {
|
|
3696
|
-
const config = JSON.parse(
|
|
4044
|
+
const config = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
3697
4045
|
if (typeof config.privateKey === "string") {
|
|
3698
4046
|
return config.privateKey;
|
|
3699
4047
|
}
|
|
@@ -3822,6 +4170,7 @@ var envSchema = z4.object({
|
|
|
3822
4170
|
ZERO_API_URL: z4.string().default("https://api.zero.xyz"),
|
|
3823
4171
|
ZERO_WEB_URL: z4.string().default("https://zero.xyz"),
|
|
3824
4172
|
ZERO_PRIVATE_KEY: z4.string().optional(),
|
|
4173
|
+
ZERO_SESSION_TOKEN: z4.string().optional(),
|
|
3825
4174
|
ZERO_ENV: z4.enum(["development", "production"]).default("production")
|
|
3826
4175
|
});
|
|
3827
4176
|
var getEnv = () => {
|
|
@@ -3838,12 +4187,12 @@ var getEnv = () => {
|
|
|
3838
4187
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3839
4188
|
import { existsSync as existsSync7 } from "fs";
|
|
3840
4189
|
import { homedir as homedir6 } from "os";
|
|
3841
|
-
import { join as
|
|
4190
|
+
import { join as join8 } from "path";
|
|
3842
4191
|
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
3843
4192
|
|
|
3844
4193
|
// src/services/analytics-service.ts
|
|
3845
4194
|
import { randomUUID } from "crypto";
|
|
3846
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as
|
|
4195
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
3847
4196
|
import { dirname as dirname2 } from "path";
|
|
3848
4197
|
import { PostHog } from "posthog-node";
|
|
3849
4198
|
var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
|
|
@@ -3866,7 +4215,7 @@ var AnalyticsService = class {
|
|
|
3866
4215
|
let persistedAnonId;
|
|
3867
4216
|
try {
|
|
3868
4217
|
if (existsSync5(opts.configPath)) {
|
|
3869
|
-
const config = JSON.parse(
|
|
4218
|
+
const config = JSON.parse(readFileSync10(opts.configPath, "utf8"));
|
|
3870
4219
|
if (config.telemetry === false) {
|
|
3871
4220
|
telemetryEnabled = false;
|
|
3872
4221
|
}
|
|
@@ -3888,15 +4237,17 @@ var AnalyticsService = class {
|
|
|
3888
4237
|
} else {
|
|
3889
4238
|
const newAnonId = randomUUID();
|
|
3890
4239
|
this.distinctId = newAnonId;
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
4240
|
+
if (!process.env.VITEST) {
|
|
4241
|
+
try {
|
|
4242
|
+
const dir = dirname2(opts.configPath);
|
|
4243
|
+
mkdirSync4(dir, { recursive: true });
|
|
4244
|
+
const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync10(opts.configPath, "utf8")) : {};
|
|
4245
|
+
writeFileSync4(
|
|
4246
|
+
opts.configPath,
|
|
4247
|
+
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
4248
|
+
);
|
|
4249
|
+
} catch {
|
|
4250
|
+
}
|
|
3900
4251
|
}
|
|
3901
4252
|
}
|
|
3902
4253
|
const originalConsoleError = console.error;
|
|
@@ -3931,7 +4282,7 @@ var AnalyticsService = class {
|
|
|
3931
4282
|
if (anonId === walletAddress) return;
|
|
3932
4283
|
let aliasedTo;
|
|
3933
4284
|
try {
|
|
3934
|
-
const config = JSON.parse(
|
|
4285
|
+
const config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
3935
4286
|
if (typeof config.aliasedTo === "string") {
|
|
3936
4287
|
aliasedTo = config.aliasedTo;
|
|
3937
4288
|
}
|
|
@@ -3939,8 +4290,9 @@ var AnalyticsService = class {
|
|
|
3939
4290
|
}
|
|
3940
4291
|
if (aliasedTo === walletAddress) return;
|
|
3941
4292
|
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
4293
|
+
if (process.env.VITEST) return;
|
|
3942
4294
|
try {
|
|
3943
|
-
const config = existsSync5(configPath) ? JSON.parse(
|
|
4295
|
+
const config = existsSync5(configPath) ? JSON.parse(readFileSync10(configPath, "utf8")) : {};
|
|
3944
4296
|
writeFileSync4(
|
|
3945
4297
|
configPath,
|
|
3946
4298
|
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
@@ -4015,15 +4367,43 @@ var AnalyticsService = class {
|
|
|
4015
4367
|
}
|
|
4016
4368
|
};
|
|
4017
4369
|
|
|
4370
|
+
// src/services/api-account.ts
|
|
4371
|
+
import {
|
|
4372
|
+
bytesToHex,
|
|
4373
|
+
parseSignature,
|
|
4374
|
+
serializeTransaction
|
|
4375
|
+
} from "viem";
|
|
4376
|
+
import { toAccount } from "viem/accounts";
|
|
4377
|
+
var createApiAccount = (walletAddress, api) => toAccount({
|
|
4378
|
+
address: walletAddress,
|
|
4379
|
+
async signMessage({ message }) {
|
|
4380
|
+
const asMessage = typeof message === "string" ? message : typeof message.raw === "string" ? message.raw : bytesToHex(message.raw);
|
|
4381
|
+
const { signature } = await api.signMessageRemote(asMessage);
|
|
4382
|
+
return signature;
|
|
4383
|
+
},
|
|
4384
|
+
async signTypedData(typedData) {
|
|
4385
|
+
const { signature } = await api.signTypedDataRemote(typedData);
|
|
4386
|
+
return signature;
|
|
4387
|
+
},
|
|
4388
|
+
async signTransaction(transaction, options) {
|
|
4389
|
+
const serialize = options?.serializer ?? serializeTransaction;
|
|
4390
|
+
const unsigned = await serialize(transaction);
|
|
4391
|
+
const { signature } = await api.signTransactionRemote({
|
|
4392
|
+
unsignedTransaction: unsigned
|
|
4393
|
+
});
|
|
4394
|
+
return await serialize(transaction, parseSignature(signature));
|
|
4395
|
+
}
|
|
4396
|
+
});
|
|
4397
|
+
|
|
4018
4398
|
// src/services/state-service.ts
|
|
4019
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as
|
|
4020
|
-
import { join as
|
|
4399
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
4400
|
+
import { join as join7 } from "path";
|
|
4021
4401
|
var RECENT_SEARCH_LIMIT = 10;
|
|
4022
4402
|
var StateService = class {
|
|
4023
4403
|
constructor(zeroDir) {
|
|
4024
4404
|
this.zeroDir = zeroDir;
|
|
4025
|
-
this.lastSearchPath =
|
|
4026
|
-
this.recentSearchesPath =
|
|
4405
|
+
this.lastSearchPath = join7(zeroDir, "last_search.json");
|
|
4406
|
+
this.recentSearchesPath = join7(zeroDir, "recent_searches.json");
|
|
4027
4407
|
}
|
|
4028
4408
|
lastSearchPath;
|
|
4029
4409
|
recentSearchesPath;
|
|
@@ -4043,7 +4423,7 @@ var StateService = class {
|
|
|
4043
4423
|
loadLastSearch = () => {
|
|
4044
4424
|
try {
|
|
4045
4425
|
if (!existsSync6(this.lastSearchPath)) return null;
|
|
4046
|
-
const raw =
|
|
4426
|
+
const raw = readFileSync11(this.lastSearchPath, "utf8");
|
|
4047
4427
|
return JSON.parse(raw);
|
|
4048
4428
|
} catch {
|
|
4049
4429
|
return null;
|
|
@@ -4055,7 +4435,7 @@ var StateService = class {
|
|
|
4055
4435
|
const last = this.loadLastSearch();
|
|
4056
4436
|
return { searches: last ? [last] : [] };
|
|
4057
4437
|
}
|
|
4058
|
-
const raw =
|
|
4438
|
+
const raw = readFileSync11(this.recentSearchesPath, "utf8");
|
|
4059
4439
|
const parsed = JSON.parse(raw);
|
|
4060
4440
|
return { searches: parsed.searches ?? [] };
|
|
4061
4441
|
} catch {
|
|
@@ -4154,30 +4534,20 @@ var detectAgentHost = (env = process.env) => {
|
|
|
4154
4534
|
// src/app/app-services.ts
|
|
4155
4535
|
var CLI_VERSION = package_default.version;
|
|
4156
4536
|
var resolveCredentials = (env, config) => {
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
privateKey: env.ZERO_PRIVATE_KEY
|
|
4172
|
-
};
|
|
4173
|
-
}
|
|
4174
|
-
if (config.privateKey) {
|
|
4175
|
-
return {
|
|
4176
|
-
credentials: { kind: "none" },
|
|
4177
|
-
privateKey: config.privateKey
|
|
4178
|
-
};
|
|
4179
|
-
}
|
|
4180
|
-
return { credentials: { kind: "none" }, privateKey: null };
|
|
4537
|
+
const injectedToken = env.ZERO_SESSION_TOKEN;
|
|
4538
|
+
const credentials = injectedToken ? {
|
|
4539
|
+
kind: "session",
|
|
4540
|
+
accessToken: injectedToken,
|
|
4541
|
+
refreshToken: "",
|
|
4542
|
+
userId: ""
|
|
4543
|
+
} : config.session ? {
|
|
4544
|
+
kind: "session",
|
|
4545
|
+
accessToken: config.session.accessToken,
|
|
4546
|
+
refreshToken: config.session.refreshToken,
|
|
4547
|
+
userId: config.session.userId
|
|
4548
|
+
} : { kind: "none" };
|
|
4549
|
+
const privateKey = env.ZERO_PRIVATE_KEY ?? config.privateKey ?? null;
|
|
4550
|
+
return { credentials, privateKey };
|
|
4181
4551
|
};
|
|
4182
4552
|
var buildOnSessionRefreshed = (configPath) => async (tokens) => {
|
|
4183
4553
|
const current = readConfig(configPath);
|
|
@@ -4192,20 +4562,31 @@ var buildOnSessionRefreshed = (configPath) => async (tokens) => {
|
|
|
4192
4562
|
};
|
|
4193
4563
|
writeSecureFile(configPath, JSON.stringify(next, null, 2));
|
|
4194
4564
|
};
|
|
4195
|
-
var getServices = (env) => {
|
|
4196
|
-
const zeroDir =
|
|
4197
|
-
const configPath =
|
|
4565
|
+
var getServices = async (env) => {
|
|
4566
|
+
const zeroDir = join8(homedir6(), ".zero");
|
|
4567
|
+
const configPath = join8(zeroDir, "config.json");
|
|
4198
4568
|
const config = existsSync7(configPath) ? readConfig(configPath) : {};
|
|
4199
4569
|
const { credentials, privateKey } = resolveCredentials(env, config);
|
|
4200
|
-
const account = privateKey ? privateKeyToAccount4(privateKey) : null;
|
|
4201
4570
|
const lowBalanceWarning = typeof config.lowBalanceWarning === "number" ? config.lowBalanceWarning : 1;
|
|
4202
4571
|
const apiService = new ApiService(
|
|
4203
4572
|
env.ZERO_API_URL,
|
|
4204
|
-
|
|
4573
|
+
privateKey ? privateKeyToAccount4(privateKey) : null,
|
|
4205
4574
|
credentials,
|
|
4206
4575
|
buildOnSessionRefreshed(configPath)
|
|
4207
4576
|
);
|
|
4208
|
-
|
|
4577
|
+
let account = privateKey ? privateKeyToAccount4(privateKey) : null;
|
|
4578
|
+
if (!account && credentials.kind === "session") {
|
|
4579
|
+
const address = await resolveManagedWalletAddress(apiService);
|
|
4580
|
+
if (address) {
|
|
4581
|
+
account = createApiAccount(address, apiService);
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
if (account && !apiService.walletAddress) {
|
|
4585
|
+
apiService.setWalletAddress(account.address);
|
|
4586
|
+
}
|
|
4587
|
+
const paymentService = new PaymentService(account, {
|
|
4588
|
+
lowBalanceWarning
|
|
4589
|
+
});
|
|
4209
4590
|
const stateService = new StateService(zeroDir);
|
|
4210
4591
|
const walletService = new WalletService(
|
|
4211
4592
|
apiService.walletAddress,
|
|
@@ -4228,16 +4609,29 @@ var getServices = (env) => {
|
|
|
4228
4609
|
walletService
|
|
4229
4610
|
};
|
|
4230
4611
|
};
|
|
4612
|
+
var resolveManagedWalletAddress = async (api) => {
|
|
4613
|
+
try {
|
|
4614
|
+
const wallets = await api.getWallets();
|
|
4615
|
+
const primary = wallets.find(
|
|
4616
|
+
(w) => w.source === "privy_embedded" && w.isPrimary
|
|
4617
|
+
);
|
|
4618
|
+
if (primary) return primary.walletAddress;
|
|
4619
|
+
const provisioned = await api.provisionWallet();
|
|
4620
|
+
return provisioned.walletAddress ?? null;
|
|
4621
|
+
} catch {
|
|
4622
|
+
return null;
|
|
4623
|
+
}
|
|
4624
|
+
};
|
|
4231
4625
|
|
|
4232
4626
|
// src/app/app-context.ts
|
|
4233
|
-
var createAppContext = () => {
|
|
4627
|
+
var createAppContext = async () => {
|
|
4234
4628
|
const env = getEnv();
|
|
4235
4629
|
if (!env) {
|
|
4236
4630
|
return null;
|
|
4237
4631
|
}
|
|
4238
4632
|
return {
|
|
4239
4633
|
env,
|
|
4240
|
-
services: getServices(env),
|
|
4634
|
+
services: await getServices(env),
|
|
4241
4635
|
invocation: { current: null }
|
|
4242
4636
|
};
|
|
4243
4637
|
};
|
|
@@ -4247,12 +4641,12 @@ import {
|
|
|
4247
4641
|
existsSync as existsSync8,
|
|
4248
4642
|
lstatSync,
|
|
4249
4643
|
mkdirSync as mkdirSync6,
|
|
4250
|
-
readFileSync as
|
|
4644
|
+
readFileSync as readFileSync12,
|
|
4251
4645
|
readlinkSync,
|
|
4252
4646
|
writeFileSync as writeFileSync6
|
|
4253
4647
|
} from "fs";
|
|
4254
4648
|
import { homedir as homedir7 } from "os";
|
|
4255
|
-
import { dirname as dirname3, join as
|
|
4649
|
+
import { dirname as dirname3, join as join9, resolve } from "path";
|
|
4256
4650
|
var CACHE_FILENAME = "update_check.json";
|
|
4257
4651
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@zeroxyz/cli/latest";
|
|
4258
4652
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -4279,7 +4673,7 @@ var detectInstallMethod = (opts = {}) => {
|
|
|
4279
4673
|
const home = opts.home ?? homedir7();
|
|
4280
4674
|
if (pkg) return "binary";
|
|
4281
4675
|
const resolved = resolveExecPath(execPath);
|
|
4282
|
-
const zeroBin =
|
|
4676
|
+
const zeroBin = join9(home, ".zero", "bin");
|
|
4283
4677
|
if (resolved.startsWith(zeroBin)) return "binary";
|
|
4284
4678
|
return "npm";
|
|
4285
4679
|
};
|
|
@@ -4304,12 +4698,12 @@ var compareVersions = (a, b) => {
|
|
|
4304
4698
|
if (pb.pre === null) return -1;
|
|
4305
4699
|
return pa.pre < pb.pre ? -1 : 1;
|
|
4306
4700
|
};
|
|
4307
|
-
var cachePath = (zeroDir) =>
|
|
4701
|
+
var cachePath = (zeroDir) => join9(zeroDir, CACHE_FILENAME);
|
|
4308
4702
|
var readCache = (zeroDir) => {
|
|
4309
4703
|
try {
|
|
4310
4704
|
const path = cachePath(zeroDir);
|
|
4311
4705
|
if (!existsSync8(path)) return emptyCache;
|
|
4312
|
-
const raw =
|
|
4706
|
+
const raw = readFileSync12(path, "utf8");
|
|
4313
4707
|
const parsed = JSON.parse(raw);
|
|
4314
4708
|
return {
|
|
4315
4709
|
lastCheckedMs: typeof parsed.lastCheckedMs === "number" ? parsed.lastCheckedMs : 0,
|
|
@@ -4393,12 +4787,12 @@ var maybePrintUpdateBanner = (zeroDir, currentVersion) => {
|
|
|
4393
4787
|
|
|
4394
4788
|
// src/index.ts
|
|
4395
4789
|
var main = async () => {
|
|
4396
|
-
const appContext = createAppContext();
|
|
4790
|
+
const appContext = await createAppContext();
|
|
4397
4791
|
if (!appContext) {
|
|
4398
4792
|
console.error("Failed to create app context");
|
|
4399
4793
|
process.exit(1);
|
|
4400
4794
|
}
|
|
4401
|
-
const zeroDir =
|
|
4795
|
+
const zeroDir = join10(homedir8(), ".zero");
|
|
4402
4796
|
maybePrintUpdateBanner(zeroDir, package_default.version);
|
|
4403
4797
|
const app = createApp(appContext);
|
|
4404
4798
|
let caughtError = null;
|