@zeroxyz/cli 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1136 -636
- package/package.json +3 -3
- package/skills/zero/SKILL.md +12 -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 Command11 } 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.26",
|
|
10
10
|
type: "module",
|
|
11
11
|
bin: {
|
|
12
12
|
zero: "dist/index.js",
|
|
@@ -24,8 +24,8 @@ var package_default = {
|
|
|
24
24
|
build: "tsup src/index.ts --format esm --out-dir dist --clean",
|
|
25
25
|
"build:binary": "tsup --config tsup.binary.ts && cp -r skills hooks dist/pkg/ && pnpm exec pkg dist/pkg/index.cjs --config pkg.json --targets node24-macos-arm64,node24-macos-x64,node24-linux-x64 --output dist/bin/zero",
|
|
26
26
|
prepublishOnly: "pnpm run build",
|
|
27
|
-
dev: "tsx src/index.ts",
|
|
28
|
-
cli: "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
27
|
+
dev: "ZERO_ENV=development tsx src/index.ts",
|
|
28
|
+
cli: "ZERO_ENV=development ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
29
29
|
"test:integration": "vitest run --project integration",
|
|
30
30
|
"test:online": "vitest run --project online",
|
|
31
31
|
"test:unit": "vitest run --project unit",
|
|
@@ -279,10 +279,14 @@ var ApiService = class {
|
|
|
279
279
|
const json = await this.request("POST", "/v1/bug-reports", data);
|
|
280
280
|
return createBugReportResponseSchema.parse(json);
|
|
281
281
|
};
|
|
282
|
-
getFundingUrl = async (amount) => {
|
|
282
|
+
getFundingUrl = async (amount, provider = "coinbase") => {
|
|
283
283
|
try {
|
|
284
|
-
const
|
|
285
|
-
|
|
284
|
+
const params = new URLSearchParams({ provider });
|
|
285
|
+
if (amount) params.set("amount", amount);
|
|
286
|
+
const json = await this.request(
|
|
287
|
+
"GET",
|
|
288
|
+
`/v1/wallet/fund-url?${params.toString()}`
|
|
289
|
+
);
|
|
286
290
|
const parsed = z.object({ url: z.string() }).parse(json);
|
|
287
291
|
return parsed.url;
|
|
288
292
|
} catch {
|
|
@@ -376,7 +380,10 @@ Categories the classifier picks from:
|
|
|
376
380
|
).option("--idempotency-key <key>", "Override the auto-generated dedup key").option(
|
|
377
381
|
"--from-file <path>",
|
|
378
382
|
"Submit bug reports in bulk from a JSONL file (one report per line)"
|
|
379
|
-
).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(
|
|
380
387
|
async (description, options) => {
|
|
381
388
|
try {
|
|
382
389
|
const { analyticsService, apiService, stateService } = appContext.services;
|
|
@@ -417,7 +424,10 @@ Categories the classifier picks from:
|
|
|
417
424
|
category: result2.category,
|
|
418
425
|
severity: parsed.severity ?? 2,
|
|
419
426
|
bulk: true,
|
|
420
|
-
deduped: result2.deduped
|
|
427
|
+
deduped: result2.deduped,
|
|
428
|
+
hasCapability: !!result2.attached.capabilityId,
|
|
429
|
+
hasRun: !!result2.attached.runId,
|
|
430
|
+
hasSearch: !!result2.attached.searchId
|
|
421
431
|
});
|
|
422
432
|
} catch (err) {
|
|
423
433
|
failed += 1;
|
|
@@ -502,9 +512,15 @@ Bulk bug-report complete: ${ok} ok, ${failed} failed`
|
|
|
502
512
|
}
|
|
503
513
|
analyticsService.capture("bug_report_submitted", {
|
|
504
514
|
category: result.category,
|
|
515
|
+
categoryOverridden: !!options.category,
|
|
505
516
|
severity: options.severity ?? 2,
|
|
506
517
|
deduped: result.deduped,
|
|
507
|
-
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
|
|
508
524
|
});
|
|
509
525
|
} catch (err) {
|
|
510
526
|
console.error(
|
|
@@ -567,125 +583,677 @@ var configCommand = (_appContext) => new Command2("config").description("View or
|
|
|
567
583
|
|
|
568
584
|
// src/commands/fetch-command.ts
|
|
569
585
|
import { Command as Command3 } from "commander";
|
|
586
|
+
import { formatUnits as formatUnits2 } from "viem";
|
|
570
587
|
|
|
571
|
-
// src/
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
588
|
+
// src/services/payment-service.ts
|
|
589
|
+
import {
|
|
590
|
+
adaptViemWallet,
|
|
591
|
+
convertViemChainToRelayChain,
|
|
592
|
+
createClient as createRelayClient,
|
|
593
|
+
getClient as getRelayClient,
|
|
594
|
+
MAINNET_RELAY_API
|
|
595
|
+
} from "@relayprotocol/relay-sdk";
|
|
596
|
+
import { x402Client as X402Client } from "@x402/core/client";
|
|
597
|
+
import { decodePaymentResponseHeader, x402HTTPClient } from "@x402/core/http";
|
|
598
|
+
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
599
|
+
import { createSIWxClientHook } from "@x402/extensions/sign-in-with-x";
|
|
600
|
+
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
601
|
+
import { Challenge, Receipt } from "mppx";
|
|
602
|
+
import { Mppx, tempo } from "mppx/client";
|
|
603
|
+
import {
|
|
604
|
+
createPublicClient,
|
|
605
|
+
createWalletClient,
|
|
606
|
+
formatUnits,
|
|
607
|
+
http
|
|
608
|
+
} from "viem";
|
|
609
|
+
import { base, baseSepolia } from "viem/chains";
|
|
610
|
+
var SessionCloseFailedError = class extends Error {
|
|
611
|
+
session;
|
|
612
|
+
response;
|
|
613
|
+
capturedAmount;
|
|
614
|
+
constructor(params) {
|
|
615
|
+
super(params.message);
|
|
616
|
+
this.name = "SessionCloseFailedError";
|
|
617
|
+
this.session = params.session;
|
|
618
|
+
this.response = params.response;
|
|
619
|
+
this.capturedAmount = params.capturedAmount;
|
|
581
620
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
621
|
+
};
|
|
622
|
+
var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
623
|
+
var USDC_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
624
|
+
var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
625
|
+
var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
|
|
626
|
+
var BASE_CHAIN_ID = 8453;
|
|
627
|
+
var TEMPO_CHAIN_ID = 4217;
|
|
628
|
+
var TEMPO_TESTNET_CHAIN_ID = 42431;
|
|
629
|
+
var DEFAULT_MAX_DEPOSIT = "100";
|
|
630
|
+
var KNOWN_EIP712_DOMAINS = {
|
|
631
|
+
// USDC on Base
|
|
632
|
+
[USDC_BASE.toLowerCase()]: { name: "USDC", version: "2" },
|
|
633
|
+
// USDC on Base Sepolia
|
|
634
|
+
[USDC_BASE_SEPOLIA.toLowerCase()]: { name: "USDC", version: "2" }
|
|
635
|
+
};
|
|
636
|
+
var buildRelayClientOptions = () => ({
|
|
637
|
+
baseApiUrl: MAINNET_RELAY_API,
|
|
638
|
+
source: "zero-cli",
|
|
639
|
+
chains: [convertViemChainToRelayChain(base)]
|
|
640
|
+
});
|
|
641
|
+
var calculateBuffer = (baseBalance) => {
|
|
642
|
+
const twentyFivePercent = baseBalance / 4n;
|
|
643
|
+
const twoDollars = 2000000n;
|
|
644
|
+
return twentyFivePercent < twoDollars ? twentyFivePercent : twoDollars;
|
|
645
|
+
};
|
|
646
|
+
var tempoChain = {
|
|
647
|
+
id: TEMPO_CHAIN_ID,
|
|
648
|
+
name: "Tempo",
|
|
649
|
+
nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
|
|
650
|
+
rpcUrls: {
|
|
651
|
+
default: { http: ["https://rpc.tempo.xyz"] }
|
|
591
652
|
}
|
|
592
|
-
return { type: typeOf(value) };
|
|
593
653
|
};
|
|
594
|
-
var
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
654
|
+
var tempoTestnetChain = {
|
|
655
|
+
id: TEMPO_TESTNET_CHAIN_ID,
|
|
656
|
+
name: "Tempo Testnet",
|
|
657
|
+
nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
|
|
658
|
+
rpcUrls: {
|
|
659
|
+
default: { http: ["https://rpc.moderato.tempo.xyz"] }
|
|
660
|
+
}
|
|
598
661
|
};
|
|
599
|
-
var
|
|
662
|
+
var ERC20_BALANCE_ABI = [
|
|
663
|
+
{
|
|
664
|
+
inputs: [{ name: "account", type: "address" }],
|
|
665
|
+
name: "balanceOf",
|
|
666
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
667
|
+
stateMutability: "view",
|
|
668
|
+
type: "function"
|
|
669
|
+
}
|
|
670
|
+
];
|
|
671
|
+
var decodeSessionReceiptHeader = (header) => {
|
|
672
|
+
if (!header) return null;
|
|
600
673
|
try {
|
|
601
|
-
|
|
674
|
+
const padLen = (4 - header.length % 4) % 4;
|
|
675
|
+
const padded = header.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat(padLen);
|
|
676
|
+
const json = Buffer.from(padded, "base64").toString("utf8");
|
|
677
|
+
const parsed = JSON.parse(json);
|
|
678
|
+
if (typeof parsed.channelId !== "string" || typeof parsed.acceptedCumulative !== "string" || typeof parsed.spent !== "string" || typeof parsed.challengeId !== "string") {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
return parsed;
|
|
602
682
|
} catch {
|
|
603
683
|
return null;
|
|
604
684
|
}
|
|
605
685
|
};
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
686
|
+
var readSessionReceipt = (response) => decodeSessionReceiptHeader(
|
|
687
|
+
response.headers.get("Payment-Receipt") ?? response.headers.get("payment-receipt")
|
|
688
|
+
);
|
|
689
|
+
var pickSessionCloseAmount = (receipt, openTimeCumulative) => {
|
|
690
|
+
if (!receipt) return openTimeCumulative;
|
|
691
|
+
const accepted = BigInt(receipt.acceptedCumulative);
|
|
692
|
+
const spent = BigInt(receipt.spent);
|
|
693
|
+
const fromReceipt = accepted > spent ? accepted : spent;
|
|
694
|
+
return fromReceipt > openTimeCumulative ? fromReceipt : openTimeCumulative;
|
|
695
|
+
};
|
|
696
|
+
var PaymentService = class {
|
|
697
|
+
constructor(account, config, deps = {}) {
|
|
698
|
+
this.account = account;
|
|
699
|
+
this.config = config;
|
|
700
|
+
this.fetchOverride = deps.fetchImpl;
|
|
701
|
+
}
|
|
702
|
+
relayInitialized = false;
|
|
703
|
+
fetchOverride;
|
|
704
|
+
/**
|
|
705
|
+
* Resolve the fetch implementation lazily so tests can `vi.stubGlobal`
|
|
706
|
+
* the global `fetch` after the service is constructed.
|
|
707
|
+
*/
|
|
708
|
+
get fetchImpl() {
|
|
709
|
+
return this.fetchOverride ?? globalThis.fetch;
|
|
710
|
+
}
|
|
711
|
+
getAccount = () => this.account;
|
|
712
|
+
ensureRelayClient = () => {
|
|
713
|
+
if (!this.relayInitialized) {
|
|
714
|
+
createRelayClient(buildRelayClientOptions());
|
|
715
|
+
this.relayInitialized = true;
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
bridgeToTempo = async (requiredAmount, onProgress) => {
|
|
719
|
+
if (!this.account) throw new Error("No wallet configured");
|
|
720
|
+
this.ensureRelayClient();
|
|
721
|
+
const baseBalance = await this.getBalanceRaw("base");
|
|
722
|
+
const buffer = calculateBuffer(baseBalance);
|
|
723
|
+
const bridgeAmount = requiredAmount + buffer;
|
|
724
|
+
if (baseBalance < bridgeAmount) {
|
|
725
|
+
throw new Error(
|
|
726
|
+
`Insufficient Base USDC to bridge: have ${formatUnits(baseBalance, 6)}, need ${formatUnits(bridgeAmount, 6)} (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer)`
|
|
615
727
|
);
|
|
616
|
-
return { protocol: "x402", raw: decoded };
|
|
617
|
-
} catch {
|
|
618
|
-
return { protocol: "x402", raw: { encoded: x402Header } };
|
|
619
728
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (options.header) {
|
|
649
|
-
for (const h of options.header) {
|
|
650
|
-
const colonIdx = h.indexOf(":");
|
|
651
|
-
if (colonIdx > 0) {
|
|
652
|
-
headers[h.slice(0, colonIdx).trim()] = h.slice(colonIdx + 1).trim();
|
|
653
|
-
}
|
|
729
|
+
onProgress?.(
|
|
730
|
+
`Bridging ${formatUnits(bridgeAmount, 6)} USDC from Base to Tempo...`
|
|
731
|
+
);
|
|
732
|
+
const walletClient = createWalletClient({
|
|
733
|
+
account: this.account,
|
|
734
|
+
chain: base,
|
|
735
|
+
transport: http()
|
|
736
|
+
});
|
|
737
|
+
const quote = await getRelayClient().actions.getQuote({
|
|
738
|
+
chainId: BASE_CHAIN_ID,
|
|
739
|
+
toChainId: TEMPO_CHAIN_ID,
|
|
740
|
+
currency: USDC_BASE,
|
|
741
|
+
toCurrency: USDC_TEMPO,
|
|
742
|
+
amount: bridgeAmount.toString(),
|
|
743
|
+
tradeType: "EXACT_INPUT",
|
|
744
|
+
user: this.account.address,
|
|
745
|
+
recipient: this.account.address,
|
|
746
|
+
options: {
|
|
747
|
+
usePermit: true
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
let bridgeTxHash = null;
|
|
751
|
+
await getRelayClient().actions.execute({
|
|
752
|
+
quote,
|
|
753
|
+
wallet: adaptViemWallet(walletClient),
|
|
754
|
+
onProgress: ({ txHashes }) => {
|
|
755
|
+
if (txHashes?.length && !bridgeTxHash) {
|
|
756
|
+
bridgeTxHash = txHashes[0]?.txHash ?? null;
|
|
654
757
|
}
|
|
655
758
|
}
|
|
656
|
-
|
|
657
|
-
|
|
759
|
+
});
|
|
760
|
+
return bridgeTxHash;
|
|
761
|
+
};
|
|
762
|
+
handlePayment = async (url, request, paymentRequirement, maxPay, onProgress) => {
|
|
763
|
+
if (!this.account) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
"No wallet configured \u2014 run `zero init` or set ZERO_PRIVATE_KEY"
|
|
658
766
|
);
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
767
|
+
}
|
|
768
|
+
if (paymentRequirement.protocol === "x402") {
|
|
769
|
+
onProgress?.("Paying via x402 on Base...");
|
|
770
|
+
return this.payX402(url, request, paymentRequirement.raw, maxPay);
|
|
771
|
+
}
|
|
772
|
+
if (paymentRequirement.protocol === "mpp") {
|
|
773
|
+
onProgress?.("Paying via MPP on Tempo...");
|
|
774
|
+
return this.payMpp(
|
|
775
|
+
url,
|
|
776
|
+
request,
|
|
777
|
+
paymentRequirement.raw,
|
|
778
|
+
maxPay,
|
|
779
|
+
onProgress
|
|
672
780
|
);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
781
|
+
}
|
|
782
|
+
throw new Error("Unrecognized 402 payment protocol");
|
|
783
|
+
};
|
|
784
|
+
payX402 = async (url, request, _raw, maxPay) => {
|
|
785
|
+
if (!this.account) throw new Error("No wallet configured");
|
|
786
|
+
let capturedAmount = "0";
|
|
787
|
+
const client = new X402Client().register(
|
|
788
|
+
"eip155:*",
|
|
789
|
+
new ExactEvmScheme(this.account)
|
|
790
|
+
);
|
|
791
|
+
client.onBeforePaymentCreation(async (context) => {
|
|
792
|
+
const selected = context.selectedRequirements;
|
|
793
|
+
if (selected && (!selected.extra?.name || !selected.extra?.version)) {
|
|
794
|
+
const known = KNOWN_EIP712_DOMAINS[selected.asset?.toLowerCase() ?? ""];
|
|
795
|
+
if (known) {
|
|
796
|
+
selected.extra = { ...selected.extra, ...known };
|
|
797
|
+
}
|
|
680
798
|
}
|
|
681
|
-
|
|
682
|
-
|
|
799
|
+
const requirement = context.paymentRequired.accepts[0];
|
|
800
|
+
if (!requirement) return;
|
|
801
|
+
capturedAmount = formatUnits(BigInt(requirement.amount), 6);
|
|
802
|
+
if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
|
|
803
|
+
return {
|
|
804
|
+
abort: true,
|
|
805
|
+
reason: `Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
const httpClient = new x402HTTPClient(client).onPaymentRequired(
|
|
810
|
+
createSIWxClientHook(this.account)
|
|
811
|
+
);
|
|
812
|
+
const wrappedFetch = wrapFetchWithPayment(fetch, httpClient);
|
|
813
|
+
const response = await wrappedFetch(url, {
|
|
814
|
+
method: request.method,
|
|
815
|
+
headers: request.headers,
|
|
816
|
+
body: request.body
|
|
817
|
+
});
|
|
818
|
+
let txHash = null;
|
|
819
|
+
const paymentResponseHeader = response.headers.get("payment-response") ?? response.headers.get("x-payment-response");
|
|
820
|
+
if (paymentResponseHeader) {
|
|
821
|
+
try {
|
|
822
|
+
const settlement = decodePaymentResponseHeader(paymentResponseHeader);
|
|
823
|
+
txHash = settlement.transaction ?? null;
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
response,
|
|
829
|
+
protocol: "x402",
|
|
830
|
+
chain: "base",
|
|
831
|
+
txHash,
|
|
832
|
+
amount: capturedAmount,
|
|
833
|
+
asset: "USDC"
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
/**
|
|
837
|
+
* Shared pre-payment work: compute the Tempo amount we need, enforce
|
|
838
|
+
* --max-pay, and bridge from Base USDC if the Tempo balance is short.
|
|
839
|
+
* Invoked from mppx's `onChallenge` — i.e. AFTER the server's 402 and
|
|
840
|
+
* BEFORE mppx signs a credential — so balance/bridge logic runs in-band
|
|
841
|
+
* without adding a pre-probe round-trip.
|
|
842
|
+
*/
|
|
843
|
+
prepareTempoFunds = async (challenge, maxPay, onProgress) => {
|
|
844
|
+
const challengeRequest = challenge.request;
|
|
845
|
+
let requiredRaw;
|
|
846
|
+
if (challenge.intent === "session") {
|
|
847
|
+
const suggestedDeposit = challengeRequest.suggestedDeposit;
|
|
848
|
+
requiredRaw = suggestedDeposit ? BigInt(suggestedDeposit) : BigInt(
|
|
849
|
+
Math.floor(
|
|
850
|
+
Number.parseFloat(maxPay ?? DEFAULT_MAX_DEPOSIT) * 1e6
|
|
851
|
+
)
|
|
852
|
+
);
|
|
853
|
+
} else {
|
|
854
|
+
requiredRaw = BigInt(challengeRequest.amount);
|
|
855
|
+
}
|
|
856
|
+
const capturedAmount = formatUnits(requiredRaw, 6);
|
|
857
|
+
if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
|
|
858
|
+
throw new Error(
|
|
859
|
+
`Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
const methodDetails = challengeRequest.methodDetails;
|
|
863
|
+
const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
|
|
864
|
+
const isTestnet = challengeChainId === TEMPO_TESTNET_CHAIN_ID;
|
|
865
|
+
onProgress?.(`Checking Tempo balance...`);
|
|
866
|
+
const tempoBalance = await this.getBalanceRaw(
|
|
867
|
+
isTestnet ? "tempo-testnet" : "tempo"
|
|
868
|
+
);
|
|
869
|
+
if (tempoBalance < requiredRaw) {
|
|
870
|
+
if (isTestnet) {
|
|
871
|
+
throw new Error(
|
|
872
|
+
`Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
await this.bridgeToTempo(requiredRaw, onProgress);
|
|
876
|
+
}
|
|
877
|
+
return capturedAmount;
|
|
878
|
+
};
|
|
879
|
+
/**
|
|
880
|
+
* MPP payment entrypoint. Runs a SINGLE `mppx.fetch` and uses mppx's
|
|
881
|
+
* `onChallenge` callback to run balance/bridge/max-pay logic in-band
|
|
882
|
+
* with the server's 402. After the 200 comes back, we branch on the
|
|
883
|
+
* captured challenge intent + whether the tempo method reported the
|
|
884
|
+
* channel as opened (only session lifecycle fires `onChannelUpdate`).
|
|
885
|
+
*
|
|
886
|
+
* Why no pre-probe: a pre-probe adds an extra unauthenticated POST
|
|
887
|
+
* before mppx's own 402 dance (2 → 3 server-side requests). Session-
|
|
888
|
+
* intent facilitators that re-run the LLM per request can't reconcile
|
|
889
|
+
* the extra state and return `410 "channel not found"` on the close.
|
|
890
|
+
* This mirrors the working production v0.0.21 single-fetch flow.
|
|
891
|
+
*/
|
|
892
|
+
payMpp = async (url, request, _raw, maxPay, onProgress) => {
|
|
893
|
+
const account = this.account;
|
|
894
|
+
if (!account) throw new Error("No wallet configured");
|
|
895
|
+
let capturedAmount = "0";
|
|
896
|
+
let capturedChallenge;
|
|
897
|
+
let channelEntry;
|
|
898
|
+
const mppx = Mppx.create({
|
|
899
|
+
polyfill: false,
|
|
900
|
+
fetch: this.fetchImpl,
|
|
901
|
+
methods: [
|
|
902
|
+
tempo({
|
|
903
|
+
account,
|
|
904
|
+
maxDeposit: maxPay ?? DEFAULT_MAX_DEPOSIT,
|
|
905
|
+
onChannelUpdate: (entry) => {
|
|
906
|
+
channelEntry = {
|
|
907
|
+
channelId: entry.channelId,
|
|
908
|
+
escrowContract: entry.escrowContract,
|
|
909
|
+
cumulativeAmount: entry.cumulativeAmount,
|
|
910
|
+
opened: entry.opened
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
})
|
|
914
|
+
],
|
|
915
|
+
onChallenge: async (challenge) => {
|
|
916
|
+
capturedChallenge = challenge;
|
|
917
|
+
capturedAmount = await this.prepareTempoFunds(
|
|
918
|
+
challenge,
|
|
919
|
+
maxPay,
|
|
920
|
+
onProgress
|
|
921
|
+
);
|
|
922
|
+
return void 0;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
const response = await mppx.fetch(url, {
|
|
926
|
+
method: request.method,
|
|
927
|
+
headers: request.headers,
|
|
928
|
+
body: request.body
|
|
929
|
+
});
|
|
930
|
+
if (capturedChallenge?.intent === "session" && channelEntry?.opened) {
|
|
931
|
+
return this.completeMppSession({
|
|
932
|
+
url,
|
|
933
|
+
request,
|
|
934
|
+
challenge: capturedChallenge,
|
|
935
|
+
channelEntry,
|
|
936
|
+
response,
|
|
937
|
+
capturedAmount,
|
|
938
|
+
mppx
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
let txHash = null;
|
|
942
|
+
try {
|
|
943
|
+
const receipt = Receipt.fromResponse(response);
|
|
944
|
+
txHash = receipt.reference ?? null;
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
response,
|
|
949
|
+
protocol: "mpp",
|
|
950
|
+
chain: "tempo",
|
|
951
|
+
txHash,
|
|
952
|
+
amount: capturedAmount,
|
|
953
|
+
asset: "USDC"
|
|
954
|
+
};
|
|
955
|
+
};
|
|
956
|
+
/**
|
|
957
|
+
* Close an opened MPP session channel after the seller returns a 200.
|
|
958
|
+
*
|
|
959
|
+
* Decodes the server's `Payment-Receipt` to get the settled
|
|
960
|
+
* `acceptedCumulative`/`spent`, signs a close voucher at that amount
|
|
961
|
+
* (never below on-chain settled), and POSTs it back to the seller.
|
|
962
|
+
* Needed for dynamic-pricing sellers whose open-time `amount` is `0` —
|
|
963
|
+
* signing close with `cumulativeAmount=0` would be rejected with
|
|
964
|
+
* `voucher cumulativeAmount is below on-chain settled amount`.
|
|
965
|
+
*
|
|
966
|
+
* When the seller's facilitator rejects the close (e.g. strict matching
|
|
967
|
+
* of fresh challenge IDs per request), `SessionCloseFailedError` is
|
|
968
|
+
* thrown carrying the session metadata so the caller can record the run
|
|
969
|
+
* against the orphaned channel.
|
|
970
|
+
*/
|
|
971
|
+
completeMppSession = async (params) => {
|
|
972
|
+
const {
|
|
973
|
+
url,
|
|
974
|
+
request,
|
|
975
|
+
challenge,
|
|
976
|
+
channelEntry,
|
|
977
|
+
response,
|
|
978
|
+
capturedAmount,
|
|
979
|
+
mppx
|
|
980
|
+
} = params;
|
|
981
|
+
const challengeRequest = challenge.request;
|
|
982
|
+
const recipient = challengeRequest.recipient;
|
|
983
|
+
const methodDetails = challengeRequest.methodDetails;
|
|
984
|
+
const challengeEscrow = methodDetails?.escrowContract;
|
|
985
|
+
const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
|
|
986
|
+
if (!recipient || !challengeEscrow) {
|
|
987
|
+
throw new Error("session challenge missing recipient/escrowContract");
|
|
988
|
+
}
|
|
989
|
+
if (!challengeChainId) {
|
|
990
|
+
throw new Error("session challenge missing chainId");
|
|
991
|
+
}
|
|
992
|
+
const {
|
|
993
|
+
channelId,
|
|
994
|
+
escrowContract,
|
|
995
|
+
cumulativeAmount: openTimeCumulative
|
|
996
|
+
} = channelEntry;
|
|
997
|
+
const receipt = readSessionReceipt(response);
|
|
998
|
+
const closeAmount = pickSessionCloseAmount(receipt, openTimeCumulative);
|
|
999
|
+
const orphanedSession = {
|
|
1000
|
+
channelId,
|
|
1001
|
+
escrowContract,
|
|
1002
|
+
chainId: challengeChainId,
|
|
1003
|
+
recipient,
|
|
1004
|
+
cumulativeAmount: closeAmount.toString()
|
|
1005
|
+
};
|
|
1006
|
+
const closeFailed = (message) => new SessionCloseFailedError({
|
|
1007
|
+
message,
|
|
1008
|
+
session: orphanedSession,
|
|
1009
|
+
response,
|
|
1010
|
+
capturedAmount
|
|
1011
|
+
});
|
|
1012
|
+
const closeChallengeResponse = new Response("", {
|
|
1013
|
+
status: 402,
|
|
1014
|
+
headers: { "WWW-Authenticate": Challenge.serialize(challenge) }
|
|
1015
|
+
});
|
|
1016
|
+
let closeCredential;
|
|
1017
|
+
try {
|
|
1018
|
+
closeCredential = await mppx.createCredential(closeChallengeResponse, {
|
|
1019
|
+
action: "close",
|
|
1020
|
+
channelId,
|
|
1021
|
+
cumulativeAmountRaw: closeAmount.toString()
|
|
1022
|
+
});
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
throw closeFailed(
|
|
1025
|
+
`Session close credential build failed for channel ${channelId}: ${err instanceof Error ? err.message : String(err)}`
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
const closeMethod = request.method.toUpperCase();
|
|
1029
|
+
const methodCarriesBody = closeMethod === "POST" || closeMethod === "PUT" || closeMethod === "PATCH";
|
|
1030
|
+
const closeResponse = await this.fetchImpl(url, {
|
|
1031
|
+
method: closeMethod,
|
|
1032
|
+
headers: {
|
|
1033
|
+
...request.headers,
|
|
1034
|
+
// biome-ignore lint/style/useNamingConvention: HTTP header name
|
|
1035
|
+
Authorization: closeCredential
|
|
1036
|
+
},
|
|
1037
|
+
...methodCarriesBody && request.body ? { body: request.body } : {}
|
|
1038
|
+
});
|
|
1039
|
+
if (!closeResponse.ok) {
|
|
1040
|
+
const body = await closeResponse.text().catch(() => "");
|
|
1041
|
+
throw closeFailed(
|
|
1042
|
+
`Seller rejected close credential (status ${closeResponse.status}${body ? `: ${body.slice(0, 200)}` : ""}) for channel ${channelId}`
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
const closeReceipt = readSessionReceipt(closeResponse);
|
|
1046
|
+
return {
|
|
1047
|
+
response,
|
|
1048
|
+
protocol: "mpp",
|
|
1049
|
+
chain: "tempo",
|
|
1050
|
+
txHash: null,
|
|
1051
|
+
amount: capturedAmount,
|
|
1052
|
+
asset: "USDC",
|
|
1053
|
+
session: {
|
|
1054
|
+
channelId,
|
|
1055
|
+
escrowContract,
|
|
1056
|
+
chainId: challengeChainId,
|
|
1057
|
+
recipient,
|
|
1058
|
+
cumulativeAmount: closeReceipt?.acceptedCumulative ?? closeAmount.toString()
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
};
|
|
1062
|
+
resolveChainConfig = (chain) => {
|
|
1063
|
+
switch (chain) {
|
|
1064
|
+
case "base":
|
|
1065
|
+
return { viemChain: base, token: USDC_BASE };
|
|
1066
|
+
case "base-sepolia":
|
|
1067
|
+
return { viemChain: baseSepolia, token: USDC_BASE_SEPOLIA };
|
|
1068
|
+
case "tempo":
|
|
1069
|
+
return { viemChain: tempoChain, token: USDC_TEMPO };
|
|
1070
|
+
case "tempo-testnet":
|
|
1071
|
+
return { viemChain: tempoTestnetChain, token: PATHUSD_TEMPO };
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
getBalanceRaw = async (chain) => {
|
|
1075
|
+
if (!this.account) return 0n;
|
|
1076
|
+
const { viemChain, token } = this.resolveChainConfig(chain);
|
|
1077
|
+
const client = createPublicClient({
|
|
1078
|
+
chain: viemChain,
|
|
1079
|
+
transport: http()
|
|
1080
|
+
});
|
|
1081
|
+
const balance = await client.readContract({
|
|
1082
|
+
address: token,
|
|
1083
|
+
abi: ERC20_BALANCE_ABI,
|
|
1084
|
+
functionName: "balanceOf",
|
|
1085
|
+
args: [this.account.address]
|
|
1086
|
+
});
|
|
1087
|
+
return balance;
|
|
1088
|
+
};
|
|
1089
|
+
getBalance = async (chain) => {
|
|
1090
|
+
const raw = await this.getBalanceRaw(chain);
|
|
1091
|
+
return { amount: formatUnits(raw, 6), asset: "USDC" };
|
|
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
|
+
};
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
// src/util/infer-schema.ts
|
|
1108
|
+
var inferSchema = (value, depth = 0) => {
|
|
1109
|
+
if (depth > 6) return { type: typeOf(value) };
|
|
1110
|
+
if (value === null) return { type: "null" };
|
|
1111
|
+
if (Array.isArray(value)) {
|
|
1112
|
+
const itemSchemas = value.slice(0, 3).map((v) => inferSchema(v, depth + 1));
|
|
1113
|
+
return {
|
|
1114
|
+
type: "array",
|
|
1115
|
+
items: itemSchemas[0] ?? { type: "unknown" }
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (typeof value === "object") {
|
|
1119
|
+
const obj = value;
|
|
1120
|
+
const properties = {};
|
|
1121
|
+
const required = [];
|
|
1122
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1123
|
+
properties[k] = inferSchema(v, depth + 1);
|
|
1124
|
+
required.push(k);
|
|
1125
|
+
}
|
|
1126
|
+
return { type: "object", properties, required };
|
|
1127
|
+
}
|
|
1128
|
+
return { type: typeOf(value) };
|
|
1129
|
+
};
|
|
1130
|
+
var typeOf = (v) => {
|
|
1131
|
+
if (v === null) return "null";
|
|
1132
|
+
if (Array.isArray(v)) return "array";
|
|
1133
|
+
return typeof v;
|
|
1134
|
+
};
|
|
1135
|
+
var tryParseJson = (text) => {
|
|
1136
|
+
try {
|
|
1137
|
+
return JSON.parse(text);
|
|
1138
|
+
} catch {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
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
|
+
|
|
1157
|
+
// src/commands/fetch-command.ts
|
|
1158
|
+
var isTextContentType = (contentType) => {
|
|
1159
|
+
if (!contentType) return true;
|
|
1160
|
+
const ct = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
1161
|
+
if (ct.startsWith("text/")) return true;
|
|
1162
|
+
if (ct === "application/json" || ct.endsWith("+json")) return true;
|
|
1163
|
+
if (ct === "application/xml" || ct.endsWith("+xml")) return true;
|
|
1164
|
+
if (ct === "application/javascript" || ct === "application/ecmascript") {
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
if (ct === "application/x-www-form-urlencoded") return true;
|
|
1168
|
+
return false;
|
|
1169
|
+
};
|
|
1170
|
+
var detectPaymentRequirement = (headers, status) => {
|
|
1171
|
+
if (status !== 402) return null;
|
|
1172
|
+
const x402Header = headers.get("payment-required") ?? headers.get("x-payment-required");
|
|
1173
|
+
if (x402Header) {
|
|
1174
|
+
try {
|
|
1175
|
+
const decoded = JSON.parse(
|
|
1176
|
+
Buffer.from(x402Header, "base64").toString("utf8")
|
|
1177
|
+
);
|
|
1178
|
+
return { protocol: "x402", raw: decoded };
|
|
1179
|
+
} catch {
|
|
1180
|
+
return { protocol: "x402", raw: { encoded: x402Header } };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const wwwAuth = headers.get("www-authenticate");
|
|
1184
|
+
if (wwwAuth?.toLowerCase().includes("payment")) {
|
|
1185
|
+
return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
|
|
1186
|
+
}
|
|
1187
|
+
return { protocol: "unknown", raw: {} };
|
|
1188
|
+
};
|
|
1189
|
+
var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a capability URL with automatic payment handling").argument("<url>", "URL to fetch").option(
|
|
1190
|
+
"-X, --method <method>",
|
|
1191
|
+
"HTTP method (GET, POST, PUT, PATCH, DELETE). Defaults to POST when -d is set, otherwise GET"
|
|
1192
|
+
).option("-d, --data <body>", "Request body (JSON string)").option("-H, --header <header...>", "Headers in Key:Value format").option("--max-pay <amount>", "Maximum amount willing to pay (USDC)").option(
|
|
1193
|
+
"--capability <id>",
|
|
1194
|
+
"Bind this fetch to a capability (uid or slug) so a reviewable run is recorded even without a prior `zero search`"
|
|
1195
|
+
).option(
|
|
1196
|
+
"--json",
|
|
1197
|
+
"Emit {runId, status, latencyMs, payment, body} as JSON on stdout (for batch/non-TTY use)"
|
|
1198
|
+
).option(
|
|
1199
|
+
"--agent <name>",
|
|
1200
|
+
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
1201
|
+
).action(
|
|
1202
|
+
async (url, options) => {
|
|
1203
|
+
try {
|
|
1204
|
+
const {
|
|
1205
|
+
analyticsService,
|
|
1206
|
+
apiService,
|
|
1207
|
+
paymentService,
|
|
1208
|
+
stateService,
|
|
1209
|
+
walletService
|
|
1210
|
+
} = appContext.services;
|
|
1211
|
+
const startTime = Date.now();
|
|
1212
|
+
const headers = {};
|
|
1213
|
+
if (options.header) {
|
|
1214
|
+
for (const h of options.header) {
|
|
1215
|
+
const colonIdx = h.indexOf(":");
|
|
1216
|
+
if (colonIdx > 0) {
|
|
1217
|
+
headers[h.slice(0, colonIdx).trim()] = h.slice(colonIdx + 1).trim();
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
const hasContentType = Object.keys(headers).some(
|
|
1222
|
+
(k) => k.toLowerCase() === "content-type"
|
|
1223
|
+
);
|
|
1224
|
+
if (options.data && !hasContentType) {
|
|
1225
|
+
headers["content-type"] = "application/json";
|
|
1226
|
+
}
|
|
1227
|
+
const log = (msg) => console.error(` ${msg}`);
|
|
1228
|
+
const method = options.method ? options.method.toUpperCase() : options.data ? "POST" : "GET";
|
|
1229
|
+
const requestInit = {
|
|
1230
|
+
method,
|
|
1231
|
+
headers,
|
|
1232
|
+
body: options.data
|
|
1233
|
+
};
|
|
1234
|
+
const lastSearch = stateService.loadLastSearch();
|
|
1235
|
+
const matchedCapability = lastSearch?.capabilities.find(
|
|
1236
|
+
(c) => url.startsWith(c.url)
|
|
1237
|
+
);
|
|
1238
|
+
const capabilityId = options.capability ?? matchedCapability?.id ?? null;
|
|
1239
|
+
const searchId = matchedCapability ? lastSearch?.searchId : void 0;
|
|
1240
|
+
const skipReasons = [];
|
|
1241
|
+
if (!apiService.walletAddress) {
|
|
1242
|
+
skipReasons.push(
|
|
1243
|
+
"no wallet configured (run `zero wallet import` / set ZERO_PRIVATE_KEY)"
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
if (!capabilityId) {
|
|
1247
|
+
skipReasons.push(
|
|
683
1248
|
"no capability resolved \u2014 pass --capability <uid|slug> or run `zero search` first so the URL can be matched"
|
|
684
1249
|
);
|
|
685
1250
|
}
|
|
686
1251
|
let finalResponse;
|
|
1252
|
+
let bodyBytes;
|
|
687
1253
|
let body = "";
|
|
1254
|
+
let bodyIsBinary = false;
|
|
688
1255
|
let paymentMeta;
|
|
1256
|
+
let sessionMeta;
|
|
689
1257
|
let fetchError;
|
|
690
1258
|
try {
|
|
691
1259
|
log(`Calling ${url}...`);
|
|
@@ -711,30 +1279,73 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
711
1279
|
chain: result.chain,
|
|
712
1280
|
txHash: result.txHash,
|
|
713
1281
|
amount: result.amount,
|
|
714
|
-
asset: result.asset
|
|
1282
|
+
asset: result.asset,
|
|
1283
|
+
...result.session && { session: result.session }
|
|
715
1284
|
};
|
|
716
1285
|
log(
|
|
717
1286
|
`Paid ${result.amount} ${result.asset} via ${result.protocol} on ${result.chain}`
|
|
718
1287
|
);
|
|
1288
|
+
if (result.session) {
|
|
1289
|
+
sessionMeta = result.session;
|
|
1290
|
+
log(
|
|
1291
|
+
`MPP session closed \u2014 channel ${result.session.channelId.slice(0, 10)}... \u2192 ${result.session.recipient.slice(0, 8)}... (${formatUnits2(BigInt(result.session.cumulativeAmount), 6)} USDC settled)`
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
719
1294
|
} else {
|
|
720
1295
|
finalResponse = response;
|
|
721
1296
|
}
|
|
722
|
-
|
|
1297
|
+
const buf = Buffer.from(await finalResponse.arrayBuffer());
|
|
1298
|
+
bodyBytes = buf;
|
|
1299
|
+
bodyIsBinary = !isTextContentType(
|
|
1300
|
+
finalResponse.headers.get("content-type")
|
|
1301
|
+
);
|
|
1302
|
+
body = bodyIsBinary ? "" : buf.toString("utf8");
|
|
723
1303
|
} catch (err) {
|
|
724
|
-
|
|
1304
|
+
if (err instanceof SessionCloseFailedError) {
|
|
1305
|
+
finalResponse = err.response;
|
|
1306
|
+
try {
|
|
1307
|
+
const buf = Buffer.from(await err.response.arrayBuffer());
|
|
1308
|
+
bodyBytes = buf;
|
|
1309
|
+
bodyIsBinary = !isTextContentType(
|
|
1310
|
+
err.response.headers.get("content-type")
|
|
1311
|
+
);
|
|
1312
|
+
body = bodyIsBinary ? "" : buf.toString("utf8");
|
|
1313
|
+
} catch {
|
|
1314
|
+
body = "";
|
|
1315
|
+
}
|
|
1316
|
+
paymentMeta = {
|
|
1317
|
+
protocol: "mpp",
|
|
1318
|
+
chain: "tempo",
|
|
1319
|
+
txHash: null,
|
|
1320
|
+
amount: err.capturedAmount,
|
|
1321
|
+
asset: "USDC",
|
|
1322
|
+
session: err.session
|
|
1323
|
+
};
|
|
1324
|
+
sessionMeta = err.session;
|
|
1325
|
+
fetchError = err;
|
|
1326
|
+
console.error(
|
|
1327
|
+
[
|
|
1328
|
+
"",
|
|
1329
|
+
" WARNING: failed to auto-close MPP session.",
|
|
1330
|
+
` ${err.message}`,
|
|
1331
|
+
` Channel ${err.session.channelId} is still open on chain \u2014 see payment.session in --json output.`,
|
|
1332
|
+
" Contact the seller to settle the channel from their facilitator, or use",
|
|
1333
|
+
" your wallet to call escrow.close/requestClose directly.",
|
|
1334
|
+
""
|
|
1335
|
+
].join("\n")
|
|
1336
|
+
);
|
|
1337
|
+
} else {
|
|
1338
|
+
fetchError = err instanceof Error ? err : new Error(String(err));
|
|
1339
|
+
}
|
|
725
1340
|
}
|
|
726
1341
|
const latencyMs = Date.now() - startTime;
|
|
727
1342
|
if (finalResponse && !options.json) {
|
|
728
|
-
|
|
1343
|
+
if (bodyIsBinary && bodyBytes) {
|
|
1344
|
+
process.stdout.write(bodyBytes);
|
|
1345
|
+
} else {
|
|
1346
|
+
console.log(body);
|
|
1347
|
+
}
|
|
729
1348
|
}
|
|
730
|
-
analyticsService.capture("fetch_executed", {
|
|
731
|
-
url,
|
|
732
|
-
status: finalResponse?.status,
|
|
733
|
-
hasPayment: !!paymentMeta,
|
|
734
|
-
paymentProtocol: paymentMeta?.protocol,
|
|
735
|
-
paymentAmount: paymentMeta?.amount,
|
|
736
|
-
...fetchError && { error: fetchError.message }
|
|
737
|
-
});
|
|
738
1349
|
if (paymentMeta) {
|
|
739
1350
|
try {
|
|
740
1351
|
const balance = await walletService.getBalance();
|
|
@@ -747,6 +1358,11 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
747
1358
|
Warning: Balance is $${balance.amount} \u2014 run \`zero wallet fund\` soon.
|
|
748
1359
|
`
|
|
749
1360
|
);
|
|
1361
|
+
analyticsService.capture("low_balance_warning_shown", {
|
|
1362
|
+
balance: balance.amount,
|
|
1363
|
+
threshold,
|
|
1364
|
+
paymentProtocol: paymentMeta.protocol
|
|
1365
|
+
});
|
|
750
1366
|
}
|
|
751
1367
|
}
|
|
752
1368
|
} catch {
|
|
@@ -782,7 +1398,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
782
1398
|
paymentProtocol: paymentMeta.protocol,
|
|
783
1399
|
paymentChain: paymentMeta.chain,
|
|
784
1400
|
paymentTxHash: paymentMeta.txHash ?? void 0,
|
|
785
|
-
paymentMode: "charge"
|
|
1401
|
+
paymentMode: sessionMeta ? "session" : "charge"
|
|
786
1402
|
}
|
|
787
1403
|
});
|
|
788
1404
|
runId = runResult.runId;
|
|
@@ -792,17 +1408,35 @@ var fetchCommand = (appContext) => new Command3("fetch").description("Fetch a ca
|
|
|
792
1408
|
);
|
|
793
1409
|
}
|
|
794
1410
|
}
|
|
1411
|
+
const status = finalResponse?.status;
|
|
1412
|
+
const outcome = !finalResponse ? "network_error" : status === 402 && !paymentMeta ? "payment_failed" : status !== void 0 && status >= 400 && status !== 402 ? "server_error" : "success";
|
|
1413
|
+
analyticsService.capture("fetch_executed", {
|
|
1414
|
+
url: redactUrl(url),
|
|
1415
|
+
status,
|
|
1416
|
+
outcome,
|
|
1417
|
+
latencyMs,
|
|
1418
|
+
hasPayment: !!paymentMeta,
|
|
1419
|
+
paymentProtocol: paymentMeta?.protocol,
|
|
1420
|
+
paymentAmount: paymentMeta?.amount,
|
|
1421
|
+
capabilityId: capabilityId ?? void 0,
|
|
1422
|
+
searchId: searchId ?? void 0,
|
|
1423
|
+
runId: runId ?? void 0,
|
|
1424
|
+
runTracked: !!runId,
|
|
1425
|
+
...fetchError && { error: truncateError(fetchError.message) }
|
|
1426
|
+
});
|
|
795
1427
|
if (fetchError && !options.json) {
|
|
796
1428
|
console.error(` Fetch failed: ${fetchError.message}`);
|
|
797
1429
|
}
|
|
798
1430
|
if (options.json) {
|
|
1431
|
+
const jsonBody = !finalResponse ? null : bodyIsBinary ? (bodyBytes ?? Buffer.alloc(0)).toString("base64") : body;
|
|
799
1432
|
console.log(
|
|
800
1433
|
JSON.stringify({
|
|
801
1434
|
runId,
|
|
802
1435
|
status: finalResponse?.status ?? null,
|
|
803
1436
|
latencyMs,
|
|
804
1437
|
payment: paymentMeta ?? null,
|
|
805
|
-
body:
|
|
1438
|
+
body: jsonBody,
|
|
1439
|
+
...bodyIsBinary && { bodyEncoding: "base64" },
|
|
806
1440
|
...fetchError && { error: fetchError.message },
|
|
807
1441
|
...skipReasons.length > 0 && {
|
|
808
1442
|
runTrackingSkipped: skipReasons
|
|
@@ -907,7 +1541,10 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
907
1541
|
).argument(
|
|
908
1542
|
"<identifier>",
|
|
909
1543
|
"Position number from search results, or a capability slug"
|
|
910
|
-
).option("--formatted", "Output formatted trust breakdown").
|
|
1544
|
+
).option("--formatted", "Output formatted trust breakdown").option(
|
|
1545
|
+
"--agent <name>",
|
|
1546
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1547
|
+
).action(async (identifier, options) => {
|
|
911
1548
|
try {
|
|
912
1549
|
const { analyticsService, apiService, stateService } = appContext.services;
|
|
913
1550
|
const position = Number.parseInt(identifier, 10);
|
|
@@ -947,7 +1584,9 @@ var getCommand = (appContext) => new Command4("get").description(
|
|
|
947
1584
|
}
|
|
948
1585
|
analyticsService.capture("capability_viewed", {
|
|
949
1586
|
capabilityId,
|
|
950
|
-
|
|
1587
|
+
fromLastSearch: isPosition,
|
|
1588
|
+
...isPosition ? { position } : {},
|
|
1589
|
+
...searchId ? { searchId } : {}
|
|
951
1590
|
});
|
|
952
1591
|
} catch (err) {
|
|
953
1592
|
console.error(err instanceof Error ? err.message : "Get failed");
|
|
@@ -1171,107 +1810,126 @@ var installSkills = (home) => {
|
|
|
1171
1810
|
return installed;
|
|
1172
1811
|
};
|
|
1173
1812
|
var initCommand = (appContext) => new Command5("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").action(async (options) => {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
let
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
return false;
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
const account = privateKeyToAccount(existing.privateKey);
|
|
1813
|
+
appContext.services.analyticsService.capture("init_started", {
|
|
1814
|
+
force: options.force ?? false
|
|
1815
|
+
});
|
|
1816
|
+
let currentStep = "wallet";
|
|
1817
|
+
try {
|
|
1818
|
+
const home = homedir2();
|
|
1819
|
+
const zeroDir = join2(home, ".zero");
|
|
1820
|
+
const configPath = join2(zeroDir, "config.json");
|
|
1821
|
+
let walletCreated = false;
|
|
1822
|
+
let walletAddress = null;
|
|
1823
|
+
const walletExists = (() => {
|
|
1824
|
+
if (!existsSync2(configPath)) return false;
|
|
1825
|
+
try {
|
|
1826
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1827
|
+
return !!existing.privateKey;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
})();
|
|
1832
|
+
if (!walletExists || options.force) {
|
|
1833
|
+
const privateKey = generatePrivateKey();
|
|
1834
|
+
const account = privateKeyToAccount(privateKey);
|
|
1835
|
+
mkdirSync2(zeroDir, { recursive: true });
|
|
1836
|
+
const existing = existsSync2(configPath) ? JSON.parse(readFileSync3(configPath, "utf8")) : {};
|
|
1837
|
+
writeFileSync2(
|
|
1838
|
+
configPath,
|
|
1839
|
+
JSON.stringify(
|
|
1840
|
+
{ ...existing, privateKey, lowBalanceWarning: 1 },
|
|
1841
|
+
null,
|
|
1842
|
+
2
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
walletCreated = true;
|
|
1208
1846
|
walletAddress = account.address;
|
|
1209
|
-
|
|
1847
|
+
console.log(`Wallet address: ${account.address}`);
|
|
1848
|
+
} else {
|
|
1849
|
+
try {
|
|
1850
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1851
|
+
const account = privateKeyToAccount(existing.privateKey);
|
|
1852
|
+
walletAddress = account.address;
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1210
1855
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1856
|
+
const agentsDetected = [];
|
|
1857
|
+
const agentsWithSkills = [];
|
|
1858
|
+
let skillsError = null;
|
|
1859
|
+
let hookInstalled = false;
|
|
1860
|
+
let hookError = null;
|
|
1861
|
+
for (const tool of AGENT_TOOLS) {
|
|
1862
|
+
if (existsSync2(join2(home, tool.configDir))) {
|
|
1863
|
+
agentsDetected.push(tool.name);
|
|
1864
|
+
}
|
|
1220
1865
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1866
|
+
currentStep = "skills";
|
|
1867
|
+
try {
|
|
1868
|
+
const installed = installSkills(home);
|
|
1869
|
+
for (const entry of installed) {
|
|
1870
|
+
const toolName = entry.split(":")[0];
|
|
1871
|
+
if (toolName && !agentsWithSkills.includes(toolName)) {
|
|
1872
|
+
agentsWithSkills.push(toolName);
|
|
1873
|
+
}
|
|
1228
1874
|
}
|
|
1875
|
+
} catch (err) {
|
|
1876
|
+
skillsError = err instanceof Error ? err.message : "unknown skills error";
|
|
1229
1877
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
`
|
|
1878
|
+
currentStep = "hook";
|
|
1879
|
+
try {
|
|
1880
|
+
hookInstalled = installHook(home);
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
hookError = err instanceof Error ? err.message : "unknown hook error";
|
|
1883
|
+
}
|
|
1884
|
+
currentStep = "cleanup_scan";
|
|
1885
|
+
const conflictingSkills = findConflictingSkills(home);
|
|
1886
|
+
if (conflictingSkills.length > 0) {
|
|
1887
|
+
const skillList = conflictingSkills.map((s) => ` - ${s.tool}: ${s.skillName}`).join("\n");
|
|
1888
|
+
console.error(
|
|
1889
|
+
`
|
|
1243
1890
|
Found deprecated skills that may conflict with Zero:
|
|
1244
1891
|
${skillList}
|
|
1245
1892
|
|
|
1246
1893
|
To remove them, run: zero init cleanup`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
console.error(
|
|
1897
|
+
'Zero is ready! Run `zero search` to find capabilities.\n\nBy using Zero, you agree to our Terms of Service:\n https://zero.xyz/terms-of-service\n\nRun `zero terms` to view the full terms.\n\nTry:\n zero search "translate text to Spanish"\n zero search "generate an image"\n zero search "weather forecast"'
|
|
1247
1898
|
);
|
|
1899
|
+
currentStep = "complete";
|
|
1900
|
+
appContext.services.analyticsService.capture("wallet_initialized", {
|
|
1901
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1902
|
+
wallet_created: walletCreated,
|
|
1903
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1904
|
+
wallet_address: walletAddress,
|
|
1905
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1906
|
+
agents_detected: agentsDetected,
|
|
1907
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1908
|
+
agents_detected_count: agentsDetected.length,
|
|
1909
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1910
|
+
skills_installed: agentsWithSkills.length > 0,
|
|
1911
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1912
|
+
skills_installed_for: agentsWithSkills,
|
|
1913
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1914
|
+
skills_error: skillsError,
|
|
1915
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1916
|
+
hook_installed: hookInstalled,
|
|
1917
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1918
|
+
hook_error: hookError,
|
|
1919
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1920
|
+
conflicting_skills_found: conflictingSkills.length,
|
|
1921
|
+
force: options.force ?? false
|
|
1922
|
+
});
|
|
1923
|
+
} catch (err) {
|
|
1924
|
+
appContext.services.analyticsService.capture("init_failed", {
|
|
1925
|
+
step: currentStep,
|
|
1926
|
+
error: truncateError(
|
|
1927
|
+
err instanceof Error ? err.message : String(err)
|
|
1928
|
+
),
|
|
1929
|
+
force: options.force ?? false
|
|
1930
|
+
});
|
|
1931
|
+
throw err;
|
|
1248
1932
|
}
|
|
1249
|
-
console.error(
|
|
1250
|
-
'Zero is ready! Run `zero search` to find capabilities.\n\nTry:\n zero search "translate text to Spanish"\n zero search "generate an image"\n zero search "weather forecast"'
|
|
1251
|
-
);
|
|
1252
|
-
appContext.services.analyticsService.capture("wallet_initialized", {
|
|
1253
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1254
|
-
wallet_created: walletCreated,
|
|
1255
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1256
|
-
wallet_address: walletAddress,
|
|
1257
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1258
|
-
agents_detected: agentsDetected,
|
|
1259
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1260
|
-
agents_detected_count: agentsDetected.length,
|
|
1261
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1262
|
-
skills_installed: agentsWithSkills.length > 0,
|
|
1263
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1264
|
-
skills_installed_for: agentsWithSkills,
|
|
1265
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1266
|
-
skills_error: skillsError,
|
|
1267
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1268
|
-
hook_installed: hookInstalled,
|
|
1269
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1270
|
-
hook_error: hookError,
|
|
1271
|
-
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1272
|
-
conflicting_skills_found: conflictingSkills.length,
|
|
1273
|
-
force: options.force ?? false
|
|
1274
|
-
});
|
|
1275
1933
|
}).addCommand(
|
|
1276
1934
|
new Command5("cleanup").description(
|
|
1277
1935
|
"Remove deprecated skills (zam, tempo) that conflict with Zero"
|
|
@@ -1285,7 +1943,7 @@ To remove them, run: zero init cleanup`
|
|
|
1285
1943
|
const removedList = removed.map((s) => ` - ${s}`).join("\n");
|
|
1286
1944
|
console.error(`Removed deprecated skills:
|
|
1287
1945
|
${removedList}`);
|
|
1288
|
-
appContext.services.analyticsService.capture("
|
|
1946
|
+
appContext.services.analyticsService.capture("skills_cleaned_up", {
|
|
1289
1947
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
1290
1948
|
skills_removed: removed,
|
|
1291
1949
|
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
@@ -1329,6 +1987,9 @@ Examples:
|
|
|
1329
1987
|
).option("--success", "The capability succeeded").option("--no-success", "The capability failed").option("--accuracy <n>", "Accuracy rating (1-5)", Number.parseInt).option("--value <n>", "Value rating (1-5)", Number.parseInt).option("--reliability <n>", "Reliability rating (1-5)", Number.parseInt).option("--content <text>", "Optional review text").option(
|
|
1330
1988
|
"--from-file <path>",
|
|
1331
1989
|
"Submit reviews in bulk from a JSONL file (one review object per line: {runId, success, accuracy, value, reliability, content?})"
|
|
1990
|
+
).option(
|
|
1991
|
+
"--agent <name>",
|
|
1992
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1332
1993
|
).action(
|
|
1333
1994
|
async (runId, options) => {
|
|
1334
1995
|
try {
|
|
@@ -1350,6 +2011,10 @@ Examples:
|
|
|
1350
2011
|
analyticsService.capture("review_submitted", {
|
|
1351
2012
|
runId: parsed.runId,
|
|
1352
2013
|
success: parsed.success,
|
|
2014
|
+
accuracy: parsed.accuracy,
|
|
2015
|
+
value: parsed.value,
|
|
2016
|
+
reliability: parsed.reliability,
|
|
2017
|
+
hasContent: !!parsed.content,
|
|
1353
2018
|
bulk: true
|
|
1354
2019
|
});
|
|
1355
2020
|
} catch (err) {
|
|
@@ -1432,7 +2097,12 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
|
|
|
1432
2097
|
console.log(`Review submitted: ${result.reviewId}`);
|
|
1433
2098
|
analyticsService.capture("review_submitted", {
|
|
1434
2099
|
runId,
|
|
1435
|
-
success: options.success
|
|
2100
|
+
success: options.success,
|
|
2101
|
+
accuracy,
|
|
2102
|
+
value,
|
|
2103
|
+
reliability,
|
|
2104
|
+
hasContent: !!options.content,
|
|
2105
|
+
resolvedByCapability: !!options.capability
|
|
1436
2106
|
});
|
|
1437
2107
|
} catch (err) {
|
|
1438
2108
|
console.error(err instanceof Error ? err.message : "Review failed");
|
|
@@ -1457,6 +2127,9 @@ Examples:
|
|
|
1457
2127
|
"--limit <n>",
|
|
1458
2128
|
"Max rows (1-100, default 25)",
|
|
1459
2129
|
(v) => Number.parseInt(v, 10)
|
|
2130
|
+
).option(
|
|
2131
|
+
"--agent <name>",
|
|
2132
|
+
"Identify your agent host for this invocation. Overrides auto-detect for this call only."
|
|
1460
2133
|
).action(
|
|
1461
2134
|
async (options) => {
|
|
1462
2135
|
try {
|
|
@@ -1538,6 +2211,9 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
1538
2211
|
).option(
|
|
1539
2212
|
"--exclude-source <source>",
|
|
1540
2213
|
"Exclude results from this crawl source"
|
|
2214
|
+
).option(
|
|
2215
|
+
"--agent <name>",
|
|
2216
|
+
"Identify your agent host for this invocation (e.g. claude-web, codex). Overrides auto-detect for this call only."
|
|
1541
2217
|
).action(
|
|
1542
2218
|
async (query, options) => {
|
|
1543
2219
|
try {
|
|
@@ -1572,8 +2248,24 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
1572
2248
|
excludeSource: options.excludeSource
|
|
1573
2249
|
});
|
|
1574
2250
|
analyticsService.capture("search_executed", {
|
|
1575
|
-
query,
|
|
1576
|
-
|
|
2251
|
+
query: truncateQuery(query),
|
|
2252
|
+
queryLength: query.length,
|
|
2253
|
+
resultCount: result.capabilities.length,
|
|
2254
|
+
searchId: result.searchId,
|
|
2255
|
+
total: result.total,
|
|
2256
|
+
hasMore: result.hasMore,
|
|
2257
|
+
offset: options.offset,
|
|
2258
|
+
limit: options.limit,
|
|
2259
|
+
freeOnly: options.free ?? false,
|
|
2260
|
+
maxCost: options.maxCost,
|
|
2261
|
+
minRating: options.minRating,
|
|
2262
|
+
protocol: options.protocol,
|
|
2263
|
+
minTrust: options.minTrust,
|
|
2264
|
+
availabilityStatus: options.status,
|
|
2265
|
+
includeAll: options.all ?? false,
|
|
2266
|
+
source: options.source,
|
|
2267
|
+
excludeSource: options.excludeSource,
|
|
2268
|
+
json: options.json ?? false
|
|
1577
2269
|
});
|
|
1578
2270
|
if (options.json) {
|
|
1579
2271
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1606,14 +2298,34 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
|
|
|
1606
2298
|
}
|
|
1607
2299
|
);
|
|
1608
2300
|
|
|
2301
|
+
// src/commands/terms-command.ts
|
|
2302
|
+
import { Command as Command9 } from "commander";
|
|
2303
|
+
var TERMS_URL = "https://zero.xyz/terms-of-service";
|
|
2304
|
+
var termsCommand = (_appContext) => new Command9("terms").description("View the ZeroClick Terms of Service").action(async () => {
|
|
2305
|
+
console.log(
|
|
2306
|
+
`ZeroClick Agentic Capability Search \u2014 Terms of Service
|
|
2307
|
+
|
|
2308
|
+
By using Zero, you agree to our Terms of Service.
|
|
2309
|
+
|
|
2310
|
+
Read the full terms at: ${TERMS_URL}
|
|
2311
|
+
`
|
|
2312
|
+
);
|
|
2313
|
+
try {
|
|
2314
|
+
const { exec } = await import("child_process");
|
|
2315
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2316
|
+
exec(`${openCmd} ${TERMS_URL}`);
|
|
2317
|
+
} catch {
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
|
|
1609
2321
|
// src/commands/wallet-command.ts
|
|
1610
2322
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1611
2323
|
import { homedir as homedir3 } from "os";
|
|
1612
2324
|
import { join as join3 } from "path";
|
|
1613
|
-
import { Command as
|
|
2325
|
+
import { Command as Command10 } from "commander";
|
|
1614
2326
|
import open from "open";
|
|
1615
2327
|
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
1616
|
-
var walletBalanceCommand = (appContext) => new
|
|
2328
|
+
var walletBalanceCommand = (appContext) => new Command10("balance").description("Show wallet balance").action(async () => {
|
|
1617
2329
|
const { walletService } = appContext.services;
|
|
1618
2330
|
const balance = await walletService.getBalance();
|
|
1619
2331
|
if (balance === null) {
|
|
@@ -1628,7 +2340,11 @@ var walletBalanceCommand = (appContext) => new Command9("balance").description("
|
|
|
1628
2340
|
}
|
|
1629
2341
|
console.log(`${balance.amount} ${balance.asset}`);
|
|
1630
2342
|
});
|
|
1631
|
-
var walletFundCommand = (appContext) => new
|
|
2343
|
+
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(
|
|
2344
|
+
"--use <provider>",
|
|
2345
|
+
"Onramp provider: coinbase or stripe",
|
|
2346
|
+
"coinbase"
|
|
2347
|
+
).action(
|
|
1632
2348
|
async (amount, options) => {
|
|
1633
2349
|
const { analyticsService, walletService } = appContext.services;
|
|
1634
2350
|
const address = walletService.getAddress();
|
|
@@ -1646,7 +2362,11 @@ ${address}`);
|
|
|
1646
2362
|
});
|
|
1647
2363
|
return;
|
|
1648
2364
|
}
|
|
1649
|
-
const
|
|
2365
|
+
const provider = options.use === "stripe" ? "stripe" : "coinbase";
|
|
2366
|
+
const url = await appContext.services.apiService.getFundingUrl(
|
|
2367
|
+
amount,
|
|
2368
|
+
provider
|
|
2369
|
+
);
|
|
1650
2370
|
if (url) {
|
|
1651
2371
|
await open(url);
|
|
1652
2372
|
console.log("Opened funding page in your browser.");
|
|
@@ -1665,7 +2385,7 @@ ${address}`);
|
|
|
1665
2385
|
}
|
|
1666
2386
|
}
|
|
1667
2387
|
);
|
|
1668
|
-
var walletAddressCommand = (appContext) => new
|
|
2388
|
+
var walletAddressCommand = (appContext) => new Command10("address").description("Show wallet address").action(() => {
|
|
1669
2389
|
const { walletService } = appContext.services;
|
|
1670
2390
|
const address = walletService.getAddress();
|
|
1671
2391
|
if (!address) {
|
|
@@ -1675,7 +2395,7 @@ var walletAddressCommand = (appContext) => new Command9("address").description("
|
|
|
1675
2395
|
}
|
|
1676
2396
|
console.log(address);
|
|
1677
2397
|
});
|
|
1678
|
-
var walletSetCommand = (appContext) => new
|
|
2398
|
+
var walletSetCommand = (appContext) => new Command10("set").description("Set wallet from an existing private key").argument("<privateKey>", "Hex-encoded private key (0x-prefixed)").option("--force", "Overwrite existing wallet without prompting").action(async (privateKey, options) => {
|
|
1679
2399
|
const { analyticsService } = appContext.services;
|
|
1680
2400
|
if (!privateKey.startsWith("0x")) {
|
|
1681
2401
|
console.error("Private key must be 0x-prefixed hex string.");
|
|
@@ -1727,7 +2447,7 @@ var walletSetCommand = (appContext) => new Command9("set").description("Set wall
|
|
|
1727
2447
|
});
|
|
1728
2448
|
});
|
|
1729
2449
|
var walletCommand = (appContext) => {
|
|
1730
|
-
const cmd = new
|
|
2450
|
+
const cmd = new Command10("wallet").description("Manage your wallet");
|
|
1731
2451
|
cmd.addCommand(walletBalanceCommand(appContext));
|
|
1732
2452
|
cmd.addCommand(walletFundCommand(appContext));
|
|
1733
2453
|
cmd.addCommand(walletAddressCommand(appContext));
|
|
@@ -1738,436 +2458,191 @@ var walletCommand = (appContext) => {
|
|
|
1738
2458
|
// src/app.ts
|
|
1739
2459
|
var createApp = (appContext) => {
|
|
1740
2460
|
const { analyticsService } = appContext.services;
|
|
1741
|
-
const program = new
|
|
2461
|
+
const program = new Command11().name("zero").description("Zero CLI \u2014 Search engine and payment platform for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
|
|
2462
|
+
const agentFlag = actionCommand.opts().agent;
|
|
2463
|
+
if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
|
|
2464
|
+
analyticsService.setAgentHost(agentFlag.trim());
|
|
2465
|
+
}
|
|
2466
|
+
appContext.invocation.current = {
|
|
2467
|
+
command: actionCommand.name(),
|
|
2468
|
+
startMs: Date.now()
|
|
2469
|
+
};
|
|
1742
2470
|
analyticsService.capture("command_executed", {
|
|
1743
2471
|
command: actionCommand.name()
|
|
1744
2472
|
});
|
|
1745
2473
|
});
|
|
1746
2474
|
program.addCommand(initCommand(appContext));
|
|
1747
2475
|
program.addCommand(searchCommand(appContext));
|
|
1748
|
-
program.addCommand(getCommand(appContext));
|
|
1749
|
-
program.addCommand(fetchCommand(appContext));
|
|
1750
|
-
program.addCommand(reviewCommand(appContext));
|
|
1751
|
-
program.addCommand(runsCommand(appContext));
|
|
1752
|
-
program.addCommand(bugReportCommand(appContext));
|
|
1753
|
-
program.addCommand(walletCommand(appContext));
|
|
1754
|
-
program.addCommand(configCommand(appContext));
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
import {
|
|
1778
|
-
import {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
import {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
import {
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
} else if (persistedAnonId) {
|
|
1815
|
-
this.distinctId = persistedAnonId;
|
|
1816
|
-
} else {
|
|
1817
|
-
const newAnonId = randomUUID();
|
|
1818
|
-
this.distinctId = newAnonId;
|
|
1819
|
-
try {
|
|
1820
|
-
const dir = dirname2(opts.configPath);
|
|
1821
|
-
mkdirSync4(dir, { recursive: true });
|
|
1822
|
-
const existing = existsSync4(opts.configPath) ? JSON.parse(readFileSync6(opts.configPath, "utf8")) : {};
|
|
1823
|
-
writeFileSync4(
|
|
1824
|
-
opts.configPath,
|
|
1825
|
-
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
1826
|
-
);
|
|
1827
|
-
} catch {
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
const originalConsoleError = console.error;
|
|
1831
|
-
console.error = (...args) => {
|
|
1832
|
-
const first = args[0];
|
|
1833
|
-
if (typeof first === "string" && first.includes("Error while flushing PostHog")) {
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
1836
|
-
originalConsoleError.apply(console, args);
|
|
1837
|
-
};
|
|
1838
|
-
this.posthog = new PostHog(POSTHOG_API_KEY, {
|
|
1839
|
-
host: POSTHOG_HOST,
|
|
1840
|
-
flushAt: 1,
|
|
1841
|
-
flushInterval: 0
|
|
1842
|
-
});
|
|
1843
|
-
this.posthog.on("error", () => {
|
|
1844
|
-
});
|
|
1845
|
-
}
|
|
1846
|
-
capture(event, properties) {
|
|
1847
|
-
if (!this.posthog) return;
|
|
1848
|
-
this.posthog.capture({
|
|
1849
|
-
distinctId: this.distinctId,
|
|
1850
|
-
event,
|
|
1851
|
-
properties: {
|
|
1852
|
-
source: "cli",
|
|
1853
|
-
// biome-ignore lint/style/useNamingConvention: snake_case is standard for analytics event properties
|
|
1854
|
-
cli_version: this.cliVersion,
|
|
1855
|
-
...properties
|
|
1856
|
-
}
|
|
1857
|
-
});
|
|
1858
|
-
}
|
|
1859
|
-
async shutdown() {
|
|
1860
|
-
if (!this.posthog) return;
|
|
1861
|
-
try {
|
|
1862
|
-
await this.posthog.shutdown();
|
|
1863
|
-
} catch {
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
};
|
|
1867
|
-
|
|
1868
|
-
// src/services/payment-service.ts
|
|
1869
|
-
import {
|
|
1870
|
-
adaptViemWallet,
|
|
1871
|
-
convertViemChainToRelayChain,
|
|
1872
|
-
createClient as createRelayClient,
|
|
1873
|
-
getClient as getRelayClient,
|
|
1874
|
-
MAINNET_RELAY_API
|
|
1875
|
-
} from "@relayprotocol/relay-sdk";
|
|
1876
|
-
import { x402Client as X402Client } from "@x402/core/client";
|
|
1877
|
-
import { decodePaymentResponseHeader, x402HTTPClient } from "@x402/core/http";
|
|
1878
|
-
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
1879
|
-
import { createSIWxClientHook } from "@x402/extensions/sign-in-with-x";
|
|
1880
|
-
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
1881
|
-
import { Receipt } from "mppx";
|
|
1882
|
-
import { Mppx, tempo } from "mppx/client";
|
|
1883
|
-
import {
|
|
1884
|
-
createPublicClient,
|
|
1885
|
-
createWalletClient,
|
|
1886
|
-
formatUnits,
|
|
1887
|
-
http
|
|
1888
|
-
} from "viem";
|
|
1889
|
-
import { base, baseSepolia } from "viem/chains";
|
|
1890
|
-
var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
1891
|
-
var USDC_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
1892
|
-
var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
1893
|
-
var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
|
|
1894
|
-
var BASE_CHAIN_ID = 8453;
|
|
1895
|
-
var TEMPO_CHAIN_ID = 4217;
|
|
1896
|
-
var TEMPO_TESTNET_CHAIN_ID = 42431;
|
|
1897
|
-
var DEFAULT_MAX_DEPOSIT = "100";
|
|
1898
|
-
var KNOWN_EIP712_DOMAINS = {
|
|
1899
|
-
// USDC on Base
|
|
1900
|
-
[USDC_BASE.toLowerCase()]: { name: "USDC", version: "2" },
|
|
1901
|
-
// USDC on Base Sepolia
|
|
1902
|
-
[USDC_BASE_SEPOLIA.toLowerCase()]: { name: "USDC", version: "2" }
|
|
1903
|
-
};
|
|
1904
|
-
var buildRelayClientOptions = () => ({
|
|
1905
|
-
baseApiUrl: MAINNET_RELAY_API,
|
|
1906
|
-
source: "zero-cli",
|
|
1907
|
-
chains: [convertViemChainToRelayChain(base)]
|
|
1908
|
-
});
|
|
1909
|
-
var calculateBuffer = (baseBalance) => {
|
|
1910
|
-
const twentyFivePercent = baseBalance / 4n;
|
|
1911
|
-
const twoDollars = 2000000n;
|
|
1912
|
-
return twentyFivePercent < twoDollars ? twentyFivePercent : twoDollars;
|
|
1913
|
-
};
|
|
1914
|
-
var tempoChain = {
|
|
1915
|
-
id: TEMPO_CHAIN_ID,
|
|
1916
|
-
name: "Tempo",
|
|
1917
|
-
nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
|
|
1918
|
-
rpcUrls: {
|
|
1919
|
-
default: { http: ["https://rpc.tempo.xyz"] }
|
|
1920
|
-
}
|
|
1921
|
-
};
|
|
1922
|
-
var tempoTestnetChain = {
|
|
1923
|
-
id: TEMPO_TESTNET_CHAIN_ID,
|
|
1924
|
-
name: "Tempo Testnet",
|
|
1925
|
-
nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
|
|
1926
|
-
rpcUrls: {
|
|
1927
|
-
default: { http: ["https://rpc.moderato.tempo.xyz"] }
|
|
1928
|
-
}
|
|
1929
|
-
};
|
|
1930
|
-
var ERC20_BALANCE_ABI = [
|
|
1931
|
-
{
|
|
1932
|
-
inputs: [{ name: "account", type: "address" }],
|
|
1933
|
-
name: "balanceOf",
|
|
1934
|
-
outputs: [{ name: "", type: "uint256" }],
|
|
1935
|
-
stateMutability: "view",
|
|
1936
|
-
type: "function"
|
|
1937
|
-
}
|
|
1938
|
-
];
|
|
1939
|
-
var PaymentService = class {
|
|
1940
|
-
constructor(account, config) {
|
|
1941
|
-
this.account = account;
|
|
1942
|
-
this.config = config;
|
|
1943
|
-
}
|
|
1944
|
-
relayInitialized = false;
|
|
1945
|
-
ensureRelayClient = () => {
|
|
1946
|
-
if (!this.relayInitialized) {
|
|
1947
|
-
createRelayClient(buildRelayClientOptions());
|
|
1948
|
-
this.relayInitialized = true;
|
|
1949
|
-
}
|
|
1950
|
-
};
|
|
1951
|
-
bridgeToTempo = async (requiredAmount, onProgress) => {
|
|
1952
|
-
if (!this.account) throw new Error("No wallet configured");
|
|
1953
|
-
this.ensureRelayClient();
|
|
1954
|
-
const baseBalance = await this.getBalanceRaw("base");
|
|
1955
|
-
const buffer = calculateBuffer(baseBalance);
|
|
1956
|
-
const bridgeAmount = requiredAmount + buffer;
|
|
1957
|
-
if (baseBalance < bridgeAmount) {
|
|
1958
|
-
throw new Error(
|
|
1959
|
-
`Insufficient Base USDC to bridge: have ${formatUnits(baseBalance, 6)}, need ${formatUnits(bridgeAmount, 6)} (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer)`
|
|
1960
|
-
);
|
|
1961
|
-
}
|
|
1962
|
-
onProgress?.(
|
|
1963
|
-
`Bridging ${formatUnits(bridgeAmount, 6)} USDC from Base to Tempo...`
|
|
1964
|
-
);
|
|
1965
|
-
const walletClient = createWalletClient({
|
|
1966
|
-
account: this.account,
|
|
1967
|
-
chain: base,
|
|
1968
|
-
transport: http()
|
|
1969
|
-
});
|
|
1970
|
-
const quote = await getRelayClient().actions.getQuote({
|
|
1971
|
-
chainId: BASE_CHAIN_ID,
|
|
1972
|
-
toChainId: TEMPO_CHAIN_ID,
|
|
1973
|
-
currency: USDC_BASE,
|
|
1974
|
-
toCurrency: USDC_TEMPO,
|
|
1975
|
-
amount: bridgeAmount.toString(),
|
|
1976
|
-
tradeType: "EXACT_INPUT",
|
|
1977
|
-
user: this.account.address,
|
|
1978
|
-
recipient: this.account.address,
|
|
1979
|
-
options: {
|
|
1980
|
-
usePermit: true
|
|
1981
|
-
}
|
|
1982
|
-
});
|
|
1983
|
-
let bridgeTxHash = null;
|
|
1984
|
-
await getRelayClient().actions.execute({
|
|
1985
|
-
quote,
|
|
1986
|
-
wallet: adaptViemWallet(walletClient),
|
|
1987
|
-
onProgress: ({ txHashes }) => {
|
|
1988
|
-
if (txHashes?.length && !bridgeTxHash) {
|
|
1989
|
-
bridgeTxHash = txHashes[0]?.txHash ?? null;
|
|
2476
|
+
program.addCommand(getCommand(appContext));
|
|
2477
|
+
program.addCommand(fetchCommand(appContext));
|
|
2478
|
+
program.addCommand(reviewCommand(appContext));
|
|
2479
|
+
program.addCommand(runsCommand(appContext));
|
|
2480
|
+
program.addCommand(bugReportCommand(appContext));
|
|
2481
|
+
program.addCommand(walletCommand(appContext));
|
|
2482
|
+
program.addCommand(configCommand(appContext));
|
|
2483
|
+
program.addCommand(termsCommand(appContext));
|
|
2484
|
+
return program;
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
// src/app/app-env.ts
|
|
2488
|
+
import z4 from "zod";
|
|
2489
|
+
var envSchema = z4.object({
|
|
2490
|
+
ZERO_API_URL: z4.string().default("https://api.zero.xyz"),
|
|
2491
|
+
ZERO_PRIVATE_KEY: z4.string().optional(),
|
|
2492
|
+
ZERO_ENV: z4.enum(["development", "production"]).default("production")
|
|
2493
|
+
});
|
|
2494
|
+
var getEnv = () => {
|
|
2495
|
+
try {
|
|
2496
|
+
return envSchema.parse(process.env);
|
|
2497
|
+
} catch (error) {
|
|
2498
|
+
console.error("Error parsing environment variables");
|
|
2499
|
+
console.error(error);
|
|
2500
|
+
return null;
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
// src/app/app-services.ts
|
|
2505
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2506
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
2507
|
+
import { homedir as homedir4 } from "os";
|
|
2508
|
+
import { join as join5 } from "path";
|
|
2509
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
2510
|
+
|
|
2511
|
+
// src/services/analytics-service.ts
|
|
2512
|
+
import { randomUUID } from "crypto";
|
|
2513
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
2514
|
+
import { dirname as dirname2 } from "path";
|
|
2515
|
+
import { PostHog } from "posthog-node";
|
|
2516
|
+
var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
|
|
2517
|
+
var POSTHOG_HOST = "https://us.i.posthog.com";
|
|
2518
|
+
var AnalyticsService = class {
|
|
2519
|
+
posthog;
|
|
2520
|
+
distinctId;
|
|
2521
|
+
walletAddress;
|
|
2522
|
+
cliVersion;
|
|
2523
|
+
environment;
|
|
2524
|
+
requestId;
|
|
2525
|
+
agentHost;
|
|
2526
|
+
constructor(opts) {
|
|
2527
|
+
this.cliVersion = opts.cliVersion;
|
|
2528
|
+
this.environment = opts.environment;
|
|
2529
|
+
this.walletAddress = opts.walletAddress;
|
|
2530
|
+
this.requestId = opts.requestId;
|
|
2531
|
+
this.agentHost = opts.agentHost;
|
|
2532
|
+
let telemetryEnabled = true;
|
|
2533
|
+
let persistedAnonId;
|
|
2534
|
+
try {
|
|
2535
|
+
if (existsSync4(opts.configPath)) {
|
|
2536
|
+
const config = JSON.parse(readFileSync6(opts.configPath, "utf8"));
|
|
2537
|
+
if (config.telemetry === false) {
|
|
2538
|
+
telemetryEnabled = false;
|
|
2539
|
+
}
|
|
2540
|
+
if (typeof config.anonId === "string") {
|
|
2541
|
+
persistedAnonId = config.anonId;
|
|
1990
2542
|
}
|
|
1991
2543
|
}
|
|
1992
|
-
}
|
|
1993
|
-
return bridgeTxHash;
|
|
1994
|
-
};
|
|
1995
|
-
handlePayment = async (url, request, paymentRequirement, maxPay, onProgress) => {
|
|
1996
|
-
if (!this.account) {
|
|
1997
|
-
throw new Error(
|
|
1998
|
-
"No wallet configured \u2014 run `zero init` or set ZERO_PRIVATE_KEY"
|
|
1999
|
-
);
|
|
2000
|
-
}
|
|
2001
|
-
if (paymentRequirement.protocol === "x402") {
|
|
2002
|
-
onProgress?.("Paying via x402 on Base...");
|
|
2003
|
-
return this.payX402(url, request, paymentRequirement.raw, maxPay);
|
|
2544
|
+
} catch {
|
|
2004
2545
|
}
|
|
2005
|
-
if (
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
request,
|
|
2010
|
-
paymentRequirement.raw,
|
|
2011
|
-
maxPay,
|
|
2012
|
-
onProgress
|
|
2013
|
-
);
|
|
2546
|
+
if (!telemetryEnabled) {
|
|
2547
|
+
this.posthog = null;
|
|
2548
|
+
this.distinctId = "";
|
|
2549
|
+
return;
|
|
2014
2550
|
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
new ExactEvmScheme(this.account)
|
|
2023
|
-
);
|
|
2024
|
-
client.onBeforePaymentCreation(async (context) => {
|
|
2025
|
-
const selected = context.selectedRequirements;
|
|
2026
|
-
if (selected && (!selected.extra?.name || !selected.extra?.version)) {
|
|
2027
|
-
const known = KNOWN_EIP712_DOMAINS[selected.asset?.toLowerCase() ?? ""];
|
|
2028
|
-
if (known) {
|
|
2029
|
-
selected.extra = { ...selected.extra, ...known };
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
const requirement = context.paymentRequired.accepts[0];
|
|
2033
|
-
if (!requirement) return;
|
|
2034
|
-
capturedAmount = formatUnits(BigInt(requirement.amount), 6);
|
|
2035
|
-
if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
|
|
2036
|
-
return {
|
|
2037
|
-
abort: true,
|
|
2038
|
-
reason: `Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
|
|
2039
|
-
};
|
|
2040
|
-
}
|
|
2041
|
-
});
|
|
2042
|
-
const httpClient = new x402HTTPClient(client).onPaymentRequired(
|
|
2043
|
-
createSIWxClientHook(this.account)
|
|
2044
|
-
);
|
|
2045
|
-
const wrappedFetch = wrapFetchWithPayment(fetch, httpClient);
|
|
2046
|
-
const response = await wrappedFetch(url, {
|
|
2047
|
-
method: request.method,
|
|
2048
|
-
headers: request.headers,
|
|
2049
|
-
body: request.body
|
|
2050
|
-
});
|
|
2051
|
-
let txHash = null;
|
|
2052
|
-
const paymentResponseHeader = response.headers.get("payment-response") ?? response.headers.get("x-payment-response");
|
|
2053
|
-
if (paymentResponseHeader) {
|
|
2551
|
+
if (opts.walletAddress) {
|
|
2552
|
+
this.distinctId = opts.walletAddress;
|
|
2553
|
+
} else if (persistedAnonId) {
|
|
2554
|
+
this.distinctId = persistedAnonId;
|
|
2555
|
+
} else {
|
|
2556
|
+
const newAnonId = randomUUID();
|
|
2557
|
+
this.distinctId = newAnonId;
|
|
2054
2558
|
try {
|
|
2055
|
-
const
|
|
2056
|
-
|
|
2559
|
+
const dir = dirname2(opts.configPath);
|
|
2560
|
+
mkdirSync4(dir, { recursive: true });
|
|
2561
|
+
const existing = existsSync4(opts.configPath) ? JSON.parse(readFileSync6(opts.configPath, "utf8")) : {};
|
|
2562
|
+
writeFileSync4(
|
|
2563
|
+
opts.configPath,
|
|
2564
|
+
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
2565
|
+
);
|
|
2057
2566
|
} catch {
|
|
2058
2567
|
}
|
|
2059
2568
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
amount: capturedAmount,
|
|
2066
|
-
asset: "USDC"
|
|
2067
|
-
};
|
|
2068
|
-
};
|
|
2069
|
-
payMpp = async (url, request, _raw, maxPay, onProgress) => {
|
|
2070
|
-
if (!this.account) throw new Error("No wallet configured");
|
|
2071
|
-
let capturedAmount = "0";
|
|
2072
|
-
const mppx = Mppx.create({
|
|
2073
|
-
polyfill: false,
|
|
2074
|
-
methods: [
|
|
2075
|
-
tempo({
|
|
2076
|
-
account: this.account,
|
|
2077
|
-
maxDeposit: maxPay ?? DEFAULT_MAX_DEPOSIT
|
|
2078
|
-
})
|
|
2079
|
-
],
|
|
2080
|
-
onChallenge: async (challenge) => {
|
|
2081
|
-
const challengeRequest = challenge.request;
|
|
2082
|
-
const intent = challenge.intent;
|
|
2083
|
-
let requiredRaw;
|
|
2084
|
-
if (intent === "session") {
|
|
2085
|
-
const suggestedDeposit = challengeRequest.suggestedDeposit;
|
|
2086
|
-
if (suggestedDeposit) {
|
|
2087
|
-
requiredRaw = BigInt(suggestedDeposit);
|
|
2088
|
-
} else {
|
|
2089
|
-
const depositStr = maxPay ?? DEFAULT_MAX_DEPOSIT;
|
|
2090
|
-
requiredRaw = BigInt(
|
|
2091
|
-
Math.floor(Number.parseFloat(depositStr) * 1e6)
|
|
2092
|
-
);
|
|
2093
|
-
}
|
|
2094
|
-
} else {
|
|
2095
|
-
requiredRaw = BigInt(challengeRequest.amount);
|
|
2096
|
-
}
|
|
2097
|
-
capturedAmount = formatUnits(requiredRaw, 6);
|
|
2098
|
-
if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
|
|
2099
|
-
throw new Error(
|
|
2100
|
-
`Payment of ${capturedAmount} USDC exceeds --max-pay ${maxPay}`
|
|
2101
|
-
);
|
|
2102
|
-
}
|
|
2103
|
-
const methodDetails = challengeRequest.methodDetails;
|
|
2104
|
-
const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
|
|
2105
|
-
const isTestnet = challengeChainId === TEMPO_TESTNET_CHAIN_ID;
|
|
2106
|
-
const balanceChain = isTestnet ? "tempo-testnet" : "tempo";
|
|
2107
|
-
onProgress?.(`Checking Tempo balance...`);
|
|
2108
|
-
const tempoBalance = await this.getBalanceRaw(balanceChain);
|
|
2109
|
-
if (tempoBalance < requiredRaw) {
|
|
2110
|
-
if (isTestnet) {
|
|
2111
|
-
throw new Error(
|
|
2112
|
-
`Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
await this.bridgeToTempo(requiredRaw, onProgress);
|
|
2116
|
-
}
|
|
2117
|
-
return void 0;
|
|
2569
|
+
const originalConsoleError = console.error;
|
|
2570
|
+
console.error = (...args) => {
|
|
2571
|
+
const first = args[0];
|
|
2572
|
+
if (typeof first === "string" && first.includes("Error while flushing PostHog")) {
|
|
2573
|
+
return;
|
|
2118
2574
|
}
|
|
2575
|
+
originalConsoleError.apply(console, args);
|
|
2576
|
+
};
|
|
2577
|
+
this.posthog = new PostHog(POSTHOG_API_KEY, {
|
|
2578
|
+
host: POSTHOG_HOST,
|
|
2579
|
+
flushAt: 1,
|
|
2580
|
+
flushInterval: 0
|
|
2119
2581
|
});
|
|
2120
|
-
|
|
2121
|
-
method: request.method,
|
|
2122
|
-
headers: request.headers,
|
|
2123
|
-
body: request.body
|
|
2582
|
+
this.posthog.on("error", () => {
|
|
2124
2583
|
});
|
|
2125
|
-
|
|
2584
|
+
if (opts.walletAddress && persistedAnonId) {
|
|
2585
|
+
this.maybeAliasAnonToWallet(
|
|
2586
|
+
opts.configPath,
|
|
2587
|
+
persistedAnonId,
|
|
2588
|
+
opts.walletAddress
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
maybeAliasAnonToWallet(configPath, anonId, walletAddress) {
|
|
2593
|
+
if (!this.posthog) return;
|
|
2594
|
+
if (anonId === walletAddress) return;
|
|
2595
|
+
let aliasedTo;
|
|
2126
2596
|
try {
|
|
2127
|
-
const
|
|
2128
|
-
|
|
2597
|
+
const config = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2598
|
+
if (typeof config.aliasedTo === "string") {
|
|
2599
|
+
aliasedTo = config.aliasedTo;
|
|
2600
|
+
}
|
|
2129
2601
|
} catch {
|
|
2130
2602
|
}
|
|
2131
|
-
return
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
resolveChainConfig = (chain) => {
|
|
2141
|
-
switch (chain) {
|
|
2142
|
-
case "base":
|
|
2143
|
-
return { viemChain: base, token: USDC_BASE };
|
|
2144
|
-
case "base-sepolia":
|
|
2145
|
-
return { viemChain: baseSepolia, token: USDC_BASE_SEPOLIA };
|
|
2146
|
-
case "tempo":
|
|
2147
|
-
return { viemChain: tempoChain, token: USDC_TEMPO };
|
|
2148
|
-
case "tempo-testnet":
|
|
2149
|
-
return { viemChain: tempoTestnetChain, token: PATHUSD_TEMPO };
|
|
2603
|
+
if (aliasedTo === walletAddress) return;
|
|
2604
|
+
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
2605
|
+
try {
|
|
2606
|
+
const config = existsSync4(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
|
|
2607
|
+
writeFileSync4(
|
|
2608
|
+
configPath,
|
|
2609
|
+
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
2610
|
+
);
|
|
2611
|
+
} catch {
|
|
2150
2612
|
}
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2613
|
+
}
|
|
2614
|
+
// Per-invocation override applied by the preAction hook when `--agent`
|
|
2615
|
+
// is passed. Stateless — affects only this process.
|
|
2616
|
+
setAgentHost(next) {
|
|
2617
|
+
this.agentHost = next;
|
|
2618
|
+
}
|
|
2619
|
+
capture(event, properties) {
|
|
2620
|
+
if (!this.posthog) return;
|
|
2621
|
+
this.posthog.capture({
|
|
2622
|
+
distinctId: this.distinctId,
|
|
2623
|
+
event,
|
|
2624
|
+
properties: {
|
|
2625
|
+
source: "cli",
|
|
2626
|
+
// biome-ignore lint/style/useNamingConvention: snake_case is standard for analytics event properties
|
|
2627
|
+
cli_version: this.cliVersion,
|
|
2628
|
+
environment: this.environment,
|
|
2629
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2630
|
+
wallet_address: this.walletAddress,
|
|
2631
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2632
|
+
request_id: this.requestId,
|
|
2633
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2634
|
+
agent_host: this.agentHost,
|
|
2635
|
+
...properties
|
|
2636
|
+
}
|
|
2164
2637
|
});
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2638
|
+
}
|
|
2639
|
+
async shutdown() {
|
|
2640
|
+
if (!this.posthog) return;
|
|
2641
|
+
try {
|
|
2642
|
+
await this.posthog.shutdown();
|
|
2643
|
+
} catch {
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2171
2646
|
};
|
|
2172
2647
|
|
|
2173
2648
|
// src/services/state-service.ts
|
|
@@ -2206,7 +2681,7 @@ var WalletService = class {
|
|
|
2206
2681
|
if (!this.address) return null;
|
|
2207
2682
|
if (this.balanceGetter) {
|
|
2208
2683
|
try {
|
|
2209
|
-
const result = await this.balanceGetter(
|
|
2684
|
+
const result = await this.balanceGetter();
|
|
2210
2685
|
return { amount: result.amount, asset: result.asset };
|
|
2211
2686
|
} catch {
|
|
2212
2687
|
return {
|
|
@@ -2221,6 +2696,15 @@ var WalletService = class {
|
|
|
2221
2696
|
getLowBalanceWarning = () => this.config.lowBalanceWarning;
|
|
2222
2697
|
};
|
|
2223
2698
|
|
|
2699
|
+
// src/util/agent-host.ts
|
|
2700
|
+
var detectAgentHost = (env = process.env) => {
|
|
2701
|
+
if (env.ZERO_AGENT) return env.ZERO_AGENT;
|
|
2702
|
+
if (env.CLAUDECODE === "1") return "claude-code";
|
|
2703
|
+
if (env.CURSOR_TRACE_ID) return "cursor";
|
|
2704
|
+
if (env.TERM_PROGRAM === "vscode") return "vscode";
|
|
2705
|
+
return "unknown";
|
|
2706
|
+
};
|
|
2707
|
+
|
|
2224
2708
|
// src/app/app-services.ts
|
|
2225
2709
|
var CLI_VERSION = package_default.version;
|
|
2226
2710
|
var getServices = (env) => {
|
|
@@ -2257,12 +2741,15 @@ var getServices = (env) => {
|
|
|
2257
2741
|
{
|
|
2258
2742
|
lowBalanceWarning
|
|
2259
2743
|
},
|
|
2260
|
-
paymentService.
|
|
2744
|
+
paymentService.getTotalBalance
|
|
2261
2745
|
);
|
|
2262
2746
|
const analyticsService = new AnalyticsService({
|
|
2263
2747
|
walletAddress: account?.address ?? null,
|
|
2264
2748
|
configPath,
|
|
2265
|
-
cliVersion: CLI_VERSION
|
|
2749
|
+
cliVersion: CLI_VERSION,
|
|
2750
|
+
environment: env.ZERO_ENV,
|
|
2751
|
+
requestId: randomUUID2(),
|
|
2752
|
+
agentHost: detectAgentHost()
|
|
2266
2753
|
});
|
|
2267
2754
|
return {
|
|
2268
2755
|
analyticsService,
|
|
@@ -2281,7 +2768,8 @@ var createAppContext = () => {
|
|
|
2281
2768
|
}
|
|
2282
2769
|
return {
|
|
2283
2770
|
env,
|
|
2284
|
-
services: getServices(env)
|
|
2771
|
+
services: getServices(env),
|
|
2772
|
+
invocation: { current: null }
|
|
2285
2773
|
};
|
|
2286
2774
|
};
|
|
2287
2775
|
|
|
@@ -2293,15 +2781,27 @@ var main = async () => {
|
|
|
2293
2781
|
process.exit(1);
|
|
2294
2782
|
}
|
|
2295
2783
|
const app = createApp(appContext);
|
|
2784
|
+
let caughtError = null;
|
|
2296
2785
|
try {
|
|
2297
2786
|
await app.parseAsync(process.argv);
|
|
2298
2787
|
} catch (err) {
|
|
2299
2788
|
const isCommanderExit = err instanceof Error && "exitCode" in err && err.exitCode === 0;
|
|
2300
2789
|
if (!isCommanderExit) {
|
|
2301
|
-
|
|
2790
|
+
caughtError = err instanceof Error ? err : new Error(String(err));
|
|
2791
|
+
console.error(caughtError.message);
|
|
2302
2792
|
process.exitCode = 1;
|
|
2303
2793
|
}
|
|
2304
2794
|
} finally {
|
|
2795
|
+
const invocation = appContext.invocation.current;
|
|
2796
|
+
if (invocation) {
|
|
2797
|
+
appContext.services.analyticsService.capture("command_completed", {
|
|
2798
|
+
command: invocation.command,
|
|
2799
|
+
success: !caughtError && process.exitCode !== 1,
|
|
2800
|
+
// biome-ignore lint/style/useNamingConvention: snake_case for analytics
|
|
2801
|
+
duration_ms: Date.now() - invocation.startMs,
|
|
2802
|
+
...caughtError && { error: truncateError(caughtError.message) }
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2305
2805
|
await appContext.services.analyticsService.shutdown();
|
|
2306
2806
|
}
|
|
2307
2807
|
};
|