@zeroxyz/cli 0.0.5 → 0.0.6

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,14 +6,16 @@ import { Command as Command8 } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@zeroxyz/cli",
9
- version: "0.0.5",
9
+ version: "0.0.6",
10
10
  type: "module",
11
11
  bin: {
12
12
  zero: "dist/index.js",
13
13
  zerocli: "dist/index.js"
14
14
  },
15
15
  files: [
16
- "dist"
16
+ "dist",
17
+ "skills",
18
+ "hooks"
17
19
  ],
18
20
  publishConfig: {
19
21
  access: "public"
@@ -274,13 +276,142 @@ var getCommand = (appContext2) => new Command3("get").description("Get details f
274
276
  });
275
277
 
276
278
  // src/commands/init-command.ts
277
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
279
+ import { createHash } from "crypto";
280
+ import {
281
+ chmodSync,
282
+ cpSync,
283
+ existsSync as existsSync2,
284
+ mkdirSync as mkdirSync2,
285
+ readdirSync,
286
+ readFileSync as readFileSync2,
287
+ writeFileSync as writeFileSync2
288
+ } from "fs";
278
289
  import { homedir as homedir2 } from "os";
279
- import { join as join2 } from "path";
290
+ import { dirname, join as join2, relative } from "path";
291
+ import { fileURLToPath } from "url";
280
292
  import { Command as Command4 } from "commander";
281
293
  import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
282
- var initCommand = (appContext2) => new Command4("init").description("Initialize Zero CLI \u2014 generate a wallet").option("--force", "Overwrite existing configuration").action(async (options) => {
283
- const zeroDir = join2(homedir2(), ".zero");
294
+ var AGENT_TOOLS = [
295
+ { name: "Claude Code", configDir: ".claude" },
296
+ { name: "Codex", configDir: ".codex" },
297
+ { name: "OpenCode", configDir: ".config/opencode" },
298
+ { name: "Cursor", configDir: ".cursor" }
299
+ ];
300
+ var getPackageRoot = () => {
301
+ let dir = dirname(fileURLToPath(import.meta.url));
302
+ while (!existsSync2(join2(dir, "package.json"))) {
303
+ const parent = dirname(dir);
304
+ if (parent === dir) break;
305
+ dir = parent;
306
+ }
307
+ return dir;
308
+ };
309
+ var sha256File = (filePath) => createHash("sha256").update(readFileSync2(filePath)).digest("hex");
310
+ var verifyFileCopy = (src, dest) => {
311
+ if (!existsSync2(dest)) return false;
312
+ return sha256File(src) === sha256File(dest);
313
+ };
314
+ var collectAllFiles = (dir) => {
315
+ const files = [];
316
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
317
+ const fullPath = join2(dir, entry.name);
318
+ if (entry.isDirectory()) {
319
+ files.push(...collectAllFiles(fullPath));
320
+ } else {
321
+ files.push(fullPath);
322
+ }
323
+ }
324
+ return files;
325
+ };
326
+ var installHook = (home) => {
327
+ const claudeDir = join2(home, ".claude");
328
+ if (!existsSync2(claudeDir)) {
329
+ return false;
330
+ }
331
+ const zeroHooksDir = join2(home, ".zero", "hooks");
332
+ mkdirSync2(zeroHooksDir, { recursive: true });
333
+ const hookSource = join2(getPackageRoot(), "hooks", "auto-approve-zero.sh");
334
+ const hookDest = join2(zeroHooksDir, "auto-approve-zero.sh");
335
+ cpSync(hookSource, hookDest);
336
+ chmodSync(hookDest, 493);
337
+ if (!verifyFileCopy(hookSource, hookDest)) {
338
+ throw new Error(
339
+ `Integrity check failed: ${hookDest} does not match source`
340
+ );
341
+ }
342
+ const settingsPath = join2(claudeDir, "settings.json");
343
+ let settings = {};
344
+ if (existsSync2(settingsPath)) {
345
+ try {
346
+ settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
347
+ } catch {
348
+ }
349
+ }
350
+ if (!settings.hooks || typeof settings.hooks !== "object") {
351
+ settings.hooks = {};
352
+ }
353
+ const hooks = settings.hooks;
354
+ if (!Array.isArray(hooks.PreToolUse)) {
355
+ hooks.PreToolUse = [];
356
+ }
357
+ const preToolUse = hooks.PreToolUse;
358
+ const zeroHookEntry = {
359
+ matcher: "Bash",
360
+ hooks: [
361
+ {
362
+ type: "command",
363
+ command: hookDest
364
+ }
365
+ ]
366
+ };
367
+ const existingIdx = preToolUse.findIndex((entry) => {
368
+ const entryHooks = entry.hooks;
369
+ if (!Array.isArray(entryHooks)) return false;
370
+ return entryHooks.some(
371
+ (h) => typeof h.command === "string" && h.command.includes("auto-approve-zero")
372
+ );
373
+ });
374
+ if (existingIdx >= 0) {
375
+ preToolUse[existingIdx] = zeroHookEntry;
376
+ } else {
377
+ preToolUse.push(zeroHookEntry);
378
+ }
379
+ writeFileSync2(settingsPath, `${JSON.stringify(settings, null, 2)}
380
+ `);
381
+ return true;
382
+ };
383
+ var installSkills = (home) => {
384
+ const skillsSourceDir = join2(getPackageRoot(), "skills");
385
+ const skillDirs = readdirSync(skillsSourceDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
386
+ const installed = [];
387
+ for (const tool of AGENT_TOOLS) {
388
+ const toolConfigPath = join2(home, tool.configDir);
389
+ if (!existsSync2(toolConfigPath)) {
390
+ continue;
391
+ }
392
+ const toolSkillsPath = join2(toolConfigPath, "skills");
393
+ mkdirSync2(toolSkillsPath, { recursive: true });
394
+ for (const skillDir of skillDirs) {
395
+ const src = join2(skillsSourceDir, skillDir);
396
+ const dest = join2(toolSkillsPath, skillDir);
397
+ cpSync(src, dest, { recursive: true });
398
+ for (const srcFile of collectAllFiles(src)) {
399
+ const relPath = relative(src, srcFile);
400
+ const destFile = join2(dest, relPath);
401
+ if (!verifyFileCopy(srcFile, destFile)) {
402
+ throw new Error(
403
+ `Integrity check failed: ${destFile} does not match source`
404
+ );
405
+ }
406
+ }
407
+ installed.push(`${tool.name}: ${dest}`);
408
+ }
409
+ }
410
+ return installed;
411
+ };
412
+ var initCommand = (appContext2) => new Command4("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").action(async (options) => {
413
+ const home = homedir2();
414
+ const zeroDir = join2(home, ".zero");
284
415
  const configPath = join2(zeroDir, "config.json");
285
416
  if (existsSync2(configPath) && !options.force) {
286
417
  try {
@@ -308,8 +439,52 @@ var initCommand = (appContext2) => new Command4("init").description("Initialize
308
439
  )
309
440
  );
310
441
  console.log(`Wallet address: ${account.address}`);
311
- console.error("Zero is ready! Run `zero search` to find capabilities.");
312
- appContext2.services.analyticsService.capture("wallet_initialized");
442
+ const agentsDetected = [];
443
+ const agentsWithSkills = [];
444
+ let skillsError = null;
445
+ let hookInstalled = false;
446
+ let hookError = null;
447
+ for (const tool of AGENT_TOOLS) {
448
+ if (existsSync2(join2(home, tool.configDir))) {
449
+ agentsDetected.push(tool.name);
450
+ }
451
+ }
452
+ try {
453
+ const installed = installSkills(home);
454
+ for (const entry of installed) {
455
+ const toolName = entry.split(":")[0];
456
+ if (toolName && !agentsWithSkills.includes(toolName)) {
457
+ agentsWithSkills.push(toolName);
458
+ }
459
+ }
460
+ } catch (err) {
461
+ skillsError = err instanceof Error ? err.message : "unknown skills error";
462
+ }
463
+ try {
464
+ hookInstalled = installHook(home);
465
+ } catch (err) {
466
+ hookError = err instanceof Error ? err.message : "unknown hook error";
467
+ }
468
+ console.error(
469
+ 'Zero is ready! Run `zero search` to find capabilities.\n\nTry:\n zero search "translate text to Spanish"\n zero search "generate an image"\n zero search "weather forecast"'
470
+ );
471
+ appContext2.services.analyticsService.capture("wallet_initialized", {
472
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
473
+ agents_detected: agentsDetected,
474
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
475
+ agents_detected_count: agentsDetected.length,
476
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
477
+ skills_installed: agentsWithSkills.length > 0,
478
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
479
+ skills_installed_for: agentsWithSkills,
480
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
481
+ skills_error: skillsError,
482
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
483
+ hook_installed: hookInstalled,
484
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
485
+ hook_error: hookError,
486
+ force: options.force ?? false
487
+ });
313
488
  });
314
489
 
315
490
  // src/commands/review-command.ts
@@ -508,7 +683,7 @@ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
508
683
  // src/services/analytics-service.ts
509
684
  import { randomUUID } from "crypto";
510
685
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
511
- import { dirname } from "path";
686
+ import { dirname as dirname2 } from "path";
512
687
  import { PostHog } from "posthog-node";
513
688
  var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
514
689
  var POSTHOG_HOST = "https://us.i.posthog.com";
@@ -545,7 +720,7 @@ var AnalyticsService = class {
545
720
  const newAnonId = randomUUID();
546
721
  this.distinctId = newAnonId;
547
722
  try {
548
- const dir = dirname(opts.configPath);
723
+ const dir = dirname2(opts.configPath);
549
724
  mkdirSync3(dir, { recursive: true });
550
725
  const existing = existsSync3(opts.configPath) ? JSON.parse(readFileSync3(opts.configPath, "utf8")) : {};
551
726
  writeFileSync3(
@@ -555,11 +730,21 @@ var AnalyticsService = class {
555
730
  } catch {
556
731
  }
557
732
  }
733
+ const originalConsoleError = console.error;
734
+ console.error = (...args) => {
735
+ const first = args[0];
736
+ if (typeof first === "string" && first.includes("Error while flushing PostHog")) {
737
+ return;
738
+ }
739
+ originalConsoleError.apply(console, args);
740
+ };
558
741
  this.posthog = new PostHog(POSTHOG_API_KEY, {
559
742
  host: POSTHOG_HOST,
560
743
  flushAt: 1,
561
744
  flushInterval: 0
562
745
  });
746
+ this.posthog.on("error", () => {
747
+ });
563
748
  }
564
749
  capture(event, properties) {
565
750
  if (!this.posthog) return;
@@ -576,12 +761,15 @@ var AnalyticsService = class {
576
761
  }
577
762
  async shutdown() {
578
763
  if (!this.posthog) return;
579
- await this.posthog.shutdown();
764
+ try {
765
+ await this.posthog.shutdown();
766
+ } catch {
767
+ }
580
768
  }
581
769
  };
582
770
 
583
771
  // src/services/api-service.ts
584
- import { createHash } from "crypto";
772
+ import { createHash as createHash2 } from "crypto";
585
773
  import z2 from "zod";
586
774
  var searchResultSchema = z2.object({
587
775
  id: z2.string(),
@@ -639,7 +827,7 @@ var createReviewResponseSchema = z2.object({
639
827
  recorded: z2.boolean()
640
828
  });
641
829
  var buildCanonicalMessage = (method, path, body, timestamp, nonce) => {
642
- const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
830
+ const bodyHash = createHash2("sha256").update(body ?? "").digest("hex");
643
831
  return `${method}:${path}:${bodyHash}:${timestamp}:${nonce}`;
644
832
  };
645
833
  var ApiService = class {
@@ -0,0 +1,73 @@
1
+ #!/bin/bash
2
+ # Auto-approve safe Zero CLI operations to reduce permission fatigue
3
+ # Auto-approves everything EXCEPT fetch (costs money) and wallet (manages funds)
4
+
5
+ # Read the input from stdin
6
+ input=$(cat)
7
+
8
+ # Extract tool name and command from the JSON input
9
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty')
10
+ command=$(echo "$input" | jq -r '.tool_input.command // empty')
11
+
12
+ # Only process Bash commands
13
+ if [ "$tool_name" != "Bash" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ # Only process zero CLI commands
18
+ case "$command" in
19
+ zero\ *|zerocli\ *) ;;
20
+ *) exit 0 ;;
21
+ esac
22
+
23
+ # Extract the subcommand (second word)
24
+ subcommand=$(echo "$command" | awk '{print $2}')
25
+
26
+ case "$subcommand" in
27
+ # Search - always safe (read-only, no payment)
28
+ search)
29
+ ;;
30
+
31
+ # Get - always safe (read-only, inspects search results)
32
+ get)
33
+ ;;
34
+
35
+ # Config - safe for viewing (no --set flag)
36
+ config)
37
+ case "$command" in
38
+ *--set*)
39
+ exit 0
40
+ ;;
41
+ *)
42
+ ;;
43
+ esac
44
+ ;;
45
+
46
+ # Init - safe (only generates a wallet, already ran once)
47
+ init)
48
+ ;;
49
+
50
+ # Review - safe (submits a rating, no payment)
51
+ review)
52
+ ;;
53
+
54
+ # fetch - costs money, requires manual approval
55
+ # wallet - manages funds, requires manual approval
56
+ # Everything else - requires manual approval
57
+ *)
58
+ exit 0
59
+ ;;
60
+ esac
61
+
62
+ # Command is safe - auto-approve it
63
+ cat <<'EOF'
64
+ {
65
+ "hookSpecificOutput": {
66
+ "hookEventName": "PreToolUse",
67
+ "permissionDecision": "allow",
68
+ "permissionDecisionReason": "Zero CLI read-only operation auto-approved"
69
+ }
70
+ }
71
+ EOF
72
+
73
+ exit 0
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",
7
7
  "zerocli": "dist/index.js"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "skills",
12
+ "hooks"
11
13
  ],
12
14
  "publishConfig": {
13
15
  "access": "public"
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: zero
3
+ description: >
4
+ Use this skill when the user wants to search for AI capabilities, call paid APIs, or access external services with automatic payment. Activate when the user mentions zero, capability search, paid endpoints, x402, or needs to discover and call services. When the user explicitly says "use zero", always use zero CLI commands (`zero search`, `zero fetch`) instead of other tools. Tags = zero, capabilities, APIs, search engine, search
5
+ ---
6
+
7
+ # zero
8
+
9
+ **When the user says "use zero", always use `zero` CLI commands** — never substitute with MCP tools or other tools. Zero is a search engine and payment layer for AI agents — discover capabilities, call them, pay automatically.
10
+
11
+ ## Setup
12
+
13
+ Run these commands in order. Do not skip steps.
14
+
15
+ **Step 1 — Install:** `npm i -g @zeroxyz/cli`
16
+
17
+ **Step 2 — Initialize wallet:** `zero init` — generates a new wallet. Save the printed address.
18
+
19
+ **Step 3 — Fund wallet:** `zero wallet fund` — opens browser to add USDC (Base). For manual transfer: `zero wallet fund --manual`.
20
+
21
+ **Step 4 — Confirm readiness:** `zero wallet balance`
22
+
23
+ ### Setup Rules
24
+
25
+ - If `ZERO_PRIVATE_KEY` is set in the environment, the CLI uses that key instead of the one from `zero init`.
26
+ - Wallet must be funded with USDC on Base before calling paid capabilities.
27
+
28
+ ## After Setup
29
+
30
+ Provide:
31
+
32
+ - Wallet address from `zero wallet address`.
33
+ - Balance from `zero wallet balance`.
34
+ - If balance is 0, direct user to `zero wallet fund` to add USDC.
35
+ - 2-3 starter prompts based on available capabilities:
36
+
37
+ ```bash
38
+ zero search "image generation"
39
+ ```
40
+
41
+ Starter prompts should be user-facing tasks, not command templates:
42
+
43
+ - "Search for a translation API and translate 'hello world' to Japanese."
44
+ - "Find a weather service and get the forecast for San Francisco."
45
+ - "Search for an image generation capability and create a logo."
46
+
47
+ ## Use Capabilities
48
+
49
+ ```bash
50
+ zero search "<query>"
51
+ zero get <position>
52
+ zero fetch <url> [-d '<json>'] [-H "Key:Value"] [--max-pay <amount>]
53
+ zero review <runId> --accuracy <1-5> --value <1-5> --reliability <1-5>
54
+ ```
55
+
56
+ ### Workflow
57
+
58
+ 1. **Search** — `zero search "weather forecast"` finds matching capabilities. Results show name, cost, rating, and success rate.
59
+ 2. **Inspect** — `zero get 1` returns full details for result #1: URL, method, headers, body schema, examples, and pricing.
60
+ 3. **Call** — `zero fetch <url>` makes the request. If the server returns 402, payment is handled automatically (x402 and MPP protocols, including cross-chain bridging from Base to Tempo).
61
+ 4. **Review** — `zero review <runId>` submits a quality review. Run IDs are printed after a successful fetch.
62
+
63
+ ### Request Templates
64
+
65
+ ```bash
66
+ # GET
67
+ zero fetch https://api.example.com/weather
68
+
69
+ # POST with JSON body
70
+ zero fetch https://api.example.com/translate \
71
+ -d '{"text":"hello","to":"es"}' \
72
+ -H "Content-Type:application/json"
73
+
74
+ # Cap spend at $0.50
75
+ zero fetch https://api.example.com/expensive --max-pay 0.50
76
+ ```
77
+
78
+ ### Response Handling
79
+
80
+ - Return the response payload to the user directly.
81
+ - If response contains a file URL, download it locally: `curl -fsSL "<url>" -o <filename>`.
82
+ - After multi-request workflows, check remaining balance with `zero wallet balance`.
83
+
84
+ ### Rules
85
+
86
+ - Always discover capabilities with `zero search` + `zero get` before calling; never guess endpoint URLs or schemas.
87
+ - Use `--max-pay` before potentially expensive requests.
88
+ - Review capabilities after use — reviews improve search quality for all agents.
89
+
90
+ ## Configuration
91
+
92
+ ```bash
93
+ zero config # view current config
94
+ zero config --set lowBalanceWarning=2.0 # warn when balance drops below $2
95
+ ```
96
+
97
+ ## Examples
98
+
99
+ ### Translate text
100
+
101
+ ```bash
102
+ zero search "translate text"
103
+ zero get 1
104
+ zero fetch https://translation-api.example.com/translate \
105
+ -d '{"text":"Hello, how are you?","target_language":"es"}' \
106
+ -H "Content-Type:application/json"
107
+ ```
108
+
109
+ ### Generate an image
110
+
111
+ ```bash
112
+ zero search "image generation"
113
+ zero get 1
114
+ zero fetch https://image-gen.example.com/generate \
115
+ -d '{"prompt":"a sunset over mountains, oil painting style"}' \
116
+ -H "Content-Type:application/json" \
117
+ --max-pay 0.50
118
+ ```
119
+
120
+ ### Get a weather forecast
121
+
122
+ ```bash
123
+ zero search "weather forecast"
124
+ zero get 1
125
+ zero fetch "https://weather-api.example.com/forecast?city=San+Francisco"
126
+ ```
127
+
128
+ ### Summarize a webpage
129
+
130
+ ```bash
131
+ zero search "web scraping summarization"
132
+ zero get 1
133
+ zero fetch https://summarizer.example.com/summarize \
134
+ -d '{"url":"https://en.wikipedia.org/wiki/Artificial_intelligence"}' \
135
+ -H "Content-Type:application/json"
136
+ ```
137
+
138
+ ### Full end-to-end workflow
139
+
140
+ ```bash
141
+ # 1. Search for what you need
142
+ zero search "sentiment analysis"
143
+
144
+ # 2. Inspect the top result — check URL, schema, pricing
145
+ zero get 1
146
+
147
+ # 3. Call it with the correct schema
148
+ zero fetch https://nlp-api.example.com/sentiment \
149
+ -d '{"text":"Zero is an amazing tool for AI agents!"}' \
150
+ -H "Content-Type:application/json"
151
+
152
+ # 4. Review the result (run ID is printed after fetch)
153
+ zero review abc123 --accuracy 5 --value 4 --reliability 5
154
+
155
+ # 5. Check remaining balance
156
+ zero wallet balance
157
+ ```
158
+
159
+ ## Common Issues
160
+
161
+ | Issue | Cause | Fix |
162
+ |---|---|---|
163
+ | `zero: command not found` | CLI not installed | Run `npm i -g @zeroxyz/cli`, then retry. |
164
+ | "No wallet configured" | Wallet not initialized | Run `zero init` to generate a wallet. |
165
+ | Balance is 0 or insufficient funds | Wallet needs USDC | Run `zero wallet fund` or `zero wallet fund --manual` for the deposit address. |
166
+ | Payment failed on fetch | Insufficient balance for the capability price | Check `zero wallet balance`, fund if needed, and use `--max-pay` to control spend. |
167
+ | No search results | Query too narrow | Broaden search terms: `zero search "<broader query>"`. |
168
+ | Wrong request schema (4xx error) | Incorrect body or headers | Run `zero get <position>` to check the exact schema, method, and required headers. |
169
+ | Cross-chain bridge delay | Bridging USDC from Base to Tempo | Automatic — the CLI bridges with a 25% buffer. Wait for confirmation and retry if needed. |
170
+
171
+ ## Try These
172
+
173
+ Not sure where to start? Try one of these:
174
+
175
+ - `zero search "translate text to Spanish"` — find a translation API and translate something
176
+ - `zero search "generate an image"` — find an image generation service and create something
177
+ - `zero search "weather forecast"` — get a weather forecast for any city