@zeroxyz/cli 0.0.30 → 0.0.32

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 CHANGED
@@ -6,7 +6,7 @@ import { Command as Command12 } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@zeroxyz/cli",
9
- version: "0.0.30",
9
+ version: "0.0.32",
10
10
  type: "module",
11
11
  bin: {
12
12
  zero: "dist/index.js",
@@ -629,7 +629,7 @@ import {
629
629
  } from "@relayprotocol/relay-sdk";
630
630
  import { x402Client as X402Client } from "@x402/core/client";
631
631
  import { decodePaymentResponseHeader, x402HTTPClient } from "@x402/core/http";
632
- import { ExactEvmScheme } from "@x402/evm/exact/client";
632
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
633
633
  import { createSIWxClientHook } from "@x402/extensions/sign-in-with-x";
634
634
  import { wrapFetchWithPayment } from "@x402/fetch";
635
635
  import { Challenge, Receipt } from "mppx";
@@ -831,10 +831,9 @@ var PaymentService = class {
831
831
  payX402 = async (url, request, _raw, maxPay) => {
832
832
  if (!this.account) throw new Error("No wallet configured");
833
833
  let capturedAmount = "0";
834
- const client = new X402Client().register(
835
- "eip155:*",
836
- new ExactEvmScheme(this.account)
837
- );
834
+ const client = registerExactEvmScheme(new X402Client(), {
835
+ signer: this.account
836
+ });
838
837
  client.onBeforePaymentCreation(async (context) => {
839
838
  const selected = context.selectedRequirements;
840
839
  if (selected && (!selected.extra?.name || !selected.extra?.version)) {
@@ -845,7 +844,9 @@ var PaymentService = class {
845
844
  }
846
845
  const requirement = context.paymentRequired.accepts[0];
847
846
  if (!requirement) return;
848
- capturedAmount = formatUnits(BigInt(requirement.amount), 6);
847
+ const rawAmount = requirement.amount ?? requirement.maxAmountRequired;
848
+ if (!rawAmount) return;
849
+ capturedAmount = formatUnits(BigInt(rawAmount), 6);
849
850
  if (maxPay && Number.parseFloat(capturedAmount) > Number.parseFloat(maxPay)) {
850
851
  return {
851
852
  abort: true,
@@ -1222,27 +1223,24 @@ var isJsonContentType = (contentType) => {
1222
1223
  var MAX_REQUEST_BODY_BYTES = 10 * 1024 * 1024;
1223
1224
  var resolveRequestBody = (rawData, readStdin) => {
1224
1225
  const fromFile = (spec) => {
1225
- if (spec === "@-") {
1226
- return readFileSync3(0, "utf8");
1227
- }
1228
- const path = resolvePath(spec.slice(1));
1229
- return readFileSync3(path, "utf8");
1226
+ if (spec === "@-") return readFileSync3(0);
1227
+ return readFileSync3(resolvePath(spec.slice(1)));
1230
1228
  };
1231
- let body;
1232
1229
  if (readStdin && rawData !== void 0) {
1233
1230
  throw new Error(
1234
1231
  "Conflicting body sources: use either --data-stdin or -d, not both."
1235
1232
  );
1236
1233
  }
1234
+ let body;
1237
1235
  if (readStdin) {
1238
- body = readFileSync3(0, "utf8");
1236
+ body = readFileSync3(0);
1239
1237
  } else if (rawData?.startsWith("@")) {
1240
1238
  body = fromFile(rawData);
1241
1239
  } else {
1242
1240
  body = rawData;
1243
1241
  }
1244
1242
  if (body !== void 0) {
1245
- const bytes = Buffer.byteLength(body, "utf8");
1243
+ const bytes = Buffer.isBuffer(body) ? body.length : Buffer.byteLength(body, "utf8");
1246
1244
  if (bytes > MAX_REQUEST_BODY_BYTES) {
1247
1245
  throw new Error(
1248
1246
  `Request body is ${bytes} bytes \u2014 exceeds the ${MAX_REQUEST_BODY_BYTES} byte limit. Split the payload, compress it, or contact the capability owner about raising the cap.`
@@ -1345,7 +1343,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
1345
1343
  (k) => k.toLowerCase() === "content-type"
1346
1344
  );
1347
1345
  if (resolvedBody && !hasContentType) {
1348
- headers["content-type"] = "application/json";
1346
+ headers["content-type"] = Buffer.isBuffer(resolvedBody) ? "application/octet-stream" : "application/json";
1349
1347
  }
1350
1348
  const log = (msg) => console.error(` ${msg}`);
1351
1349
  const method = options.method ? options.method.toUpperCase() : resolvedBody ? "POST" : "GET";
@@ -1493,7 +1491,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
1493
1491
  if (capabilityId && apiService.walletAddress) {
1494
1492
  let requestSchema;
1495
1493
  let responseSchema;
1496
- if (resolvedBody) {
1494
+ if (typeof resolvedBody === "string") {
1497
1495
  const parsedReq = tryParseJson(resolvedBody);
1498
1496
  if (parsedReq !== null && typeof parsedReq === "object") {
1499
1497
  requestSchema = inferSchema(parsedReq);
@@ -1834,21 +1832,153 @@ var getCommand = (appContext) => new Command4("get").description(
1834
1832
  // src/commands/init-command.ts
1835
1833
  import { createHash as createHash3 } from "crypto";
1836
1834
  import {
1837
- chmodSync,
1835
+ chmodSync as chmodSync2,
1838
1836
  cpSync,
1839
1837
  existsSync as existsSync2,
1840
- mkdirSync as mkdirSync2,
1838
+ mkdirSync as mkdirSync3,
1841
1839
  readdirSync,
1842
1840
  readFileSync as readFileSync4,
1843
1841
  rmSync,
1844
1842
  statSync,
1845
- writeFileSync as writeFileSync2
1843
+ writeFileSync as writeFileSync3
1846
1844
  } from "fs";
1847
1845
  import { homedir as homedir2 } from "os";
1848
1846
  import { dirname, join as join2, relative } from "path";
1849
1847
  import { fileURLToPath } from "url";
1850
1848
  import { Command as Command5 } from "commander";
1851
1849
  import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
1850
+
1851
+ // src/util/install-banner.ts
1852
+ var ZEROMAN_ART = `
1853
+ :++-.
1854
+ .:--==+===--:. +@%@#+-
1855
+ .-+*%@@@@%%%%%%@@@@@%#*=.-# #%-=*=
1856
+ :+#@@@#+-:. .-+#@@@@@%#. %# =#:
1857
+ :+%@@#=: :+@%%@@%=+@: .#=
1858
+ -*@@#=. :%%%%@@%%+ #=
1859
+ :#@@#- .:-====--. :%%%%%@@+ %:
1860
+ +@@#- .-+#%@@@@@@@@@@%*- *@%%%%%+ =#
1861
+ .#@@= .. .-*%@@@@%%@%*+#%%%@@@#:=@%%%%@- %.
1862
+ .%@%: :#=:+%@@@%@%%@@+:-+*%%%%%%@%#%%%%%% %-
1863
+ #@%: .%%@@%%%%#%%*--+=%@@%%%%%%@@%%%%@- %-
1864
+ +@%: :#* :%%%%@==+=*= .%%%%%%%%%%%%%@* %:
1865
+ .%%+ :%@- %#.%%@==@# #@%%%%%%%%%%@# -#
1866
+ .::::: -@%: .%%%* .-%%%+ .. =%%%%%%%%%%%@# #+.
1867
+ .+=-::..-* -@% +@%%@*=-+%%%@@#-..-*@%%%%%%%%%@@+ -@%%%#+:
1868
+ %- .:::*=*%% #%%%%@@@@@@@%%%@@@@@%%%%%%%%%@%- :%%%%@@@@#-
1869
+ ++ ..*- %@@- *@%%%%%%#-. .-#@%%%%%%%@%+. :@@@@@@%%%@@-
1870
+ :# .:--*:-%%%%. .%@%%%%%=:----: :#%%%%@@%+. -#-:.:-#@@@%@%
1871
+ .# *#+=@%@%- .#@@@@@%@@@@@@%*-.=%@@@%+. +* +%%%%%%=
1872
+ =* .====-*:#%%@@#=. .=*#%@%%%%%%%@@@@@%*- -#= -*:.. .:*:
1873
+ -*-. :%*##:-#@@@%#*+==+*#%%%@@@@@@%#+- :*+. .-*+:. .:#-
1874
+ .*+=--+#@@@@@%: .-+#%@@@@@@@@@@%%#+=:. -*+. =+: -*
1875
+ ..:+*###*+=**. .:::::::. .-+=. =# - #:
1876
+ :*+. .=*%%: .+==*- #.
1877
+ -#@@#=:. .:=*#%@@%%%= %- .- .+-*=
1878
+ *@@%%@@@#+==-:::...:::--==+=-=#@@%%%%%@* .+=-=++-==-:.
1879
+ =@%%%%%%- .::---====---:. :#@%%%%%@*
1880
+ =@%%%%%%. *@%%%%%@+
1881
+ #%%%%%@%. #@%%%%%@:
1882
+ .-=++*%%%%%%@* :%%%%%%%#---:.
1883
+ .*%@@@@@%%%%%%%@: %%%%%%%@@@@@%*:
1884
+ %@@@@@@@@@@@@@%%. .%@@@@@@@@@@@@@@:
1885
+ -==++++====--:. -==+++++++++++=.
1886
+ `;
1887
+ var ZERO_BANNER = [
1888
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
1889
+ "\u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2588\u2588\u2588\u2588\u2557",
1890
+ " \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2551",
1891
+ " \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551",
1892
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
1893
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D "
1894
+ ].join("\n");
1895
+ var colorsEnabled = () => {
1896
+ if (process.env.NO_COLOR) return false;
1897
+ if (process.env.FORCE_COLOR) return true;
1898
+ return Boolean(process.stdout.isTTY);
1899
+ };
1900
+ var wrap = (code, text) => colorsEnabled() ? `\x1B[${code}m${text}\x1B[0m` : text;
1901
+ var color = {
1902
+ bold: (s) => wrap("1", s),
1903
+ dim: (s) => wrap("2", s),
1904
+ cyan: (s) => wrap("36", s),
1905
+ magenta: (s) => wrap("35", s),
1906
+ green: (s) => wrap("32", s),
1907
+ yellow: (s) => wrap("33", s),
1908
+ gray: (s) => wrap("90", s),
1909
+ boldCyan: (s) => wrap("1;36", s),
1910
+ boldMagenta: (s) => wrap("1;35", s),
1911
+ boldGreen: (s) => wrap("1;32", s)
1912
+ };
1913
+ var printZeroBanner = () => {
1914
+ console.log("");
1915
+ console.log(ZEROMAN_ART);
1916
+ console.log(color.boldCyan(ZERO_BANNER));
1917
+ console.log("");
1918
+ console.log(` ${color.dim("The search engine for AI agents.")}`);
1919
+ console.log("");
1920
+ 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"));
1921
+ console.log("");
1922
+ };
1923
+ var supportsUnicode = () => {
1924
+ if (process.platform !== "win32") return true;
1925
+ return Boolean(
1926
+ process.env.WT_SESSION || process.env.TERM_PROGRAM === "vscode"
1927
+ );
1928
+ };
1929
+ var SYMBOLS = supportsUnicode() ? { check: "\u2713", arrow: "\u203A", warn: "!" } : { check: "OK", arrow: ">", warn: "!" };
1930
+ var stepSuccess = (label, detail) => {
1931
+ const line = detail ? ` ${color.boldGreen(SYMBOLS.check)} ${label} ${color.dim(detail)}` : ` ${color.boldGreen(SYMBOLS.check)} ${label}`;
1932
+ console.log(line);
1933
+ };
1934
+ var stepWarn = (label, detail) => {
1935
+ const line = detail ? ` ${color.yellow(SYMBOLS.warn)} ${label} ${color.dim(detail)}` : ` ${color.yellow(SYMBOLS.warn)} ${label}`;
1936
+ console.log(line);
1937
+ };
1938
+ var stepSkip = (label, detail) => {
1939
+ const line = detail ? ` ${color.gray(SYMBOLS.arrow)} ${color.dim(`${label} ${detail}`)}` : ` ${color.gray(SYMBOLS.arrow)} ${color.dim(label)}`;
1940
+ console.log(line);
1941
+ };
1942
+ var stepInfo = (message) => {
1943
+ console.log(` ${color.gray("\xB7")} ${color.dim(message)}`);
1944
+ };
1945
+ var sectionDivider = () => {
1946
+ console.log("");
1947
+ 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"));
1948
+ console.log("");
1949
+ };
1950
+ var printReadyFooter = () => {
1951
+ const lines = [
1952
+ "",
1953
+ ` ${color.boldGreen("Zero is ready!")} Run ${color.cyan("`zero search`")} to find capabilities.`,
1954
+ "",
1955
+ ` ${color.dim("Try:")}`,
1956
+ ` ${color.cyan('zero search "translate text to Spanish"')}`,
1957
+ ` ${color.cyan('zero search "generate an image"')}`,
1958
+ ` ${color.cyan('zero search "weather forecast"')}`,
1959
+ "",
1960
+ ` ${color.dim("By using Zero, you agree to our Terms of Service:")}`,
1961
+ ` ${color.dim("https://zero.xyz/terms-of-service")}`,
1962
+ ` ${color.dim("Run `zero terms` to view the full terms.")}`,
1963
+ ""
1964
+ ];
1965
+ return lines.join("\n");
1966
+ };
1967
+
1968
+ // src/util/secure-config.ts
1969
+ import { chmodSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1970
+ var SECURE_DIR_MODE = 448;
1971
+ var SECURE_FILE_MODE = 384;
1972
+ var ensureSecureDir = (path) => {
1973
+ mkdirSync2(path, { recursive: true, mode: SECURE_DIR_MODE });
1974
+ chmodSync(path, SECURE_DIR_MODE);
1975
+ };
1976
+ var writeSecureFile = (path, contents) => {
1977
+ writeFileSync2(path, contents, { mode: SECURE_FILE_MODE });
1978
+ chmodSync(path, SECURE_FILE_MODE);
1979
+ };
1980
+
1981
+ // src/commands/init-command.ts
1852
1982
  var AGENT_TOOLS = [
1853
1983
  { name: "Claude Code", detectDir: ".claude", skillsDir: ".claude/skills" },
1854
1984
  { name: "Codex", detectDir: ".codex", skillsDir: ".agents/skills" },
@@ -1859,19 +1989,27 @@ var AGENT_TOOLS = [
1859
1989
  },
1860
1990
  { name: "Cursor", detectDir: ".cursor", skillsDir: ".cursor/skills" }
1861
1991
  ];
1862
- var getPackageRoot = () => {
1863
- let dir;
1864
- if (import.meta.url) {
1865
- dir = dirname(fileURLToPath(import.meta.url));
1866
- } else {
1867
- dir = __dirname;
1992
+ var findResourceDir = (startDir, resourceName) => {
1993
+ let current = startDir;
1994
+ while (true) {
1995
+ const candidate = join2(current, resourceName);
1996
+ if (existsSync2(candidate)) {
1997
+ return candidate;
1998
+ }
1999
+ const parent = dirname(current);
2000
+ if (parent === current) {
2001
+ throw new Error(
2002
+ `Could not locate bundled '${resourceName}' directory from ${startDir}`
2003
+ );
2004
+ }
2005
+ current = parent;
1868
2006
  }
1869
- while (!existsSync2(join2(dir, "package.json"))) {
1870
- const parent = dirname(dir);
1871
- if (parent === dir) break;
1872
- dir = parent;
2007
+ };
2008
+ var getCliModuleDir = () => {
2009
+ if (import.meta.url) {
2010
+ return dirname(fileURLToPath(import.meta.url));
1873
2011
  }
1874
- return dir;
2012
+ return __dirname;
1875
2013
  };
1876
2014
  var sha256File = (filePath) => createHash3("sha256").update(readFileSync4(filePath)).digest("hex");
1877
2015
  var verifyFileCopy = (src, dest) => {
@@ -1891,7 +2029,7 @@ var collectAllFiles = (dir) => {
1891
2029
  return files;
1892
2030
  };
1893
2031
  var copyDirRecursive = (src, dest) => {
1894
- mkdirSync2(dest, { recursive: true });
2032
+ mkdirSync3(dest, { recursive: true });
1895
2033
  for (const entry of readdirSync(src, { withFileTypes: true })) {
1896
2034
  const srcPath = join2(src, entry.name);
1897
2035
  const destPath = join2(dest, entry.name);
@@ -1899,42 +2037,61 @@ var copyDirRecursive = (src, dest) => {
1899
2037
  copyDirRecursive(srcPath, destPath);
1900
2038
  } else {
1901
2039
  const data = readFileSync4(srcPath);
1902
- writeFileSync2(destPath, data);
2040
+ writeFileSync3(destPath, data);
1903
2041
  try {
1904
2042
  const mode = statSync(srcPath).mode;
1905
- chmodSync(destPath, mode);
2043
+ chmodSync2(destPath, mode);
1906
2044
  } catch {
1907
2045
  }
1908
2046
  }
1909
2047
  }
1910
2048
  };
1911
- var installHook = (home) => {
2049
+ var installHook = (home, verbose = false) => {
1912
2050
  const claudeDir = join2(home, ".claude");
1913
2051
  if (!existsSync2(claudeDir)) {
2052
+ if (verbose) {
2053
+ stepInfo(`~/.claude not found \u2014 Claude Code not installed, skipping`);
2054
+ }
1914
2055
  return false;
1915
2056
  }
1916
2057
  const zeroHooksDir = join2(home, ".zero", "hooks");
1917
- mkdirSync2(zeroHooksDir, { recursive: true });
2058
+ mkdirSync3(zeroHooksDir, { recursive: true });
2059
+ if (verbose) stepInfo(`staged hook dir at ${zeroHooksDir}`);
1918
2060
  const hookFiles = ["auto-approve-zero.sh", "zero-context.sh"];
1919
2061
  const hookDests = {};
2062
+ const hooksSourceDir = findResourceDir(getCliModuleDir(), "hooks");
1920
2063
  for (const hookFile of hookFiles) {
1921
- const hookSource = join2(getPackageRoot(), "hooks", hookFile);
2064
+ const hookSource = join2(hooksSourceDir, hookFile);
1922
2065
  const hookDest = join2(zeroHooksDir, hookFile);
1923
2066
  cpSync(hookSource, hookDest);
1924
- chmodSync(hookDest, 493);
2067
+ chmodSync2(hookDest, 493);
1925
2068
  if (!verifyFileCopy(hookSource, hookDest)) {
1926
2069
  throw new Error(
1927
2070
  `Integrity check failed: ${hookDest} does not match source`
1928
2071
  );
1929
2072
  }
1930
2073
  hookDests[hookFile] = hookDest;
2074
+ if (verbose) stepInfo(`copied ${hookFile} \u2192 ${hookDest} (verified)`);
1931
2075
  }
1932
2076
  const settingsPath = join2(claudeDir, "settings.json");
1933
2077
  let settings = {};
2078
+ let settingsExisted = false;
2079
+ let settingsCorrupted = false;
1934
2080
  if (existsSync2(settingsPath)) {
2081
+ settingsExisted = true;
1935
2082
  try {
1936
2083
  settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
1937
2084
  } catch {
2085
+ settingsCorrupted = true;
2086
+ }
2087
+ }
2088
+ if (verbose) {
2089
+ if (!settingsExisted) {
2090
+ stepInfo(`${settingsPath} did not exist \u2014 creating`);
2091
+ } else if (settingsCorrupted) {
2092
+ stepInfo(`${settingsPath} was unparseable JSON \u2014 rewriting from scratch`);
2093
+ } else {
2094
+ stepInfo(`merging into existing ${settingsPath}`);
1938
2095
  }
1939
2096
  }
1940
2097
  if (!settings.hooks || typeof settings.hooks !== "object") {
@@ -1963,8 +2120,10 @@ var installHook = (home) => {
1963
2120
  });
1964
2121
  if (existingIdx >= 0) {
1965
2122
  preToolUse[existingIdx] = zeroHookEntry;
2123
+ if (verbose) stepInfo(`PreToolUse auto-approve-zero entry replaced`);
1966
2124
  } else {
1967
2125
  preToolUse.push(zeroHookEntry);
2126
+ if (verbose) stepInfo(`PreToolUse auto-approve-zero entry appended`);
1968
2127
  }
1969
2128
  if (!Array.isArray(hooks.UserPromptSubmit)) {
1970
2129
  hooks.UserPromptSubmit = [];
@@ -1987,8 +2146,10 @@ var installHook = (home) => {
1987
2146
  });
1988
2147
  if (existingContextIdx >= 0) {
1989
2148
  userPromptSubmit[existingContextIdx] = contextHookEntry;
2149
+ if (verbose) stepInfo(`UserPromptSubmit zero-context entry replaced`);
1990
2150
  } else {
1991
2151
  userPromptSubmit.push(contextHookEntry);
2152
+ if (verbose) stepInfo(`UserPromptSubmit zero-context entry appended`);
1992
2153
  }
1993
2154
  if (!settings.sandbox || typeof settings.sandbox !== "object") {
1994
2155
  settings.sandbox = {};
@@ -2005,12 +2166,15 @@ var installHook = (home) => {
2005
2166
  const zeroDomain = "*.zero.xyz";
2006
2167
  if (!allowedDomains.includes(zeroDomain)) {
2007
2168
  allowedDomains.push(zeroDomain);
2169
+ if (verbose) stepInfo(`sandbox.network.allowedDomains += ${zeroDomain}`);
2170
+ } else if (verbose) {
2171
+ stepInfo(`${zeroDomain} already in sandbox allowlist \u2014 not modified`);
2008
2172
  }
2009
- writeFileSync2(settingsPath, `${JSON.stringify(settings, null, 2)}
2173
+ writeFileSync3(settingsPath, `${JSON.stringify(settings, null, 2)}
2010
2174
  `);
2011
2175
  return true;
2012
2176
  };
2013
- var CONFLICTING_SKILL_PATTERNS = ["zam", "tempo"];
2177
+ var CONFLICTING_SKILL_PATTERNS = ["zam"];
2014
2178
  var findConflictingSkills = (home) => {
2015
2179
  const found = [];
2016
2180
  for (const tool of AGENT_TOOLS) {
@@ -2040,22 +2204,38 @@ var removeConflictingSkills = (home) => {
2040
2204
  }
2041
2205
  return removed;
2042
2206
  };
2043
- var installSkills = (home) => {
2044
- const skillsSourceDir = join2(getPackageRoot(), "skills");
2207
+ var installSkills = (home, verbose = false) => {
2208
+ const skillsSourceDir = findResourceDir(getCliModuleDir(), "skills");
2045
2209
  const skillDirs = readdirSync(skillsSourceDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
2210
+ if (verbose) {
2211
+ stepInfo(
2212
+ `found ${skillDirs.length} bundled skill(s): ${skillDirs.join(", ")}`
2213
+ );
2214
+ }
2046
2215
  const installed = [];
2047
2216
  const errors = [];
2048
2217
  for (const tool of AGENT_TOOLS) {
2049
2218
  const toolDetectPath = join2(home, tool.detectDir);
2050
2219
  if (!existsSync2(toolDetectPath)) {
2220
+ if (verbose) {
2221
+ stepInfo(
2222
+ `${tool.name}: ~/${tool.detectDir} not found \u2014 skipping install`
2223
+ );
2224
+ }
2051
2225
  continue;
2052
2226
  }
2227
+ if (verbose) {
2228
+ stepInfo(
2229
+ `${tool.name}: detected at ~/${tool.detectDir} \u2014 installing to ~/${tool.skillsDir}`
2230
+ );
2231
+ }
2053
2232
  try {
2054
2233
  const toolSkillsPath = join2(home, tool.skillsDir);
2055
- mkdirSync2(toolSkillsPath, { recursive: true });
2234
+ mkdirSync3(toolSkillsPath, { recursive: true });
2056
2235
  for (const skillDir of skillDirs) {
2057
2236
  const src = join2(skillsSourceDir, skillDir);
2058
2237
  const dest = join2(toolSkillsPath, skillDir);
2238
+ const existed = existsSync2(dest);
2059
2239
  copyDirRecursive(src, dest);
2060
2240
  for (const srcFile of collectAllFiles(src)) {
2061
2241
  const relPath = relative(src, srcFile);
@@ -2067,6 +2247,11 @@ var installSkills = (home) => {
2067
2247
  }
2068
2248
  }
2069
2249
  installed.push(`${tool.name}: ${dest}`);
2250
+ if (verbose) {
2251
+ stepInfo(
2252
+ `${tool.name}: ${existed ? "updated" : "installed"} skill '${skillDir}'`
2253
+ );
2254
+ }
2070
2255
  }
2071
2256
  } catch (err) {
2072
2257
  errors.push({
@@ -2078,11 +2263,13 @@ var installSkills = (home) => {
2078
2263
  return { installed, errors };
2079
2264
  };
2080
2265
  var runInit = async (appContext, options = {}) => {
2266
+ const verbose = options.verbose ?? false;
2081
2267
  appContext.services.analyticsService.capture("init_started", {
2082
2268
  force: options.force ?? false
2083
2269
  });
2084
2270
  let currentStep = "wallet";
2085
2271
  try {
2272
+ printZeroBanner();
2086
2273
  const home = homedir2();
2087
2274
  const zeroDir = join2(home, ".zero");
2088
2275
  const configPath = join2(zeroDir, "config.json");
@@ -2100,9 +2287,9 @@ var runInit = async (appContext, options = {}) => {
2100
2287
  if (!walletExists || options.force) {
2101
2288
  const privateKey = generatePrivateKey();
2102
2289
  const account = privateKeyToAccount(privateKey);
2103
- mkdirSync2(zeroDir, { recursive: true });
2290
+ ensureSecureDir(zeroDir);
2104
2291
  const existing = existsSync2(configPath) ? JSON.parse(readFileSync4(configPath, "utf8")) : {};
2105
- writeFileSync2(
2292
+ writeSecureFile(
2106
2293
  configPath,
2107
2294
  JSON.stringify(
2108
2295
  { ...existing, privateKey, lowBalanceWarning: 1 },
@@ -2112,7 +2299,16 @@ var runInit = async (appContext, options = {}) => {
2112
2299
  );
2113
2300
  walletCreated = true;
2114
2301
  walletAddress = account.address;
2115
- console.log(`Wallet address: ${account.address}`);
2302
+ stepSuccess("Wallet created", account.address);
2303
+ if (verbose) {
2304
+ if (options.force) {
2305
+ stepInfo(
2306
+ `--force passed \u2014 generated a new key and overwrote ${configPath}`
2307
+ );
2308
+ } else {
2309
+ stepInfo(`no wallet found at ${configPath} \u2014 generated a new key`);
2310
+ }
2311
+ }
2116
2312
  } else {
2117
2313
  try {
2118
2314
  const existing = JSON.parse(readFileSync4(configPath, "utf8"));
@@ -2120,6 +2316,15 @@ var runInit = async (appContext, options = {}) => {
2120
2316
  walletAddress = account.address;
2121
2317
  } catch {
2122
2318
  }
2319
+ stepSkip(
2320
+ "Wallet already configured",
2321
+ walletAddress ?? "run `zero init --force` to reset"
2322
+ );
2323
+ if (verbose) {
2324
+ stepInfo(
2325
+ `existing wallet at ${configPath} was preserved \u2014 pass --force to regenerate`
2326
+ );
2327
+ }
2123
2328
  }
2124
2329
  const agentsDetected = [];
2125
2330
  const agentsWithSkills = [];
@@ -2131,9 +2336,20 @@ var runInit = async (appContext, options = {}) => {
2131
2336
  agentsDetected.push(tool.name);
2132
2337
  }
2133
2338
  }
2339
+ if (verbose) {
2340
+ const missing = AGENT_TOOLS.filter(
2341
+ (t) => !agentsDetected.includes(t.name)
2342
+ ).map((t) => t.name);
2343
+ stepInfo(
2344
+ `agents detected: ${agentsDetected.length > 0 ? agentsDetected.join(", ") : "none"}`
2345
+ );
2346
+ if (missing.length > 0) {
2347
+ stepInfo(`agents not detected: ${missing.join(", ")}`);
2348
+ }
2349
+ }
2134
2350
  currentStep = "skills";
2135
2351
  try {
2136
- const { installed, errors } = installSkills(home);
2352
+ const { installed, errors } = installSkills(home, verbose);
2137
2353
  for (const entry of installed) {
2138
2354
  const toolName = entry.split(":")[0];
2139
2355
  if (toolName && !agentsWithSkills.includes(toolName)) {
@@ -2152,27 +2368,45 @@ var runInit = async (appContext, options = {}) => {
2152
2368
  skillsError = err instanceof Error ? err.message : "unknown skills error";
2153
2369
  console.error(`Warning: skills install failed: ${skillsError}`);
2154
2370
  }
2371
+ if (agentsWithSkills.length > 0) {
2372
+ stepSuccess("Skills installed");
2373
+ } else if (agentsDetected.length === 0) {
2374
+ stepSkip("No agent tools detected");
2375
+ } else if (skillsError) {
2376
+ stepWarn("Skills install had errors", skillsError);
2377
+ }
2155
2378
  currentStep = "hook";
2156
2379
  try {
2157
- hookInstalled = installHook(home);
2380
+ hookInstalled = installHook(home, verbose);
2158
2381
  } catch (err) {
2159
2382
  hookError = err instanceof Error ? err.message : "unknown hook error";
2160
2383
  }
2384
+ if (hookInstalled) {
2385
+ stepSuccess("Agents configured");
2386
+ } else if (hookError) {
2387
+ stepWarn("Agent config failed", hookError);
2388
+ }
2161
2389
  currentStep = "cleanup_scan";
2162
2390
  const conflictingSkills = findConflictingSkills(home);
2391
+ if (verbose) {
2392
+ stepInfo(
2393
+ `scanning agent skill dirs for deprecated skills matching [${CONFLICTING_SKILL_PATTERNS.join(", ")}]`
2394
+ );
2395
+ }
2163
2396
  if (conflictingSkills.length > 0) {
2164
2397
  const skillList = conflictingSkills.map((s) => ` - ${s.tool}: ${s.skillName}`).join("\n");
2165
2398
  console.error(
2166
2399
  `
2167
- Found deprecated skills that may conflict with Zero:
2400
+ ${color.yellow("Found deprecated skills that may conflict with Zero:")}
2168
2401
  ${skillList}
2169
2402
 
2170
- To remove them, run: zero init cleanup`
2403
+ To remove them, run: ${color.cyan("zero init cleanup")}`
2171
2404
  );
2405
+ } else if (verbose) {
2406
+ stepInfo(`no deprecated skills found`);
2172
2407
  }
2173
- console.error(
2174
- '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"'
2175
- );
2408
+ sectionDivider();
2409
+ console.error(printReadyFooter());
2176
2410
  currentStep = "complete";
2177
2411
  appContext.services.analyticsService.capture("wallet_initialized", {
2178
2412
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
@@ -2207,12 +2441,13 @@ To remove them, run: zero init cleanup`
2207
2441
  throw err;
2208
2442
  }
2209
2443
  };
2210
- var initCommand = (appContext) => new Command5("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").action(async (options) => {
2444
+ var initCommand = (appContext) => new Command5("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").option(
2445
+ "-v, --verbose",
2446
+ "Explain why each install step was taken or skipped"
2447
+ ).action(async (options) => {
2211
2448
  await runInit(appContext, options);
2212
2449
  }).addCommand(
2213
- new Command5("cleanup").description(
2214
- "Remove deprecated skills (zam, tempo) that conflict with Zero"
2215
- ).action(() => {
2450
+ new Command5("cleanup").description("Remove deprecated skills (zam) that conflict with Zero").action(() => {
2216
2451
  const home = homedir2();
2217
2452
  const removed = removeConflictingSkills(home);
2218
2453
  if (removed.length === 0) {
@@ -2630,7 +2865,7 @@ Read the full terms at: ${TERMS_URL}
2630
2865
  });
2631
2866
 
2632
2867
  // src/commands/wallet-command.ts
2633
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
2868
+ import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
2634
2869
  import { homedir as homedir3 } from "os";
2635
2870
  import { join as join3 } from "path";
2636
2871
  import { Command as Command10 } from "commander";
@@ -2652,6 +2887,9 @@ var walletBalanceCommand = (appContext) => new Command10("balance").description(
2652
2887
  console.log(`${balance.amount} ${balance.asset}`);
2653
2888
  });
2654
2889
  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(
2890
+ "--no-open",
2891
+ "Print the funding URL instead of opening a browser (for agents \u2014 funding links are one-time use, hand the URL to the user)"
2892
+ ).option(
2655
2893
  "--use <provider>",
2656
2894
  "Onramp provider: coinbase or stripe",
2657
2895
  "coinbase"
@@ -2679,12 +2917,19 @@ ${address}`);
2679
2917
  provider
2680
2918
  );
2681
2919
  if (url) {
2682
- await open(url);
2683
- console.log("Opened funding page in your browser.");
2684
- console.log(`If it didn't open, visit: ${url}`);
2920
+ if (options.open) {
2921
+ await open(url);
2922
+ console.log("Opened funding page in your browser.");
2923
+ console.log(`If it didn't open, visit: ${url}`);
2924
+ } else {
2925
+ console.log(
2926
+ "Funding URL (one-time use \u2014 open it in a browser to fund):"
2927
+ );
2928
+ console.log(url);
2929
+ }
2685
2930
  console.log(`Your wallet address: ${address}`);
2686
2931
  analyticsService.capture("wallet_funded", {
2687
- method: "browser",
2932
+ method: options.open ? "browser" : "url",
2688
2933
  amount
2689
2934
  });
2690
2935
  } else {
@@ -2736,9 +2981,9 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
2736
2981
  } catch {
2737
2982
  }
2738
2983
  }
2739
- mkdirSync3(zeroDir, { recursive: true });
2984
+ ensureSecureDir(zeroDir);
2740
2985
  const existing = existsSync3(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
2741
- writeFileSync3(
2986
+ writeSecureFile(
2742
2987
  configPath,
2743
2988
  JSON.stringify(
2744
2989
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",
@@ -53,7 +53,10 @@ ZERO_AGENT=claude-web zero search "translate hello world"
53
53
 
54
54
  Both are per-invocation and stateless — there's no persistent config to go stale if you move between sandboxes. Resolution order: `--agent` flag > `ZERO_AGENT` env var > host-specific env signals (`CLAUDECODE`, `CURSOR_TRACE_ID`, `TERM_PROGRAM=vscode`) > `unknown`. Canonical names: `claude-code`, `cursor`, `vscode`, `claude-web`, `codex`, `opencode`. Any string is accepted.
55
55
 
56
- **Step 3 — Fund wallet:** `zero wallet fund` — opens browser to add USDC (Base). For manual transfer: `zero wallet fund --manual`.
56
+ **Step 3 — Fund wallet:**
57
+
58
+ - **Running inside an agent (you — Claude, Cursor, Codex, etc.):** always pass `--no-open`. Funding links are **one-time use**, so opening them in a headless agent environment burns the link before the user can use it. Run `zero wallet fund --no-open`, then hand the printed URL to the user and ask them to open it in their browser. For manual transfer instead: `zero wallet fund --manual`.
59
+ - **Human at a terminal:** `zero wallet fund` — opens a browser to add USDC (Base). For manual transfer: `zero wallet fund --manual`.
57
60
 
58
61
  **Step 4 — Confirm readiness:** `zero wallet balance`
59
62
 
@@ -68,7 +71,7 @@ Provide:
68
71
 
69
72
  - Wallet address from `zero wallet address`.
70
73
  - Balance from `zero wallet balance`.
71
- - If balance is 0, direct user to `zero wallet fund` to add USDC.
74
+ - If balance is 0, run `zero wallet fund --no-open` and give the printed one-time URL to the user to open (or suggest `zero wallet fund --manual` for a direct deposit address).
72
75
  - 2-3 starter prompts based on available capabilities:
73
76
 
74
77
  ```bash
@@ -88,8 +91,8 @@ zero search "<query>"
88
91
  zero get <position> [--formatted]
89
92
  zero fetch <url> [-X <method>] [-d '<json>' | -d @file | --data-stdin] [-H "Key:Value"] [--max-pay <amount>] [--json [--raw-body]] [--capability <id>]
90
93
  zero runs [--capability <slug>] [--unreviewed]
91
- zero review <runId> --accuracy <1-5> --value <1-5> --reliability <1-5>
92
- zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --reliability <1-5>
94
+ zero review <runId> --accuracy <1-5> --value <1-5> --reliability <1-5> [--content "<notes>"]
95
+ zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --reliability <1-5> [--content "<notes>"]
93
96
  ```
94
97
 
95
98
  ### Workflow
@@ -97,9 +100,25 @@ zero review --capability <slug> --success --accuracy <1-5> --value <1-5> --relia
97
100
  1. **Search** — `zero search "weather forecast"` finds matching capabilities. Results show name, cost, rating, and success rate.
98
101
  2. **Inspect** — `zero get 1 --formatted` prints a human summary **and a copy-pasteable `Try it:` command** wired to the capability's schema. Plain `zero get 1` returns full JSON (URL, method, `bodySchema`, examples, pricing) for `jq` pipelines. **If `bodySchema` is `null`**, the capability hasn't been schema-indexed yet — skip it and `zero get 2`, don't invent field names.
99
102
  3. **Call** — `zero fetch <url>` makes the request. If the server returns 402, payment is handled automatically (x402 and MPP, including cross-chain bridging from Base to Tempo).
100
- 4. **Review** — `zero review <runId>` submits a quality review. Run IDs are printed to **stderr** after a successful fetch (or returned on stdout in `--json` mode). Always review after a paid call.
103
+ 4. **Review** — `zero review <runId>` submits a quality review. Run IDs are printed to **stderr** after a successful fetch (or returned on stdout in `--json` mode). Always review after a paid call, and **pass `--content "<notes>"` whenever you have something specific to say** — the content line lands on the capability's public detail page on zero.xyz, so it's what the next human buyer (and the next agent) reads when deciding whether to call this capability. See "Writing review content" below.
101
104
  5. **Retroactive review** — if you lost a runId, run `zero runs --unreviewed` (or `zero runs --capability <slug> --unreviewed`). `zero review --capability <slug> ...` auto-resolves to your most recent un-reviewed run for that capability.
102
105
 
106
+ ### Writing review content
107
+
108
+ `--content` is free-form and optional, but strongly encouraged. It's what appears on the capability's public page — so it's doing double duty as a signal for the next agent *and* as human-readable copy for buyers browsing zero.xyz.
109
+
110
+ **What makes a useful review:** concrete detail a reader can act on. Good examples from real reviews:
111
+
112
+ - *"Generated the requested gremlin-on-couch image faithfully in ~140ms. Schema straightforward, output URL loaded cleanly, zero surprises. At $0.003 the price-to-quality ratio is excellent."*
113
+ - *"Call worked on second attempt. Key learnings: set `wait_for_greeting=false` and use `first_sentence` to lead with your message, otherwise the AI hangs up on voicemail greetings."*
114
+ - *"Returned relevant market-report links quickly, but not a synthesized ranked answer. Better as raw web-search input than finished research."*
115
+
116
+ Each names the task attempted, what the output actually was, and a specific observation (latency, a gotcha, a fit/misfit note). That's the kind of line a human buyer trusts and another agent can learn from.
117
+
118
+ **Review failures with content too.** Failure notes are arguably more valuable — they warn the next caller. Example: *"FLUX Schnell returned HTTP 500 Internal Server Error — paid 0.003 USDC via MPP but got no image."* Pair with `--no-success`.
119
+
120
+ **Skip `--content` rather than write filler.** "Worked great", "Fast response", or test strings like "trial 1" add noise, pollute the capability's public page, and dilute the signal agents rely on. If you don't have a specific observation, just submit the numeric ratings.
121
+
103
122
  **Fastest path:** `zero search "..." → zero get <n> --formatted → copy the `Try it:` line → edit placeholders → run it`. The `Try it` block already knows whether to use querystring vs `-d`, and labels every header as `[caller-provided]` so you know which `-H` flags to fill in yourself.
104
123
 
105
124
  ### Request Shape Cheatsheet
@@ -205,7 +224,7 @@ cat payload.json | zero fetch https://api.example.com --data-stdin
205
224
  - **Check `ok`, not `status`, for success.** `ok` is a pre-computed 2xx boolean; `status` is the raw HTTP code (useful for distinguishing 404 from 500 but not a success flag).
206
225
  - **`--max-pay` is your cost guard.** Always set it before calling an unfamiliar capability or one with per-call pricing you haven't verified.
207
226
  - **Capability must be resolvable.** If you skip `zero search` and call `zero fetch <url>` directly, pass `--capability <uid|slug>` so the run is recorded for review.
208
- - **Review failures too, when they're the capability's fault.** A 4xx/5xx from the upstream API counts as a real result — submit `zero review <runId> --no-success` so future agents see the failure. Do **not** review failures caused by CLI-internal bugs (see Common Issues).
227
+ - **Review failures too, when they're the capability's fault.** A 4xx/5xx from the upstream API counts as a real result — submit `zero review <runId> --no-success --content "<what broke>"` so future agents see the failure and the specific symptom. Do **not** review failures caused by CLI-internal bugs (see Common Issues).
209
228
 
210
229
  ### Rules
211
230
 
@@ -213,7 +232,7 @@ cat payload.json | zero fetch https://api.example.com --data-stdin
213
232
  - **Always `zero get` before `zero fetch`.** Even if you "know" the URL, re-fetch the full details to confirm the URL, method, required headers, body schema, and current price. Do not reconstruct a fetch call from memory.
214
233
  - Never guess endpoint URLs or schemas.
215
234
  - Use `--max-pay` before potentially expensive requests.
216
- - Review capabilities after every paid call. The review signal feeds search ranking skipping reviews means your next search is less informed.
235
+ - Review capabilities after every paid call, and include `--content` with a concrete observation whenever you have one. Numeric ratings feed search ranking; the content line is what humans read on the capability's public page and what other agents quote when deciding whether to call it. Skip `--content` rather than write generic filler.
217
236
  - Before ending a multi-call task, run `zero runs --unreviewed` and review anything you missed.
218
237
 
219
238
  ## Configuration
@@ -287,7 +306,8 @@ zero fetch https://nlp-api.example.com/sentiment \
287
306
  -H "Content-Type:application/json"
288
307
 
289
308
  # 4. Review the result (run ID is printed after fetch)
290
- zero review abc123 --accuracy 5 --value 4 --reliability 5
309
+ zero review abc123 --accuracy 5 --value 4 --reliability 5 \
310
+ --content "Classified a 200-char product-review snippet as positive in ~180ms; confidence 0.94, matched my manual read. Schema clean, no auth needed."
291
311
 
292
312
  # 5. Check remaining balance
293
313
  zero wallet balance
@@ -299,7 +319,7 @@ zero wallet balance
299
319
  |---|---|---|
300
320
  | `zero: command not found` | CLI not installed | Run `npm i -g @zeroxyz/cli`, then retry. |
301
321
  | "No wallet configured" | Wallet not initialized | Run `zero init` to generate a wallet, or `zero wallet set <key>` to import one. |
302
- | Balance is 0 or insufficient funds | Wallet needs USDC | Run `zero wallet fund` or `zero wallet fund --manual` for the deposit address. |
322
+ | Balance is 0 or insufficient funds | Wallet needs USDC | From an agent: `zero wallet fund --no-open` and hand the one-time URL to the user. From a human terminal: `zero wallet fund` or `zero wallet fund --manual` for the deposit address. |
303
323
  | Payment failed on fetch | Insufficient balance for the capability price | Check `zero wallet balance`, fund if needed, and use `--max-pay` to control spend. |
304
324
  | No search results | Query too narrow | Broaden search terms: `zero search "<broader query>"`. |
305
325
  | Wrong request schema (4xx error) | Incorrect body or headers | Run `zero get <position>` to check the exact schema, method, and required headers. |