@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 +309 -64
- package/package.json +1 -1
- package/skills/zero/SKILL.md +29 -9
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.
|
|
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 {
|
|
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()
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1838
|
+
mkdirSync as mkdirSync3,
|
|
1841
1839
|
readdirSync,
|
|
1842
1840
|
readFileSync as readFileSync4,
|
|
1843
1841
|
rmSync,
|
|
1844
1842
|
statSync,
|
|
1845
|
-
writeFileSync as
|
|
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
|
|
1863
|
-
let
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
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
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2007
|
+
};
|
|
2008
|
+
var getCliModuleDir = () => {
|
|
2009
|
+
if (import.meta.url) {
|
|
2010
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
1873
2011
|
}
|
|
1874
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
2040
|
+
writeFileSync3(destPath, data);
|
|
1903
2041
|
try {
|
|
1904
2042
|
const mode = statSync(srcPath).mode;
|
|
1905
|
-
|
|
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
|
-
|
|
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(
|
|
2064
|
+
const hookSource = join2(hooksSourceDir, hookFile);
|
|
1922
2065
|
const hookDest = join2(zeroHooksDir, hookFile);
|
|
1923
2066
|
cpSync(hookSource, hookDest);
|
|
1924
|
-
|
|
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
|
-
|
|
2173
|
+
writeFileSync3(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
2010
2174
|
`);
|
|
2011
2175
|
return true;
|
|
2012
2176
|
};
|
|
2013
|
-
var CONFLICTING_SKILL_PATTERNS = ["zam"
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2290
|
+
ensureSecureDir(zeroDir);
|
|
2104
2291
|
const existing = existsSync2(configPath) ? JSON.parse(readFileSync4(configPath, "utf8")) : {};
|
|
2105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2174
|
-
|
|
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").
|
|
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,
|
|
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
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
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
|
-
|
|
2984
|
+
ensureSecureDir(zeroDir);
|
|
2740
2985
|
const existing = existsSync3(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
|
|
2741
|
-
|
|
2986
|
+
writeSecureFile(
|
|
2742
2987
|
configPath,
|
|
2743
2988
|
JSON.stringify(
|
|
2744
2989
|
{
|
package/package.json
CHANGED
package/skills/zero/SKILL.md
CHANGED
|
@@ -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:**
|
|
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,
|
|
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
|
|
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 |
|
|
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. |
|