@zeroxyz/cli 0.0.41 → 0.0.42

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.
Files changed (2) hide show
  1. package/dist/index.js +371 -103
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { homedir as homedir8 } from "os";
5
- import { join as join10 } from "path";
4
+ import { homedir as homedir9 } from "os";
5
+ import { join as join11 } from "path";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@zeroxyz/cli",
10
- version: "0.0.41",
10
+ version: "0.0.42",
11
11
  type: "module",
12
12
  bin: {
13
13
  zero: "dist/index.js",
@@ -61,7 +61,7 @@ var package_default = {
61
61
  };
62
62
 
63
63
  // src/app.ts
64
- import { Command as Command13 } from "commander";
64
+ import { Command as Command14 } from "commander";
65
65
 
66
66
  // src/commands/auth-command.ts
67
67
  import { homedir } from "os";
@@ -204,8 +204,7 @@ var searchResultSchema = z.object({
204
204
  cost: z.object({ amount: z.string(), asset: z.string() }),
205
205
  reviewCount: z.number().optional(),
206
206
  rating: ratingSchema,
207
- availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
208
- displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
207
+ availabilityStatus: z.enum(["healthy", "unknown", "down"]).nullable().optional(),
209
208
  // Pass-through: API stamps this on Zero-published / withzero.{ai,xyz}
210
209
  // services so `zero search --json` consumers can render their own
211
210
  // provenance UI. CLI doesn't render a badge today.
@@ -258,8 +257,7 @@ var capabilityResponseSchema = z.object({
258
257
  priority: z.number()
259
258
  })
260
259
  ).nullable(),
261
- availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
262
- displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
260
+ availabilityStatus: z.enum(["healthy", "unknown", "down"]).nullable().optional(),
263
261
  activationCount: z.number().optional(),
264
262
  lastUsedAt: z.string().nullable().optional(),
265
263
  lastSuccessfullyRanAt: z.string().nullable().optional(),
@@ -586,16 +584,36 @@ var ApiService = class _ApiService {
586
584
  });
587
585
  return createBugReportResponseSchema.parse(json);
588
586
  };
587
+ // Mints a one-time onramp URL for the caller's wallet. Two paths, picked by
588
+ // which identity is available:
589
+ // • BYO key present (this.account) → /v1/wallet/fund-url, proving ownership
590
+ // with an EIP-191 signature. Honors the `provider` choice.
591
+ // • No key but a session (managed/runner) → /v1/users/me/fund-url, which
592
+ // resolves the user's managed wallet server-side from the Bearer. Coinbase
593
+ // only (the managed onramp doesn't take a provider), so `provider` is
594
+ // ignored on this path.
595
+ // Returns null on any failure so callers can fall back to manual transfer.
589
596
  getFundingUrl = async (amount, provider = "coinbase") => {
590
597
  try {
591
- const params = new URLSearchParams({ provider });
592
- if (amount) params.set("amount", amount);
593
- const json = await this.request(
594
- "GET",
595
- `/v1/wallet/fund-url?${params.toString()}`
596
- );
597
- const parsed = z.object({ url: z.string() }).parse(json);
598
- return parsed.url;
598
+ if (this.account) {
599
+ const params = new URLSearchParams({ provider });
600
+ if (amount) params.set("amount", amount);
601
+ const json = await this.request(
602
+ "GET",
603
+ `/v1/wallet/fund-url?${params.toString()}`,
604
+ void 0,
605
+ { auth: "wallet-attributed" }
606
+ );
607
+ return z.object({ url: z.string() }).parse(json).url;
608
+ }
609
+ if (this.credentials.kind === "session") {
610
+ const params = new URLSearchParams();
611
+ if (amount) params.set("amount", amount);
612
+ const qs = params.toString() ? `?${params.toString()}` : "";
613
+ const json = await this.request("GET", `/v1/users/me/fund-url${qs}`);
614
+ return z.object({ url: z.string(), walletAddress: z.string() }).parse(json).url;
615
+ }
616
+ return null;
599
617
  } catch {
600
618
  return null;
601
619
  }
@@ -2403,7 +2421,7 @@ var formatCapability = (capability) => {
2403
2421
  const lines = [];
2404
2422
  lines.push(capability.name);
2405
2423
  lines.push(` Rating: ${formatRating(capability.rating)}`);
2406
- lines.push(` Status: ${capability.displayStatus ?? "unknown"}`);
2424
+ lines.push(` Status: ${capability.availabilityStatus ?? "unknown"}`);
2407
2425
  lines.push(...formatCost(capability));
2408
2426
  lines.push(` URL: ${capability.url}`);
2409
2427
  lines.push(` Method: ${capability.method}`);
@@ -2645,6 +2663,8 @@ var AGENT_TOOLS = [
2645
2663
  },
2646
2664
  { name: "Cursor", detectDir: ".cursor", skillsDir: ".cursor/skills" }
2647
2665
  ];
2666
+ var HOOK_FILES = ["auto-approve-zero.sh", "zero-context.sh"];
2667
+ var ZERO_SANDBOX_DOMAIN = "*.zero.xyz";
2648
2668
  var findResourceDir = (startDir, resourceName) => {
2649
2669
  let current = startDir;
2650
2670
  while (true) {
@@ -2714,10 +2734,9 @@ var installHook = (home, verbose = false) => {
2714
2734
  const zeroHooksDir = join3(home, ".zero", "hooks");
2715
2735
  mkdirSync3(zeroHooksDir, { recursive: true });
2716
2736
  if (verbose) stepInfo(`staged hook dir at ${zeroHooksDir}`);
2717
- const hookFiles = ["auto-approve-zero.sh", "zero-context.sh"];
2718
2737
  const hookDests = {};
2719
2738
  const hooksSourceDir = findResourceDir(getCliModuleDir(), "hooks");
2720
- for (const hookFile of hookFiles) {
2739
+ for (const hookFile of HOOK_FILES) {
2721
2740
  const hookSource = join3(hooksSourceDir, hookFile);
2722
2741
  const hookDest = join3(zeroHooksDir, hookFile);
2723
2742
  copyFile(hookSource, hookDest);
@@ -2820,12 +2839,15 @@ var installHook = (home, verbose = false) => {
2820
2839
  network.allowedDomains = [];
2821
2840
  }
2822
2841
  const allowedDomains = network.allowedDomains;
2823
- const zeroDomain = "*.zero.xyz";
2824
- if (!allowedDomains.includes(zeroDomain)) {
2825
- allowedDomains.push(zeroDomain);
2826
- if (verbose) stepInfo(`sandbox.network.allowedDomains += ${zeroDomain}`);
2842
+ if (!allowedDomains.includes(ZERO_SANDBOX_DOMAIN)) {
2843
+ allowedDomains.push(ZERO_SANDBOX_DOMAIN);
2844
+ if (verbose) {
2845
+ stepInfo(`sandbox.network.allowedDomains += ${ZERO_SANDBOX_DOMAIN}`);
2846
+ }
2827
2847
  } else if (verbose) {
2828
- stepInfo(`${zeroDomain} already in sandbox allowlist \u2014 not modified`);
2848
+ stepInfo(
2849
+ `${ZERO_SANDBOX_DOMAIN} already in sandbox allowlist \u2014 not modified`
2850
+ );
2829
2851
  }
2830
2852
  writeFileSync3(settingsPath, `${JSON.stringify(settings, null, 2)}
2831
2853
  `);
@@ -2861,6 +2883,14 @@ var removeConflictingSkills = (home) => {
2861
2883
  }
2862
2884
  return removed;
2863
2885
  };
2886
+ var getBundledSkillNames = () => {
2887
+ try {
2888
+ const skillsSourceDir = findResourceDir(getCliModuleDir(), "skills");
2889
+ return readdirSync(skillsSourceDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
2890
+ } catch {
2891
+ return ["zero"];
2892
+ }
2893
+ };
2864
2894
  var installSkills = (home, verbose = false) => {
2865
2895
  const skillsSourceDir = findResourceDir(getCliModuleDir(), "skills");
2866
2896
  const skillDirs = readdirSync(skillsSourceDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -3404,8 +3434,16 @@ var formatRatingBadge = (rating) => {
3404
3434
  return `${successPct} success \xB7 ${reviews} reviews`;
3405
3435
  };
3406
3436
  var formatStatusBadge = (status) => {
3407
- if (!status || status === "unknown") return "";
3408
- return ` \u2014 ${status}`;
3437
+ switch (status) {
3438
+ case "healthy":
3439
+ return ` \u2014 ${color.green("\u2713 healthy")}`;
3440
+ case "down":
3441
+ return ` \u2014 ${color.red("\u2717 down")}`;
3442
+ case "unknown":
3443
+ return ` \u2014 ${color.yellow("? unknown")}`;
3444
+ default:
3445
+ return "";
3446
+ }
3409
3447
  };
3410
3448
  var formatSearchResults = (results) => {
3411
3449
  if (results.length === 0) return "No capabilities found.";
@@ -3414,7 +3452,7 @@ var formatSearchResults = (results) => {
3414
3452
  const displayName = r.brandName ? `${r.brandName} ${baseName}` : baseName;
3415
3453
  const displayDescription = r.whatItDoes ?? r.description;
3416
3454
  const ratingBadge = formatRatingBadge(r.rating);
3417
- const statusBadge = formatStatusBadge(r.displayStatus);
3455
+ const statusBadge = formatStatusBadge(r.availabilityStatus);
3418
3456
  const costLabel = r.cost.amount === "0" ? "Free" : r.cost.amount === "unknown" ? "variable pricing" : `$${r.cost.amount}/call`;
3419
3457
  return ` ${r.position}. ${displayName} \u2014 ${costLabel} \u2014 ${ratingBadge}${statusBadge}
3420
3458
  "${displayDescription}"`;
@@ -3425,7 +3463,7 @@ var searchCommand = (appContext) => new Command9("search").description("Search f
3425
3463
  `Maximum cost per call in USD (default: ${DEFAULT_MAX_COST_USD})`
3426
3464
  ).option("--protocol <protocol>", "Payment protocol (x402 or mpp)").option(
3427
3465
  "--status <status>",
3428
- "Filter by availability (healthy, unknown, down). Defaults to healthy when neither --status nor --all is set; pass --all to see everything."
3466
+ "Filter by availability (healthy, unknown, down). Default (unset) returns healthy + unknown with healthy first; pass 'healthy' for only-healthy, or --all to include everything."
3429
3467
  ).option("--all", "Disable default quality filtering").option(
3430
3468
  "--source <source>",
3431
3469
  "Only show results from this crawl source (e.g. mpp, bazaar)"
@@ -3460,12 +3498,8 @@ var searchCommand = (appContext) => new Command9("search").description("Search f
3460
3498
  process.exitCode = 1;
3461
3499
  return;
3462
3500
  }
3463
- let effectiveStatus = options.status;
3464
- let appliedDefaultStatus = false;
3465
- if (effectiveStatus === void 0 && !options.all) {
3466
- effectiveStatus = "healthy";
3467
- appliedDefaultStatus = true;
3468
- }
3501
+ const effectiveStatus = options.status;
3502
+ const appliedDefaultStatus = effectiveStatus === void 0 && !options.all;
3469
3503
  const result = await apiService.search({
3470
3504
  query,
3471
3505
  offset: options.offset,
@@ -3562,23 +3596,256 @@ Read the full terms at: ${TERMS_URL}
3562
3596
  }
3563
3597
  });
3564
3598
 
3565
- // src/commands/wallet-command.ts
3566
- import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
3599
+ // src/commands/uninstall-command.ts
3600
+ import {
3601
+ existsSync as existsSync3,
3602
+ readdirSync as readdirSync2,
3603
+ readFileSync as readFileSync7,
3604
+ rmSync as rmSync2,
3605
+ writeFileSync as writeFileSync4
3606
+ } from "fs";
3567
3607
  import { homedir as homedir4 } from "os";
3568
- import { join as join5 } from "path";
3569
- import { confirm, isCancel } from "@clack/prompts";
3608
+ import { join as join4 } from "path";
3570
3609
  import { Command as Command11 } from "commander";
3610
+ var removeSkills = (home, verbose = false) => {
3611
+ const skillNames = getBundledSkillNames();
3612
+ const removed = [];
3613
+ for (const tool of AGENT_TOOLS) {
3614
+ const toolSkillsPath = join4(home, tool.skillsDir);
3615
+ if (!existsSync3(toolSkillsPath)) {
3616
+ if (verbose) {
3617
+ stepInfo(
3618
+ `${tool.name}: ~/${tool.skillsDir} not found \u2014 nothing to remove`
3619
+ );
3620
+ }
3621
+ continue;
3622
+ }
3623
+ for (const skillName of skillNames) {
3624
+ const skillPath = join4(toolSkillsPath, skillName);
3625
+ if (!existsSync3(skillPath)) continue;
3626
+ rmSync2(skillPath, { recursive: true, force: true });
3627
+ removed.push(`${tool.name}: ${skillPath}`);
3628
+ if (verbose) stepInfo(`${tool.name}: removed skill '${skillName}'`);
3629
+ }
3630
+ }
3631
+ return removed;
3632
+ };
3633
+ var removeHooks = (home, verbose = false) => {
3634
+ let settingsChanged = false;
3635
+ const settingsPath = join4(home, ".claude", "settings.json");
3636
+ if (!existsSync3(settingsPath)) {
3637
+ if (verbose) stepInfo(`${settingsPath} not found \u2014 no settings to clean`);
3638
+ } else {
3639
+ let settings = null;
3640
+ try {
3641
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
3642
+ } catch {
3643
+ if (verbose) {
3644
+ stepInfo(`${settingsPath} was unparseable JSON \u2014 leaving it untouched`);
3645
+ }
3646
+ }
3647
+ if (settings) {
3648
+ settingsChanged = stripZeroFromSettings(settings, verbose);
3649
+ if (settingsChanged) {
3650
+ writeFileSync4(settingsPath, `${JSON.stringify(settings, null, 2)}
3651
+ `);
3652
+ if (verbose) stepInfo(`rewrote ${settingsPath} without Zero entries`);
3653
+ } else if (verbose) {
3654
+ stepInfo(`no Zero entries found in ${settingsPath}`);
3655
+ }
3656
+ }
3657
+ }
3658
+ let scriptsRemoved = 0;
3659
+ const zeroHooksDir = join4(home, ".zero", "hooks");
3660
+ if (existsSync3(zeroHooksDir)) {
3661
+ for (const hookFile of HOOK_FILES) {
3662
+ const hookPath = join4(zeroHooksDir, hookFile);
3663
+ if (!existsSync3(hookPath)) continue;
3664
+ rmSync2(hookPath, { force: true });
3665
+ scriptsRemoved++;
3666
+ if (verbose) stepInfo(`removed ${hookPath}`);
3667
+ }
3668
+ if (readdirSync2(zeroHooksDir).length === 0) {
3669
+ rmSync2(zeroHooksDir, { recursive: true, force: true });
3670
+ if (verbose) stepInfo(`removed empty ${zeroHooksDir}`);
3671
+ }
3672
+ } else if (verbose) {
3673
+ stepInfo(`${zeroHooksDir} not found \u2014 no hook scripts to remove`);
3674
+ }
3675
+ return { settingsChanged, scriptsRemoved };
3676
+ };
3677
+ var stripZeroFromSettings = (settings, verbose) => {
3678
+ let changed = false;
3679
+ const hooks = settings.hooks && typeof settings.hooks === "object" ? settings.hooks : null;
3680
+ if (hooks) {
3681
+ changed = removeHookEntries(hooks, "PreToolUse", "auto-approve-zero", verbose) || changed;
3682
+ changed = removeHookEntries(hooks, "UserPromptSubmit", "zero-context", verbose) || changed;
3683
+ for (const key of ["PreToolUse", "UserPromptSubmit"]) {
3684
+ const arr = hooks[key];
3685
+ if (Array.isArray(arr) && arr.length === 0) delete hooks[key];
3686
+ }
3687
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
3688
+ }
3689
+ const sandbox = settings.sandbox && typeof settings.sandbox === "object" ? settings.sandbox : null;
3690
+ const network = sandbox?.network && typeof sandbox.network === "object" ? sandbox.network : null;
3691
+ if (network && Array.isArray(network.allowedDomains)) {
3692
+ const domains = network.allowedDomains;
3693
+ const next = domains.filter((d) => d !== ZERO_SANDBOX_DOMAIN);
3694
+ if (next.length !== domains.length) {
3695
+ changed = true;
3696
+ if (verbose) {
3697
+ stepInfo(`sandbox.network.allowedDomains -= ${ZERO_SANDBOX_DOMAIN}`);
3698
+ }
3699
+ if (next.length === 0) {
3700
+ delete network.allowedDomains;
3701
+ if (sandbox && Object.keys(network).length === 0)
3702
+ delete sandbox.network;
3703
+ if (sandbox && Object.keys(sandbox).length === 0)
3704
+ delete settings.sandbox;
3705
+ } else {
3706
+ network.allowedDomains = next;
3707
+ }
3708
+ }
3709
+ }
3710
+ return changed;
3711
+ };
3712
+ var removeHookEntries = (hooks, hookType, commandSubstring, verbose) => {
3713
+ const entries = hooks[hookType];
3714
+ if (!Array.isArray(entries)) return false;
3715
+ const next = entries.filter((entry) => {
3716
+ const entryHooks = entry?.hooks;
3717
+ if (!Array.isArray(entryHooks)) return true;
3718
+ return !entryHooks.some(
3719
+ (h) => typeof h.command === "string" && h.command.includes(commandSubstring)
3720
+ );
3721
+ });
3722
+ if (next.length === entries.length) return false;
3723
+ hooks[hookType] = next;
3724
+ if (verbose) stepInfo(`removed ${hookType} entry (${commandSubstring})`);
3725
+ return true;
3726
+ };
3727
+ var removeWallet = (home, verbose = false) => {
3728
+ const zeroDir = join4(home, ".zero");
3729
+ const configPath = join4(zeroDir, "config.json");
3730
+ let removed = false;
3731
+ if (existsSync3(configPath)) {
3732
+ rmSync2(configPath, { force: true });
3733
+ removed = true;
3734
+ if (verbose) stepInfo(`removed ${configPath}`);
3735
+ }
3736
+ if (existsSync3(zeroDir) && readdirSync2(zeroDir).length === 0) {
3737
+ rmSync2(zeroDir, { recursive: true, force: true });
3738
+ if (verbose) stepInfo(`removed empty ${zeroDir}`);
3739
+ }
3740
+ return removed;
3741
+ };
3742
+ var runUninstall = async (appContext, options = {}) => {
3743
+ const verbose = options.verbose ?? false;
3744
+ const purge = options.purge ?? false;
3745
+ appContext.services.analyticsService.capture("uninstall_started", { purge });
3746
+ let currentStep = "skills";
3747
+ try {
3748
+ console.log("");
3749
+ console.log(` ${color.boldCyan("Uninstalling Zero")}`);
3750
+ console.log("");
3751
+ console.log(color.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
3752
+ console.log("");
3753
+ const home = homedir4();
3754
+ currentStep = "skills";
3755
+ const skillsRemoved = removeSkills(home, verbose);
3756
+ if (skillsRemoved.length > 0) {
3757
+ stepSuccess("Skills removed", `${skillsRemoved.length} removed`);
3758
+ } else {
3759
+ stepSkip("No Zero skills found");
3760
+ }
3761
+ currentStep = "hooks";
3762
+ const { settingsChanged, scriptsRemoved } = removeHooks(home, verbose);
3763
+ if (settingsChanged || scriptsRemoved > 0) {
3764
+ stepSuccess("Hooks removed");
3765
+ } else {
3766
+ stepSkip("No Zero hooks found");
3767
+ }
3768
+ currentStep = "wallet";
3769
+ let walletRemoved = false;
3770
+ if (purge) {
3771
+ walletRemoved = removeWallet(home, verbose);
3772
+ if (walletRemoved) {
3773
+ stepWarn("Wallet config deleted", "~/.zero/config.json");
3774
+ } else {
3775
+ stepSkip("No wallet config to delete");
3776
+ }
3777
+ } else {
3778
+ stepSkip(
3779
+ "Wallet preserved",
3780
+ "run `zero uninstall --purge` to also delete ~/.zero"
3781
+ );
3782
+ }
3783
+ currentStep = "complete";
3784
+ appContext.services.analyticsService.capture("uninstall_completed", {
3785
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
3786
+ skills_removed: skillsRemoved,
3787
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
3788
+ skills_removed_count: skillsRemoved.length,
3789
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
3790
+ hooks_removed: settingsChanged,
3791
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
3792
+ hook_scripts_removed: scriptsRemoved,
3793
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
3794
+ wallet_removed: walletRemoved,
3795
+ purge
3796
+ });
3797
+ sectionDivider();
3798
+ console.log(` ${color.boldGreen("Zero uninstalled.")}`);
3799
+ if (!purge) {
3800
+ console.log(
3801
+ ` ${color.dim("Your wallet is kept at ~/.zero \u2014 reinstall anytime with `zero init`.")}`
3802
+ );
3803
+ }
3804
+ console.log("");
3805
+ return {
3806
+ skillsRemoved,
3807
+ hooksRemoved: settingsChanged,
3808
+ hookScriptsRemoved: scriptsRemoved,
3809
+ walletRemoved
3810
+ };
3811
+ } catch (err) {
3812
+ appContext.services.analyticsService.capture("uninstall_failed", {
3813
+ step: currentStep,
3814
+ error: truncateError(err instanceof Error ? err.message : String(err)),
3815
+ purge
3816
+ });
3817
+ throw err;
3818
+ }
3819
+ };
3820
+ var uninstallCommand = (appContext) => new Command11("uninstall").description(
3821
+ "Remove Zero skills and hooks installed by `zero init` (keeps your wallet)"
3822
+ ).option(
3823
+ "--purge",
3824
+ "Also delete the wallet config at ~/.zero (irreversible \u2014 destroys the private key)"
3825
+ ).option(
3826
+ "-v, --verbose",
3827
+ "Explain why each removal step was taken or skipped"
3828
+ ).action(async (options) => {
3829
+ await runUninstall(appContext, options);
3830
+ });
3831
+
3832
+ // src/commands/wallet-command.ts
3833
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
3834
+ import { homedir as homedir5 } from "os";
3835
+ import { join as join6 } from "path";
3836
+ import { confirm, isCancel } from "@clack/prompts";
3837
+ import { Command as Command12 } from "commander";
3571
3838
  import open2 from "open";
3572
3839
  import { isAddress } from "viem";
3573
3840
  import { generatePrivateKey as generatePrivateKey2, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
3574
3841
 
3575
3842
  // src/util/migrate-config.ts
3576
- import { readFileSync as readFileSync7 } from "fs";
3577
- import { join as join4 } from "path";
3843
+ import { readFileSync as readFileSync8 } from "fs";
3844
+ import { join as join5 } from "path";
3578
3845
  var backupAndStripPrivateKey = (zeroDir) => {
3579
- const configPath = join4(zeroDir, "config.json");
3580
- const backupPath = join4(zeroDir, "config.backup.json");
3581
- const raw = readFileSync7(configPath, "utf8");
3846
+ const configPath = join5(zeroDir, "config.json");
3847
+ const backupPath = join5(zeroDir, "config.backup.json");
3848
+ const raw = readFileSync8(configPath, "utf8");
3582
3849
  ensureSecureDir(zeroDir);
3583
3850
  writeSecureFile(backupPath, raw);
3584
3851
  const parsed = JSON.parse(raw);
@@ -3599,7 +3866,7 @@ var readStdin = async () => {
3599
3866
  // src/commands/wallet-command.ts
3600
3867
  var PRIVATE_KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
3601
3868
  var parseProvider = (raw) => raw === "stripe" ? "stripe" : "coinbase";
3602
- var walletBalanceCommand = (appContext) => new Command11("balance").description("Show wallet balance").option(
3869
+ var walletBalanceCommand = (appContext) => new Command12("balance").description("Show wallet balance").option(
3603
3870
  "--address <address>",
3604
3871
  "Check balance for an arbitrary address (no key needed). Useful with `zero wallet generate` \u2014 verify funds arrived without setting the wallet as your default."
3605
3872
  ).action(async (options) => {
@@ -3648,7 +3915,7 @@ var readPrivateKeyFromStdin = async () => {
3648
3915
  }
3649
3916
  return raw;
3650
3917
  };
3651
- var walletFundCommand = (appContext) => new Command11("fund").description("Fund your wallet").argument("[amount]", "Amount to fund in USDC").option("--manual", "Show wallet address for manual transfer").option(
3918
+ var walletFundCommand = (appContext) => new Command12("fund").description("Fund your wallet").argument("[amount]", "Amount to fund in USDC").option("--manual", "Show wallet address for manual transfer").option(
3652
3919
  "--no-open",
3653
3920
  "Print the funding URL instead of opening a browser (for agents \u2014 funding links are one-time use, hand the URL to the user)"
3654
3921
  ).option(
@@ -3745,7 +4012,7 @@ ${address}`);
3745
4012
  }
3746
4013
  }
3747
4014
  );
3748
- var walletAddressCommand = (appContext) => new Command11("address").description("Show wallet address").action(() => {
4015
+ var walletAddressCommand = (appContext) => new Command12("address").description("Show wallet address").action(() => {
3749
4016
  const { walletService } = appContext.services;
3750
4017
  const address = walletService.getAddress();
3751
4018
  if (!address) {
@@ -3755,7 +4022,7 @@ var walletAddressCommand = (appContext) => new Command11("address").description(
3755
4022
  }
3756
4023
  console.log(address);
3757
4024
  });
3758
- var walletSetCommand = (appContext) => new Command11("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) => {
4025
+ var walletSetCommand = (appContext) => new Command12("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) => {
3759
4026
  const { analyticsService } = appContext.services;
3760
4027
  if (!privateKey.startsWith("0x")) {
3761
4028
  console.error("Private key must be 0x-prefixed hex string.");
@@ -3770,11 +4037,11 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
3770
4037
  process.exitCode = 1;
3771
4038
  return;
3772
4039
  }
3773
- const zeroDir = join5(homedir4(), ".zero");
3774
- const configPath = join5(zeroDir, "config.json");
3775
- if (!options.force && existsSync3(configPath)) {
4040
+ const zeroDir = join6(homedir5(), ".zero");
4041
+ const configPath = join6(zeroDir, "config.json");
4042
+ if (!options.force && existsSync4(configPath)) {
3776
4043
  try {
3777
- const existing2 = JSON.parse(readFileSync8(configPath, "utf8"));
4044
+ const existing2 = JSON.parse(readFileSync9(configPath, "utf8"));
3778
4045
  if (existing2.privateKey) {
3779
4046
  console.error(
3780
4047
  "Wallet already configured. Use --force to overwrite."
@@ -3786,7 +4053,7 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
3786
4053
  }
3787
4054
  }
3788
4055
  ensureSecureDir(zeroDir);
3789
- const existing = existsSync3(configPath) ? JSON.parse(readFileSync8(configPath, "utf8")) : {};
4056
+ const existing = existsSync4(configPath) ? JSON.parse(readFileSync9(configPath, "utf8")) : {};
3790
4057
  writeSecureFile(
3791
4058
  configPath,
3792
4059
  JSON.stringify(
@@ -3805,7 +4072,7 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
3805
4072
  force: options.force ?? false
3806
4073
  });
3807
4074
  });
3808
- var walletGenerateCommand = (appContext) => new Command11("generate").description(
4075
+ var walletGenerateCommand = (appContext) => new Command12("generate").description(
3809
4076
  "Generate a fresh wallet (address + private key) without touching your configured wallet"
3810
4077
  ).option("--json", "Emit { address, privateKey } as JSON").option(
3811
4078
  "--fund",
@@ -3895,13 +4162,13 @@ var resolveZeroWalletAddress = async (apiService) => {
3895
4162
  return null;
3896
4163
  }
3897
4164
  };
3898
- var walletMigrateCommand = (appContext) => new Command11("migrate").description(
4165
+ var walletMigrateCommand = (appContext) => new Command12("migrate").description(
3899
4166
  "Sweep all USDC from your private-key wallet into your Zero wallet"
3900
4167
  ).option("--json", "Emit the migration result as JSON").option("-y, --yes", "Skip the confirmation prompt").action(async (options) => {
3901
4168
  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) : {};
4169
+ const zeroDir = join6(homedir5(), ".zero");
4170
+ const configPath = join6(zeroDir, "config.json");
4171
+ const config = existsSync4(configPath) ? readConfig(configPath) : {};
3905
4172
  const privateKey = appContext.env.ZERO_PRIVATE_KEY ?? config.privateKey;
3906
4173
  if (!privateKey) {
3907
4174
  console.error(
@@ -4019,7 +4286,7 @@ var walletMigrateCommand = (appContext) => new Command11("migrate").description(
4019
4286
  if (anyFailed) process.exitCode = 1;
4020
4287
  });
4021
4288
  var walletCommand = (appContext) => {
4022
- const cmd = new Command11("wallet").description("Manage your wallet");
4289
+ const cmd = new Command12("wallet").description("Manage your wallet");
4023
4290
  cmd.addCommand(walletBalanceCommand(appContext));
4024
4291
  cmd.addCommand(walletFundCommand(appContext));
4025
4292
  cmd.addCommand(walletAddressCommand(appContext));
@@ -4030,18 +4297,18 @@ var walletCommand = (appContext) => {
4030
4297
  };
4031
4298
 
4032
4299
  // src/commands/welcome-command.ts
4033
- import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
4034
- import { homedir as homedir5 } from "os";
4035
- import { join as join6 } from "path";
4036
- import { Command as Command12 } from "commander";
4300
+ import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
4301
+ import { homedir as homedir6 } from "os";
4302
+ import { join as join7 } from "path";
4303
+ import { Command as Command13 } from "commander";
4037
4304
  import open3 from "open";
4038
4305
  import { getAddress } from "viem";
4039
4306
  import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
4040
4307
  var readPrivateKey = () => {
4041
- const configPath = join6(homedir5(), ".zero", "config.json");
4042
- if (!existsSync4(configPath)) return null;
4308
+ const configPath = join7(homedir6(), ".zero", "config.json");
4309
+ if (!existsSync5(configPath)) return null;
4043
4310
  try {
4044
- const config = JSON.parse(readFileSync9(configPath, "utf8"));
4311
+ const config = JSON.parse(readFileSync10(configPath, "utf8"));
4045
4312
  if (typeof config.privateKey === "string") {
4046
4313
  return config.privateKey;
4047
4314
  }
@@ -4076,7 +4343,7 @@ var printManualFallback = (url) => {
4076
4343
  }
4077
4344
  console.log(lines.join("\n"));
4078
4345
  };
4079
- var welcomeCommand = (appContext) => new Command12("welcome").description("Claim your $5 welcome bonus.").action(async () => {
4346
+ var welcomeCommand = (appContext) => new Command13("welcome").description("Claim your $5 welcome bonus.").action(async () => {
4080
4347
  const { analyticsService } = appContext.services;
4081
4348
  analyticsService.capture("welcome_started", {});
4082
4349
  let walletAddress;
@@ -4136,7 +4403,7 @@ If your browser didn't open, paste the URL above.`
4136
4403
  // src/app.ts
4137
4404
  var createApp = (appContext) => {
4138
4405
  const { analyticsService } = appContext.services;
4139
- const program = new Command13().name("zero").description("Zero CLI \u2014 Search engine for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
4406
+ const program = new Command14().name("zero").description("Zero CLI \u2014 Search engine for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
4140
4407
  const agentFlag = actionCommand.opts().agent;
4141
4408
  if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
4142
4409
  analyticsService.setAgentHost(agentFlag.trim());
@@ -4150,6 +4417,7 @@ var createApp = (appContext) => {
4150
4417
  });
4151
4418
  });
4152
4419
  program.addCommand(initCommand(appContext));
4420
+ program.addCommand(uninstallCommand(appContext));
4153
4421
  program.addCommand(searchCommand(appContext));
4154
4422
  program.addCommand(getCommand(appContext));
4155
4423
  program.addCommand(fetchCommand(appContext));
@@ -4185,14 +4453,14 @@ var getEnv = () => {
4185
4453
 
4186
4454
  // src/app/app-services.ts
4187
4455
  import { randomUUID as randomUUID2 } from "crypto";
4188
- import { existsSync as existsSync7 } from "fs";
4189
- import { homedir as homedir6 } from "os";
4190
- import { join as join8 } from "path";
4456
+ import { existsSync as existsSync8 } from "fs";
4457
+ import { homedir as homedir7 } from "os";
4458
+ import { join as join9 } from "path";
4191
4459
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
4192
4460
 
4193
4461
  // src/services/analytics-service.ts
4194
4462
  import { randomUUID } from "crypto";
4195
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
4463
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
4196
4464
  import { dirname as dirname2 } from "path";
4197
4465
  import { PostHog } from "posthog-node";
4198
4466
  var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
@@ -4214,8 +4482,8 @@ var AnalyticsService = class {
4214
4482
  let telemetryEnabled = true;
4215
4483
  let persistedAnonId;
4216
4484
  try {
4217
- if (existsSync5(opts.configPath)) {
4218
- const config = JSON.parse(readFileSync10(opts.configPath, "utf8"));
4485
+ if (existsSync6(opts.configPath)) {
4486
+ const config = JSON.parse(readFileSync11(opts.configPath, "utf8"));
4219
4487
  if (config.telemetry === false) {
4220
4488
  telemetryEnabled = false;
4221
4489
  }
@@ -4241,8 +4509,8 @@ var AnalyticsService = class {
4241
4509
  try {
4242
4510
  const dir = dirname2(opts.configPath);
4243
4511
  mkdirSync4(dir, { recursive: true });
4244
- const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync10(opts.configPath, "utf8")) : {};
4245
- writeFileSync4(
4512
+ const existing = existsSync6(opts.configPath) ? JSON.parse(readFileSync11(opts.configPath, "utf8")) : {};
4513
+ writeFileSync5(
4246
4514
  opts.configPath,
4247
4515
  JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
4248
4516
  );
@@ -4282,7 +4550,7 @@ var AnalyticsService = class {
4282
4550
  if (anonId === walletAddress) return;
4283
4551
  let aliasedTo;
4284
4552
  try {
4285
- const config = JSON.parse(readFileSync10(configPath, "utf8"));
4553
+ const config = JSON.parse(readFileSync11(configPath, "utf8"));
4286
4554
  if (typeof config.aliasedTo === "string") {
4287
4555
  aliasedTo = config.aliasedTo;
4288
4556
  }
@@ -4292,8 +4560,8 @@ var AnalyticsService = class {
4292
4560
  this.posthog.alias({ distinctId: walletAddress, alias: anonId });
4293
4561
  if (process.env.VITEST) return;
4294
4562
  try {
4295
- const config = existsSync5(configPath) ? JSON.parse(readFileSync10(configPath, "utf8")) : {};
4296
- writeFileSync4(
4563
+ const config = existsSync6(configPath) ? JSON.parse(readFileSync11(configPath, "utf8")) : {};
4564
+ writeFileSync5(
4297
4565
  configPath,
4298
4566
  JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
4299
4567
  );
@@ -4396,34 +4664,34 @@ var createApiAccount = (walletAddress, api) => toAccount({
4396
4664
  });
4397
4665
 
4398
4666
  // src/services/state-service.ts
4399
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
4400
- import { join as join7 } from "path";
4667
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
4668
+ import { join as join8 } from "path";
4401
4669
  var RECENT_SEARCH_LIMIT = 10;
4402
4670
  var StateService = class {
4403
4671
  constructor(zeroDir) {
4404
4672
  this.zeroDir = zeroDir;
4405
- this.lastSearchPath = join7(zeroDir, "last_search.json");
4406
- this.recentSearchesPath = join7(zeroDir, "recent_searches.json");
4673
+ this.lastSearchPath = join8(zeroDir, "last_search.json");
4674
+ this.recentSearchesPath = join8(zeroDir, "recent_searches.json");
4407
4675
  }
4408
4676
  lastSearchPath;
4409
4677
  recentSearchesPath;
4410
4678
  saveLastSearch = (data) => {
4411
4679
  mkdirSync5(this.zeroDir, { recursive: true });
4412
- writeFileSync5(this.lastSearchPath, JSON.stringify(data, null, 2));
4680
+ writeFileSync6(this.lastSearchPath, JSON.stringify(data, null, 2));
4413
4681
  const recent = this.loadRecentSearches();
4414
4682
  const filtered = recent.searches.filter(
4415
4683
  (s) => s.searchId !== data.searchId
4416
4684
  );
4417
4685
  const next = [data, ...filtered].slice(0, RECENT_SEARCH_LIMIT);
4418
- writeFileSync5(
4686
+ writeFileSync6(
4419
4687
  this.recentSearchesPath,
4420
4688
  JSON.stringify({ searches: next }, null, 2)
4421
4689
  );
4422
4690
  };
4423
4691
  loadLastSearch = () => {
4424
4692
  try {
4425
- if (!existsSync6(this.lastSearchPath)) return null;
4426
- const raw = readFileSync11(this.lastSearchPath, "utf8");
4693
+ if (!existsSync7(this.lastSearchPath)) return null;
4694
+ const raw = readFileSync12(this.lastSearchPath, "utf8");
4427
4695
  return JSON.parse(raw);
4428
4696
  } catch {
4429
4697
  return null;
@@ -4431,11 +4699,11 @@ var StateService = class {
4431
4699
  };
4432
4700
  loadRecentSearches = () => {
4433
4701
  try {
4434
- if (!existsSync6(this.recentSearchesPath)) {
4702
+ if (!existsSync7(this.recentSearchesPath)) {
4435
4703
  const last = this.loadLastSearch();
4436
4704
  return { searches: last ? [last] : [] };
4437
4705
  }
4438
- const raw = readFileSync11(this.recentSearchesPath, "utf8");
4706
+ const raw = readFileSync12(this.recentSearchesPath, "utf8");
4439
4707
  const parsed = JSON.parse(raw);
4440
4708
  return { searches: parsed.searches ?? [] };
4441
4709
  } catch {
@@ -4563,9 +4831,9 @@ var buildOnSessionRefreshed = (configPath) => async (tokens) => {
4563
4831
  writeSecureFile(configPath, JSON.stringify(next, null, 2));
4564
4832
  };
4565
4833
  var getServices = async (env) => {
4566
- const zeroDir = join8(homedir6(), ".zero");
4567
- const configPath = join8(zeroDir, "config.json");
4568
- const config = existsSync7(configPath) ? readConfig(configPath) : {};
4834
+ const zeroDir = join9(homedir7(), ".zero");
4835
+ const configPath = join9(zeroDir, "config.json");
4836
+ const config = existsSync8(configPath) ? readConfig(configPath) : {};
4569
4837
  const { credentials, privateKey } = resolveCredentials(env, config);
4570
4838
  const lowBalanceWarning = typeof config.lowBalanceWarning === "number" ? config.lowBalanceWarning : 1;
4571
4839
  const apiService = new ApiService(
@@ -4638,15 +4906,15 @@ var createAppContext = async () => {
4638
4906
 
4639
4907
  // src/util/update-check.ts
4640
4908
  import {
4641
- existsSync as existsSync8,
4909
+ existsSync as existsSync9,
4642
4910
  lstatSync,
4643
4911
  mkdirSync as mkdirSync6,
4644
- readFileSync as readFileSync12,
4912
+ readFileSync as readFileSync13,
4645
4913
  readlinkSync,
4646
- writeFileSync as writeFileSync6
4914
+ writeFileSync as writeFileSync7
4647
4915
  } from "fs";
4648
- import { homedir as homedir7 } from "os";
4649
- import { dirname as dirname3, join as join9, resolve } from "path";
4916
+ import { homedir as homedir8 } from "os";
4917
+ import { dirname as dirname3, join as join10, resolve } from "path";
4650
4918
  var CACHE_FILENAME = "update_check.json";
4651
4919
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@zeroxyz/cli/latest";
4652
4920
  var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
@@ -4670,10 +4938,10 @@ var resolveExecPath = (execPath) => {
4670
4938
  var detectInstallMethod = (opts = {}) => {
4671
4939
  const execPath = opts.execPath ?? process.execPath;
4672
4940
  const pkg = opts.pkg ?? process.pkg;
4673
- const home = opts.home ?? homedir7();
4941
+ const home = opts.home ?? homedir8();
4674
4942
  if (pkg) return "binary";
4675
4943
  const resolved = resolveExecPath(execPath);
4676
- const zeroBin = join9(home, ".zero", "bin");
4944
+ const zeroBin = join10(home, ".zero", "bin");
4677
4945
  if (resolved.startsWith(zeroBin)) return "binary";
4678
4946
  return "npm";
4679
4947
  };
@@ -4698,12 +4966,12 @@ var compareVersions = (a, b) => {
4698
4966
  if (pb.pre === null) return -1;
4699
4967
  return pa.pre < pb.pre ? -1 : 1;
4700
4968
  };
4701
- var cachePath = (zeroDir) => join9(zeroDir, CACHE_FILENAME);
4969
+ var cachePath = (zeroDir) => join10(zeroDir, CACHE_FILENAME);
4702
4970
  var readCache = (zeroDir) => {
4703
4971
  try {
4704
4972
  const path = cachePath(zeroDir);
4705
- if (!existsSync8(path)) return emptyCache;
4706
- const raw = readFileSync12(path, "utf8");
4973
+ if (!existsSync9(path)) return emptyCache;
4974
+ const raw = readFileSync13(path, "utf8");
4707
4975
  const parsed = JSON.parse(raw);
4708
4976
  return {
4709
4977
  lastCheckedMs: typeof parsed.lastCheckedMs === "number" ? parsed.lastCheckedMs : 0,
@@ -4717,7 +4985,7 @@ var readCache = (zeroDir) => {
4717
4985
  var writeCache = (zeroDir, cache) => {
4718
4986
  try {
4719
4987
  mkdirSync6(zeroDir, { recursive: true });
4720
- writeFileSync6(cachePath(zeroDir), JSON.stringify(cache, null, 2));
4988
+ writeFileSync7(cachePath(zeroDir), JSON.stringify(cache, null, 2));
4721
4989
  } catch {
4722
4990
  }
4723
4991
  };
@@ -4792,7 +5060,7 @@ var main = async () => {
4792
5060
  console.error("Failed to create app context");
4793
5061
  process.exit(1);
4794
5062
  }
4795
- const zeroDir = join10(homedir8(), ".zero");
5063
+ const zeroDir = join11(homedir9(), ".zero");
4796
5064
  maybePrintUpdateBanner(zeroDir, package_default.version);
4797
5065
  const app = createApp(appContext);
4798
5066
  let caughtError = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",