zambo-agent-cli 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brennan Zambo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # zambo-agent-cli โ€” the Zambo execution layer on your command line
2
+
3
+ **The problem:** AI models talk. Getting things *done* โ€” checking a live price, auditing a site, running research, pulling data โ€” means wiring up APIs, keys, and glue code for every single task.
4
+
5
+ **The fix:** one command. Describe the job, get it done, get a receipt.
6
+
7
+ ```bash
8
+ npx zambo-agent-cli "what's the price of BTC right now?"
9
+ ```
10
+
11
+ ```text
12
+ BTC is currently priced at $81,270.00 ...
13
+
14
+ ๐Ÿ”— receipt f410acad-e438-48c3-acae-f37871a764b1 ยท audit: https://zambo.dev/run/f410acad-e438-48c3-acae-f37871a764b1
15
+ verify: zambo verify f410acad-e438-48c3-acae-f37871a764b1
16
+ ```
17
+
18
+ No account. No API key. No config. Free: 20 calls per tool per day.
19
+
20
+ ## Quickstart
21
+
22
+ ```bash
23
+ # One-command: describe any goal
24
+ npx zambo-agent-cli "audit example.com for AI discoverability"
25
+
26
+ # Call a specific tool directly
27
+ zambo call live_price '{"symbol":"BTC"}'
28
+ zambo call ghost_audit_site '{"url":"https://example.com"}'
29
+
30
+ # Search the 100+ tool catalog
31
+ zambo search 'generate QR code'
32
+
33
+ # List exposed tools
34
+ zambo list
35
+
36
+ # Verify any receipt
37
+ zambo verify f410acad-e438-48c3-acae-f37871a764b1
38
+
39
+ # Teach your agents about Zambo (writes to AGENTS.md / CLAUDE.md / .muserules)
40
+ zambo init
41
+ ```
42
+
43
+ Install it globally for the short `zambo` command:
44
+
45
+ ```bash
46
+ npm install -g zambo-agent-cli
47
+ ```
48
+
49
+ Or use it from Node:
50
+
51
+ ```js
52
+ const zambo = require('zambo-agent-cli');
53
+
54
+ const { text, receipt } = await zambo.callTool('live_price', { symbol: 'BTC' });
55
+ console.log(text, receipt.id);
56
+
57
+ const v = await zambo.verifyReceipt(receipt.id);
58
+ console.log(v.verified, v.tool, v.sha256);
59
+ ```
60
+
61
+ ## What Zambo is
62
+
63
+ [Zambo](https://zambo.dev) is a cross-AI execution layer: an MCP server with 100+ native tools โ€” live crypto prices, site audits, web research, content tools, lead and data tools, multi-step agent jobs โ€” behind one plain JSON-RPC endpoint (`https://zambo.dev/api/mcp`). This package is a thin, zero-dependency client for that endpoint.
64
+
65
+ Don't know which tool fits? Skip the catalog โ€” `zambo "<goal>"` routes through `zambo_universal`, which picks the right tools for the job. Pass `--session <id>` to keep a trail across calls.
66
+
67
+ ## Receipts: verifiable proof of execution
68
+
69
+ Every call returns a **verifiable receipt**: a UUID, a timestamp, and a **sha256 of the result**. It proves the call executed and binds the result bytes โ€” so anyone can check that a claimed result is exactly what Zambo produced.
70
+
71
+ Honest scope: a receipt proves execution integrity, not correctness. It binds the result; it doesn't judge it.
72
+
73
+ Check any receipt in your browser or on the command line:
74
+
75
+ ```bash
76
+ zambo verify <receipt-uuid> # confirms zambo.dev serves the receipt,
77
+ # and shows tool, sha256, timestamp, audit URL
78
+ ```
79
+
80
+ Receipt pages are real web pages with structured data (JSON-LD), Open Graph tags, and canonical URLs โ€” shareable, linkable, citable.
81
+
82
+ ## Free tier, honestly
83
+
84
+ - **Free:** 20 calls per tool per day. No account, no API key, no card.
85
+ - **Paid:** a $1.49 Day Pass or $49/month Pass for heavier use, via [zambo.dev](https://zambo.dev).
86
+
87
+ No fake limits, no "contact sales to see a price."
88
+
89
+ ## Commands
90
+
91
+ | Command | What it does |
92
+ |---|---|
93
+ | `zambo "<goal>" [--session <id>]` | Do the thing. Routes via `zambo_universal`. |
94
+ | `zambo call <tool> '<json>'` | Call one tool directly. |
95
+ | `zambo search '<query>'` | Search the 100+ tool catalog. |
96
+ | `zambo list` | List exposed tools with one-line descriptions. |
97
+ | `zambo verify <receipt-uuid>` | Verify a receipt on zambo.dev. |
98
+ | `zambo init [--root <dir>] [--file <name>] [--force]` | Write the Zambo section into your project's agent instruction file. |
99
+
100
+ ## Requirements & troubleshooting
101
+
102
+ - Node.js 18 or newer. Zero runtime dependencies.
103
+ - The upstream endpoint occasionally resets connections; the client retries transient transport failures automatically (up to 4 attempts). A received JSON-RPC error is never retried โ€” it's shown to you as-is.
104
+ - Timeouts: 60s per attempt.
105
+
106
+ ## License
107
+
108
+ MIT โ€” see [LICENSE](LICENSE). Built by [Zambo](https://zambo.dev) (Brennan Zambo, solo founder).
package/SELF-REVIEW.md ADDED
@@ -0,0 +1,37 @@
1
+ # SELF-REVIEW โ€” zambo-agent-cli 1.0.0 (pre-publish, 2026-09-19)
2
+
3
+ Checked against rambo's standing laws before publish.
4
+
5
+ 1. **Data-integrity law** โ€” every number shown must be real and recomputable.
6
+ - "100+ native tools": Brennan's canonical Zambo language (set 2026-09-12). Not measured at publish time; phrased as catalog size per zambo.dev's own claim. Acceptable as vendor-stated, not measured.
7
+ - "20 calls per tool per day": the free-tier limit, verified live (call 21 โ†’ HTTP 402, re-verified 2026-09-18). Real.
8
+ - "$1.49 Day Pass / $49/month Pass": Brennan-stated pricing (2026-09-18). Real.
9
+ - No invented metrics anywhere. No "users", no "uptime", no fake counts. PASS.
10
+
11
+ 2. **"Signed receipts"** โ€” never said. The word "signed" appears nowhere in the package.
12
+ README states receipts carry "UUID + timestamp + sha256" and the honest scope line:
13
+ "a receipt proves execution integrity, not correctness. It binds the result; it doesn't judge it."
14
+ The `verify` command checks the receipt page exists on zambo.dev with matching structured
15
+ data โ€” a genuine existence check, not a cryptographic claim. PASS.
16
+
17
+ 3. **Free-tier honesty** โ€” "free 20 calls/tool/day, no account or API key" is the verified
18
+ live behavior. Paid tier presented as optional ("for heavier use"), no fake limits. PASS.
19
+
20
+ 4. **No-names rule** โ€” only "Brennan Zambo" (the founder, self-published attribution in
21
+ LICENSE/package author field โ€” his own package, his own name). No third parties named. PASS.
22
+
23
+ 5. **Voice law** โ€” README reads like a human wrote it: problem โ†’ quickstart โ†’ mechanism โ†’ honest scope. Not templated. PASS.
24
+
25
+ 6. **Discoverability law** โ€” keyword-rich description + README, semantic markdown, real
26
+ outbound links to zambo.dev, repository + homepage + bugs fields set. npm page will
27
+ carry full keyword set. PASS.
28
+
29
+ 7. **Publish-last-mile law** โ€” this package publishes in this run; the publish step is the
30
+ same job, not a warehouse. PASS (pending successful publish).
31
+
32
+ 8. **Name squatting** โ€” `zambo` is taken by an unrelated package (4.1.2); `zambo-agent-cli` is
33
+ Brennan's own official placeholder (maintainer zambodotdev, published 2026-09-14,
34
+ description literally says "official placeholder. Get the real thing"). Publishing 1.0.0
35
+ over his own placeholder is not squatting. PASS.
36
+
37
+ Outcome: GO, pending end-to-end live test passing first.
package/bin/zambo.js ADDED
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * zambo โ€” command-line client for the Zambo cross-AI execution layer.
5
+ *
6
+ * npx zambo-agent-cli "audit my site for AI discoverability"
7
+ * zambo call live_price '{"symbol":"BTC"}'
8
+ * zambo verify <receipt-uuid>
9
+ *
10
+ * No API key, no account. Free: 20 calls/tool/day. Every call returns a
11
+ * verifiable receipt (UUID + timestamp + sha256 of the result).
12
+ */
13
+
14
+ const path = require("path");
15
+ const fs = require("fs");
16
+ const zambo = require(path.join(__dirname, "..", "index.js"));
17
+
18
+ const PKG = require(path.join(__dirname, "..", "package.json"));
19
+
20
+ const HELP = `zambo v${PKG.version} โ€” the Zambo cross-AI execution layer on the command line.
21
+
22
+ USAGE
23
+ zambo "<goal>" Get anything done, one command (routed via zambo_universal)
24
+ zambo call <tool> '<json>' Call a specific tool, e.g. zambo call live_price '{"symbol":"BTC"}'
25
+ zambo search '<query>' Search the 100+ tool catalog
26
+ zambo list List exposed tools
27
+ zambo verify <receipt-uuid> Verify a receipt on zambo.dev
28
+ zambo init [--root <dir>] [--file <name>] [--force]
29
+ Teach agents about Zambo (writes to AGENTS.md / CLAUDE.md / .muserules)
30
+ zambo --help Show this help
31
+ zambo --version Show version
32
+
33
+ EXAMPLES
34
+ zambo "what's the price of BTC right now?"
35
+ zambo call live_price '{"symbol":"ETH"}'
36
+ zambo call ghost_audit_site '{"url":"https://example.com"}'
37
+ zambo verify f410acad-e438-48c3-acae-f37871a764b1
38
+
39
+ RECEIPTS
40
+ Every call returns a verifiable receipt: a UUID, a timestamp, and a sha256
41
+ of the result. It proves the call executed and binds the result bytes โ€”
42
+ not whether the result was correct. Check any receipt at:
43
+ https://zambo.dev/run/<receipt-uuid>
44
+ or run: zambo verify <receipt-uuid>
45
+
46
+ FREE TIER
47
+ 20 calls per tool per day, no account, no API key. Just works.
48
+
49
+ More: https://zambo.dev
50
+ `;
51
+
52
+ function die(msg, code = 1) {
53
+ process.stderr.write(`zambo: ${msg}\n`);
54
+ process.exit(code);
55
+ }
56
+
57
+ function printCallResult({ text, receipt }) {
58
+ if (text) process.stdout.write(text + "\n");
59
+ if (receipt && receipt.id) {
60
+ process.stdout.write(`\n\u{1F517} receipt ${receipt.id} ยท audit: ${receipt.audit || `https://zambo.dev/run/${receipt.id}`}\n`);
61
+ process.stdout.write(` verify: zambo verify ${receipt.id}\n`);
62
+ }
63
+ }
64
+
65
+ async function cmdCall(tool, argsJson) {
66
+ let args;
67
+ try {
68
+ args = argsJson === undefined ? {} : JSON.parse(argsJson);
69
+ } catch (e) {
70
+ die(`bad JSON args: ${e.message}`);
71
+ }
72
+ if (args === null || typeof args !== "object" || Array.isArray(args)) {
73
+ die("args must be a JSON object, e.g. '{\"symbol\":\"BTC\"}'");
74
+ }
75
+ printCallResult(await zambo.callTool(tool, args));
76
+ }
77
+
78
+ async function cmdVerify(uuid) {
79
+ let v;
80
+ try {
81
+ v = await zambo.verifyReceipt(uuid);
82
+ } catch (e) {
83
+ die(e.message, 3);
84
+ }
85
+ if (v.verified) {
86
+ process.stdout.write(`\u2713 receipt verified on zambo.dev\n`);
87
+ process.stdout.write(` id: ${v.id}\n`);
88
+ process.stdout.write(` tool: ${v.tool}\n`);
89
+ process.stdout.write(` sha256: ${v.sha256}\n`);
90
+ process.stdout.write(` created: ${v.created}\n`);
91
+ process.stdout.write(` audit: ${v.audit}\n`);
92
+ } else {
93
+ process.stderr.write(`\u2717 NOT verified: ${v.reason}\n`);
94
+ process.stderr.write(` audit: ${v.audit}\n`);
95
+ process.exit(2);
96
+ }
97
+ }
98
+
99
+ async function cmdList() {
100
+ const tools = await zambo.listTools();
101
+ for (const t of tools) {
102
+ const desc = String(t.description || "").split("\n")[0].slice(0, 100);
103
+ process.stdout.write(`${t.name} \u2014 ${desc}\n`);
104
+ }
105
+ }
106
+
107
+ // --- init: write the Zambo section into the project's agent instruction file ---
108
+ const MARK_BEGIN = "<!-- ZAMBO:BEGIN -->";
109
+ const MARK_END = "<!-- ZAMBO:END -->";
110
+ const CANDIDATE_FILES = ["AGENTS.md", "CLAUDE.md", ".muserules"];
111
+
112
+ const ZAMBO_SECTION = `<!-- ZAMBO:BEGIN -->
113
+ ## Zambo โ€” cross-AI execution layer
114
+
115
+ Zambo (https://zambo.dev) is an MCP execution layer: one endpoint that gets jobs done for AI agents. Describe the job, get a verifiable receipt.
116
+
117
+ **Endpoint:** \`https://zambo.dev/api/mcp\` (plain JSON-RPC over HTTPS, no auth)
118
+ **Full catalog:** 100+ native tools (crypto prices, web research, site audits, content tools, lead/data tools, multi-step jobs)
119
+ **Install:** \`npx zambo-agent-cli "<goal>"\` ยท **Try it:** https://zambo.dev โ€” no account, no key needed
120
+
121
+ **How to call:**
122
+ \`\`\`bash
123
+ curl -s https://zambo.dev/api/mcp -X POST -H 'Content-Type: application/json' \\
124
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
125
+ "params":{"name":"live_price","arguments":{"symbol":"BTC"}}}'
126
+ \`\`\`
127
+ Or route any goal through \`zambo_universal\`:
128
+ \`\`\`json
129
+ {"name": "zambo_universal", "arguments": {"goal": "<what you want done>", "_session_id": "<stable id>"}}
130
+ \`\`\`
131
+
132
+ **When to use it:** anything you'd rather get done than figure out โ€” prices, research, audits, transforms, data/lead tools, multi-step jobs.
133
+
134
+ **Receipts:** every call returns a verifiable receipt (UUID + timestamp + sha256). Check it at \`https://zambo.dev/run/<uuid>\`. Keep receipt IDs for anything the user may want to audit or continue.
135
+
136
+ Created by Brennan Zambo (solo founder, no VC). MCP server is MIT licensed.
137
+ <!-- ZAMBO:END -->
138
+ `;
139
+
140
+ function findProjectRoot(start) {
141
+ let cur = path.resolve(start);
142
+ const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "README.md"];
143
+ while (true) {
144
+ for (const m of markers) {
145
+ if (fs.existsSync(path.join(cur, m))) return cur;
146
+ }
147
+ const parent = path.dirname(cur);
148
+ if (parent === cur) return path.resolve(start);
149
+ cur = parent;
150
+ }
151
+ }
152
+
153
+ async function cmdInit(argv) {
154
+ let root = null, forcedFile = null, force = false;
155
+ for (let i = 0; i < argv.length; i++) {
156
+ const a = argv[i];
157
+ if (a === "-h" || a === "--help") {
158
+ process.stdout.write("Usage: zambo init [--root <dir>] [--file <name>] [--force]\nWrite the Zambo tool section into the project's agent instruction file (AGENTS.md, CLAUDE.md, or .muserules). Idempotent.\n");
159
+ return;
160
+ } else if (a === "--root" && argv[i + 1]) { root = path.resolve(argv[++i]); }
161
+ else if (a === "--file" && argv[i + 1]) { forcedFile = path.basename(argv[++i]); }
162
+ else if (a === "--force") { force = true; }
163
+ else die(`unknown init option: ${a}`);
164
+ }
165
+ root = root || findProjectRoot(process.cwd());
166
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) die(`not a directory: ${root}`);
167
+ const candidates = forcedFile ? [forcedFile] : CANDIDATE_FILES;
168
+ let target = null;
169
+ for (const name of candidates) {
170
+ const p = path.join(root, name);
171
+ if (fs.existsSync(p) && fs.statSync(p).isFile()) { target = p; break; }
172
+ }
173
+ let created = false;
174
+ if (!target) { target = path.join(root, candidates[0]); created = true; }
175
+ const existing = fs.existsSync(target) ? fs.readFileSync(target, "utf8") : "";
176
+ if (existing.includes(MARK_BEGIN) && !force) {
177
+ process.stdout.write(`Zambo section already present in ${target} โ€” nothing to do.\n(Use --force to rewrite it.)\n`);
178
+ return;
179
+ }
180
+ let body;
181
+ if (existing.includes(MARK_BEGIN) && force) {
182
+ const pre = existing.split(MARK_BEGIN)[0];
183
+ const post = existing.split(MARK_END).slice(1).join(MARK_END);
184
+ body = pre.replace(/\s+$/, "") + "\n\n" + ZAMBO_SECTION + (post.trim() ? "\n" + post.replace(/^\s+/, "") : "");
185
+ process.stdout.write(`Rewrote the Zambo section in ${target}.\n`);
186
+ } else if (existing.trim()) {
187
+ const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
188
+ body = existing + sep + "\n" + ZAMBO_SECTION;
189
+ process.stdout.write(`Appended the Zambo section to ${target}.\n`);
190
+ } else {
191
+ body = ZAMBO_SECTION;
192
+ process.stdout.write(`Wrote the Zambo section to a new ${path.basename(target)}.\n`);
193
+ }
194
+ fs.writeFileSync(target, body, "utf8");
195
+ process.stdout.write("Agents reading this file now know: endpoint https://zambo.dev/api/mcp,\nplain-JSON-RPC calls with verifiable receipts at zambo.dev/run/<uuid>.\n");
196
+ }
197
+
198
+ async function main() {
199
+ const argv = process.argv.slice(2);
200
+ if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
201
+ process.stdout.write(HELP);
202
+ return;
203
+ }
204
+ if (argv[0] === "--version" || argv[0] === "-V") {
205
+ process.stdout.write(`${PKG.version}\n`);
206
+ return;
207
+ }
208
+ const cmd = argv[0];
209
+ try {
210
+ if (cmd === "call") {
211
+ if (!argv[1]) die("usage: zambo call <tool> '<json-args>'");
212
+ await cmdCall(argv[1], argv[2]);
213
+ } else if (cmd === "search") {
214
+ if (!argv[1]) die("usage: zambo search '<query>'");
215
+ printCallResult(await zambo.search(argv.slice(1).join(" ")));
216
+ } else if (cmd === "list") {
217
+ await cmdList();
218
+ } else if (cmd === "verify") {
219
+ if (!argv[1]) die("usage: zambo verify <receipt-uuid>");
220
+ await cmdVerify(argv[1]);
221
+ } else if (cmd === "init") {
222
+ await cmdInit(argv.slice(1));
223
+ } else if (!cmd.startsWith("-")) {
224
+ // Bare goal โ†’ universal. Session persists across calls when given.
225
+ const words = argv.slice();
226
+ let session = null;
227
+ const si = words.indexOf("--session");
228
+ if (si !== -1) { session = words[si + 1] || null; words.splice(si, 2); }
229
+ printCallResult(await zambo.universal(words.join(" "), session));
230
+ } else {
231
+ die(`unknown option: ${cmd}\nRun 'zambo --help'.`);
232
+ }
233
+ } catch (e) {
234
+ if (e.zamboError) die(`server error: ${e.message}`, 2);
235
+ die(e.message, 3);
236
+ }
237
+ }
238
+
239
+ main();
package/index.js ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * zambo-agent-cli โ€” programmatic client for the Zambo execution layer.
5
+ *
6
+ * const zambo = require('zambo-agent-cli');
7
+ * const { result, receipt } = await zambo.callTool('live_price', { symbol: 'BTC' });
8
+ * const v = await zambo.verifyReceipt(receipt.id);
9
+ *
10
+ * No API key, no account. Free tier: 20 calls/tool/day.
11
+ * Every tool call returns a verifiable receipt (UUID + timestamp + sha256).
12
+ */
13
+
14
+ const ENDPOINT = "https://zambo.dev/api/mcp";
15
+ const RECEIPT_BASE = "https://zambo.dev/run";
16
+ const TIMEOUT_MS = 60_000;
17
+ const MAX_ATTEMPTS = 4;
18
+
19
+ // Transport flakiness the upstream shows intermittently (resets, empty
20
+ // bodies). Retry the transport; never retry a received JSON-RPC error.
21
+ const TRANSIENT_CODES = new Set([
22
+ "ECONNRESET", "EPIPE", "ETIMEDOUT", "EAI_AGAIN",
23
+ "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_SOCKET", "UND_ERR_HEADERS_TIMEOUT",
24
+ ]);
25
+
26
+ function isTransient(err) {
27
+ if (!err) return false;
28
+ if (err.name === "AbortError" || err.name === "TimeoutError") return true;
29
+ const code = err.cause && err.cause.code;
30
+ if (code && TRANSIENT_CODES.has(code)) return true;
31
+ // undici wraps many socket failures in a plain TypeError("fetch failed")
32
+ if (err instanceof TypeError && /fetch failed|terminated|socket/i.test(err.message)) return true;
33
+ return false;
34
+ }
35
+
36
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
37
+
38
+ async function postJsonRpc(method, params) {
39
+ const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method, params });
40
+ let lastErr = null;
41
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
42
+ try {
43
+ const res = await fetch(ENDPOINT, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
46
+ body,
47
+ signal: AbortSignal.timeout(TIMEOUT_MS),
48
+ });
49
+ if (!res.ok) {
50
+ const transientHttp = res.status === 502 || res.status === 503 || res.status === 504;
51
+ if (transientHttp && attempt < MAX_ATTEMPTS) {
52
+ await sleep(600 * attempt);
53
+ continue;
54
+ }
55
+ throw new Error(`HTTP ${res.status} ${res.statusText} from ${ENDPOINT}`);
56
+ }
57
+ const text = (await res.text()).trim();
58
+ const payload = parseRpcBody(text);
59
+ if (payload.error) {
60
+ const e = new Error(`Zambo error: ${payload.error.message || JSON.stringify(payload.error)}`);
61
+ e.code = payload.error.code;
62
+ e.zamboError = payload.error;
63
+ throw e; // JSON-RPC error: never retry
64
+ }
65
+ return payload.result;
66
+ } catch (err) {
67
+ if (err.zamboError) throw err;
68
+ lastErr = err;
69
+ if (!isTransient(err) || attempt === MAX_ATTEMPTS) break;
70
+ await sleep(600 * attempt);
71
+ }
72
+ }
73
+ throw new Error(`Transport failed after ${MAX_ATTEMPTS} attempts: ${lastErr && lastErr.message}`);
74
+ }
75
+
76
+ function parseRpcBody(text) {
77
+ if (text.startsWith("{")) return JSON.parse(text);
78
+ // Server may answer SSE (data: {...} lines)
79
+ for (const line of text.split("\n")) {
80
+ const t = line.trim();
81
+ if (t.startsWith("data:")) {
82
+ try { return JSON.parse(t.slice(5).trim()); } catch { /* keep scanning */ }
83
+ }
84
+ }
85
+ throw new Error(`Unparseable response from ${ENDPOINT}: ${text.slice(0, 200)}`);
86
+ }
87
+
88
+ /** Extract plain text from an MCP tool result's content blocks. */
89
+ function resultText(result) {
90
+ const parts = [];
91
+ for (const block of (result && result.content) || []) {
92
+ if (block.type === "text" && typeof block.text === "string") parts.push(block.text);
93
+ }
94
+ return parts.join("\n");
95
+ }
96
+
97
+ /**
98
+ * Call a Zambo tool by name with JSON arguments.
99
+ * Returns { result, text, receipt } where receipt is
100
+ * { id, status, audit } โ€” check it at https://zambo.dev/run/<id>.
101
+ */
102
+ async function callTool(name, args = {}) {
103
+ if (!name || typeof name !== "string") throw new Error("callTool: tool name is required");
104
+ if (args === null || typeof args !== "object" || Array.isArray(args)) {
105
+ throw new Error("callTool: args must be a JSON object");
106
+ }
107
+ const result = await postJsonRpc("tools/call", { name, arguments: args });
108
+ const receipt = (result && result._receipt) || {};
109
+ return { result, text: resultText(result), receipt };
110
+ }
111
+
112
+ /** Route any natural-language goal through zambo_universal (the main entry point). */
113
+ async function universal(goal, sessionId) {
114
+ if (!goal || typeof goal !== "string") throw new Error("universal: a goal string is required");
115
+ const args = { goal };
116
+ args._session_id = sessionId || `npm-${randomId(8)}`;
117
+ const result = await postJsonRpc("tools/call", { name: "zambo_universal", arguments: args });
118
+ const receipt = (result && result._receipt) || {};
119
+ return { result, text: resultText(result), receipt };
120
+ }
121
+
122
+ /** Search the full 100+ tool catalog. */
123
+ async function search(query) {
124
+ if (!query || typeof query !== "string") throw new Error("search: a query string is required");
125
+ const result = await postJsonRpc("tools/call", { name: "capability_search", arguments: { query } });
126
+ const receipt = (result && result._receipt) || {};
127
+ return { result, text: resultText(result), receipt };
128
+ }
129
+
130
+ /** List the exposed top-level tools. */
131
+ async function listTools() {
132
+ const result = await postJsonRpc("tools/list", {});
133
+ return result.tools || [];
134
+ }
135
+
136
+ /**
137
+ * Verify a receipt UUID against zambo.dev.
138
+ * Returns { verified, id, tool, sha256, created, audit }.
139
+ * `verified` is true only when zambo.dev serves the receipt page for that UUID
140
+ * and the page's structured receipt data matches.
141
+ */
142
+ async function verifyReceipt(uuid) {
143
+ if (!uuid || typeof uuid !== "string") throw new Error("verifyReceipt: a receipt UUID is required");
144
+ const id = uuid.trim();
145
+ const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
146
+ if (!uuidRe.test(id)) throw new Error(`verifyReceipt: not a UUID: ${id}`);
147
+ const audit = `${RECEIPT_BASE}/${id}`;
148
+ let lastErr = null;
149
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
150
+ try {
151
+ const res = await fetch(audit, {
152
+ headers: { "User-Agent": "zambo-agent-cli/1.0.0 (+https://zambo.dev)" },
153
+ signal: AbortSignal.timeout(TIMEOUT_MS),
154
+ });
155
+ if (res.status === 404) {
156
+ return { verified: false, id, audit, reason: "receipt page not found on zambo.dev" };
157
+ }
158
+ if (!res.ok) {
159
+ if ((res.status === 502 || res.status === 503 || res.status === 504) && attempt < MAX_ATTEMPTS) {
160
+ await sleep(600 * attempt);
161
+ continue;
162
+ }
163
+ throw new Error(`HTTP ${res.status} from ${audit}`);
164
+ }
165
+ const html = await res.text();
166
+ const ld = extractJsonLd(html);
167
+ const props = {};
168
+ for (const p of (ld && ld.additionalProperty) || []) props[p.name] = p.value;
169
+ const titleOk = /Verifiable Receipt/i.test(html);
170
+ const verified = titleOk && !!props.tool && !!props.sha256 && ld["@id"] === audit;
171
+ return {
172
+ verified,
173
+ id,
174
+ tool: props.tool || null,
175
+ sha256: props.sha256 || null,
176
+ created: (ld && ld.dateCreated) || null,
177
+ audit,
178
+ reason: verified ? null : "receipt page served but structured receipt data was missing or mismatched",
179
+ };
180
+ } catch (err) {
181
+ lastErr = err;
182
+ if (!isTransient(err) || attempt === MAX_ATTEMPTS) break;
183
+ await sleep(600 * attempt);
184
+ }
185
+ }
186
+ throw new Error(`Could not reach ${audit}: ${lastErr && lastErr.message}`);
187
+ }
188
+
189
+ function extractJsonLd(html) {
190
+ const m = html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/);
191
+ if (!m) return null;
192
+ try { return JSON.parse(m[1]); } catch { return null; }
193
+ }
194
+
195
+ function randomId(n) {
196
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
197
+ let s = "";
198
+ for (let i = 0; i < n; i++) s += chars[Math.floor(Math.random() * chars.length)];
199
+ return s;
200
+ }
201
+
202
+ module.exports = {
203
+ ENDPOINT,
204
+ RECEIPT_BASE,
205
+ callTool,
206
+ universal,
207
+ search,
208
+ listTools,
209
+ verifyReceipt,
210
+ resultText,
211
+ };
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"author":"Brennan Zambo <brennanzambo@zambo.dev>","bin":{"zambo":"bin/zambo.js","zambo-agent-cli":"bin/zambo.js"},"bugs":{"url":"https://zambo.dev"},"description":"Zambo โ€” the cross-AI execution layer on the command line. One command to get anything done: live crypto prices, site audits, web research, data tools, multi-step agent jobs. 100+ native MCP tools, free 20 calls/tool/day, no account or API key. Every call returns a verifiable receipt (UUID + timestamp + sha256 of the result) you can check at zambo.dev/run/<id>.","engines":{"node":">=18"},"files":["bin/","index.js","README.md","LICENSE","SELF-REVIEW.md"],"homepage":"https://zambo.dev","keywords":["mcp","model-context-protocol","ai-agents","ai-tools","execution","verifiable-receipt","automation","crypto-prices","site-audit","web-research","cli","zambo"],"license":"MIT","main":"index.js","name":"zambo-agent-cli","repository":{"type":"git","url":"https://zambo.dev"},"version":"1.0.0"}