@zeroxyz/cli 0.0.38 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +403 -168
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { homedir as homedir7 } from "os";
5
- import { join as join8 } from "path";
4
+ import { homedir as homedir8 } from "os";
5
+ import { join as join9 } from "path";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@zeroxyz/cli",
10
- version: "0.0.38",
10
+ version: "0.0.39",
11
11
  type: "module",
12
12
  bin: {
13
13
  zero: "dist/index.js",
@@ -61,12 +61,124 @@ var package_default = {
61
61
  };
62
62
 
63
63
  // src/app.ts
64
- import { Command as Command12 } from "commander";
64
+ import { Command as Command13 } from "commander";
65
+
66
+ // src/commands/auth-command.ts
67
+ import { homedir } from "os";
68
+ import { join } from "path";
69
+ import { Command } from "commander";
70
+ import open from "open";
71
+
72
+ // src/util/secure-config.ts
73
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
74
+ var SECURE_DIR_MODE = 448;
75
+ var SECURE_FILE_MODE = 384;
76
+ var ensureSecureDir = (path) => {
77
+ mkdirSync(path, { recursive: true, mode: SECURE_DIR_MODE });
78
+ chmodSync(path, SECURE_DIR_MODE);
79
+ };
80
+ var writeSecureFile = (path, contents) => {
81
+ writeFileSync(path, contents, { mode: SECURE_FILE_MODE });
82
+ chmodSync(path, SECURE_FILE_MODE);
83
+ };
84
+ var readConfig = (path) => {
85
+ try {
86
+ const raw = readFileSync(path, "utf8");
87
+ return JSON.parse(raw);
88
+ } catch {
89
+ return {};
90
+ }
91
+ };
92
+
93
+ // src/commands/auth-command.ts
94
+ var getConfigPath = () => join(homedir(), ".zero", "config.json");
95
+ var authLoginCommand = (appContext) => new Command("login").description("Sign in to Zero").option("--no-open", "Do not open the browser automatically").action(async (opts) => {
96
+ const { apiService } = appContext.services;
97
+ const start = await apiService.startDeviceLogin();
98
+ const url = `${start.verificationUri}?code=${start.userCode}`;
99
+ process.stdout.write(
100
+ `Open this URL to authorize:
101
+ ${url}
102
+ User code: ${start.userCode}
103
+ `
104
+ );
105
+ if (opts.open) {
106
+ await open(url).catch(() => {
107
+ });
108
+ }
109
+ while (Date.now() < start.expiresAt) {
110
+ await new Promise(
111
+ (r) => setTimeout(r, start.pollInterval * 1e3)
112
+ );
113
+ const result = await apiService.pollDeviceLogin(start.deviceCode);
114
+ if (result.status === "pending") {
115
+ process.stdout.write(".");
116
+ continue;
117
+ }
118
+ if (result.status === "expired") {
119
+ process.stderr.write(
120
+ "\nDevice code expired. Run `zero auth login` again.\n"
121
+ );
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ const path = getConfigPath();
126
+ const current = readConfig(path);
127
+ const next = {
128
+ ...current,
129
+ session: {
130
+ userId: result.user.id,
131
+ authMethod: "workos",
132
+ accessToken: result.accessToken,
133
+ refreshToken: result.refreshToken
134
+ }
135
+ };
136
+ writeSecureFile(path, JSON.stringify(next, null, 2));
137
+ process.stdout.write(
138
+ `
139
+ Signed in as ${result.user.email ?? result.user.id}
140
+ `
141
+ );
142
+ return;
143
+ }
144
+ process.stderr.write(
145
+ "\nDevice code expired. Run `zero auth login` again.\n"
146
+ );
147
+ process.exitCode = 1;
148
+ });
149
+ var authLogoutCommand = (appContext) => new Command("logout").description("Sign out of Zero").action(async () => {
150
+ const { apiService } = appContext.services;
151
+ const path = getConfigPath();
152
+ const config = readConfig(path);
153
+ if (config.session) {
154
+ await apiService.logout(config.session.refreshToken);
155
+ }
156
+ const { session: _drop, ...rest } = config;
157
+ writeSecureFile(path, JSON.stringify(rest, null, 2));
158
+ process.stdout.write("Signed out.\n");
159
+ });
160
+ var authWhoamiCommand = (appContext) => new Command("whoami").description("Print the current Zero identity").action(async () => {
161
+ const { apiService } = appContext.services;
162
+ const me = await apiService.getMe();
163
+ process.stdout.write(
164
+ `${me.email ?? me.id}
165
+ uid: ${me.id}
166
+ authMethod: ${me.authMethod}
167
+ `
168
+ );
169
+ });
170
+ var authCommand = (appContext) => {
171
+ const cmd = new Command("auth").description("Authentication commands");
172
+ cmd.addCommand(authLoginCommand(appContext));
173
+ cmd.addCommand(authLogoutCommand(appContext));
174
+ cmd.addCommand(authWhoamiCommand(appContext), { hidden: true });
175
+ return cmd;
176
+ };
65
177
 
66
178
  // src/commands/bug-report-command.ts
67
179
  import { createHash as createHash2 } from "crypto";
68
- import { readFileSync } from "fs";
69
- import { Command } from "commander";
180
+ import { readFileSync as readFileSync2 } from "fs";
181
+ import { Command as Command2 } from "commander";
70
182
  import { z as z2 } from "zod";
71
183
 
72
184
  // src/services/api-service.ts
@@ -225,18 +337,47 @@ var listRunsResponseSchema = z.object({
225
337
  runs: z.array(runListItemSchema),
226
338
  nextCursor: z.string().nullable()
227
339
  });
340
+ var userDtoSchema = z.object({
341
+ id: z.string(),
342
+ email: z.string().nullable(),
343
+ authMethod: z.string(),
344
+ createdAt: z.union([z.string(), z.date()]).optional(),
345
+ lastLoginAt: z.union([z.string(), z.date()]).nullable().optional()
346
+ });
347
+ var deviceStartResultSchema = z.object({
348
+ deviceCode: z.string(),
349
+ userCode: z.string(),
350
+ verificationUri: z.string(),
351
+ pollInterval: z.number(),
352
+ expiresAt: z.number()
353
+ });
354
+ var devicePollResponseSchema = z.union([
355
+ z.object({ error: z.literal("authorization_pending") }),
356
+ z.object({ error: z.literal("expired_token") }),
357
+ z.object({
358
+ accessToken: z.string(),
359
+ refreshToken: z.string(),
360
+ expiresIn: z.number(),
361
+ user: userDtoSchema
362
+ })
363
+ ]);
228
364
  var buildCanonicalMessage = (method, path, body, timestamp, nonce) => {
229
365
  const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
230
366
  return `${method}:${path}:${bodyHash}:${timestamp}:${nonce}`;
231
367
  };
232
368
  var ApiService = class _ApiService {
233
- constructor(baseUrl, account) {
369
+ constructor(baseUrl, account, credentials = { kind: "none" }, onSessionRefreshed = async () => {
370
+ }) {
234
371
  this.baseUrl = baseUrl;
235
372
  this.account = account;
236
373
  this.walletAddress = this.account?.address ?? null;
374
+ this.credentials = credentials;
375
+ this.onSessionRefreshed = onSessionRefreshed;
237
376
  }
238
377
  walletAddress;
239
378
  account;
379
+ credentials;
380
+ onSessionRefreshed;
240
381
  withAccount = (account) => new _ApiService(this.baseUrl, account);
241
382
  signRequest = async (method, path, body) => {
242
383
  if (!this.account) throw new Error("No private key configured");
@@ -251,21 +392,59 @@ var ApiService = class _ApiService {
251
392
  "x-zero-signature": signature
252
393
  };
253
394
  };
254
- request = async (method, path, body) => {
255
- const url = `${this.baseUrl}${path}`;
256
- const bodyStr = body ? JSON.stringify(body) : void 0;
257
- const headers = {
395
+ // Session credentials take precedence over EIP-191. We never combine them
396
+ // in a single request: if the user has signed in, the JWT is the identity
397
+ // the API trusts, and the wallet signing path stays out of the picture.
398
+ buildHeaders = async (method, path, bodyStr) => {
399
+ const base2 = {
258
400
  "content-type": "application/json"
259
401
  };
402
+ if (this.credentials.kind === "session") {
403
+ base2.authorization = `Bearer ${this.credentials.accessToken}`;
404
+ return base2;
405
+ }
260
406
  if (this.account) {
261
407
  const walletHeaders = await this.signRequest(method, path, bodyStr);
262
- Object.assign(headers, walletHeaders);
408
+ return { ...base2, ...walletHeaders };
263
409
  }
264
- const response = await fetch(url, {
410
+ return base2;
411
+ };
412
+ // Rotates the in-memory tokens and fires onSessionRefreshed so the
413
+ // caller can persist them. Returns false on any refresh failure — the
414
+ // outer request then propagates the original 401 to the caller.
415
+ tryRefreshSession = async () => {
416
+ if (this.credentials.kind !== "session") return false;
417
+ const { refreshToken } = this.credentials;
418
+ const resp = await fetch(`${this.baseUrl}/v1/auth/refresh`, {
419
+ method: "POST",
420
+ headers: { "content-type": "application/json" },
421
+ body: JSON.stringify({ refreshToken })
422
+ });
423
+ if (!resp.ok) return false;
424
+ const body = await resp.json();
425
+ this.credentials = {
426
+ ...this.credentials,
427
+ accessToken: body.accessToken,
428
+ refreshToken: body.refreshToken
429
+ };
430
+ await this.onSessionRefreshed(body);
431
+ return true;
432
+ };
433
+ request = async (method, path, body) => {
434
+ const url = `${this.baseUrl}${path}`;
435
+ const bodyStr = body ? JSON.stringify(body) : void 0;
436
+ const makeInit = async () => ({
265
437
  method,
266
- headers,
438
+ headers: await this.buildHeaders(method, path, bodyStr),
267
439
  body: bodyStr
268
440
  });
441
+ let response = await fetch(url, await makeInit());
442
+ if (response.status === 401 && this.credentials.kind === "session") {
443
+ const refreshed = await this.tryRefreshSession();
444
+ if (refreshed) {
445
+ response = await fetch(url, await makeInit());
446
+ }
447
+ }
269
448
  if (!response.ok) {
270
449
  const errorBody = await response.json().catch(() => ({ error: "Unknown error" }));
271
450
  throw new Error(
@@ -274,6 +453,44 @@ var ApiService = class _ApiService {
274
453
  }
275
454
  return response.json();
276
455
  };
456
+ startDeviceLogin = async () => {
457
+ const resp = await fetch(`${this.baseUrl}/v1/auth/device/start`, {
458
+ method: "POST"
459
+ });
460
+ if (!resp.ok) throw new Error(`device_start_${resp.status}`);
461
+ return deviceStartResultSchema.parse(await resp.json());
462
+ };
463
+ pollDeviceLogin = async (deviceCode) => {
464
+ const resp = await fetch(`${this.baseUrl}/v1/auth/device/poll`, {
465
+ method: "POST",
466
+ headers: { "content-type": "application/json" },
467
+ body: JSON.stringify({ deviceCode })
468
+ });
469
+ if (!resp.ok) throw new Error(`device_poll_${resp.status}`);
470
+ const parsed = devicePollResponseSchema.parse(await resp.json());
471
+ if ("error" in parsed) {
472
+ return parsed.error === "authorization_pending" ? { status: "pending" } : { status: "expired" };
473
+ }
474
+ return {
475
+ status: "ok",
476
+ accessToken: parsed.accessToken,
477
+ refreshToken: parsed.refreshToken,
478
+ expiresIn: parsed.expiresIn,
479
+ user: parsed.user
480
+ };
481
+ };
482
+ logout = async (refreshToken) => {
483
+ await fetch(`${this.baseUrl}/v1/auth/logout`, {
484
+ method: "POST",
485
+ headers: { "content-type": "application/json" },
486
+ body: JSON.stringify({ refreshToken })
487
+ }).catch(() => {
488
+ });
489
+ };
490
+ getMe = async () => {
491
+ const json = await this.request("GET", "/v1/users/me");
492
+ return userDtoSchema.parse(json);
493
+ };
277
494
  search = async (options) => {
278
495
  const json = await this.request("POST", "/v1/search", options);
279
496
  return searchResponseSchema.parse(json);
@@ -351,7 +568,7 @@ var autoIdempotencyKey = (description, contextSeed) => {
351
568
  const seed = `${description}|${contextSeed ?? ""}|${bucket}`;
352
569
  return `auto-${createHash2("sha256").update(seed).digest("hex").slice(0, 16)}`;
353
570
  };
354
- var bugReportCommand = (appContext) => new Command("bug-report").description(
571
+ var bugReportCommand = (appContext) => new Command2("bug-report").description(
355
572
  "Report a Zero platform bug \u2014 bad search ranking, wrong indexed URL, CLI bugs, billing issues. The CLI auto-attaches your most recent context (last search + last run), auto-derives a title, and lets the server classify the category. For 'this capability returned a bad result', use `zero review` instead."
356
573
  ).addHelpText(
357
574
  "after",
@@ -428,7 +645,7 @@ Categories the classifier picks from:
428
645
  return;
429
646
  }
430
647
  if (options.fromFile) {
431
- const contents = readFileSync(options.fromFile, "utf8");
648
+ const contents = readFileSync2(options.fromFile, "utf8");
432
649
  const lines = contents.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
433
650
  let ok = 0;
434
651
  let failed = 0;
@@ -565,21 +782,21 @@ Bulk bug-report complete: ${ok} ok, ${failed} failed`
565
782
  );
566
783
 
567
784
  // src/commands/config-command.ts
568
- import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
569
- import { homedir } from "os";
570
- import { join } from "path";
571
- import { Command as Command2 } from "commander";
785
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
786
+ import { homedir as homedir2 } from "os";
787
+ import { join as join2 } from "path";
788
+ import { Command as Command3 } from "commander";
572
789
  var VALID_KEYS = ["lowBalanceWarning", "auth", "telemetry"];
573
790
  var loadConfig = (configPath) => {
574
791
  try {
575
792
  if (!existsSync(configPath)) return {};
576
- return JSON.parse(readFileSync2(configPath, "utf8"));
793
+ return JSON.parse(readFileSync3(configPath, "utf8"));
577
794
  } catch {
578
795
  return {};
579
796
  }
580
797
  };
581
- var configCommand = (_appContext) => new Command2("config").description("View or update CLI configuration").option("--set <keyValue>", "Set a config value (key=value)").action((options) => {
582
- const configPath = join(homedir(), ".zero", "config.json");
798
+ var configCommand = (_appContext) => new Command3("config").description("View or update CLI configuration").option("--set <keyValue>", "Set a config value (key=value)").action((options) => {
799
+ const configPath = join2(homedir2(), ".zero", "config.json");
583
800
  if (!options.set) {
584
801
  const config2 = loadConfig(configPath);
585
802
  console.log(JSON.stringify(config2, null, 2));
@@ -609,16 +826,16 @@ var configCommand = (_appContext) => new Command2("config").description("View or
609
826
  }
610
827
  const config = loadConfig(configPath);
611
828
  config[key] = value;
612
- mkdirSync(join(homedir(), ".zero"), { recursive: true });
613
- writeFileSync(configPath, JSON.stringify(config, null, 2));
829
+ mkdirSync2(join2(homedir2(), ".zero"), { recursive: true });
830
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
614
831
  console.log(`Set ${key} = ${JSON.stringify(value)}`);
615
832
  });
616
833
 
617
834
  // src/commands/fetch-command.ts
618
835
  import { createHash as createHash3 } from "crypto";
619
- import { readFileSync as readFileSync3 } from "fs";
836
+ import { readFileSync as readFileSync4 } from "fs";
620
837
  import { resolve as resolvePath } from "path";
621
- import { Command as Command3 } from "commander";
838
+ import { Command as Command4 } from "commander";
622
839
  import { formatUnits as formatUnits2 } from "viem";
623
840
 
624
841
  // src/services/payment-service.ts
@@ -1285,8 +1502,8 @@ var extractUpstreamErrorMessage = (body) => {
1285
1502
  };
1286
1503
  var resolveRequestBody = (rawData, readStdin2) => {
1287
1504
  const fromFile = (spec) => {
1288
- if (spec === "@-") return readFileSync3(0);
1289
- return readFileSync3(resolvePath(spec.slice(1)));
1505
+ if (spec === "@-") return readFileSync4(0);
1506
+ return readFileSync4(resolvePath(spec.slice(1)));
1290
1507
  };
1291
1508
  if (readStdin2 && rawData !== void 0) {
1292
1509
  throw new Error(
@@ -1296,7 +1513,7 @@ var resolveRequestBody = (rawData, readStdin2) => {
1296
1513
  let body;
1297
1514
  let cap;
1298
1515
  if (readStdin2) {
1299
- body = readFileSync3(0);
1516
+ body = readFileSync4(0);
1300
1517
  cap = MAX_FILE_REQUEST_BODY_BYTES;
1301
1518
  } else if (rawData?.startsWith("@")) {
1302
1519
  body = fromFile(rawData);
@@ -1349,7 +1566,7 @@ var detectPaymentRequirement = async (response) => {
1349
1566
  }
1350
1567
  return { protocol: "unknown", raw: {} };
1351
1568
  };
1352
- var fetchCommand = (appContext) => new Command3("fetch").description(
1569
+ var fetchCommand = (appContext) => new Command4("fetch").description(
1353
1570
  "Fetch a capability URL, handling 402 challenges automatically"
1354
1571
  ).argument(
1355
1572
  "[url]",
@@ -1848,7 +2065,7 @@ var fetchCommand = (appContext) => new Command3("fetch").description(
1848
2065
  );
1849
2066
 
1850
2067
  // src/commands/get-command.ts
1851
- import { Command as Command4 } from "commander";
2068
+ import { Command as Command5 } from "commander";
1852
2069
 
1853
2070
  // src/util/format-price.ts
1854
2071
  var centsToDollars = (cents) => {
@@ -2007,7 +2224,7 @@ var formatCapability = (capability) => {
2007
2224
  lines.push(...buildTryItExample(capability));
2008
2225
  return lines.join("\n");
2009
2226
  };
2010
- var getCommand = (appContext) => new Command4("get").description(
2227
+ var getCommand = (appContext) => new Command5("get").description(
2011
2228
  "Get details for a capability by position from last search, or by slug"
2012
2229
  ).argument(
2013
2230
  "<identifier>",
@@ -2082,15 +2299,15 @@ import {
2082
2299
  existsSync as existsSync2,
2083
2300
  mkdirSync as mkdirSync3,
2084
2301
  readdirSync,
2085
- readFileSync as readFileSync4,
2302
+ readFileSync as readFileSync5,
2086
2303
  rmSync,
2087
2304
  statSync,
2088
2305
  writeFileSync as writeFileSync3
2089
2306
  } from "fs";
2090
- import { homedir as homedir2 } from "os";
2091
- import { dirname, join as join2, relative } from "path";
2307
+ import { homedir as homedir3 } from "os";
2308
+ import { dirname, join as join3, relative } from "path";
2092
2309
  import { fileURLToPath } from "url";
2093
- import { Command as Command5 } from "commander";
2310
+ import { Command as Command6 } from "commander";
2094
2311
  import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
2095
2312
 
2096
2313
  // src/util/install-banner.ts
@@ -2222,19 +2439,6 @@ var printReadyFooter = () => {
2222
2439
  return lines.join("\n");
2223
2440
  };
2224
2441
 
2225
- // src/util/secure-config.ts
2226
- import { chmodSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2227
- var SECURE_DIR_MODE = 448;
2228
- var SECURE_FILE_MODE = 384;
2229
- var ensureSecureDir = (path) => {
2230
- mkdirSync2(path, { recursive: true, mode: SECURE_DIR_MODE });
2231
- chmodSync(path, SECURE_DIR_MODE);
2232
- };
2233
- var writeSecureFile = (path, contents) => {
2234
- writeFileSync2(path, contents, { mode: SECURE_FILE_MODE });
2235
- chmodSync(path, SECURE_FILE_MODE);
2236
- };
2237
-
2238
2442
  // src/commands/init-command.ts
2239
2443
  var AGENT_TOOLS = [
2240
2444
  { name: "Claude Code", detectDir: ".claude", skillsDir: ".claude/skills" },
@@ -2249,7 +2453,7 @@ var AGENT_TOOLS = [
2249
2453
  var findResourceDir = (startDir, resourceName) => {
2250
2454
  let current = startDir;
2251
2455
  while (true) {
2252
- const candidate = join2(current, resourceName);
2456
+ const candidate = join3(current, resourceName);
2253
2457
  if (existsSync2(candidate)) {
2254
2458
  return candidate;
2255
2459
  }
@@ -2268,7 +2472,7 @@ var getCliModuleDir = () => {
2268
2472
  }
2269
2473
  return __dirname;
2270
2474
  };
2271
- var sha256File = (filePath) => createHash4("sha256").update(readFileSync4(filePath)).digest("hex");
2475
+ var sha256File = (filePath) => createHash4("sha256").update(readFileSync5(filePath)).digest("hex");
2272
2476
  var verifyFileCopy = (src, dest) => {
2273
2477
  if (!existsSync2(dest)) return false;
2274
2478
  return sha256File(src) === sha256File(dest);
@@ -2276,7 +2480,7 @@ var verifyFileCopy = (src, dest) => {
2276
2480
  var collectAllFiles = (dir) => {
2277
2481
  const files = [];
2278
2482
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
2279
- const fullPath = join2(dir, entry.name);
2483
+ const fullPath = join3(dir, entry.name);
2280
2484
  if (entry.isDirectory()) {
2281
2485
  files.push(...collectAllFiles(fullPath));
2282
2486
  } else {
@@ -2286,7 +2490,7 @@ var collectAllFiles = (dir) => {
2286
2490
  return files;
2287
2491
  };
2288
2492
  var copyFile = (src, dest) => {
2289
- writeFileSync3(dest, readFileSync4(src));
2493
+ writeFileSync3(dest, readFileSync5(src));
2290
2494
  try {
2291
2495
  chmodSync2(dest, statSync(src).mode);
2292
2496
  } catch {
@@ -2295,8 +2499,8 @@ var copyFile = (src, dest) => {
2295
2499
  var copyDirRecursive = (src, dest) => {
2296
2500
  mkdirSync3(dest, { recursive: true });
2297
2501
  for (const entry of readdirSync(src, { withFileTypes: true })) {
2298
- const srcPath = join2(src, entry.name);
2299
- const destPath = join2(dest, entry.name);
2502
+ const srcPath = join3(src, entry.name);
2503
+ const destPath = join3(dest, entry.name);
2300
2504
  if (entry.isDirectory()) {
2301
2505
  copyDirRecursive(srcPath, destPath);
2302
2506
  } else {
@@ -2305,22 +2509,22 @@ var copyDirRecursive = (src, dest) => {
2305
2509
  }
2306
2510
  };
2307
2511
  var installHook = (home, verbose = false) => {
2308
- const claudeDir = join2(home, ".claude");
2512
+ const claudeDir = join3(home, ".claude");
2309
2513
  if (!existsSync2(claudeDir)) {
2310
2514
  if (verbose) {
2311
2515
  stepInfo(`~/.claude not found \u2014 Claude Code not installed, skipping`);
2312
2516
  }
2313
2517
  return false;
2314
2518
  }
2315
- const zeroHooksDir = join2(home, ".zero", "hooks");
2519
+ const zeroHooksDir = join3(home, ".zero", "hooks");
2316
2520
  mkdirSync3(zeroHooksDir, { recursive: true });
2317
2521
  if (verbose) stepInfo(`staged hook dir at ${zeroHooksDir}`);
2318
2522
  const hookFiles = ["auto-approve-zero.sh", "zero-context.sh"];
2319
2523
  const hookDests = {};
2320
2524
  const hooksSourceDir = findResourceDir(getCliModuleDir(), "hooks");
2321
2525
  for (const hookFile of hookFiles) {
2322
- const hookSource = join2(hooksSourceDir, hookFile);
2323
- const hookDest = join2(zeroHooksDir, hookFile);
2526
+ const hookSource = join3(hooksSourceDir, hookFile);
2527
+ const hookDest = join3(zeroHooksDir, hookFile);
2324
2528
  copyFile(hookSource, hookDest);
2325
2529
  chmodSync2(hookDest, 493);
2326
2530
  if (!verifyFileCopy(hookSource, hookDest)) {
@@ -2331,14 +2535,14 @@ var installHook = (home, verbose = false) => {
2331
2535
  hookDests[hookFile] = hookDest;
2332
2536
  if (verbose) stepInfo(`copied ${hookFile} \u2192 ${hookDest} (verified)`);
2333
2537
  }
2334
- const settingsPath = join2(claudeDir, "settings.json");
2538
+ const settingsPath = join3(claudeDir, "settings.json");
2335
2539
  let settings = {};
2336
2540
  let settingsExisted = false;
2337
2541
  let settingsCorrupted = false;
2338
2542
  if (existsSync2(settingsPath)) {
2339
2543
  settingsExisted = true;
2340
2544
  try {
2341
- settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
2545
+ settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
2342
2546
  } catch {
2343
2547
  settingsCorrupted = true;
2344
2548
  }
@@ -2436,7 +2640,7 @@ var CONFLICTING_SKILL_PATTERNS = ["zam"];
2436
2640
  var findConflictingSkills = (home) => {
2437
2641
  const found = [];
2438
2642
  for (const tool of AGENT_TOOLS) {
2439
- const toolSkillsPath = join2(home, tool.skillsDir);
2643
+ const toolSkillsPath = join3(home, tool.skillsDir);
2440
2644
  if (!existsSync2(toolSkillsPath)) continue;
2441
2645
  const entries = readdirSync(toolSkillsPath, { withFileTypes: true });
2442
2646
  for (const entry of entries) {
@@ -2445,7 +2649,7 @@ var findConflictingSkills = (home) => {
2445
2649
  if (CONFLICTING_SKILL_PATTERNS.some((p) => lower.includes(p))) {
2446
2650
  found.push({
2447
2651
  tool: tool.name,
2448
- skillPath: join2(toolSkillsPath, entry.name),
2652
+ skillPath: join3(toolSkillsPath, entry.name),
2449
2653
  skillName: entry.name
2450
2654
  });
2451
2655
  }
@@ -2473,7 +2677,7 @@ var installSkills = (home, verbose = false) => {
2473
2677
  const installed = [];
2474
2678
  const errors = [];
2475
2679
  for (const tool of AGENT_TOOLS) {
2476
- const toolDetectPath = join2(home, tool.detectDir);
2680
+ const toolDetectPath = join3(home, tool.detectDir);
2477
2681
  if (!existsSync2(toolDetectPath)) {
2478
2682
  if (verbose) {
2479
2683
  stepInfo(
@@ -2488,16 +2692,16 @@ var installSkills = (home, verbose = false) => {
2488
2692
  );
2489
2693
  }
2490
2694
  try {
2491
- const toolSkillsPath = join2(home, tool.skillsDir);
2695
+ const toolSkillsPath = join3(home, tool.skillsDir);
2492
2696
  mkdirSync3(toolSkillsPath, { recursive: true });
2493
2697
  for (const skillDir of skillDirs) {
2494
- const src = join2(skillsSourceDir, skillDir);
2495
- const dest = join2(toolSkillsPath, skillDir);
2698
+ const src = join3(skillsSourceDir, skillDir);
2699
+ const dest = join3(toolSkillsPath, skillDir);
2496
2700
  const existed = existsSync2(dest);
2497
2701
  copyDirRecursive(src, dest);
2498
2702
  for (const srcFile of collectAllFiles(src)) {
2499
2703
  const relPath = relative(src, srcFile);
2500
- const destFile = join2(dest, relPath);
2704
+ const destFile = join3(dest, relPath);
2501
2705
  if (!verifyFileCopy(srcFile, destFile)) {
2502
2706
  throw new Error(
2503
2707
  `Integrity check failed: ${destFile} does not match source`
@@ -2528,15 +2732,15 @@ var runInit = async (appContext, options = {}) => {
2528
2732
  let currentStep = "wallet";
2529
2733
  try {
2530
2734
  printZeroBanner();
2531
- const home = homedir2();
2532
- const zeroDir = join2(home, ".zero");
2533
- const configPath = join2(zeroDir, "config.json");
2735
+ const home = homedir3();
2736
+ const zeroDir = join3(home, ".zero");
2737
+ const configPath = join3(zeroDir, "config.json");
2534
2738
  let walletCreated = false;
2535
2739
  let walletAddress = null;
2536
2740
  const walletExists = (() => {
2537
2741
  if (!existsSync2(configPath)) return false;
2538
2742
  try {
2539
- const existing = JSON.parse(readFileSync4(configPath, "utf8"));
2743
+ const existing = JSON.parse(readFileSync5(configPath, "utf8"));
2540
2744
  return !!existing.privateKey;
2541
2745
  } catch {
2542
2746
  return false;
@@ -2546,7 +2750,7 @@ var runInit = async (appContext, options = {}) => {
2546
2750
  const privateKey = generatePrivateKey();
2547
2751
  const account = privateKeyToAccount(privateKey);
2548
2752
  ensureSecureDir(zeroDir);
2549
- const existing = existsSync2(configPath) ? JSON.parse(readFileSync4(configPath, "utf8")) : {};
2753
+ const existing = existsSync2(configPath) ? JSON.parse(readFileSync5(configPath, "utf8")) : {};
2550
2754
  writeSecureFile(
2551
2755
  configPath,
2552
2756
  JSON.stringify(
@@ -2569,7 +2773,7 @@ var runInit = async (appContext, options = {}) => {
2569
2773
  }
2570
2774
  } else {
2571
2775
  try {
2572
- const existing = JSON.parse(readFileSync4(configPath, "utf8"));
2776
+ const existing = JSON.parse(readFileSync5(configPath, "utf8"));
2573
2777
  const account = privateKeyToAccount(existing.privateKey);
2574
2778
  walletAddress = account.address;
2575
2779
  } catch {
@@ -2590,7 +2794,7 @@ var runInit = async (appContext, options = {}) => {
2590
2794
  let hookInstalled = false;
2591
2795
  let hookError = null;
2592
2796
  for (const tool of AGENT_TOOLS) {
2593
- if (existsSync2(join2(home, tool.detectDir))) {
2797
+ if (existsSync2(join3(home, tool.detectDir))) {
2594
2798
  agentsDetected.push(tool.name);
2595
2799
  }
2596
2800
  }
@@ -2700,14 +2904,14 @@ To remove them, run: ${color.cyan("zero init cleanup")}`
2700
2904
  throw err;
2701
2905
  }
2702
2906
  };
2703
- var initCommand = (appContext) => new Command5("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").option(
2907
+ var initCommand = (appContext) => new Command6("init").description("Initialize Zero CLI for usage").option("--force", "Overwrite existing configuration").option(
2704
2908
  "-v, --verbose",
2705
2909
  "Explain why each install step was taken or skipped"
2706
2910
  ).action(async (options) => {
2707
2911
  await runInit(appContext, options);
2708
2912
  }).addCommand(
2709
- new Command5("cleanup").description("Remove deprecated skills (zam) that conflict with Zero").action(() => {
2710
- const home = homedir2();
2913
+ new Command6("cleanup").description("Remove deprecated skills (zam) that conflict with Zero").action(() => {
2914
+ const home = homedir3();
2711
2915
  const removed = removeConflictingSkills(home);
2712
2916
  if (removed.length === 0) {
2713
2917
  console.error("No conflicting skills found. Nothing to remove.");
@@ -2726,8 +2930,8 @@ ${removedList}`);
2726
2930
  );
2727
2931
 
2728
2932
  // src/commands/review-command.ts
2729
- import { readFileSync as readFileSync5 } from "fs";
2730
- import { Command as Command6 } from "commander";
2933
+ import { readFileSync as readFileSync6 } from "fs";
2934
+ import { Command as Command7 } from "commander";
2731
2935
  import { z as z3 } from "zod";
2732
2936
  var bulkEntrySchema2 = z3.object({
2733
2937
  runId: z3.string(),
@@ -2737,7 +2941,7 @@ var bulkEntrySchema2 = z3.object({
2737
2941
  reliability: z3.number().int().min(1).max(5),
2738
2942
  content: z3.string().optional()
2739
2943
  });
2740
- var reviewCommand = (appContext) => new Command6("review").description("Submit a review for a capability run").addHelpText(
2944
+ var reviewCommand = (appContext) => new Command7("review").description("Submit a review for a capability run").addHelpText(
2741
2945
  "after",
2742
2946
  `
2743
2947
  Tips for a great review:
@@ -2768,7 +2972,7 @@ Examples:
2768
2972
  try {
2769
2973
  const { analyticsService, apiService } = appContext.services;
2770
2974
  if (options.fromFile) {
2771
- const contents = readFileSync5(options.fromFile, "utf8");
2975
+ const contents = readFileSync6(options.fromFile, "utf8");
2772
2976
  const lines = contents.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
2773
2977
  const parsed = [];
2774
2978
  let parseFailures = 0;
@@ -2916,7 +3120,7 @@ Bulk review complete: ${ok} ok, ${failed} failed`);
2916
3120
  );
2917
3121
 
2918
3122
  // src/commands/runs-command.ts
2919
- import { Command as Command7 } from "commander";
3123
+ import { Command as Command8 } from "commander";
2920
3124
  var USD_ASSETS = /* @__PURE__ */ new Set(["USD", "USDC"]);
2921
3125
  var formatCost2 = (cost) => {
2922
3126
  if (!cost) return "free";
@@ -2931,7 +3135,7 @@ var formatPayment = (payment) => {
2931
3135
  const mode = payment.mode ? ` (${payment.mode})` : "";
2932
3136
  return `${payment.protocol}${chain}${mode}`;
2933
3137
  };
2934
- var runsCommand = (appContext) => new Command7("runs").description("List your recent capability runs").addHelpText(
3138
+ var runsCommand = (appContext) => new Command8("runs").description("List your recent capability runs").addHelpText(
2935
3139
  "after",
2936
3140
  `
2937
3141
  View your recent capability runs \u2014 status, latency, cost, and payment info.
@@ -2989,7 +3193,7 @@ Examples:
2989
3193
  );
2990
3194
 
2991
3195
  // src/commands/search-command.ts
2992
- import { Command as Command8 } from "commander";
3196
+ import { Command as Command9 } from "commander";
2993
3197
  var DEFAULT_MAX_COST_USD = "30";
2994
3198
  var formatReviewCount2 = (count) => {
2995
3199
  if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
@@ -3021,12 +3225,12 @@ var formatSearchResults = (results) => {
3021
3225
  "${displayDescription}"`;
3022
3226
  }).join("\n");
3023
3227
  };
3024
- var searchCommand = (appContext) => new Command8("search").description("Search for capabilities").argument("<query>", "Search query").option("--json", "Output raw JSON to stdout").option("--offset <n>", "Pagination offset", Number).option("--limit <n>", "Results per page", Number).option("--free", "Only show free capabilities").option(
3228
+ var searchCommand = (appContext) => new Command9("search").description("Search for capabilities").argument("<query>", "Search query").option("--json", "Output raw JSON to stdout").option("--offset <n>", "Pagination offset", Number).option("--limit <n>", "Results per page", Number).option("--free", "Only show free capabilities").option(
3025
3229
  "--max-cost <amount>",
3026
3230
  `Maximum cost per call in USD (default: ${DEFAULT_MAX_COST_USD})`
3027
3231
  ).option("--protocol <protocol>", "Payment protocol (x402 or mpp)").option(
3028
3232
  "--status <status>",
3029
- "Filter by availability (healthy, degraded, down)"
3233
+ "Filter by availability (healthy, unknown, down). Defaults to healthy when neither --status nor --all is set; pass --all to see everything."
3030
3234
  ).option("--all", "Disable default quality filtering").option(
3031
3235
  "--source <source>",
3032
3236
  "Only show results from this crawl source (e.g. mpp, bazaar)"
@@ -3053,14 +3257,20 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
3053
3257
  effectiveMaxCost = DEFAULT_MAX_COST_USD;
3054
3258
  appliedDefaultMaxCost = true;
3055
3259
  }
3056
- const validStatuses = ["healthy", "degraded", "down"];
3260
+ const validStatuses = ["healthy", "unknown", "degraded", "down"];
3057
3261
  if (options.status && !validStatuses.includes(options.status)) {
3058
3262
  console.error(
3059
- `Invalid status "${options.status}". Must be one of: healthy, degraded, down.`
3263
+ `Invalid status "${options.status}". Must be one of: ${validStatuses.join(", ")}.`
3060
3264
  );
3061
3265
  process.exitCode = 1;
3062
3266
  return;
3063
3267
  }
3268
+ let effectiveStatus = options.status;
3269
+ let appliedDefaultStatus = false;
3270
+ if (effectiveStatus === void 0 && !options.all) {
3271
+ effectiveStatus = "healthy";
3272
+ appliedDefaultStatus = true;
3273
+ }
3064
3274
  const result = await apiService.search({
3065
3275
  query,
3066
3276
  offset: options.offset,
@@ -3068,7 +3278,7 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
3068
3278
  freeOnly: options.free,
3069
3279
  maxCost: effectiveMaxCost,
3070
3280
  protocol: options.protocol,
3071
- availabilityStatus: options.status,
3281
+ availabilityStatus: effectiveStatus,
3072
3282
  includeAll: options.all,
3073
3283
  source: options.source,
3074
3284
  excludeSource: options.excludeSource
@@ -3086,7 +3296,8 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
3086
3296
  maxCost: effectiveMaxCost,
3087
3297
  maxCostDefaulted: appliedDefaultMaxCost,
3088
3298
  protocol: options.protocol,
3089
- availabilityStatus: options.status,
3299
+ availabilityStatus: effectiveStatus,
3300
+ availabilityStatusDefaulted: appliedDefaultStatus,
3090
3301
  includeAll: options.all ?? false,
3091
3302
  listingSource: options.source,
3092
3303
  excludeListingSource: options.excludeSource,
@@ -3137,9 +3348,9 @@ var searchCommand = (appContext) => new Command8("search").description("Search f
3137
3348
  );
3138
3349
 
3139
3350
  // src/commands/terms-command.ts
3140
- import { Command as Command9 } from "commander";
3351
+ import { Command as Command10 } from "commander";
3141
3352
  var TERMS_URL = "https://zero.xyz/terms-of-service";
3142
- var termsCommand = (_appContext) => new Command9("terms").description("View the ZeroClick Terms of Service").action(async () => {
3353
+ var termsCommand = (_appContext) => new Command10("terms").description("View the ZeroClick Terms of Service").action(async () => {
3143
3354
  console.log(
3144
3355
  `ZeroClick Agentic Capability Search \u2014 Terms of Service
3145
3356
 
@@ -3157,11 +3368,11 @@ Read the full terms at: ${TERMS_URL}
3157
3368
  });
3158
3369
 
3159
3370
  // src/commands/wallet-command.ts
3160
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
3161
- import { homedir as homedir3 } from "os";
3162
- import { join as join3 } from "path";
3163
- import { Command as Command10 } from "commander";
3164
- import open from "open";
3371
+ import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
3372
+ import { homedir as homedir4 } from "os";
3373
+ import { join as join4 } from "path";
3374
+ import { Command as Command11 } from "commander";
3375
+ import open2 from "open";
3165
3376
  import { isAddress } from "viem";
3166
3377
  import { generatePrivateKey as generatePrivateKey2, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
3167
3378
 
@@ -3177,7 +3388,7 @@ var readStdin = async () => {
3177
3388
  // src/commands/wallet-command.ts
3178
3389
  var PRIVATE_KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
3179
3390
  var parseProvider = (raw) => raw === "stripe" ? "stripe" : "coinbase";
3180
- var walletBalanceCommand = (appContext) => new Command10("balance").description("Show wallet balance").option(
3391
+ var walletBalanceCommand = (appContext) => new Command11("balance").description("Show wallet balance").option(
3181
3392
  "--address <address>",
3182
3393
  "Check balance for an arbitrary address (no key needed). Useful with `zero wallet generate` \u2014 verify funds arrived without setting the wallet as your default."
3183
3394
  ).action(async (options) => {
@@ -3226,7 +3437,7 @@ var readPrivateKeyFromStdin = async () => {
3226
3437
  }
3227
3438
  return raw;
3228
3439
  };
3229
- 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(
3440
+ var walletFundCommand = (appContext) => new Command11("fund").description("Fund your wallet").argument("[amount]", "Amount to fund in USDC").option("--manual", "Show wallet address for manual transfer").option(
3230
3441
  "--no-open",
3231
3442
  "Print the funding URL instead of opening a browser (for agents \u2014 funding links are one-time use, hand the URL to the user)"
3232
3443
  ).option(
@@ -3300,7 +3511,7 @@ ${address}`);
3300
3511
  const url = await apiService.getFundingUrl(amount, provider);
3301
3512
  if (url) {
3302
3513
  if (options.open) {
3303
- await open(url);
3514
+ await open2(url);
3304
3515
  console.log("Opened funding page in your browser.");
3305
3516
  console.log(`If it didn't open, visit: ${url}`);
3306
3517
  } else {
@@ -3323,7 +3534,7 @@ ${address}`);
3323
3534
  }
3324
3535
  }
3325
3536
  );
3326
- var walletAddressCommand = (appContext) => new Command10("address").description("Show wallet address").action(() => {
3537
+ var walletAddressCommand = (appContext) => new Command11("address").description("Show wallet address").action(() => {
3327
3538
  const { walletService } = appContext.services;
3328
3539
  const address = walletService.getAddress();
3329
3540
  if (!address) {
@@ -3333,7 +3544,7 @@ var walletAddressCommand = (appContext) => new Command10("address").description(
3333
3544
  }
3334
3545
  console.log(address);
3335
3546
  });
3336
- var walletSetCommand = (appContext) => new Command10("set").description("Set wallet from an existing private key").argument("<privateKey>", "Hex-encoded private key (0x-prefixed)").option("--force", "Overwrite existing wallet without prompting").action(async (privateKey, options) => {
3547
+ var walletSetCommand = (appContext) => new Command11("set").description("Set wallet from an existing private key").argument("<privateKey>", "Hex-encoded private key (0x-prefixed)").option("--force", "Overwrite existing wallet without prompting").action(async (privateKey, options) => {
3337
3548
  const { analyticsService } = appContext.services;
3338
3549
  if (!privateKey.startsWith("0x")) {
3339
3550
  console.error("Private key must be 0x-prefixed hex string.");
@@ -3348,11 +3559,11 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
3348
3559
  process.exitCode = 1;
3349
3560
  return;
3350
3561
  }
3351
- const zeroDir = join3(homedir3(), ".zero");
3352
- const configPath = join3(zeroDir, "config.json");
3562
+ const zeroDir = join4(homedir4(), ".zero");
3563
+ const configPath = join4(zeroDir, "config.json");
3353
3564
  if (!options.force && existsSync3(configPath)) {
3354
3565
  try {
3355
- const existing2 = JSON.parse(readFileSync6(configPath, "utf8"));
3566
+ const existing2 = JSON.parse(readFileSync7(configPath, "utf8"));
3356
3567
  if (existing2.privateKey) {
3357
3568
  console.error(
3358
3569
  "Wallet already configured. Use --force to overwrite."
@@ -3364,7 +3575,7 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
3364
3575
  }
3365
3576
  }
3366
3577
  ensureSecureDir(zeroDir);
3367
- const existing = existsSync3(configPath) ? JSON.parse(readFileSync6(configPath, "utf8")) : {};
3578
+ const existing = existsSync3(configPath) ? JSON.parse(readFileSync7(configPath, "utf8")) : {};
3368
3579
  writeSecureFile(
3369
3580
  configPath,
3370
3581
  JSON.stringify(
@@ -3383,7 +3594,7 @@ var walletSetCommand = (appContext) => new Command10("set").description("Set wal
3383
3594
  force: options.force ?? false
3384
3595
  });
3385
3596
  });
3386
- var walletGenerateCommand = (appContext) => new Command10("generate").description(
3597
+ var walletGenerateCommand = (appContext) => new Command11("generate").description(
3387
3598
  "Generate a fresh wallet (address + private key) without touching your configured wallet"
3388
3599
  ).option("--json", "Emit { address, privateKey } as JSON").option(
3389
3600
  "--fund",
@@ -3461,7 +3672,7 @@ var walletGenerateCommand = (appContext) => new Command10("generate").descriptio
3461
3672
  }
3462
3673
  );
3463
3674
  var walletCommand = (appContext) => {
3464
- const cmd = new Command10("wallet").description("Manage your wallet");
3675
+ const cmd = new Command11("wallet").description("Manage your wallet");
3465
3676
  cmd.addCommand(walletBalanceCommand(appContext));
3466
3677
  cmd.addCommand(walletFundCommand(appContext));
3467
3678
  cmd.addCommand(walletAddressCommand(appContext));
@@ -3471,18 +3682,18 @@ var walletCommand = (appContext) => {
3471
3682
  };
3472
3683
 
3473
3684
  // src/commands/welcome-command.ts
3474
- import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
3475
- import { homedir as homedir4 } from "os";
3476
- import { join as join4 } from "path";
3477
- import { Command as Command11 } from "commander";
3478
- import open2 from "open";
3685
+ import { existsSync as existsSync4, readFileSync as readFileSync8 } from "fs";
3686
+ import { homedir as homedir5 } from "os";
3687
+ import { join as join5 } from "path";
3688
+ import { Command as Command12 } from "commander";
3689
+ import open3 from "open";
3479
3690
  import { getAddress } from "viem";
3480
3691
  import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
3481
3692
  var readPrivateKey = () => {
3482
- const configPath = join4(homedir4(), ".zero", "config.json");
3693
+ const configPath = join5(homedir5(), ".zero", "config.json");
3483
3694
  if (!existsSync4(configPath)) return null;
3484
3695
  try {
3485
- const config = JSON.parse(readFileSync7(configPath, "utf8"));
3696
+ const config = JSON.parse(readFileSync8(configPath, "utf8"));
3486
3697
  if (typeof config.privateKey === "string") {
3487
3698
  return config.privateKey;
3488
3699
  }
@@ -3517,7 +3728,7 @@ var printManualFallback = (url) => {
3517
3728
  }
3518
3729
  console.log(lines.join("\n"));
3519
3730
  };
3520
- var welcomeCommand = (appContext) => new Command11("welcome").description("Claim your $5 welcome bonus.").action(async () => {
3731
+ var welcomeCommand = (appContext) => new Command12("welcome").description("Claim your $5 welcome bonus.").action(async () => {
3521
3732
  const { analyticsService } = appContext.services;
3522
3733
  analyticsService.capture("welcome_started", {});
3523
3734
  let walletAddress;
@@ -3550,7 +3761,7 @@ var welcomeCommand = (appContext) => new Command11("welcome").description("Claim
3550
3761
  const urlString = url.toString();
3551
3762
  analyticsService.setWalletAddress(walletAddress);
3552
3763
  try {
3553
- await open2(urlString);
3764
+ await open3(urlString);
3554
3765
  console.log(
3555
3766
  `Opening ${urlString}
3556
3767
 
@@ -3577,7 +3788,7 @@ If your browser didn't open, paste the URL above.`
3577
3788
  // src/app.ts
3578
3789
  var createApp = (appContext) => {
3579
3790
  const { analyticsService } = appContext.services;
3580
- const program = new Command12().name("zero").description("Zero CLI \u2014 Search engine for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
3791
+ const program = new Command13().name("zero").description("Zero CLI \u2014 Search engine for AI agents").version(package_default.version, "-v, --version").exitOverride().hook("preAction", async (_thisCommand, actionCommand) => {
3581
3792
  const agentFlag = actionCommand.opts().agent;
3582
3793
  if (typeof agentFlag === "string" && agentFlag.trim().length > 0) {
3583
3794
  analyticsService.setAgentHost(agentFlag.trim());
@@ -3601,6 +3812,7 @@ var createApp = (appContext) => {
3601
3812
  program.addCommand(configCommand(appContext));
3602
3813
  program.addCommand(termsCommand(appContext));
3603
3814
  program.addCommand(welcomeCommand(appContext));
3815
+ program.addCommand(authCommand(appContext), { hidden: true });
3604
3816
  return program;
3605
3817
  };
3606
3818
 
@@ -3624,14 +3836,14 @@ var getEnv = () => {
3624
3836
 
3625
3837
  // src/app/app-services.ts
3626
3838
  import { randomUUID as randomUUID2 } from "crypto";
3627
- import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
3628
- import { homedir as homedir5 } from "os";
3629
- import { join as join6 } from "path";
3839
+ import { existsSync as existsSync7 } from "fs";
3840
+ import { homedir as homedir6 } from "os";
3841
+ import { join as join7 } from "path";
3630
3842
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
3631
3843
 
3632
3844
  // src/services/analytics-service.ts
3633
3845
  import { randomUUID } from "crypto";
3634
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
3846
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
3635
3847
  import { dirname as dirname2 } from "path";
3636
3848
  import { PostHog } from "posthog-node";
3637
3849
  var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
@@ -3654,7 +3866,7 @@ var AnalyticsService = class {
3654
3866
  let persistedAnonId;
3655
3867
  try {
3656
3868
  if (existsSync5(opts.configPath)) {
3657
- const config = JSON.parse(readFileSync8(opts.configPath, "utf8"));
3869
+ const config = JSON.parse(readFileSync9(opts.configPath, "utf8"));
3658
3870
  if (config.telemetry === false) {
3659
3871
  telemetryEnabled = false;
3660
3872
  }
@@ -3679,7 +3891,7 @@ var AnalyticsService = class {
3679
3891
  try {
3680
3892
  const dir = dirname2(opts.configPath);
3681
3893
  mkdirSync4(dir, { recursive: true });
3682
- const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync8(opts.configPath, "utf8")) : {};
3894
+ const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync9(opts.configPath, "utf8")) : {};
3683
3895
  writeFileSync4(
3684
3896
  opts.configPath,
3685
3897
  JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
@@ -3719,7 +3931,7 @@ var AnalyticsService = class {
3719
3931
  if (anonId === walletAddress) return;
3720
3932
  let aliasedTo;
3721
3933
  try {
3722
- const config = JSON.parse(readFileSync8(configPath, "utf8"));
3934
+ const config = JSON.parse(readFileSync9(configPath, "utf8"));
3723
3935
  if (typeof config.aliasedTo === "string") {
3724
3936
  aliasedTo = config.aliasedTo;
3725
3937
  }
@@ -3728,7 +3940,7 @@ var AnalyticsService = class {
3728
3940
  if (aliasedTo === walletAddress) return;
3729
3941
  this.posthog.alias({ distinctId: walletAddress, alias: anonId });
3730
3942
  try {
3731
- const config = existsSync5(configPath) ? JSON.parse(readFileSync8(configPath, "utf8")) : {};
3943
+ const config = existsSync5(configPath) ? JSON.parse(readFileSync9(configPath, "utf8")) : {};
3732
3944
  writeFileSync4(
3733
3945
  configPath,
3734
3946
  JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
@@ -3804,14 +4016,14 @@ var AnalyticsService = class {
3804
4016
  };
3805
4017
 
3806
4018
  // src/services/state-service.ts
3807
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
3808
- import { join as join5 } from "path";
4019
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
4020
+ import { join as join6 } from "path";
3809
4021
  var RECENT_SEARCH_LIMIT = 10;
3810
4022
  var StateService = class {
3811
4023
  constructor(zeroDir) {
3812
4024
  this.zeroDir = zeroDir;
3813
- this.lastSearchPath = join5(zeroDir, "last_search.json");
3814
- this.recentSearchesPath = join5(zeroDir, "recent_searches.json");
4025
+ this.lastSearchPath = join6(zeroDir, "last_search.json");
4026
+ this.recentSearchesPath = join6(zeroDir, "recent_searches.json");
3815
4027
  }
3816
4028
  lastSearchPath;
3817
4029
  recentSearchesPath;
@@ -3831,7 +4043,7 @@ var StateService = class {
3831
4043
  loadLastSearch = () => {
3832
4044
  try {
3833
4045
  if (!existsSync6(this.lastSearchPath)) return null;
3834
- const raw = readFileSync9(this.lastSearchPath, "utf8");
4046
+ const raw = readFileSync10(this.lastSearchPath, "utf8");
3835
4047
  return JSON.parse(raw);
3836
4048
  } catch {
3837
4049
  return null;
@@ -3843,7 +4055,7 @@ var StateService = class {
3843
4055
  const last = this.loadLastSearch();
3844
4056
  return { searches: last ? [last] : [] };
3845
4057
  }
3846
- const raw = readFileSync9(this.recentSearchesPath, "utf8");
4058
+ const raw = readFileSync10(this.recentSearchesPath, "utf8");
3847
4059
  const parsed = JSON.parse(raw);
3848
4060
  return { searches: parsed.searches ?? [] };
3849
4061
  } catch {
@@ -3941,40 +4153,63 @@ var detectAgentHost = (env = process.env) => {
3941
4153
 
3942
4154
  // src/app/app-services.ts
3943
4155
  var CLI_VERSION = package_default.version;
3944
- var getServices = (env) => {
3945
- let privateKey = env.ZERO_PRIVATE_KEY ? env.ZERO_PRIVATE_KEY : null;
3946
- const zeroDir = join6(homedir5(), ".zero");
3947
- const configPath = join6(zeroDir, "config.json");
3948
- if (!privateKey) {
3949
- try {
3950
- if (existsSync7(configPath)) {
3951
- const config = JSON.parse(readFileSync10(configPath, "utf8"));
3952
- if (typeof config.privateKey === "string") {
3953
- privateKey = config.privateKey;
3954
- }
3955
- }
3956
- } catch {
3957
- }
4156
+ var resolveCredentials = (env, config) => {
4157
+ if (config.session) {
4158
+ return {
4159
+ credentials: {
4160
+ kind: "session",
4161
+ accessToken: config.session.accessToken,
4162
+ refreshToken: config.session.refreshToken,
4163
+ userId: config.session.userId
4164
+ },
4165
+ privateKey: null
4166
+ };
3958
4167
  }
3959
- const account = privateKey ? privateKeyToAccount4(privateKey) : null;
3960
- let lowBalanceWarning = 1;
3961
- try {
3962
- if (existsSync7(configPath)) {
3963
- const config = JSON.parse(readFileSync10(configPath, "utf8"));
3964
- if (typeof config.lowBalanceWarning === "number") {
3965
- lowBalanceWarning = config.lowBalanceWarning;
3966
- }
3967
- }
3968
- } catch {
4168
+ if (env.ZERO_PRIVATE_KEY) {
4169
+ return {
4170
+ credentials: { kind: "none" },
4171
+ privateKey: env.ZERO_PRIVATE_KEY
4172
+ };
4173
+ }
4174
+ if (config.privateKey) {
4175
+ return {
4176
+ credentials: { kind: "none" },
4177
+ privateKey: config.privateKey
4178
+ };
3969
4179
  }
3970
- const apiService = new ApiService(env.ZERO_API_URL, account);
4180
+ return { credentials: { kind: "none" }, privateKey: null };
4181
+ };
4182
+ var buildOnSessionRefreshed = (configPath) => async (tokens) => {
4183
+ const current = readConfig(configPath);
4184
+ if (!current.session) return;
4185
+ const next = {
4186
+ ...current,
4187
+ session: {
4188
+ ...current.session,
4189
+ accessToken: tokens.accessToken,
4190
+ refreshToken: tokens.refreshToken
4191
+ }
4192
+ };
4193
+ writeSecureFile(configPath, JSON.stringify(next, null, 2));
4194
+ };
4195
+ var getServices = (env) => {
4196
+ const zeroDir = join7(homedir6(), ".zero");
4197
+ const configPath = join7(zeroDir, "config.json");
4198
+ const config = existsSync7(configPath) ? readConfig(configPath) : {};
4199
+ const { credentials, privateKey } = resolveCredentials(env, config);
4200
+ const account = privateKey ? privateKeyToAccount4(privateKey) : null;
4201
+ const lowBalanceWarning = typeof config.lowBalanceWarning === "number" ? config.lowBalanceWarning : 1;
4202
+ const apiService = new ApiService(
4203
+ env.ZERO_API_URL,
4204
+ account,
4205
+ credentials,
4206
+ buildOnSessionRefreshed(configPath)
4207
+ );
3971
4208
  const paymentService = new PaymentService(account, { lowBalanceWarning });
3972
4209
  const stateService = new StateService(zeroDir);
3973
4210
  const walletService = new WalletService(
3974
4211
  apiService.walletAddress,
3975
- {
3976
- lowBalanceWarning
3977
- },
4212
+ { lowBalanceWarning },
3978
4213
  paymentService.getTotalBalance
3979
4214
  );
3980
4215
  const analyticsService = new AnalyticsService({
@@ -4016,8 +4251,8 @@ import {
4016
4251
  readlinkSync,
4017
4252
  writeFileSync as writeFileSync6
4018
4253
  } from "fs";
4019
- import { homedir as homedir6 } from "os";
4020
- import { dirname as dirname3, join as join7, resolve } from "path";
4254
+ import { homedir as homedir7 } from "os";
4255
+ import { dirname as dirname3, join as join8, resolve } from "path";
4021
4256
  var CACHE_FILENAME = "update_check.json";
4022
4257
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@zeroxyz/cli/latest";
4023
4258
  var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
@@ -4041,10 +4276,10 @@ var resolveExecPath = (execPath) => {
4041
4276
  var detectInstallMethod = (opts = {}) => {
4042
4277
  const execPath = opts.execPath ?? process.execPath;
4043
4278
  const pkg = opts.pkg ?? process.pkg;
4044
- const home = opts.home ?? homedir6();
4279
+ const home = opts.home ?? homedir7();
4045
4280
  if (pkg) return "binary";
4046
4281
  const resolved = resolveExecPath(execPath);
4047
- const zeroBin = join7(home, ".zero", "bin");
4282
+ const zeroBin = join8(home, ".zero", "bin");
4048
4283
  if (resolved.startsWith(zeroBin)) return "binary";
4049
4284
  return "npm";
4050
4285
  };
@@ -4069,7 +4304,7 @@ var compareVersions = (a, b) => {
4069
4304
  if (pb.pre === null) return -1;
4070
4305
  return pa.pre < pb.pre ? -1 : 1;
4071
4306
  };
4072
- var cachePath = (zeroDir) => join7(zeroDir, CACHE_FILENAME);
4307
+ var cachePath = (zeroDir) => join8(zeroDir, CACHE_FILENAME);
4073
4308
  var readCache = (zeroDir) => {
4074
4309
  try {
4075
4310
  const path = cachePath(zeroDir);
@@ -4163,7 +4398,7 @@ var main = async () => {
4163
4398
  console.error("Failed to create app context");
4164
4399
  process.exit(1);
4165
4400
  }
4166
- const zeroDir = join8(homedir7(), ".zero");
4401
+ const zeroDir = join9(homedir8(), ".zero");
4167
4402
  maybePrintUpdateBanner(zeroDir, package_default.version);
4168
4403
  const app = createApp(appContext);
4169
4404
  let caughtError = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",