@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.
- package/dist/index.js +403 -168
- 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
|
|
5
|
-
import { join as
|
|
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.
|
|
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
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
408
|
+
return { ...base2, ...walletHeaders };
|
|
263
409
|
}
|
|
264
|
-
|
|
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
|
|
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 =
|
|
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
|
|
569
|
-
import { homedir } from "os";
|
|
570
|
-
import { join } from "path";
|
|
571
|
-
import { Command as
|
|
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(
|
|
793
|
+
return JSON.parse(readFileSync3(configPath, "utf8"));
|
|
577
794
|
} catch {
|
|
578
795
|
return {};
|
|
579
796
|
}
|
|
580
797
|
};
|
|
581
|
-
var configCommand = (_appContext) => new
|
|
582
|
-
const configPath =
|
|
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
|
-
|
|
613
|
-
|
|
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
|
|
836
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
620
837
|
import { resolve as resolvePath } from "path";
|
|
621
|
-
import { Command as
|
|
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
|
|
1289
|
-
return
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2302
|
+
readFileSync as readFileSync5,
|
|
2086
2303
|
rmSync,
|
|
2087
2304
|
statSync,
|
|
2088
2305
|
writeFileSync as writeFileSync3
|
|
2089
2306
|
} from "fs";
|
|
2090
|
-
import { homedir as
|
|
2091
|
-
import { dirname, join as
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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,
|
|
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 =
|
|
2299
|
-
const destPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2323
|
-
const hookDest =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
2695
|
+
const toolSkillsPath = join3(home, tool.skillsDir);
|
|
2492
2696
|
mkdirSync3(toolSkillsPath, { recursive: true });
|
|
2493
2697
|
for (const skillDir of skillDirs) {
|
|
2494
|
-
const src =
|
|
2495
|
-
const dest =
|
|
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 =
|
|
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 =
|
|
2532
|
-
const zeroDir =
|
|
2533
|
-
const configPath =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
2710
|
-
const home =
|
|
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
|
|
2730
|
-
import { Command as
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
3351
|
+
import { Command as Command10 } from "commander";
|
|
3141
3352
|
var TERMS_URL = "https://zero.xyz/terms-of-service";
|
|
3142
|
-
var termsCommand = (_appContext) => new
|
|
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
|
|
3161
|
-
import { homedir as
|
|
3162
|
-
import { join as
|
|
3163
|
-
import { Command as
|
|
3164
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
3352
|
-
const configPath =
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
3475
|
-
import { homedir as
|
|
3476
|
-
import { join as
|
|
3477
|
-
import { Command as
|
|
3478
|
-
import
|
|
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 =
|
|
3693
|
+
const configPath = join5(homedir5(), ".zero", "config.json");
|
|
3483
3694
|
if (!existsSync4(configPath)) return null;
|
|
3484
3695
|
try {
|
|
3485
|
-
const config = JSON.parse(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3628
|
-
import { homedir as
|
|
3629
|
-
import { join as
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3808
|
-
import { join as
|
|
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 =
|
|
3814
|
-
this.recentSearchesPath =
|
|
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 =
|
|
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 =
|
|
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
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
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
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
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
|
-
|
|
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
|
|
4020
|
-
import { dirname as dirname3, join as
|
|
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 ??
|
|
4279
|
+
const home = opts.home ?? homedir7();
|
|
4045
4280
|
if (pkg) return "binary";
|
|
4046
4281
|
const resolved = resolveExecPath(execPath);
|
|
4047
|
-
const zeroBin =
|
|
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) =>
|
|
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 =
|
|
4401
|
+
const zeroDir = join9(homedir8(), ".zero");
|
|
4167
4402
|
maybePrintUpdateBanner(zeroDir, package_default.version);
|
|
4168
4403
|
const app = createApp(appContext);
|
|
4169
4404
|
let caughtError = null;
|