bb-browser-api 0.11.5

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/cli.js ADDED
@@ -0,0 +1,3277 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ COMMAND_TIMEOUT,
4
+ DAEMON_JSON,
5
+ generateId,
6
+ httpJson,
7
+ isProcessAlive,
8
+ readDaemonJson
9
+ } from "./chunk-CRK6CV23.js";
10
+ import "./chunk-ERAIAHQ5.js";
11
+ import {
12
+ applyJq
13
+ } from "./chunk-3H3RKS2K.js";
14
+ import {
15
+ parseOpenClawJson
16
+ } from "./chunk-YEQ2M2XG.js";
17
+ import "./chunk-D4HDZEJT.js";
18
+
19
+ // packages/cli/src/index.ts
20
+ import { fileURLToPath as fileURLToPath2 } from "url";
21
+
22
+ // packages/cli/src/daemon-manager.ts
23
+ import { spawn as spawn2 } from "child_process";
24
+ import { unlink } from "fs/promises";
25
+ import { fileURLToPath } from "url";
26
+ import { dirname, resolve } from "path";
27
+ import { existsSync as existsSync2 } from "fs";
28
+
29
+ // packages/cli/src/cdp-discovery.ts
30
+ import { execFile, execSync, spawn } from "child_process";
31
+ import { existsSync } from "fs";
32
+ import { mkdir, readFile, writeFile } from "fs/promises";
33
+ import os from "os";
34
+ import path from "path";
35
+ var DEFAULT_CDP_PORT = 19825;
36
+ var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
37
+ var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
38
+ var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
39
+ var CDP_CACHE_FILE = path.join(os.tmpdir(), "bb-browser-cdp-cache.json");
40
+ var CACHE_TTL_MS = 3e4;
41
+ function execFileAsync(command, args, timeout) {
42
+ return new Promise((resolve2, reject) => {
43
+ execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
44
+ if (error) {
45
+ reject(error);
46
+ return;
47
+ }
48
+ resolve2(stdout.trim());
49
+ });
50
+ });
51
+ }
52
+ function getArgValue(flag) {
53
+ const index = process.argv.indexOf(flag);
54
+ if (index < 0) return void 0;
55
+ return process.argv[index + 1];
56
+ }
57
+ async function tryOpenClaw() {
58
+ try {
59
+ const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 3e4);
60
+ const parsed = parseOpenClawJson(raw);
61
+ let result = null;
62
+ if (parsed?.cdpUrl) {
63
+ try {
64
+ const url = new URL(parsed.cdpUrl);
65
+ const port = Number(url.port);
66
+ if (Number.isInteger(port) && port > 0) {
67
+ result = { host: url.hostname, port };
68
+ }
69
+ } catch {
70
+ }
71
+ }
72
+ if (!result) {
73
+ const port = Number(parsed?.cdpPort);
74
+ if (Number.isInteger(port) && port > 0) {
75
+ const host = parsed?.cdpHost || "127.0.0.1";
76
+ result = { host, port };
77
+ }
78
+ }
79
+ if (result) {
80
+ try {
81
+ await writeFile(CDP_CACHE_FILE, JSON.stringify({ ...result, timestamp: Date.now() }), "utf8");
82
+ } catch {
83
+ }
84
+ }
85
+ return result;
86
+ } catch {
87
+ }
88
+ return null;
89
+ }
90
+ async function canConnect(host, port) {
91
+ try {
92
+ const controller = new AbortController();
93
+ const timeout = setTimeout(() => controller.abort(), 1200);
94
+ const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
95
+ clearTimeout(timeout);
96
+ return response.ok;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ function findBrowserExecutable() {
102
+ if (process.platform === "darwin") {
103
+ const candidates = [
104
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
105
+ "/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
106
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
107
+ "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
108
+ "/Applications/Arc.app/Contents/MacOS/Arc",
109
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
110
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
111
+ ];
112
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
113
+ }
114
+ if (process.platform === "linux") {
115
+ const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
116
+ for (const candidate of candidates) {
117
+ try {
118
+ const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
119
+ if (resolved) {
120
+ return resolved;
121
+ }
122
+ } catch {
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+ if (process.platform === "win32") {
128
+ const localAppData = process.env.LOCALAPPDATA ?? "";
129
+ const candidates = [
130
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
131
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
132
+ ...localAppData ? [
133
+ `${localAppData}\\Google\\Chrome Dev\\Application\\chrome.exe`,
134
+ `${localAppData}\\Google\\Chrome SxS\\Application\\chrome.exe`,
135
+ `${localAppData}\\Google\\Chrome Beta\\Application\\chrome.exe`
136
+ ] : [],
137
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
138
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
139
+ "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
140
+ ];
141
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
142
+ }
143
+ return null;
144
+ }
145
+ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
146
+ const executable = findBrowserExecutable();
147
+ if (!executable) {
148
+ return null;
149
+ }
150
+ await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
151
+ const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
152
+ const prefsPath = path.join(defaultProfileDir, "Preferences");
153
+ await mkdir(defaultProfileDir, { recursive: true });
154
+ try {
155
+ let prefs = {};
156
+ try {
157
+ prefs = JSON.parse(await readFile(prefsPath, "utf8"));
158
+ } catch {
159
+ }
160
+ if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
161
+ prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
162
+ await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
163
+ }
164
+ } catch {
165
+ }
166
+ const args = [
167
+ `--remote-debugging-port=${port}`,
168
+ `--user-data-dir=${MANAGED_USER_DATA_DIR}`,
169
+ "--no-first-run",
170
+ "--no-default-browser-check",
171
+ "--disable-sync",
172
+ "--disable-background-networking",
173
+ "--disable-component-update",
174
+ "--disable-features=Translate,MediaRouter",
175
+ "--disable-session-crashed-bubble",
176
+ "--hide-crash-restore-bubble",
177
+ "--use-mock-keychain",
178
+ "about:blank"
179
+ ];
180
+ try {
181
+ const child = spawn(executable, args, {
182
+ detached: true,
183
+ stdio: "ignore"
184
+ });
185
+ child.unref();
186
+ } catch {
187
+ return null;
188
+ }
189
+ await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
190
+ await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
191
+ const deadline = Date.now() + 8e3;
192
+ while (Date.now() < deadline) {
193
+ if (await canConnect("127.0.0.1", port)) {
194
+ return { host: "127.0.0.1", port };
195
+ }
196
+ await new Promise((resolve2) => setTimeout(resolve2, 250));
197
+ }
198
+ return null;
199
+ }
200
+ async function discoverCdpPort() {
201
+ const envUrl = process.env.BB_BROWSER_CDP_URL;
202
+ if (envUrl) {
203
+ try {
204
+ const url = new URL(envUrl);
205
+ const port = Number(url.port);
206
+ if (Number.isInteger(port) && port > 0 && await canConnect(url.hostname, port)) {
207
+ return { host: url.hostname, port };
208
+ }
209
+ } catch {
210
+ }
211
+ }
212
+ const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
213
+ if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
214
+ return { host: "127.0.0.1", port: explicitPort };
215
+ }
216
+ try {
217
+ const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
218
+ const managedPort = Number.parseInt(rawPort.trim(), 10);
219
+ if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
220
+ return { host: "127.0.0.1", port: managedPort };
221
+ }
222
+ } catch {
223
+ }
224
+ try {
225
+ const cacheRaw = await readFile(CDP_CACHE_FILE, "utf8");
226
+ const cache = JSON.parse(cacheRaw);
227
+ if (Date.now() - cache.timestamp < CACHE_TTL_MS && await canConnect(cache.host, cache.port)) {
228
+ return { host: cache.host, port: cache.port };
229
+ }
230
+ } catch {
231
+ }
232
+ if (process.argv.includes("--openclaw")) {
233
+ const viaOpenClaw = await tryOpenClaw();
234
+ if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
235
+ return viaOpenClaw;
236
+ }
237
+ }
238
+ const launched = await launchManagedBrowser();
239
+ if (launched) {
240
+ return launched;
241
+ }
242
+ if (!process.argv.includes("--openclaw")) {
243
+ const detectedOpenClaw = await tryOpenClaw();
244
+ if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
245
+ return detectedOpenClaw;
246
+ }
247
+ }
248
+ return null;
249
+ }
250
+
251
+ // packages/cli/src/daemon-manager.ts
252
+ var cachedInfo = null;
253
+ var daemonReady = false;
254
+ async function deleteDaemonJson() {
255
+ try {
256
+ await unlink(DAEMON_JSON);
257
+ } catch {
258
+ }
259
+ }
260
+ function getDaemonPath() {
261
+ const currentFile = fileURLToPath(import.meta.url);
262
+ const currentDir = dirname(currentFile);
263
+ const sameDirPath = resolve(currentDir, "daemon.js");
264
+ if (existsSync2(sameDirPath)) {
265
+ return sameDirPath;
266
+ }
267
+ return resolve(currentDir, "../../daemon/dist/index.js");
268
+ }
269
+ async function ensureDaemon() {
270
+ if (daemonReady && cachedInfo) {
271
+ try {
272
+ const status = await httpJson("GET", "/status", cachedInfo, void 0, 2e3);
273
+ if (status.running && status.cdpConnected !== false) {
274
+ return;
275
+ }
276
+ } catch {
277
+ }
278
+ daemonReady = false;
279
+ cachedInfo = null;
280
+ }
281
+ let info = await readDaemonJson();
282
+ if (info) {
283
+ if (!isProcessAlive(info.pid)) {
284
+ await deleteDaemonJson();
285
+ info = null;
286
+ } else {
287
+ try {
288
+ const status = await httpJson("GET", "/status", info, void 0, 2e3);
289
+ if (status.running && status.cdpConnected !== false) {
290
+ cachedInfo = info;
291
+ daemonReady = true;
292
+ return;
293
+ }
294
+ if (status.running && status.cdpConnected === false) {
295
+ await stopDaemon();
296
+ await deleteDaemonJson();
297
+ info = null;
298
+ }
299
+ } catch {
300
+ }
301
+ }
302
+ }
303
+ const cdpInfo = await discoverCdpPort();
304
+ if (!cdpInfo) {
305
+ throw new Error(
306
+ "bb-browser: Cannot find a Chromium-based browser.\n\nPlease do one of the following:\n 1. Install Google Chrome, Edge, or Brave\n 2. Start Chrome with: google-chrome --remote-debugging-port=19825\n 3. Set BB_BROWSER_CDP_URL=http://host:port"
307
+ );
308
+ }
309
+ const daemonPath = getDaemonPath();
310
+ const child = spawn2(process.execPath, [daemonPath, "--cdp-host", cdpInfo.host, "--cdp-port", String(cdpInfo.port)], {
311
+ detached: true,
312
+ stdio: "ignore"
313
+ });
314
+ child.unref();
315
+ const deadline = Date.now() + 1e4;
316
+ while (Date.now() < deadline) {
317
+ await new Promise((r) => setTimeout(r, 200));
318
+ info = await readDaemonJson();
319
+ if (!info) continue;
320
+ try {
321
+ const status = await httpJson("GET", "/status", info, void 0, 2e3);
322
+ if (status.running) {
323
+ cachedInfo = info;
324
+ daemonReady = true;
325
+ return;
326
+ }
327
+ } catch {
328
+ }
329
+ }
330
+ throw new Error(
331
+ "bb-browser: Daemon did not start in time.\n\nChrome CDP is reachable, but the daemon process failed to initialize.\nTry: bb-browser daemon status"
332
+ );
333
+ }
334
+ async function daemonCommand(request) {
335
+ if (!cachedInfo) {
336
+ cachedInfo = await readDaemonJson();
337
+ }
338
+ if (!cachedInfo) {
339
+ throw new Error("No daemon.json found. Is the daemon running?");
340
+ }
341
+ return httpJson("POST", "/command", cachedInfo, request, COMMAND_TIMEOUT);
342
+ }
343
+ async function stopDaemon() {
344
+ const info = cachedInfo ?? await readDaemonJson();
345
+ if (!info) return false;
346
+ try {
347
+ await httpJson("POST", "/shutdown", info);
348
+ daemonReady = false;
349
+ cachedInfo = null;
350
+ return true;
351
+ } catch {
352
+ return false;
353
+ }
354
+ }
355
+ async function getDaemonStatus() {
356
+ const info = cachedInfo ?? await readDaemonJson();
357
+ if (!info) return null;
358
+ try {
359
+ return await httpJson("GET", "/status", info, void 0, 2e3);
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+ var ensureDaemonRunning = ensureDaemon;
365
+
366
+ // packages/cli/src/client.ts
367
+ var jqExpression;
368
+ function setJqExpression(expression) {
369
+ jqExpression = expression;
370
+ }
371
+ function printJqResults(response) {
372
+ const target = response.data ?? response;
373
+ const results = applyJq(target, jqExpression || ".");
374
+ for (const result of results) {
375
+ console.log(typeof result === "string" ? result : JSON.stringify(result));
376
+ }
377
+ process.exit(0);
378
+ }
379
+ function handleJqResponse(response) {
380
+ if (jqExpression) {
381
+ printJqResults(response);
382
+ }
383
+ }
384
+ async function sendCommand(request) {
385
+ await ensureDaemon();
386
+ return daemonCommand(request);
387
+ }
388
+
389
+ // packages/cli/src/history-sqlite.ts
390
+ import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
391
+ import { execSync as execSync2 } from "child_process";
392
+ import { homedir, tmpdir } from "os";
393
+ import { join } from "path";
394
+ function getHistoryPathCandidates() {
395
+ const home = homedir();
396
+ const localAppData = process.env.LOCALAPPDATA || "";
397
+ const candidates = [
398
+ join(home, "Library/Application Support/Google/Chrome/Default/History"),
399
+ join(home, "Library/Application Support/Microsoft Edge/Default/History"),
400
+ join(home, "Library/Application Support/BraveSoftware/Brave-Browser/Default/History"),
401
+ join(home, "Library/Application Support/Arc/User Data/Default/History"),
402
+ join(home, ".config/google-chrome/Default/History")
403
+ ];
404
+ if (localAppData) {
405
+ candidates.push(
406
+ join(localAppData, "Google/Chrome/User Data/Default/History"),
407
+ join(localAppData, "Microsoft/Edge/User Data/Default/History")
408
+ );
409
+ }
410
+ return candidates;
411
+ }
412
+ function findHistoryPath() {
413
+ for (const historyPath of getHistoryPathCandidates()) {
414
+ if (existsSync3(historyPath)) {
415
+ return historyPath;
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ function sqlEscape(value) {
421
+ return value.replace(/'/g, "''");
422
+ }
423
+ function buildTimeWhere(days) {
424
+ if (!days || days <= 0) {
425
+ return "";
426
+ }
427
+ return `last_visit_time > (strftime('%s', 'now') - ${Math.floor(days)}*86400) * 1000000 + 11644473600000000`;
428
+ }
429
+ function runHistoryQuery(sql, mapRow) {
430
+ const historyPath = findHistoryPath();
431
+ if (!historyPath) {
432
+ return [];
433
+ }
434
+ const tmpPath = join(tmpdir(), `bb-history-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
435
+ try {
436
+ copyFileSync(historyPath, tmpPath);
437
+ const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
438
+ const escapedSql = sql.replace(/"/g, '\\"');
439
+ const output = execSync2(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
440
+ encoding: "utf-8",
441
+ stdio: ["pipe", "pipe", "pipe"]
442
+ });
443
+ return output.split("\n").filter(Boolean).map((line) => mapRow(line.split(" "))).filter((item) => item !== null);
444
+ } catch {
445
+ return [];
446
+ } finally {
447
+ try {
448
+ unlinkSync(tmpPath);
449
+ } catch {
450
+ }
451
+ }
452
+ }
453
+ function searchHistory(query, days) {
454
+ const conditions = [];
455
+ const trimmedQuery = query?.trim();
456
+ if (trimmedQuery) {
457
+ const escapedQuery = sqlEscape(trimmedQuery);
458
+ conditions.push(`(url LIKE '%${escapedQuery}%' OR title LIKE '%${escapedQuery}%')`);
459
+ }
460
+ const timeWhere = buildTimeWhere(days);
461
+ if (timeWhere) {
462
+ conditions.push(timeWhere);
463
+ }
464
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
465
+ const sql = `
466
+ SELECT
467
+ url,
468
+ REPLACE(IFNULL(title, ''), char(9), ' '),
469
+ IFNULL(visit_count, 0),
470
+ IFNULL(last_visit_time, 0)
471
+ FROM urls
472
+ ${whereClause}
473
+ ORDER BY last_visit_time DESC
474
+ LIMIT 100;
475
+ `.trim();
476
+ return runHistoryQuery(sql, (row) => {
477
+ if (row.length < 4) {
478
+ return null;
479
+ }
480
+ const chromeTimestamp = Number(row[3]) || 0;
481
+ return {
482
+ url: row[0] || "",
483
+ title: row[1] || "",
484
+ visitCount: Number(row[2]) || 0,
485
+ lastVisitTime: chromeTimestamp > 0 ? chromeTimestamp / 1e6 - 11644473600 : 0
486
+ };
487
+ });
488
+ }
489
+ function getHistoryDomains(days) {
490
+ const timeWhere = buildTimeWhere(days);
491
+ const whereClause = timeWhere ? `WHERE ${timeWhere}` : "";
492
+ const sql = `
493
+ SELECT
494
+ domain,
495
+ SUM(visit_count) AS visits,
496
+ GROUP_CONCAT(title, char(31)) AS titles
497
+ FROM (
498
+ SELECT
499
+ CASE
500
+ WHEN instr(url, '//') > 0 AND instr(substr(url, instr(url, '//') + 2), '/') > 0
501
+ THEN substr(
502
+ substr(url, instr(url, '//') + 2),
503
+ 1,
504
+ instr(substr(url, instr(url, '//') + 2), '/') - 1
505
+ )
506
+ WHEN instr(url, '//') > 0 THEN substr(url, instr(url, '//') + 2)
507
+ WHEN instr(url, '/') > 0 THEN substr(url, 1, instr(url, '/') - 1)
508
+ ELSE url
509
+ END AS domain,
510
+ IFNULL(visit_count, 0) AS visit_count,
511
+ REPLACE(IFNULL(title, ''), char(31), ' ') AS title
512
+ FROM urls
513
+ ${whereClause}
514
+ )
515
+ WHERE domain != ''
516
+ GROUP BY domain
517
+ ORDER BY visits DESC
518
+ LIMIT 50;
519
+ `.trim();
520
+ return runHistoryQuery(sql, (row) => {
521
+ if (row.length < 3) {
522
+ return null;
523
+ }
524
+ const titles = row[2] ? Array.from(new Set(row[2].split(String.fromCharCode(31)).map((title) => title.trim()).filter(Boolean))).slice(0, 10) : [];
525
+ return {
526
+ domain: row[0] || "",
527
+ visits: Number(row[1]) || 0,
528
+ titles
529
+ };
530
+ });
531
+ }
532
+
533
+ // packages/cli/src/commands/site.ts
534
+ import { readFileSync, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
535
+ import { join as join2, relative } from "path";
536
+ import { homedir as homedir2 } from "os";
537
+ import { execSync as execSync3 } from "child_process";
538
+ var BB_DIR = join2(homedir2(), ".bb-browser");
539
+ var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
540
+ var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
541
+ var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
542
+ function checkCliUpdate() {
543
+ try {
544
+ const current = execSync3("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
545
+ const latest = execSync3("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
546
+ if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
547
+ console.log(`
548
+ \u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
549
+ }
550
+ } catch {
551
+ }
552
+ }
553
+ function exitJsonError(error, extra = {}) {
554
+ console.log(JSON.stringify({ success: false, error, ...extra }, null, 2));
555
+ process.exit(1);
556
+ }
557
+ function parseSiteMeta(filePath, source) {
558
+ let content;
559
+ try {
560
+ content = readFileSync(filePath, "utf-8");
561
+ } catch {
562
+ return null;
563
+ }
564
+ const sitesDir = source === "local" ? LOCAL_SITES_DIR : COMMUNITY_SITES_DIR;
565
+ const relPath = relative(sitesDir, filePath);
566
+ const defaultName = relPath.replace(/\.js$/, "").replace(/\\/g, "/");
567
+ const metaMatch = content.match(/\/\*\s*@meta\s*\n([\s\S]*?)\*\//);
568
+ if (metaMatch) {
569
+ try {
570
+ const metaJson = JSON.parse(metaMatch[1]);
571
+ return {
572
+ name: metaJson.name || defaultName,
573
+ description: metaJson.description || "",
574
+ domain: metaJson.domain || "",
575
+ args: metaJson.args || {},
576
+ capabilities: metaJson.capabilities,
577
+ readOnly: metaJson.readOnly,
578
+ example: metaJson.example,
579
+ filePath,
580
+ source
581
+ };
582
+ } catch {
583
+ }
584
+ }
585
+ const meta = {
586
+ name: defaultName,
587
+ description: "",
588
+ domain: "",
589
+ args: {},
590
+ filePath,
591
+ source
592
+ };
593
+ const tagPattern = /\/\/\s*@(\w+)[ \t]+(.*)/g;
594
+ let match;
595
+ while ((match = tagPattern.exec(content)) !== null) {
596
+ const [, key, value] = match;
597
+ switch (key) {
598
+ case "name":
599
+ meta.name = value.trim();
600
+ break;
601
+ case "description":
602
+ meta.description = value.trim();
603
+ break;
604
+ case "domain":
605
+ meta.domain = value.trim();
606
+ break;
607
+ case "args":
608
+ for (const arg of value.trim().split(/[,\s]+/).filter(Boolean)) {
609
+ meta.args[arg] = { required: true };
610
+ }
611
+ break;
612
+ case "example":
613
+ meta.example = value.trim();
614
+ break;
615
+ }
616
+ }
617
+ return meta;
618
+ }
619
+ function scanSites(dir, source) {
620
+ if (!existsSync4(dir)) return [];
621
+ const sites = [];
622
+ function walk(currentDir) {
623
+ let entries;
624
+ try {
625
+ entries = readdirSync(currentDir, { withFileTypes: true });
626
+ } catch {
627
+ return;
628
+ }
629
+ for (const entry of entries) {
630
+ const fullPath = join2(currentDir, entry.name);
631
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
632
+ walk(fullPath);
633
+ } else if (entry.isFile() && entry.name.endsWith(".js")) {
634
+ const meta = parseSiteMeta(fullPath, source);
635
+ if (meta) sites.push(meta);
636
+ }
637
+ }
638
+ }
639
+ walk(dir);
640
+ return sites;
641
+ }
642
+ function getSiteHintForDomain(url) {
643
+ try {
644
+ const hostname = new URL(url).hostname;
645
+ const sites = getAllSites();
646
+ const matched = sites.filter((s) => s.domain && (hostname === s.domain || hostname.endsWith("." + s.domain)));
647
+ if (matched.length === 0) return null;
648
+ const names = matched.map((s) => s.name);
649
+ const example = matched[0].example || `bb-browser site ${names[0]}`;
650
+ return `\u8BE5\u7F51\u7AD9\u6709 ${names.length} \u4E2A site adapter \u53EF\u76F4\u63A5\u83B7\u53D6\u6570\u636E\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u6D4F\u89C8\u5668\u3002\u8BD5\u8BD5: ${example}`;
651
+ } catch {
652
+ return null;
653
+ }
654
+ }
655
+ function getAllSites() {
656
+ const community = scanSites(COMMUNITY_SITES_DIR, "community");
657
+ const local = scanSites(LOCAL_SITES_DIR, "local");
658
+ const byName = /* @__PURE__ */ new Map();
659
+ for (const s of community) byName.set(s.name, s);
660
+ for (const s of local) byName.set(s.name, s);
661
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
662
+ }
663
+ function matchTabOrigin(tabUrl, domain) {
664
+ try {
665
+ const tabOrigin = new URL(tabUrl).hostname;
666
+ return tabOrigin === domain || tabOrigin.endsWith("." + domain);
667
+ } catch {
668
+ return false;
669
+ }
670
+ }
671
+ function siteList(options) {
672
+ const sites = getAllSites();
673
+ if (sites.length === 0) {
674
+ if (options.json) {
675
+ console.log("[]");
676
+ return;
677
+ }
678
+ console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
679
+ console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
680
+ console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
681
+ return;
682
+ }
683
+ if (options.json) {
684
+ console.log(JSON.stringify(sites.map((s) => ({
685
+ name: s.name,
686
+ description: s.description,
687
+ domain: s.domain,
688
+ args: s.args,
689
+ source: s.source
690
+ })), null, 2));
691
+ return;
692
+ }
693
+ const groups = /* @__PURE__ */ new Map();
694
+ for (const s of sites) {
695
+ const platform = s.name.split("/")[0];
696
+ if (!groups.has(platform)) groups.set(platform, []);
697
+ groups.get(platform).push(s);
698
+ }
699
+ for (const [platform, items] of groups) {
700
+ console.log(`
701
+ ${platform}/`);
702
+ for (const s of items) {
703
+ const cmd = s.name.split("/").slice(1).join("/");
704
+ const src = s.source === "local" ? " (local)" : "";
705
+ const desc = s.description ? ` - ${s.description}` : "";
706
+ console.log(` ${cmd.padEnd(20)}${desc}${src}`);
707
+ }
708
+ }
709
+ console.log();
710
+ }
711
+ function siteSearch(query, options) {
712
+ const sites = getAllSites();
713
+ const q = query.toLowerCase();
714
+ const matches = sites.filter(
715
+ (s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
716
+ );
717
+ if (matches.length === 0) {
718
+ if (options.json) {
719
+ console.log("[]");
720
+ return;
721
+ }
722
+ console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
723
+ console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
724
+ return;
725
+ }
726
+ if (options.json) {
727
+ console.log(JSON.stringify(matches.map((s) => ({
728
+ name: s.name,
729
+ description: s.description,
730
+ domain: s.domain,
731
+ source: s.source
732
+ })), null, 2));
733
+ return;
734
+ }
735
+ for (const s of matches) {
736
+ const src = s.source === "local" ? " (local)" : "";
737
+ console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
738
+ }
739
+ }
740
+ function siteUpdate(options = {}) {
741
+ mkdirSync(BB_DIR, { recursive: true });
742
+ const updateMode = existsSync4(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
743
+ if (updateMode === "pull") {
744
+ if (!options.json) {
745
+ console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
746
+ }
747
+ try {
748
+ execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
749
+ if (!options.json) {
750
+ console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
751
+ console.log("");
752
+ console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
753
+ }
754
+ } catch (e) {
755
+ const message = e instanceof Error ? e.message : String(e);
756
+ const manualAction = "cd ~/.bb-browser/bb-sites && git pull";
757
+ if (options.json) {
758
+ exitJsonError(`\u66F4\u65B0\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
759
+ }
760
+ console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
761
+ console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
762
+ process.exit(1);
763
+ }
764
+ } else {
765
+ if (!options.json) {
766
+ console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
767
+ }
768
+ try {
769
+ execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
770
+ if (!options.json) {
771
+ console.log("\u514B\u9686\u5B8C\u6210\u3002");
772
+ console.log("");
773
+ console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
774
+ }
775
+ } catch (e) {
776
+ const message = e instanceof Error ? e.message : String(e);
777
+ const manualAction = `git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`;
778
+ if (options.json) {
779
+ exitJsonError(`\u514B\u9686\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
780
+ }
781
+ console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
782
+ console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
783
+ process.exit(1);
784
+ }
785
+ }
786
+ const sites = scanSites(COMMUNITY_SITES_DIR, "community");
787
+ if (options.json) {
788
+ console.log(JSON.stringify({
789
+ success: true,
790
+ updateMode,
791
+ communityRepo: COMMUNITY_REPO,
792
+ communityDir: COMMUNITY_SITES_DIR,
793
+ siteCount: sites.length
794
+ }, null, 2));
795
+ return;
796
+ }
797
+ console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
798
+ console.log(`\u2B50 Like bb-browser? \u2192 bb-browser star`);
799
+ checkCliUpdate();
800
+ }
801
+ function findSiteByName(name) {
802
+ return getAllSites().find((site) => site.name === name);
803
+ }
804
+ function siteInfo(name, options) {
805
+ const site = findSiteByName(name);
806
+ if (!site) {
807
+ if (options.json) {
808
+ exitJsonError(`adapter "${name}" not found`, { action: "bb-browser site list" });
809
+ }
810
+ console.error(`[error] site info: adapter "${name}" not found.`);
811
+ console.error(" Try: bb-browser site list");
812
+ process.exit(1);
813
+ }
814
+ const meta = {
815
+ name: site.name,
816
+ description: site.description,
817
+ domain: site.domain,
818
+ args: site.args,
819
+ example: site.example,
820
+ readOnly: site.readOnly
821
+ };
822
+ if (options.json) {
823
+ console.log(JSON.stringify(meta, null, 2));
824
+ return;
825
+ }
826
+ console.log(`${site.name} \u2014 ${site.description}`);
827
+ console.log();
828
+ console.log("\u53C2\u6570\uFF1A");
829
+ const argEntries = Object.entries(site.args);
830
+ if (argEntries.length === 0) {
831
+ console.log(" \uFF08\u65E0\uFF09");
832
+ } else {
833
+ for (const [argName, argDef] of argEntries) {
834
+ const requiredText = argDef.required ? "\u5FC5\u586B" : "\u53EF\u9009";
835
+ const description = argDef.description || "";
836
+ console.log(` ${argName} (${requiredText}) ${description}`.trimEnd());
837
+ }
838
+ }
839
+ console.log();
840
+ console.log("\u793A\u4F8B\uFF1A");
841
+ console.log(` ${site.example || `bb-browser site ${site.name}`}`);
842
+ console.log();
843
+ console.log(`\u57DF\u540D\uFF1A${site.domain || "\uFF08\u672A\u58F0\u660E\uFF09"}`);
844
+ console.log(`\u53EA\u8BFB\uFF1A${site.readOnly ? "\u662F" : "\u5426"}`);
845
+ }
846
+ async function siteRecommend(options) {
847
+ const days = options.days ?? 30;
848
+ const historyDomains = getHistoryDomains(days);
849
+ const sites = getAllSites();
850
+ const sitesByDomain = /* @__PURE__ */ new Map();
851
+ for (const site of sites) {
852
+ if (!site.domain) continue;
853
+ const domain = site.domain.toLowerCase();
854
+ const existing = sitesByDomain.get(domain) || [];
855
+ existing.push(site);
856
+ sitesByDomain.set(domain, existing);
857
+ }
858
+ const available = [];
859
+ const notAvailable = [];
860
+ for (const item of historyDomains) {
861
+ const adapters = sitesByDomain.get(item.domain.toLowerCase());
862
+ if (adapters && adapters.length > 0) {
863
+ const sortedAdapters = [...adapters].sort((a, b) => a.name.localeCompare(b.name));
864
+ available.push({
865
+ domain: item.domain,
866
+ visits: item.visits,
867
+ adapterCount: sortedAdapters.length,
868
+ adapters: sortedAdapters.map((site) => ({
869
+ name: site.name,
870
+ description: site.description,
871
+ example: site.example || `bb-browser site ${site.name}`
872
+ }))
873
+ });
874
+ } else if (item.visits >= 5 && item.domain && !item.domain.includes("localhost") && item.domain.includes(".")) {
875
+ notAvailable.push(item);
876
+ }
877
+ }
878
+ const jsonData = {
879
+ days,
880
+ available,
881
+ not_available: notAvailable
882
+ };
883
+ if (options.jq) {
884
+ handleJqResponse({ id: generateId(), success: true, data: jsonData });
885
+ }
886
+ if (options.json) {
887
+ console.log(JSON.stringify(jsonData, null, 2));
888
+ return;
889
+ }
890
+ console.log(`\u57FA\u4E8E\u4F60\u6700\u8FD1 ${days} \u5929\u7684\u6D4F\u89C8\u8BB0\u5F55\uFF1A`);
891
+ console.log();
892
+ console.log("\u{1F3AF} \u4F60\u5E38\u7528\u8FD9\u4E9B\u7F51\u7AD9\uFF0C\u53EF\u4EE5\u76F4\u63A5\u7528\uFF1A");
893
+ console.log();
894
+ if (available.length === 0) {
895
+ console.log(" \uFF08\u6682\u65E0\u5339\u914D\u7684 adapter\uFF09");
896
+ } else {
897
+ for (const item of available) {
898
+ console.log(` ${item.domain.padEnd(20)} ${item.visits} \u6B21\u8BBF\u95EE ${item.adapterCount} \u4E2A\u547D\u4EE4`);
899
+ console.log(` \u8BD5\u8BD5: ${item.adapters[0]?.example || `bb-browser site ${item.adapters[0]?.name || ""}`}`);
900
+ console.log();
901
+ }
902
+ }
903
+ console.log("\u{1F4CB} \u4F60\u5E38\u7528\u4F46\u8FD8\u6CA1\u6709 adapter\uFF1A");
904
+ console.log();
905
+ if (notAvailable.length === 0) {
906
+ console.log(" \uFF08\u6682\u65E0\uFF09");
907
+ } else {
908
+ for (const item of notAvailable) {
909
+ console.log(` ${item.domain.padEnd(20)} ${item.visits} \u6B21\u8BBF\u95EE`);
910
+ }
911
+ }
912
+ console.log();
913
+ console.log('\u{1F4A1} \u8DDF\u4F60\u7684 AI Agent \u8BF4 "\u628A notion.so CLI \u5316"\uFF0C\u5B83\u5C31\u80FD\u81EA\u52A8\u5B8C\u6210\u3002');
914
+ console.log();
915
+ console.log(`\u6240\u6709\u5206\u6790\u7EAF\u672C\u5730\u5B8C\u6210\u3002\u7528 --days 7 \u53EA\u770B\u6700\u8FD1\u4E00\u5468\u3002`);
916
+ }
917
+ async function siteRun(name, args, options) {
918
+ const sites = getAllSites();
919
+ const site = sites.find((s) => s.name === name);
920
+ if (!site) {
921
+ const fuzzy = sites.filter((s) => s.name.includes(name));
922
+ if (options.json) {
923
+ exitJsonError(`site "${name}" not found`, {
924
+ suggestions: fuzzy.slice(0, 5).map((s) => s.name),
925
+ action: fuzzy.length > 0 ? void 0 : "bb-browser site update"
926
+ });
927
+ }
928
+ console.error(`[error] site: "${name}" not found.`);
929
+ if (fuzzy.length > 0) {
930
+ console.error(" Did you mean:");
931
+ for (const s of fuzzy.slice(0, 5)) {
932
+ console.error(` bb-browser site ${s.name}`);
933
+ }
934
+ } else {
935
+ console.error(" Try: bb-browser site list");
936
+ console.error(" Or: bb-browser site update");
937
+ }
938
+ process.exit(1);
939
+ }
940
+ const argNames = Object.keys(site.args);
941
+ const argMap = {};
942
+ const positionalArgs = [];
943
+ for (let i = 0; i < args.length; i++) {
944
+ if (args[i].startsWith("--")) {
945
+ const flagName = args[i].slice(2);
946
+ if (flagName in site.args && args[i + 1]) {
947
+ argMap[flagName] = args[i + 1];
948
+ i++;
949
+ }
950
+ } else {
951
+ positionalArgs.push(args[i]);
952
+ }
953
+ }
954
+ let posIdx = 0;
955
+ for (const argName of argNames) {
956
+ if (!argMap[argName] && posIdx < positionalArgs.length) {
957
+ argMap[argName] = positionalArgs[posIdx++];
958
+ }
959
+ }
960
+ for (const [argName, argDef] of Object.entries(site.args)) {
961
+ if (argDef.required && !argMap[argName]) {
962
+ const usage = argNames.map((a) => {
963
+ const def = site.args[a];
964
+ return def.required ? `<${a}>` : `[${a}]`;
965
+ }).join(" ");
966
+ if (options.json) {
967
+ exitJsonError(`missing required argument "${argName}"`, {
968
+ usage: `bb-browser site ${name} ${usage}`,
969
+ example: site.example
970
+ });
971
+ }
972
+ console.error(`[error] site ${name}: missing required argument "${argName}".`);
973
+ console.error(` Usage: bb-browser site ${name} ${usage}`);
974
+ if (site.example) console.error(` Example: ${site.example}`);
975
+ process.exit(1);
976
+ }
977
+ }
978
+ if (site.source === "local" && !options.json) {
979
+ const communityVersion = scanSites(COMMUNITY_SITES_DIR, "community").find((s) => s.name === name);
980
+ if (communityVersion) {
981
+ console.error(`[local override] ${name} \u2014 ${site.filePath}`);
982
+ console.error(` Community version also exists. Run \`bb-browser site update\` to check for updates.`);
983
+ }
984
+ }
985
+ const jsContent = readFileSync(site.filePath, "utf-8");
986
+ const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
987
+ const argsJson = JSON.stringify(argMap);
988
+ const script = `(${jsBody})(${argsJson})`;
989
+ if (options.openclaw) {
990
+ const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-GWIKCRBD.js");
991
+ let targetId;
992
+ if (site.domain) {
993
+ const tabs = ocGetTabs();
994
+ const existing = ocFindTabByDomain(tabs, site.domain);
995
+ if (existing) {
996
+ targetId = existing.targetId;
997
+ } else {
998
+ targetId = ocOpenTab(`https://${site.domain}`);
999
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
1000
+ }
1001
+ } else {
1002
+ const tabs = ocGetTabs();
1003
+ if (tabs.length === 0) {
1004
+ throw new Error("No tabs open in OpenClaw browser");
1005
+ }
1006
+ targetId = tabs[0].targetId;
1007
+ }
1008
+ const wrappedFn = `async () => { const __fn = ${jsBody}; return await __fn(${argsJson}); }`;
1009
+ const parsed2 = ocEvaluate(targetId, wrappedFn);
1010
+ if (typeof parsed2 === "object" && parsed2 !== null && "error" in parsed2) {
1011
+ const errObj = parsed2;
1012
+ const checkText = `${errObj.error} ${errObj.hint || ""}`;
1013
+ const isAuthError = /401|403|unauthorized|forbidden|not.?logged|login.?required|sign.?in|auth/i.test(checkText);
1014
+ const loginHint = isAuthError && site.domain ? `Please log in to https://${site.domain} in your OpenClaw browser first, then retry.` : void 0;
1015
+ const hint = loginHint || errObj.hint;
1016
+ const reportHint = `If this is an adapter bug, report via: gh issue create --repo epiral/bb-sites --title "[${name}] <description>" OR: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] <description>"`;
1017
+ if (options.json) {
1018
+ console.log(JSON.stringify({ id: "openclaw", success: false, error: errObj.error, hint, reportHint }));
1019
+ } else {
1020
+ console.error(`[error] site ${name}: ${errObj.error}`);
1021
+ if (hint) console.error(` Hint: ${hint}`);
1022
+ console.error(` Report: gh issue create --repo epiral/bb-sites --title "[${name}] ..."`);
1023
+ console.error(` or: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] ..."`);
1024
+ }
1025
+ process.exit(1);
1026
+ }
1027
+ if (options.jq) {
1028
+ const { applyJq: applyJq2 } = await import("./jq-J32FKI7Z.js");
1029
+ const expr = options.jq.replace(/^\.data\./, ".");
1030
+ const results = applyJq2(parsed2, expr);
1031
+ for (const r of results) {
1032
+ console.log(typeof r === "string" ? r : JSON.stringify(r));
1033
+ }
1034
+ } else if (options.json) {
1035
+ console.log(JSON.stringify({ id: "openclaw", success: true, data: parsed2 }));
1036
+ } else {
1037
+ console.log(JSON.stringify(parsed2, null, 2));
1038
+ }
1039
+ return;
1040
+ }
1041
+ await ensureDaemonRunning();
1042
+ let targetTabId = options.tabId;
1043
+ if (!targetTabId && site.domain) {
1044
+ const listReq = { id: generateId(), action: "tab_list" };
1045
+ const listResp = await sendCommand(listReq);
1046
+ if (listResp.success && listResp.data?.tabs) {
1047
+ const matchingTab = listResp.data.tabs.find(
1048
+ (tab) => matchTabOrigin(tab.url, site.domain)
1049
+ );
1050
+ if (matchingTab) {
1051
+ targetTabId = matchingTab.tabId;
1052
+ }
1053
+ }
1054
+ if (!targetTabId) {
1055
+ const newResp = await sendCommand({
1056
+ id: generateId(),
1057
+ action: "tab_new",
1058
+ url: `https://${site.domain}`
1059
+ });
1060
+ targetTabId = newResp.data?.tabId;
1061
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
1062
+ }
1063
+ }
1064
+ const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
1065
+ const evalResp = await sendCommand(evalReq);
1066
+ if (!evalResp.success) {
1067
+ const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
1068
+ if (options.json) {
1069
+ console.log(JSON.stringify({ id: evalReq.id, success: false, error: evalResp.error || "eval failed", hint }));
1070
+ } else {
1071
+ console.error(`[error] site ${name}: ${evalResp.error || "eval failed"}`);
1072
+ if (hint) console.error(` Hint: ${hint}`);
1073
+ }
1074
+ process.exit(1);
1075
+ }
1076
+ const result = evalResp.data?.result;
1077
+ if (result === void 0 || result === null) {
1078
+ if (options.json) {
1079
+ console.log(JSON.stringify({ id: evalReq.id, success: true, data: null }));
1080
+ } else {
1081
+ console.log("(no output)");
1082
+ }
1083
+ return;
1084
+ }
1085
+ let parsed;
1086
+ try {
1087
+ parsed = typeof result === "string" ? JSON.parse(result) : result;
1088
+ } catch {
1089
+ parsed = result;
1090
+ }
1091
+ if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
1092
+ const errObj = parsed;
1093
+ const checkText = `${errObj.error} ${errObj.hint || ""}`;
1094
+ const isAuthError = /401|403|unauthorized|forbidden|not.?logged|login.?required|sign.?in|auth/i.test(checkText);
1095
+ const loginHint = isAuthError && site.domain ? `Please log in to https://${site.domain} in your browser first, then retry.` : void 0;
1096
+ const hint = loginHint || errObj.hint;
1097
+ const reportHint = `If this is an adapter bug, report via: gh issue create --repo epiral/bb-sites --title "[${name}] <description>" OR: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] <description>"`;
1098
+ if (options.json) {
1099
+ console.log(JSON.stringify({ id: evalReq.id, success: false, error: errObj.error, hint, reportHint }));
1100
+ } else {
1101
+ console.error(`[error] site ${name}: ${errObj.error}`);
1102
+ if (hint) console.error(` Hint: ${hint}`);
1103
+ console.error(` Report: gh issue create --repo epiral/bb-sites --title "[${name}] ..."`);
1104
+ console.error(` or: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] ..."`);
1105
+ }
1106
+ process.exit(1);
1107
+ }
1108
+ if (options.jq) {
1109
+ const { applyJq: applyJq2 } = await import("./jq-J32FKI7Z.js");
1110
+ const expr = options.jq.replace(/^\.data\./, ".");
1111
+ const results = applyJq2(parsed, expr);
1112
+ for (const r of results) {
1113
+ console.log(typeof r === "string" ? r : JSON.stringify(r));
1114
+ }
1115
+ } else if (options.json) {
1116
+ console.log(JSON.stringify({ id: evalReq.id, success: true, data: parsed }));
1117
+ } else {
1118
+ console.log(JSON.stringify(parsed, null, 2));
1119
+ }
1120
+ }
1121
+ async function siteCommand(args, options = {}) {
1122
+ const subCommand = args[0];
1123
+ if (!subCommand || subCommand === "--help" || subCommand === "-h") {
1124
+ console.log(`bb-browser site - \u7F51\u7AD9 CLI \u5316\uFF08\u7BA1\u7406\u548C\u8FD0\u884C site adapter\uFF09
1125
+
1126
+ \u7528\u6CD5:
1127
+ bb-browser site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter
1128
+ bb-browser site info <name> \u67E5\u770B adapter \u5143\u4FE1\u606F
1129
+ bb-browser site recommend \u57FA\u4E8E\u5386\u53F2\u8BB0\u5F55\u63A8\u8350 adapter
1130
+ bb-browser site search <query> \u641C\u7D22 adapter
1131
+ bb-browser site <name> [args...] \u8FD0\u884C adapter\uFF08\u7B80\u5199\uFF09
1132
+ bb-browser site run <name> [args...] \u8FD0\u884C adapter
1133
+ bb-browser site update \u66F4\u65B0\u793E\u533A adapter \u5E93 (git clone/pull)
1134
+
1135
+ \u76EE\u5F55:
1136
+ ${LOCAL_SITES_DIR} \u79C1\u6709 adapter\uFF08\u4F18\u5148\uFF09
1137
+ ${COMMUNITY_SITES_DIR} \u793E\u533A adapter
1138
+
1139
+ \u793A\u4F8B:
1140
+ bb-browser site update
1141
+ bb-browser site list
1142
+ bb-browser site reddit/thread https://www.reddit.com/r/LocalLLaMA/comments/...
1143
+ bb-browser site twitter/user yan5xu
1144
+ bb-browser site search reddit
1145
+
1146
+ \u521B\u5EFA\u65B0 adapter: bb-browser guide
1147
+ \u62A5\u544A\u95EE\u9898: gh issue create --repo epiral/bb-sites --title "[adapter-name] \u63CF\u8FF0"
1148
+ \u8D21\u732E\u793E\u533A: https://github.com/epiral/bb-sites`);
1149
+ return;
1150
+ }
1151
+ switch (subCommand) {
1152
+ case "list":
1153
+ siteList(options);
1154
+ break;
1155
+ case "search":
1156
+ if (!args[1]) {
1157
+ console.error("[error] site search: <query> is required.");
1158
+ console.error(" Usage: bb-browser site search <query>");
1159
+ process.exit(1);
1160
+ }
1161
+ siteSearch(args[1], options);
1162
+ break;
1163
+ case "info":
1164
+ if (!args[1]) {
1165
+ console.error("[error] site info: <name> is required.");
1166
+ console.error(" Usage: bb-browser site info <name>");
1167
+ process.exit(1);
1168
+ }
1169
+ siteInfo(args[1], options);
1170
+ break;
1171
+ case "recommend":
1172
+ await siteRecommend(options);
1173
+ break;
1174
+ case "update":
1175
+ siteUpdate(options);
1176
+ break;
1177
+ case "run":
1178
+ if (!args[1]) {
1179
+ console.error("[error] site run: <name> is required.");
1180
+ console.error(" Usage: bb-browser site run <name> [args...]");
1181
+ console.error(" Try: bb-browser site list");
1182
+ process.exit(1);
1183
+ }
1184
+ await siteRun(args[1], args.slice(2), options);
1185
+ break;
1186
+ default:
1187
+ if (subCommand.includes("/")) {
1188
+ await siteRun(subCommand, args.slice(1), options);
1189
+ } else {
1190
+ console.error(`[error] site: unknown subcommand "${subCommand}".`);
1191
+ console.error(" Available: list, info, recommend, search, run, update");
1192
+ console.error(" Try: bb-browser site --help");
1193
+ process.exit(1);
1194
+ }
1195
+ break;
1196
+ }
1197
+ silentUpdate();
1198
+ }
1199
+ function silentUpdate() {
1200
+ const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
1201
+ if (!existsSync4(gitDir)) return;
1202
+ import("child_process").then(({ spawn: spawn3 }) => {
1203
+ const child = spawn3("git", ["pull", "--ff-only"], {
1204
+ cwd: COMMUNITY_SITES_DIR,
1205
+ stdio: "ignore",
1206
+ detached: true
1207
+ });
1208
+ child.unref();
1209
+ }).catch(() => {
1210
+ });
1211
+ }
1212
+
1213
+ // packages/cli/src/commands/open.ts
1214
+ async function openCommand(url, options = {}) {
1215
+ if (!url) {
1216
+ throw new Error("\u7F3A\u5C11 URL \u53C2\u6570");
1217
+ }
1218
+ await ensureDaemonRunning();
1219
+ let normalizedUrl = url;
1220
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
1221
+ normalizedUrl = "https://" + url;
1222
+ }
1223
+ const request = {
1224
+ id: generateId(),
1225
+ action: "open",
1226
+ url: normalizedUrl
1227
+ };
1228
+ if (options.tab !== void 0) {
1229
+ if (options.tab === "current") {
1230
+ request.tabId = "current";
1231
+ } else {
1232
+ const tabId = parseInt(options.tab, 10);
1233
+ if (isNaN(tabId)) {
1234
+ throw new Error(`\u65E0\u6548\u7684 tabId: ${options.tab}`);
1235
+ }
1236
+ request.tabId = tabId;
1237
+ }
1238
+ }
1239
+ const response = await sendCommand(request);
1240
+ if (options.json) {
1241
+ console.log(JSON.stringify(response, null, 2));
1242
+ } else {
1243
+ if (response.success) {
1244
+ console.log(`\u5DF2\u6253\u5F00: ${response.data?.url ?? normalizedUrl}`);
1245
+ if (response.data?.title) {
1246
+ console.log(`\u6807\u9898: ${response.data.title}`);
1247
+ }
1248
+ if (response.data?.tabId) {
1249
+ console.log(`Tab ID: ${response.data.tabId}`);
1250
+ }
1251
+ const siteHint = getSiteHintForDomain(normalizedUrl);
1252
+ if (siteHint) {
1253
+ console.log(`
1254
+ \u{1F4A1} ${siteHint}`);
1255
+ }
1256
+ } else {
1257
+ console.error(`\u9519\u8BEF: ${response.error}`);
1258
+ process.exit(1);
1259
+ }
1260
+ }
1261
+ }
1262
+
1263
+ // packages/cli/src/commands/snapshot.ts
1264
+ async function snapshotCommand(options = {}) {
1265
+ await ensureDaemonRunning();
1266
+ const request = {
1267
+ id: generateId(),
1268
+ action: "snapshot",
1269
+ interactive: options.interactive,
1270
+ compact: options.compact,
1271
+ maxDepth: options.maxDepth,
1272
+ selector: options.selector,
1273
+ tabId: options.tabId
1274
+ };
1275
+ const response = await sendCommand(request);
1276
+ if (options.json) {
1277
+ console.log(JSON.stringify(response, null, 2));
1278
+ } else {
1279
+ if (response.success) {
1280
+ console.log(`\u6807\u9898: ${response.data?.title ?? "(\u65E0\u6807\u9898)"}`);
1281
+ console.log(`URL: ${response.data?.url ?? "(\u672A\u77E5)"}`);
1282
+ if (response.data?.snapshotData?.snapshot) {
1283
+ console.log("");
1284
+ console.log(response.data.snapshotData.snapshot);
1285
+ }
1286
+ } else {
1287
+ console.error(`\u9519\u8BEF: ${response.error}`);
1288
+ process.exit(1);
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ // packages/cli/src/commands/click.ts
1294
+ function parseRef(ref) {
1295
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1296
+ }
1297
+ async function clickCommand(ref, options = {}) {
1298
+ if (!ref) {
1299
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1300
+ }
1301
+ await ensureDaemonRunning();
1302
+ const parsedRef = parseRef(ref);
1303
+ const request = {
1304
+ id: generateId(),
1305
+ action: "click",
1306
+ ref: parsedRef,
1307
+ tabId: options.tabId
1308
+ };
1309
+ const response = await sendCommand(request);
1310
+ if (options.json) {
1311
+ console.log(JSON.stringify(response, null, 2));
1312
+ } else {
1313
+ if (response.success) {
1314
+ const role = response.data?.role ?? "element";
1315
+ const name = response.data?.name;
1316
+ if (name) {
1317
+ console.log(`\u5DF2\u70B9\u51FB: ${role} "${name}"`);
1318
+ } else {
1319
+ console.log(`\u5DF2\u70B9\u51FB: ${role}`);
1320
+ }
1321
+ } else {
1322
+ console.error(`\u9519\u8BEF: ${response.error}`);
1323
+ process.exit(1);
1324
+ }
1325
+ }
1326
+ }
1327
+
1328
+ // packages/cli/src/commands/hover.ts
1329
+ function parseRef2(ref) {
1330
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1331
+ }
1332
+ async function hoverCommand(ref, options = {}) {
1333
+ if (!ref) {
1334
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1335
+ }
1336
+ await ensureDaemonRunning();
1337
+ const parsedRef = parseRef2(ref);
1338
+ const request = {
1339
+ id: generateId(),
1340
+ action: "hover",
1341
+ ref: parsedRef,
1342
+ tabId: options.tabId
1343
+ };
1344
+ const response = await sendCommand(request);
1345
+ if (options.json) {
1346
+ console.log(JSON.stringify(response, null, 2));
1347
+ } else {
1348
+ if (response.success) {
1349
+ const role = response.data?.role ?? "element";
1350
+ const name = response.data?.name;
1351
+ if (name) {
1352
+ console.log(`\u5DF2\u60AC\u505C: ${role} "${name}"`);
1353
+ } else {
1354
+ console.log(`\u5DF2\u60AC\u505C: ${role}`);
1355
+ }
1356
+ } else {
1357
+ console.error(`\u9519\u8BEF: ${response.error}`);
1358
+ process.exit(1);
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ // packages/cli/src/commands/fill.ts
1364
+ function parseRef3(ref) {
1365
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1366
+ }
1367
+ async function fillCommand(ref, text, options = {}) {
1368
+ if (!ref) {
1369
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1370
+ }
1371
+ if (text === void 0 || text === null) {
1372
+ throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
1373
+ }
1374
+ await ensureDaemonRunning();
1375
+ const parsedRef = parseRef3(ref);
1376
+ const request = {
1377
+ id: generateId(),
1378
+ action: "fill",
1379
+ ref: parsedRef,
1380
+ text,
1381
+ tabId: options.tabId
1382
+ };
1383
+ const response = await sendCommand(request);
1384
+ if (options.json) {
1385
+ console.log(JSON.stringify(response, null, 2));
1386
+ } else {
1387
+ if (response.success) {
1388
+ const role = response.data?.role ?? "element";
1389
+ const name = response.data?.name;
1390
+ if (name) {
1391
+ console.log(`\u5DF2\u586B\u5145: ${role} "${name}"`);
1392
+ } else {
1393
+ console.log(`\u5DF2\u586B\u5145: ${role}`);
1394
+ }
1395
+ console.log(`\u5185\u5BB9: "${text}"`);
1396
+ } else {
1397
+ console.error(`\u9519\u8BEF: ${response.error}`);
1398
+ process.exit(1);
1399
+ }
1400
+ }
1401
+ }
1402
+
1403
+ // packages/cli/src/commands/type.ts
1404
+ function parseRef4(ref) {
1405
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1406
+ }
1407
+ async function typeCommand(ref, text, options = {}) {
1408
+ if (!ref) {
1409
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1410
+ }
1411
+ if (text === void 0 || text === null) {
1412
+ throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
1413
+ }
1414
+ await ensureDaemonRunning();
1415
+ const parsedRef = parseRef4(ref);
1416
+ const request = {
1417
+ id: generateId(),
1418
+ action: "type",
1419
+ ref: parsedRef,
1420
+ text,
1421
+ tabId: options.tabId
1422
+ };
1423
+ const response = await sendCommand(request);
1424
+ if (options.json) {
1425
+ console.log(JSON.stringify(response, null, 2));
1426
+ } else {
1427
+ if (response.success) {
1428
+ const role = response.data?.role ?? "element";
1429
+ const name = response.data?.name;
1430
+ if (name) {
1431
+ console.log(`\u5DF2\u8F93\u5165: ${role} "${name}"`);
1432
+ } else {
1433
+ console.log(`\u5DF2\u8F93\u5165: ${role}`);
1434
+ }
1435
+ console.log(`\u5185\u5BB9: "${text}"`);
1436
+ } else {
1437
+ console.error(`\u9519\u8BEF: ${response.error}`);
1438
+ process.exit(1);
1439
+ }
1440
+ }
1441
+ }
1442
+
1443
+ // packages/cli/src/commands/close.ts
1444
+ async function closeCommand(options = {}) {
1445
+ await ensureDaemonRunning();
1446
+ const request = {
1447
+ id: generateId(),
1448
+ action: "close",
1449
+ tabId: options.tabId
1450
+ };
1451
+ const response = await sendCommand(request);
1452
+ if (options.json) {
1453
+ console.log(JSON.stringify(response, null, 2));
1454
+ } else {
1455
+ if (response.success) {
1456
+ const title = response.data?.title ?? "";
1457
+ if (title) {
1458
+ console.log(`\u5DF2\u5173\u95ED: "${title}"`);
1459
+ } else {
1460
+ console.log("\u5DF2\u5173\u95ED\u5F53\u524D\u6807\u7B7E\u9875");
1461
+ }
1462
+ } else {
1463
+ console.error(`\u9519\u8BEF: ${response.error}`);
1464
+ process.exit(1);
1465
+ }
1466
+ }
1467
+ }
1468
+
1469
+ // packages/cli/src/commands/get.ts
1470
+ function parseRef5(ref) {
1471
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1472
+ }
1473
+ async function getCommand(attribute, ref, options = {}) {
1474
+ if (attribute === "text" && !ref) {
1475
+ throw new Error("get text \u9700\u8981 ref \u53C2\u6570\uFF0C\u5982: get text @5");
1476
+ }
1477
+ await ensureDaemonRunning();
1478
+ const request = {
1479
+ id: generateId(),
1480
+ action: "get",
1481
+ attribute,
1482
+ ref: ref ? parseRef5(ref) : void 0,
1483
+ tabId: options.tabId
1484
+ };
1485
+ const response = await sendCommand(request);
1486
+ if (options.json) {
1487
+ console.log(JSON.stringify(response, null, 2));
1488
+ } else {
1489
+ if (response.success) {
1490
+ const value = response.data?.value ?? "";
1491
+ console.log(value);
1492
+ } else {
1493
+ console.error(`\u9519\u8BEF: ${response.error}`);
1494
+ process.exit(1);
1495
+ }
1496
+ }
1497
+ }
1498
+
1499
+ // packages/cli/src/commands/screenshot.ts
1500
+ import fs from "fs";
1501
+ import path2 from "path";
1502
+ import os2 from "os";
1503
+ function getDefaultPath() {
1504
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1505
+ const filename = `bb-screenshot-${timestamp}.png`;
1506
+ return path2.join(os2.tmpdir(), filename);
1507
+ }
1508
+ function saveBase64Image(dataUrl, filePath) {
1509
+ const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
1510
+ const buffer = Buffer.from(base64Data, "base64");
1511
+ const dir = path2.dirname(filePath);
1512
+ if (!fs.existsSync(dir)) {
1513
+ fs.mkdirSync(dir, { recursive: true });
1514
+ }
1515
+ fs.writeFileSync(filePath, buffer);
1516
+ }
1517
+ async function screenshotCommand(outputPath, options = {}) {
1518
+ await ensureDaemonRunning();
1519
+ const filePath = outputPath ? path2.resolve(outputPath) : getDefaultPath();
1520
+ const request = {
1521
+ id: generateId(),
1522
+ action: "screenshot",
1523
+ tabId: options.tabId
1524
+ };
1525
+ const response = await sendCommand(request);
1526
+ if (response.success && response.data?.dataUrl) {
1527
+ const dataUrl = response.data.dataUrl;
1528
+ saveBase64Image(dataUrl, filePath);
1529
+ if (options.json) {
1530
+ console.log(JSON.stringify({
1531
+ success: true,
1532
+ path: filePath,
1533
+ base64: dataUrl
1534
+ }, null, 2));
1535
+ } else {
1536
+ console.log(`\u622A\u56FE\u5DF2\u4FDD\u5B58: ${filePath}`);
1537
+ }
1538
+ } else {
1539
+ if (options.json) {
1540
+ console.log(JSON.stringify(response, null, 2));
1541
+ } else {
1542
+ console.error(`\u9519\u8BEF: ${response.error}`);
1543
+ }
1544
+ process.exit(1);
1545
+ }
1546
+ }
1547
+
1548
+ // packages/cli/src/commands/wait.ts
1549
+ function isTimeWait(target) {
1550
+ return /^\d+$/.test(target);
1551
+ }
1552
+ function parseRef6(ref) {
1553
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1554
+ }
1555
+ async function waitCommand(target, options = {}) {
1556
+ if (!target) {
1557
+ throw new Error("\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
1558
+ }
1559
+ await ensureDaemonRunning();
1560
+ let request;
1561
+ if (isTimeWait(target)) {
1562
+ const ms = parseInt(target, 10);
1563
+ request = {
1564
+ id: generateId(),
1565
+ action: "wait",
1566
+ waitType: "time",
1567
+ ms,
1568
+ tabId: options.tabId
1569
+ };
1570
+ } else {
1571
+ const ref = parseRef6(target);
1572
+ request = {
1573
+ id: generateId(),
1574
+ action: "wait",
1575
+ waitType: "element",
1576
+ ref,
1577
+ tabId: options.tabId
1578
+ };
1579
+ }
1580
+ const response = await sendCommand(request);
1581
+ if (options.json) {
1582
+ console.log(JSON.stringify(response, null, 2));
1583
+ } else {
1584
+ if (response.success) {
1585
+ if (isTimeWait(target)) {
1586
+ console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
1587
+ } else {
1588
+ console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
1589
+ }
1590
+ } else {
1591
+ console.error(`\u9519\u8BEF: ${response.error}`);
1592
+ process.exit(1);
1593
+ }
1594
+ }
1595
+ }
1596
+
1597
+ // packages/cli/src/commands/press.ts
1598
+ function parseKey(keyString) {
1599
+ const parts = keyString.split("+");
1600
+ const modifierNames = ["Control", "Alt", "Shift", "Meta"];
1601
+ const modifiers = [];
1602
+ let key = "";
1603
+ for (const part of parts) {
1604
+ if (modifierNames.includes(part)) {
1605
+ modifiers.push(part);
1606
+ } else {
1607
+ key = part;
1608
+ }
1609
+ }
1610
+ return { key, modifiers };
1611
+ }
1612
+ async function pressCommand(keyString, options = {}) {
1613
+ if (!keyString) {
1614
+ throw new Error("\u7F3A\u5C11 key \u53C2\u6570");
1615
+ }
1616
+ await ensureDaemonRunning();
1617
+ const { key, modifiers } = parseKey(keyString);
1618
+ if (!key) {
1619
+ throw new Error("\u65E0\u6548\u7684\u6309\u952E\u683C\u5F0F");
1620
+ }
1621
+ const request = {
1622
+ id: generateId(),
1623
+ action: "press",
1624
+ key,
1625
+ modifiers,
1626
+ tabId: options.tabId
1627
+ };
1628
+ const response = await sendCommand(request);
1629
+ if (options.json) {
1630
+ console.log(JSON.stringify(response, null, 2));
1631
+ } else {
1632
+ if (response.success) {
1633
+ const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
1634
+ console.log(`\u5DF2\u6309\u4E0B: ${displayKey}`);
1635
+ } else {
1636
+ console.error(`\u9519\u8BEF: ${response.error}`);
1637
+ process.exit(1);
1638
+ }
1639
+ }
1640
+ }
1641
+
1642
+ // packages/cli/src/commands/scroll.ts
1643
+ var VALID_DIRECTIONS = ["up", "down", "left", "right"];
1644
+ var DEFAULT_PIXELS = 300;
1645
+ async function scrollCommand(direction, pixels, options = {}) {
1646
+ if (!direction) {
1647
+ throw new Error("\u7F3A\u5C11 direction \u53C2\u6570");
1648
+ }
1649
+ if (!VALID_DIRECTIONS.includes(direction)) {
1650
+ throw new Error(
1651
+ `\u65E0\u6548\u7684\u6EDA\u52A8\u65B9\u5411: ${direction}\uFF0C\u652F\u6301: ${VALID_DIRECTIONS.join(", ")}`
1652
+ );
1653
+ }
1654
+ let pixelValue = DEFAULT_PIXELS;
1655
+ if (pixels !== void 0) {
1656
+ pixelValue = parseInt(pixels, 10);
1657
+ if (isNaN(pixelValue) || pixelValue <= 0) {
1658
+ throw new Error(`\u65E0\u6548\u7684\u50CF\u7D20\u503C: ${pixels}`);
1659
+ }
1660
+ }
1661
+ await ensureDaemonRunning();
1662
+ const request = {
1663
+ id: generateId(),
1664
+ action: "scroll",
1665
+ direction,
1666
+ pixels: pixelValue,
1667
+ tabId: options.tabId
1668
+ };
1669
+ const response = await sendCommand(request);
1670
+ if (options.json) {
1671
+ console.log(JSON.stringify(response, null, 2));
1672
+ } else {
1673
+ if (response.success) {
1674
+ console.log(`\u5DF2\u6EDA\u52A8: ${direction} ${pixelValue}px`);
1675
+ } else {
1676
+ console.error(`\u9519\u8BEF: ${response.error}`);
1677
+ process.exit(1);
1678
+ }
1679
+ }
1680
+ }
1681
+
1682
+ // packages/cli/src/commands/nav.ts
1683
+ async function backCommand(options = {}) {
1684
+ await ensureDaemonRunning();
1685
+ const request = {
1686
+ id: generateId(),
1687
+ action: "back",
1688
+ tabId: options.tabId
1689
+ };
1690
+ const response = await sendCommand(request);
1691
+ if (options.json) {
1692
+ console.log(JSON.stringify(response, null, 2));
1693
+ } else {
1694
+ if (response.success) {
1695
+ const url = response.data?.url ?? "";
1696
+ if (url) {
1697
+ console.log(`\u540E\u9000\u81F3: ${url}`);
1698
+ } else {
1699
+ console.log("\u5DF2\u540E\u9000");
1700
+ }
1701
+ } else {
1702
+ console.error(`\u9519\u8BEF: ${response.error}`);
1703
+ process.exit(1);
1704
+ }
1705
+ }
1706
+ }
1707
+ async function forwardCommand(options = {}) {
1708
+ await ensureDaemonRunning();
1709
+ const request = {
1710
+ id: generateId(),
1711
+ action: "forward",
1712
+ tabId: options.tabId
1713
+ };
1714
+ const response = await sendCommand(request);
1715
+ if (options.json) {
1716
+ console.log(JSON.stringify(response, null, 2));
1717
+ } else {
1718
+ if (response.success) {
1719
+ const url = response.data?.url ?? "";
1720
+ if (url) {
1721
+ console.log(`\u524D\u8FDB\u81F3: ${url}`);
1722
+ } else {
1723
+ console.log("\u5DF2\u524D\u8FDB");
1724
+ }
1725
+ } else {
1726
+ console.error(`\u9519\u8BEF: ${response.error}`);
1727
+ process.exit(1);
1728
+ }
1729
+ }
1730
+ }
1731
+ async function refreshCommand(options = {}) {
1732
+ await ensureDaemonRunning();
1733
+ const request = {
1734
+ id: generateId(),
1735
+ action: "refresh",
1736
+ tabId: options.tabId
1737
+ };
1738
+ const response = await sendCommand(request);
1739
+ if (options.json) {
1740
+ console.log(JSON.stringify(response, null, 2));
1741
+ } else {
1742
+ if (response.success) {
1743
+ const title = response.data?.title ?? "";
1744
+ if (title) {
1745
+ console.log(`\u5DF2\u5237\u65B0: "${title}"`);
1746
+ } else {
1747
+ console.log("\u5DF2\u5237\u65B0\u9875\u9762");
1748
+ }
1749
+ } else {
1750
+ console.error(`\u9519\u8BEF: ${response.error}`);
1751
+ process.exit(1);
1752
+ }
1753
+ }
1754
+ }
1755
+
1756
+ // packages/cli/src/commands/check.ts
1757
+ function parseRef7(ref) {
1758
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1759
+ }
1760
+ async function checkCommand(ref, options = {}) {
1761
+ if (!ref) {
1762
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1763
+ }
1764
+ await ensureDaemonRunning();
1765
+ const parsedRef = parseRef7(ref);
1766
+ const request = {
1767
+ id: generateId(),
1768
+ action: "check",
1769
+ ref: parsedRef,
1770
+ tabId: options.tabId
1771
+ };
1772
+ const response = await sendCommand(request);
1773
+ if (options.json) {
1774
+ console.log(JSON.stringify(response, null, 2));
1775
+ } else {
1776
+ if (response.success) {
1777
+ const role = response.data?.role ?? "checkbox";
1778
+ const name = response.data?.name;
1779
+ const wasAlreadyChecked = response.data?.wasAlreadyChecked;
1780
+ if (wasAlreadyChecked) {
1781
+ if (name) {
1782
+ console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role} "${name}"`);
1783
+ } else {
1784
+ console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role}`);
1785
+ }
1786
+ } else {
1787
+ if (name) {
1788
+ console.log(`\u5DF2\u52FE\u9009: ${role} "${name}"`);
1789
+ } else {
1790
+ console.log(`\u5DF2\u52FE\u9009: ${role}`);
1791
+ }
1792
+ }
1793
+ } else {
1794
+ console.error(`\u9519\u8BEF: ${response.error}`);
1795
+ process.exit(1);
1796
+ }
1797
+ }
1798
+ }
1799
+ async function uncheckCommand(ref, options = {}) {
1800
+ if (!ref) {
1801
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1802
+ }
1803
+ await ensureDaemonRunning();
1804
+ const parsedRef = parseRef7(ref);
1805
+ const request = {
1806
+ id: generateId(),
1807
+ action: "uncheck",
1808
+ ref: parsedRef,
1809
+ tabId: options.tabId
1810
+ };
1811
+ const response = await sendCommand(request);
1812
+ if (options.json) {
1813
+ console.log(JSON.stringify(response, null, 2));
1814
+ } else {
1815
+ if (response.success) {
1816
+ const role = response.data?.role ?? "checkbox";
1817
+ const name = response.data?.name;
1818
+ const wasAlreadyUnchecked = response.data?.wasAlreadyUnchecked;
1819
+ if (wasAlreadyUnchecked) {
1820
+ if (name) {
1821
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role} "${name}"`);
1822
+ } else {
1823
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role}`);
1824
+ }
1825
+ } else {
1826
+ if (name) {
1827
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role} "${name}"`);
1828
+ } else {
1829
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role}`);
1830
+ }
1831
+ }
1832
+ } else {
1833
+ console.error(`\u9519\u8BEF: ${response.error}`);
1834
+ process.exit(1);
1835
+ }
1836
+ }
1837
+ }
1838
+
1839
+ // packages/cli/src/commands/select.ts
1840
+ function parseRef8(ref) {
1841
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1842
+ }
1843
+ async function selectCommand(ref, value, options = {}) {
1844
+ if (!ref) {
1845
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1846
+ }
1847
+ if (value === void 0 || value === null) {
1848
+ throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
1849
+ }
1850
+ await ensureDaemonRunning();
1851
+ const parsedRef = parseRef8(ref);
1852
+ const request = {
1853
+ id: generateId(),
1854
+ action: "select",
1855
+ ref: parsedRef,
1856
+ value,
1857
+ tabId: options.tabId
1858
+ };
1859
+ const response = await sendCommand(request);
1860
+ if (options.json) {
1861
+ console.log(JSON.stringify(response, null, 2));
1862
+ } else {
1863
+ if (response.success) {
1864
+ const role = response.data?.role ?? "combobox";
1865
+ const name = response.data?.name;
1866
+ const selectedValue = response.data?.selectedValue;
1867
+ const selectedLabel = response.data?.selectedLabel;
1868
+ if (name) {
1869
+ console.log(`\u5DF2\u9009\u62E9: ${role} "${name}"`);
1870
+ } else {
1871
+ console.log(`\u5DF2\u9009\u62E9: ${role}`);
1872
+ }
1873
+ if (selectedLabel && selectedLabel !== selectedValue) {
1874
+ console.log(`\u9009\u9879: "${selectedLabel}" (value="${selectedValue}")`);
1875
+ } else {
1876
+ console.log(`\u9009\u9879: "${selectedValue}"`);
1877
+ }
1878
+ } else {
1879
+ console.error(`\u9519\u8BEF: ${response.error}`);
1880
+ process.exit(1);
1881
+ }
1882
+ }
1883
+ }
1884
+
1885
+ // packages/cli/src/commands/eval.ts
1886
+ async function evalCommand(script, options = {}) {
1887
+ if (!script) {
1888
+ throw new Error("\u7F3A\u5C11 script \u53C2\u6570");
1889
+ }
1890
+ await ensureDaemonRunning();
1891
+ const request = {
1892
+ id: generateId(),
1893
+ action: "eval",
1894
+ script,
1895
+ tabId: options.tabId
1896
+ };
1897
+ const response = await sendCommand(request);
1898
+ if (options.json) {
1899
+ console.log(JSON.stringify(response, null, 2));
1900
+ } else {
1901
+ if (response.success) {
1902
+ const result = response.data?.result;
1903
+ if (result !== void 0) {
1904
+ if (typeof result === "object" && result !== null) {
1905
+ console.log(JSON.stringify(result, null, 2));
1906
+ } else {
1907
+ console.log(result);
1908
+ }
1909
+ } else {
1910
+ console.log("undefined");
1911
+ }
1912
+ } else {
1913
+ console.error(`\u9519\u8BEF: ${response.error}`);
1914
+ process.exit(1);
1915
+ }
1916
+ }
1917
+ }
1918
+
1919
+ // packages/cli/src/commands/tab.ts
1920
+ function parseTabSubcommand(args, rawArgv) {
1921
+ let tabId;
1922
+ if (rawArgv) {
1923
+ const idIdx = rawArgv.indexOf("--id");
1924
+ if (idIdx >= 0 && rawArgv[idIdx + 1]) {
1925
+ tabId = parseInt(rawArgv[idIdx + 1], 10);
1926
+ if (isNaN(tabId)) {
1927
+ throw new Error(`\u65E0\u6548\u7684 tabId: ${rawArgv[idIdx + 1]}`);
1928
+ }
1929
+ }
1930
+ }
1931
+ if (args.length === 0) {
1932
+ return { action: "tab_list" };
1933
+ }
1934
+ const first = args[0];
1935
+ if (first === "list") {
1936
+ return { action: "tab_list" };
1937
+ }
1938
+ if (first === "new") {
1939
+ return { action: "tab_new", url: args[1] };
1940
+ }
1941
+ if (first === "select") {
1942
+ if (tabId !== void 0) {
1943
+ return { action: "tab_select", tabId };
1944
+ }
1945
+ throw new Error("tab select \u9700\u8981 --id \u53C2\u6570\uFF0C\u7528\u6CD5\uFF1Abb-browser tab select --id <tabId>");
1946
+ }
1947
+ if (first === "close") {
1948
+ if (tabId !== void 0) {
1949
+ return { action: "tab_close", tabId };
1950
+ }
1951
+ const indexArg = args[1];
1952
+ if (indexArg !== void 0) {
1953
+ const index2 = parseInt(indexArg, 10);
1954
+ if (isNaN(index2) || index2 < 0) {
1955
+ throw new Error(`\u65E0\u6548\u7684\u6807\u7B7E\u9875\u7D22\u5F15: ${indexArg}`);
1956
+ }
1957
+ return { action: "tab_close", index: index2 };
1958
+ }
1959
+ return { action: "tab_close" };
1960
+ }
1961
+ const index = parseInt(first, 10);
1962
+ if (!isNaN(index) && index >= 0) {
1963
+ return { action: "tab_select", index };
1964
+ }
1965
+ throw new Error(`\u672A\u77E5\u7684 tab \u5B50\u547D\u4EE4: ${first}`);
1966
+ }
1967
+ function formatTabList(tabs, activeIndex) {
1968
+ const lines = [];
1969
+ lines.push(`\u6807\u7B7E\u9875\u5217\u8868\uFF08\u5171 ${tabs.length} \u4E2A\uFF0C\u5F53\u524D #${activeIndex}\uFF09\uFF1A`);
1970
+ for (const tab of tabs) {
1971
+ const prefix = tab.active ? "*" : " ";
1972
+ const title = tab.title || "(\u65E0\u6807\u9898)";
1973
+ lines.push(`${prefix} [${tab.index}] ${tab.url} - ${title}`);
1974
+ }
1975
+ return lines.join("\n");
1976
+ }
1977
+ async function tabCommand(args, options = {}) {
1978
+ await ensureDaemonRunning();
1979
+ const parsed = parseTabSubcommand(args, process.argv);
1980
+ if (options.globalTabId && parsed.tabId === void 0 && parsed.index === void 0) {
1981
+ if (parsed.action === "tab_close" || parsed.action === "tab_select") {
1982
+ const numId = parseInt(options.globalTabId, 10);
1983
+ parsed.tabId = isNaN(numId) ? options.globalTabId : numId;
1984
+ }
1985
+ }
1986
+ const request = {
1987
+ id: generateId(),
1988
+ action: parsed.action,
1989
+ url: parsed.url,
1990
+ index: parsed.index,
1991
+ tabId: parsed.tabId
1992
+ };
1993
+ const response = await sendCommand(request);
1994
+ if (options.json) {
1995
+ console.log(JSON.stringify(response, null, 2));
1996
+ } else {
1997
+ if (response.success) {
1998
+ switch (parsed.action) {
1999
+ case "tab_list": {
2000
+ const tabs = response.data?.tabs ?? [];
2001
+ const activeIndex = response.data?.activeIndex ?? 0;
2002
+ console.log(formatTabList(tabs, activeIndex));
2003
+ break;
2004
+ }
2005
+ case "tab_new": {
2006
+ const url = response.data?.url ?? "about:blank";
2007
+ console.log(`\u5DF2\u521B\u5EFA\u65B0\u6807\u7B7E\u9875: ${url}`);
2008
+ break;
2009
+ }
2010
+ case "tab_select": {
2011
+ const title = response.data?.title ?? "(\u65E0\u6807\u9898)";
2012
+ const url = response.data?.url ?? "";
2013
+ console.log(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 #${parsed.index}: ${title}`);
2014
+ console.log(` URL: ${url}`);
2015
+ break;
2016
+ }
2017
+ case "tab_close": {
2018
+ const closedTitle = response.data?.title ?? "(\u65E0\u6807\u9898)";
2019
+ console.log(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875: ${closedTitle}`);
2020
+ break;
2021
+ }
2022
+ }
2023
+ } else {
2024
+ console.error(`\u9519\u8BEF: ${response.error}`);
2025
+ process.exit(1);
2026
+ }
2027
+ }
2028
+ }
2029
+
2030
+ // packages/cli/src/commands/frame.ts
2031
+ async function frameCommand(selector, options = {}) {
2032
+ if (!selector) {
2033
+ throw new Error("\u7F3A\u5C11 selector \u53C2\u6570");
2034
+ }
2035
+ await ensureDaemonRunning();
2036
+ const request = {
2037
+ id: generateId(),
2038
+ action: "frame",
2039
+ selector,
2040
+ tabId: options.tabId
2041
+ };
2042
+ const response = await sendCommand(request);
2043
+ if (options.json) {
2044
+ console.log(JSON.stringify(response, null, 2));
2045
+ } else {
2046
+ if (response.success) {
2047
+ const frameInfo = response.data?.frameInfo;
2048
+ if (frameInfo?.url) {
2049
+ console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector} (${frameInfo.url})`);
2050
+ } else {
2051
+ console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector}`);
2052
+ }
2053
+ } else {
2054
+ console.error(`\u9519\u8BEF: ${response.error}`);
2055
+ process.exit(1);
2056
+ }
2057
+ }
2058
+ }
2059
+ async function frameMainCommand(options = {}) {
2060
+ await ensureDaemonRunning();
2061
+ const request = {
2062
+ id: generateId(),
2063
+ action: "frame_main",
2064
+ tabId: options.tabId
2065
+ };
2066
+ const response = await sendCommand(request);
2067
+ if (options.json) {
2068
+ console.log(JSON.stringify(response, null, 2));
2069
+ } else {
2070
+ if (response.success) {
2071
+ console.log("\u5DF2\u8FD4\u56DE\u4E3B frame");
2072
+ } else {
2073
+ console.error(`\u9519\u8BEF: ${response.error}`);
2074
+ process.exit(1);
2075
+ }
2076
+ }
2077
+ }
2078
+
2079
+ // packages/cli/src/commands/dialog.ts
2080
+ async function dialogCommand(subCommand, promptText, options = {}) {
2081
+ if (!subCommand || !["accept", "dismiss"].includes(subCommand)) {
2082
+ throw new Error("\u8BF7\u4F7F\u7528 'dialog accept [text]' \u6216 'dialog dismiss'");
2083
+ }
2084
+ await ensureDaemonRunning();
2085
+ const request = {
2086
+ id: generateId(),
2087
+ action: "dialog",
2088
+ dialogResponse: subCommand,
2089
+ promptText: subCommand === "accept" ? promptText : void 0,
2090
+ tabId: options.tabId
2091
+ };
2092
+ const response = await sendCommand(request);
2093
+ if (options.json) {
2094
+ console.log(JSON.stringify(response, null, 2));
2095
+ } else {
2096
+ if (response.success) {
2097
+ const dialogInfo = response.data?.dialogInfo;
2098
+ if (dialogInfo) {
2099
+ const action = subCommand === "accept" ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
2100
+ console.log(`${action}\u5BF9\u8BDD\u6846\uFF08${dialogInfo.type}\uFF09: "${dialogInfo.message}"`);
2101
+ } else {
2102
+ console.log("\u5BF9\u8BDD\u6846\u5DF2\u5904\u7406");
2103
+ }
2104
+ } else {
2105
+ console.error(`\u9519\u8BEF: ${response.error}`);
2106
+ process.exit(1);
2107
+ }
2108
+ }
2109
+ }
2110
+
2111
+ // packages/cli/src/commands/network.ts
2112
+ async function networkCommand(subCommand, urlOrFilter, options = {}) {
2113
+ let since;
2114
+ if (subCommand === "requests" && options.since) {
2115
+ const num = parseInt(options.since, 10);
2116
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2117
+ }
2118
+ const request = {
2119
+ id: generateId(),
2120
+ action: "network",
2121
+ networkCommand: subCommand,
2122
+ url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
2123
+ filter: subCommand === "requests" ? urlOrFilter : void 0,
2124
+ routeOptions: subCommand === "route" ? {
2125
+ abort: options.abort,
2126
+ body: options.body
2127
+ } : void 0,
2128
+ withBody: subCommand === "requests" ? options.withBody : void 0,
2129
+ since,
2130
+ method: subCommand === "requests" ? options.method : void 0,
2131
+ status: subCommand === "requests" ? options.status : void 0,
2132
+ tabId: options.tabId
2133
+ };
2134
+ const response = await sendCommand(request);
2135
+ if (options.json) {
2136
+ console.log(JSON.stringify(response));
2137
+ return;
2138
+ }
2139
+ if (!response.success) {
2140
+ throw new Error(response.error || "Network command failed");
2141
+ }
2142
+ const data = response.data;
2143
+ switch (subCommand) {
2144
+ case "requests": {
2145
+ const requests = data?.networkRequests || [];
2146
+ if (requests.length === 0) {
2147
+ console.log("\u6CA1\u6709\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
2148
+ console.log("\u63D0\u793A: \u4F7F\u7528 network requests \u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
2149
+ } else {
2150
+ console.log(`\u7F51\u7EDC\u8BF7\u6C42 (${requests.length} \u6761):
2151
+ `);
2152
+ for (const req of requests) {
2153
+ const status = req.failed ? `FAILED (${req.failureReason})` : req.status ? `${req.status} ${req.statusText || ""}` : "pending";
2154
+ console.log(`${req.method} ${req.url}`);
2155
+ console.log(` \u7C7B\u578B: ${req.type}, \u72B6\u6001: ${status}`);
2156
+ if (options.withBody) {
2157
+ const requestHeaderCount = req.requestHeaders ? Object.keys(req.requestHeaders).length : 0;
2158
+ const responseHeaderCount = req.responseHeaders ? Object.keys(req.responseHeaders).length : 0;
2159
+ console.log(` \u8BF7\u6C42\u5934: ${requestHeaderCount}, \u54CD\u5E94\u5934: ${responseHeaderCount}`);
2160
+ if (req.requestBody !== void 0) {
2161
+ const preview = req.requestBody.length > 200 ? `${req.requestBody.slice(0, 200)}...` : req.requestBody;
2162
+ console.log(` \u8BF7\u6C42\u4F53: ${preview}`);
2163
+ }
2164
+ if (req.responseBody !== void 0) {
2165
+ const preview = req.responseBody.length > 200 ? `${req.responseBody.slice(0, 200)}...` : req.responseBody;
2166
+ console.log(` \u54CD\u5E94\u4F53: ${preview}`);
2167
+ }
2168
+ if (req.bodyError) {
2169
+ console.log(` Body\u9519\u8BEF: ${req.bodyError}`);
2170
+ }
2171
+ }
2172
+ console.log("");
2173
+ }
2174
+ }
2175
+ break;
2176
+ }
2177
+ case "route": {
2178
+ console.log(`\u5DF2\u6DFB\u52A0\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
2179
+ if (options.abort) {
2180
+ console.log(" \u884C\u4E3A: \u963B\u6B62\u8BF7\u6C42");
2181
+ } else if (options.body) {
2182
+ console.log(" \u884C\u4E3A: \u8FD4\u56DE mock \u6570\u636E");
2183
+ } else {
2184
+ console.log(" \u884C\u4E3A: \u7EE7\u7EED\u8BF7\u6C42");
2185
+ }
2186
+ console.log(`\u5F53\u524D\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
2187
+ break;
2188
+ }
2189
+ case "unroute": {
2190
+ if (urlOrFilter) {
2191
+ console.log(`\u5DF2\u79FB\u9664\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
2192
+ } else {
2193
+ console.log("\u5DF2\u79FB\u9664\u6240\u6709\u62E6\u622A\u89C4\u5219");
2194
+ }
2195
+ console.log(`\u5269\u4F59\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
2196
+ break;
2197
+ }
2198
+ case "clear": {
2199
+ console.log("\u5DF2\u6E05\u7A7A\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
2200
+ break;
2201
+ }
2202
+ default:
2203
+ throw new Error(`\u672A\u77E5\u7684 network \u5B50\u547D\u4EE4: ${subCommand}`);
2204
+ }
2205
+ }
2206
+
2207
+ // packages/cli/src/commands/console.ts
2208
+ async function consoleCommand(options = {}) {
2209
+ let since;
2210
+ if (options.since) {
2211
+ const num = parseInt(options.since, 10);
2212
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2213
+ }
2214
+ const request = {
2215
+ id: generateId(),
2216
+ action: "console",
2217
+ consoleCommand: options.clear ? "clear" : "get",
2218
+ tabId: options.tabId,
2219
+ since
2220
+ };
2221
+ const response = await sendCommand(request);
2222
+ if (options.json) {
2223
+ console.log(JSON.stringify(response));
2224
+ return;
2225
+ }
2226
+ if (!response.success) {
2227
+ throw new Error(response.error || "Console command failed");
2228
+ }
2229
+ if (options.clear) {
2230
+ console.log("\u5DF2\u6E05\u7A7A\u63A7\u5236\u53F0\u6D88\u606F");
2231
+ return;
2232
+ }
2233
+ const messages = response.data?.consoleMessages || [];
2234
+ if (messages.length === 0) {
2235
+ console.log("\u6CA1\u6709\u63A7\u5236\u53F0\u6D88\u606F");
2236
+ console.log("\u63D0\u793A: console \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
2237
+ return;
2238
+ }
2239
+ console.log(`\u63A7\u5236\u53F0\u6D88\u606F (${messages.length} \u6761):
2240
+ `);
2241
+ const typeColors = {
2242
+ log: "",
2243
+ info: "[INFO]",
2244
+ warn: "[WARN]",
2245
+ error: "[ERROR]",
2246
+ debug: "[DEBUG]"
2247
+ };
2248
+ for (const msg of messages) {
2249
+ const prefix = typeColors[msg.type] || `[${msg.type.toUpperCase()}]`;
2250
+ const location = msg.url ? ` (${msg.url}${msg.lineNumber ? `:${msg.lineNumber}` : ""})` : "";
2251
+ if (prefix) {
2252
+ console.log(`${prefix} ${msg.text}${location}`);
2253
+ } else {
2254
+ console.log(`${msg.text}${location}`);
2255
+ }
2256
+ }
2257
+ }
2258
+
2259
+ // packages/cli/src/commands/errors.ts
2260
+ async function errorsCommand(options = {}) {
2261
+ let since;
2262
+ if (options.since) {
2263
+ const num = parseInt(options.since, 10);
2264
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2265
+ }
2266
+ const request = {
2267
+ id: generateId(),
2268
+ action: "errors",
2269
+ errorsCommand: options.clear ? "clear" : "get",
2270
+ tabId: options.tabId,
2271
+ since
2272
+ };
2273
+ const response = await sendCommand(request);
2274
+ if (options.json) {
2275
+ console.log(JSON.stringify(response));
2276
+ return;
2277
+ }
2278
+ if (!response.success) {
2279
+ throw new Error(response.error || "Errors command failed");
2280
+ }
2281
+ if (options.clear) {
2282
+ console.log("\u5DF2\u6E05\u7A7A JS \u9519\u8BEF\u8BB0\u5F55");
2283
+ return;
2284
+ }
2285
+ const errors = response.data?.jsErrors || [];
2286
+ if (errors.length === 0) {
2287
+ console.log("\u6CA1\u6709 JS \u9519\u8BEF");
2288
+ console.log("\u63D0\u793A: errors \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
2289
+ return;
2290
+ }
2291
+ console.log(`JS \u9519\u8BEF (${errors.length} \u6761):
2292
+ `);
2293
+ for (const err of errors) {
2294
+ console.log(`[ERROR] ${err.message}`);
2295
+ if (err.url) {
2296
+ console.log(` \u4F4D\u7F6E: ${err.url}:${err.lineNumber || 0}:${err.columnNumber || 0}`);
2297
+ }
2298
+ if (err.stackTrace) {
2299
+ console.log(` \u5806\u6808:`);
2300
+ console.log(err.stackTrace.split("\n").map((line) => ` ${line}`).join("\n"));
2301
+ }
2302
+ console.log("");
2303
+ }
2304
+ }
2305
+
2306
+ // packages/cli/src/commands/trace.ts
2307
+ async function traceCommand(subCommand, options = {}) {
2308
+ const response = await sendCommand({
2309
+ id: generateId(),
2310
+ action: "trace",
2311
+ traceCommand: subCommand,
2312
+ tabId: options.tabId
2313
+ });
2314
+ if (options.json) {
2315
+ console.log(JSON.stringify(response));
2316
+ return;
2317
+ }
2318
+ if (!response.success) {
2319
+ throw new Error(response.error || "Trace command failed");
2320
+ }
2321
+ const data = response.data;
2322
+ switch (subCommand) {
2323
+ case "start": {
2324
+ const status = data?.traceStatus;
2325
+ console.log("\u5F00\u59CB\u5F55\u5236\u7528\u6237\u64CD\u4F5C");
2326
+ console.log(`\u6807\u7B7E\u9875 ID: ${status?.tabId || "N/A"}`);
2327
+ console.log("\n\u5728\u6D4F\u89C8\u5668\u4E2D\u8FDB\u884C\u64CD\u4F5C\uFF0C\u5B8C\u6210\u540E\u8FD0\u884C 'bb-browser trace stop' \u505C\u6B62\u5F55\u5236");
2328
+ break;
2329
+ }
2330
+ case "stop": {
2331
+ const events = data?.traceEvents || [];
2332
+ const status = data?.traceStatus;
2333
+ console.log(`\u5F55\u5236\u5B8C\u6210\uFF0C\u5171 ${events.length} \u4E2A\u4E8B\u4EF6
2334
+ `);
2335
+ if (events.length === 0) {
2336
+ console.log("\u6CA1\u6709\u5F55\u5236\u5230\u4EFB\u4F55\u64CD\u4F5C");
2337
+ break;
2338
+ }
2339
+ for (let i = 0; i < events.length; i++) {
2340
+ const event = events[i];
2341
+ const refStr = event.ref !== void 0 ? `@${event.ref}` : "";
2342
+ switch (event.type) {
2343
+ case "navigation":
2344
+ console.log(`${i + 1}. \u5BFC\u822A\u5230: ${event.url}`);
2345
+ break;
2346
+ case "click":
2347
+ console.log(`${i + 1}. \u70B9\u51FB ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
2348
+ break;
2349
+ case "fill":
2350
+ console.log(`${i + 1}. \u586B\u5145 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
2351
+ break;
2352
+ case "select":
2353
+ console.log(`${i + 1}. \u9009\u62E9 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
2354
+ break;
2355
+ case "check":
2356
+ console.log(`${i + 1}. ${event.checked ? "\u52FE\u9009" : "\u53D6\u6D88\u52FE\u9009"} ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
2357
+ break;
2358
+ case "press":
2359
+ console.log(`${i + 1}. \u6309\u952E ${event.key}`);
2360
+ break;
2361
+ case "scroll":
2362
+ console.log(`${i + 1}. \u6EDA\u52A8 ${event.direction} ${event.pixels}px`);
2363
+ break;
2364
+ default:
2365
+ console.log(`${i + 1}. ${event.type}`);
2366
+ }
2367
+ }
2368
+ console.log(`
2369
+ \u72B6\u6001: ${status?.recording ? "\u5F55\u5236\u4E2D" : "\u5DF2\u505C\u6B62"}`);
2370
+ break;
2371
+ }
2372
+ case "status": {
2373
+ const status = data?.traceStatus;
2374
+ if (status?.recording) {
2375
+ console.log(`\u5F55\u5236\u4E2D (\u6807\u7B7E\u9875 ${status.tabId})`);
2376
+ console.log(`\u5DF2\u5F55\u5236 ${status.eventCount} \u4E2A\u4E8B\u4EF6`);
2377
+ } else {
2378
+ console.log("\u672A\u5728\u5F55\u5236");
2379
+ }
2380
+ break;
2381
+ }
2382
+ default:
2383
+ throw new Error(`\u672A\u77E5\u7684 trace \u5B50\u547D\u4EE4: ${subCommand}`);
2384
+ }
2385
+ }
2386
+
2387
+ // packages/cli/src/commands/fetch.ts
2388
+ function matchTabOrigin2(tabUrl, targetHostname) {
2389
+ try {
2390
+ const tabHostname = new URL(tabUrl).hostname;
2391
+ return tabHostname === targetHostname || tabHostname.endsWith("." + targetHostname);
2392
+ } catch {
2393
+ return false;
2394
+ }
2395
+ }
2396
+ async function ensureTabForOrigin(origin, hostname) {
2397
+ const listReq = { id: generateId(), action: "tab_list" };
2398
+ const listResp = await sendCommand(listReq);
2399
+ if (listResp.success && listResp.data?.tabs) {
2400
+ const matchingTab = listResp.data.tabs.find(
2401
+ (tab) => matchTabOrigin2(tab.url, hostname)
2402
+ );
2403
+ if (matchingTab) {
2404
+ return matchingTab.tabId;
2405
+ }
2406
+ }
2407
+ const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
2408
+ if (!newResp.success) {
2409
+ throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
2410
+ }
2411
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2412
+ return newResp.data?.tabId;
2413
+ }
2414
+ function buildFetchScript(url, options) {
2415
+ const method = (options.method || "GET").toUpperCase();
2416
+ const hasBody = options.body && method !== "GET" && method !== "HEAD";
2417
+ let headersExpr = "{}";
2418
+ if (options.headers) {
2419
+ try {
2420
+ JSON.parse(options.headers);
2421
+ headersExpr = options.headers;
2422
+ } catch {
2423
+ throw new Error(`--headers must be valid JSON. Got: ${options.headers}`);
2424
+ }
2425
+ }
2426
+ return `(async () => {
2427
+ try {
2428
+ const resp = await fetch(${JSON.stringify(url)}, {
2429
+ method: ${JSON.stringify(method)},
2430
+ credentials: 'include',
2431
+ headers: ${headersExpr}${hasBody ? `,
2432
+ body: ${JSON.stringify(options.body)}` : ""}
2433
+ });
2434
+ const contentType = resp.headers.get('content-type') || '';
2435
+ let body;
2436
+ if (contentType.includes('application/json') && resp.status !== 204) {
2437
+ try { body = await resp.json(); } catch { body = await resp.text(); }
2438
+ } else {
2439
+ body = await resp.text();
2440
+ }
2441
+ return JSON.stringify({
2442
+ status: resp.status,
2443
+ contentType,
2444
+ body
2445
+ });
2446
+ } catch (e) {
2447
+ return JSON.stringify({ error: e.message });
2448
+ }
2449
+ })()`;
2450
+ }
2451
+ async function fetchCommand(url, options = {}) {
2452
+ if (!url) {
2453
+ throw new Error(
2454
+ "\u7F3A\u5C11 URL \u53C2\u6570\n \u7528\u6CD5: bb-browser fetch <url> [--json] [--method POST] [--body '{...}']\n \u793A\u4F8B: bb-browser fetch https://www.reddit.com/api/me.json --json"
2455
+ );
2456
+ }
2457
+ await ensureDaemonRunning();
2458
+ const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
2459
+ let targetTabId = options.tabId;
2460
+ if (isAbsolute) {
2461
+ let origin;
2462
+ let hostname;
2463
+ try {
2464
+ const parsed = new URL(url);
2465
+ origin = parsed.origin;
2466
+ hostname = parsed.hostname;
2467
+ } catch {
2468
+ throw new Error(`\u65E0\u6548\u7684 URL: ${url}`);
2469
+ }
2470
+ if (!targetTabId) {
2471
+ targetTabId = await ensureTabForOrigin(origin, hostname);
2472
+ }
2473
+ }
2474
+ const script = buildFetchScript(url, options);
2475
+ const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
2476
+ const evalResp = await sendCommand(evalReq);
2477
+ if (!evalResp.success) {
2478
+ throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
2479
+ }
2480
+ const rawResult = evalResp.data?.result;
2481
+ if (rawResult === void 0 || rawResult === null) {
2482
+ throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
2483
+ }
2484
+ let result;
2485
+ try {
2486
+ result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
2487
+ } catch {
2488
+ console.log(rawResult);
2489
+ return;
2490
+ }
2491
+ if (result.error) {
2492
+ throw new Error(`Fetch error: ${result.error}`);
2493
+ }
2494
+ if (options.output) {
2495
+ const { writeFileSync } = await import("fs");
2496
+ const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
2497
+ writeFileSync(options.output, content, "utf-8");
2498
+ console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
2499
+ return;
2500
+ }
2501
+ if (typeof result.body === "object") {
2502
+ console.log(JSON.stringify(result.body, null, 2));
2503
+ } else {
2504
+ console.log(result.body);
2505
+ }
2506
+ }
2507
+
2508
+ // packages/cli/src/commands/history.ts
2509
+ async function historyCommand(subCommand, options = {}) {
2510
+ const days = options.days || 30;
2511
+ const data = subCommand === "search" ? { historyItems: searchHistory(options.query, days) } : { historyDomains: getHistoryDomains(days) };
2512
+ if (options.json) {
2513
+ console.log(JSON.stringify({
2514
+ id: generateId(),
2515
+ success: true,
2516
+ data
2517
+ }));
2518
+ return;
2519
+ }
2520
+ switch (subCommand) {
2521
+ case "search": {
2522
+ const items = data?.historyItems || [];
2523
+ console.log(`\u627E\u5230 ${items.length} \u6761\u5386\u53F2\u8BB0\u5F55
2524
+ `);
2525
+ if (items.length === 0) {
2526
+ console.log("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u5386\u53F2\u8BB0\u5F55");
2527
+ break;
2528
+ }
2529
+ for (let i = 0; i < items.length; i++) {
2530
+ const item = items[i];
2531
+ console.log(`${i + 1}. ${item.title || "(\u65E0\u6807\u9898)"}`);
2532
+ console.log(` ${item.url}`);
2533
+ console.log(` \u8BBF\u95EE\u6B21\u6570: ${item.visitCount}`);
2534
+ }
2535
+ break;
2536
+ }
2537
+ case "domains": {
2538
+ const domains = data?.historyDomains || [];
2539
+ console.log(`\u627E\u5230 ${domains.length} \u4E2A\u57DF\u540D
2540
+ `);
2541
+ if (domains.length === 0) {
2542
+ console.log("\u6CA1\u6709\u627E\u5230\u5386\u53F2\u8BB0\u5F55");
2543
+ break;
2544
+ }
2545
+ for (let i = 0; i < domains.length; i++) {
2546
+ const domain = domains[i];
2547
+ console.log(`${i + 1}. ${domain.domain}`);
2548
+ console.log(` \u8BBF\u95EE\u6B21\u6570: ${domain.visits}`);
2549
+ }
2550
+ break;
2551
+ }
2552
+ default:
2553
+ throw new Error(`\u672A\u77E5\u7684 history \u5B50\u547D\u4EE4: ${subCommand}`);
2554
+ }
2555
+ }
2556
+
2557
+ // packages/cli/src/commands/daemon.ts
2558
+ async function statusCommand(options = {}) {
2559
+ const status = await getDaemonStatus();
2560
+ if (!status) {
2561
+ if (options.json) {
2562
+ console.log(JSON.stringify({ running: false }));
2563
+ } else {
2564
+ console.log("Daemon not running");
2565
+ console.log("\n\u{1F4A1} \u542F\u52A8: bb-browser daemon start");
2566
+ }
2567
+ return;
2568
+ }
2569
+ if (options.json) {
2570
+ console.log(JSON.stringify(status, null, 2));
2571
+ return;
2572
+ }
2573
+ console.log(`Daemon running: ${status.running ? "yes" : "no"}`);
2574
+ console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
2575
+ console.log(`Uptime: ${formatUptime(status.uptime)}`);
2576
+ console.log(`Global seq: ${status.currentSeq ?? "N/A"}`);
2577
+ const tabs = status.tabs;
2578
+ if (tabs && tabs.length > 0) {
2579
+ console.log(`
2580
+ Tabs (${tabs.length}):`);
2581
+ for (const tab of tabs) {
2582
+ const active = tab.targetId === status.currentTargetId ? " *" : "";
2583
+ console.log(
2584
+ ` ${tab.shortId}${active} net:${tab.networkRequests} console:${tab.consoleMessages} err:${tab.jsErrors} seq:${tab.lastActionSeq}`
2585
+ );
2586
+ }
2587
+ } else {
2588
+ console.log("\nNo tabs");
2589
+ }
2590
+ if (status.cdpConnected === false) {
2591
+ console.log("\n\u26A0\uFE0F Chrome \u672A\u8FDE\u63A5\u3002\u8FD0\u884C bb-browser daemon stop && bb-browser tab list \u91CD\u65B0\u542F\u52A8");
2592
+ } else {
2593
+ console.log("\n\u{1F4A1} \u505C\u6B62: bb-browser daemon stop");
2594
+ }
2595
+ }
2596
+ async function startCommand(options = {}) {
2597
+ await ensureDaemon();
2598
+ const status = await getDaemonStatus();
2599
+ if (options.json) {
2600
+ console.log(JSON.stringify(status, null, 2));
2601
+ } else {
2602
+ console.log("Daemon started");
2603
+ if (status) {
2604
+ console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
2605
+ const tabs = status.tabs;
2606
+ console.log(`Tabs: ${tabs?.length ?? 0}`);
2607
+ }
2608
+ }
2609
+ }
2610
+ async function shutdownCommand(options = {}) {
2611
+ const ok = await stopDaemon();
2612
+ if (options.json) {
2613
+ console.log(JSON.stringify({ stopped: ok }));
2614
+ } else {
2615
+ console.log(ok ? "Daemon stopped" : "Daemon was not running");
2616
+ }
2617
+ }
2618
+ function formatUptime(ms) {
2619
+ if (!ms || ms <= 0) return "0s";
2620
+ const s = Math.floor(ms / 1e3);
2621
+ if (s < 60) return `${s}s`;
2622
+ const m = Math.floor(s / 60);
2623
+ if (m < 60) return `${m}m ${s % 60}s`;
2624
+ const h = Math.floor(m / 60);
2625
+ return `${h}h ${m % 60}m`;
2626
+ }
2627
+
2628
+ // packages/cli/src/index.ts
2629
+ var VERSION = "0.11.5";
2630
+ var HELP_TEXT = `
2631
+ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
2632
+
2633
+ \u5B89\u88C5\uFF1A
2634
+ npm install -g bb-browser
2635
+
2636
+ \u63D0\u793A\uFF1A\u5927\u591A\u6570\u6570\u636E\u83B7\u53D6\u4EFB\u52A1\u8BF7\u76F4\u63A5\u4F7F\u7528 site \u547D\u4EE4\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u6D4F\u89C8\u5668\uFF1A
2637
+ bb-browser site list \u67E5\u770B\u6240\u6709\u53EF\u7528\u547D\u4EE4
2638
+ bb-browser site twitter/search "AI" \u793A\u4F8B\uFF1A\u641C\u7D22\u63A8\u6587
2639
+ bb-browser site xueqiu/hot-stock 5 \u793A\u4F8B\uFF1A\u83B7\u53D6\u4EBA\u6C14\u80A1\u7968
2640
+
2641
+ \u7528\u6CD5\uFF1A
2642
+ bb-browser <command> [options]
2643
+
2644
+ \u5F00\u59CB\u4F7F\u7528\uFF1A
2645
+ site recommend \u63A8\u8350\u4F60\u53EF\u80FD\u9700\u8981\u7684 adapter\uFF08\u57FA\u4E8E\u6D4F\u89C8\u5386\u53F2\uFF09
2646
+ site list \u5217\u51FA\u6240\u6709 adapter
2647
+ site info <name> \u67E5\u770B adapter \u7528\u6CD5\uFF08\u53C2\u6570\u3001\u8FD4\u56DE\u503C\u3001\u793A\u4F8B\uFF09
2648
+ site <name> [args] \u8FD0\u884C adapter
2649
+ site update \u66F4\u65B0\u793E\u533A adapter \u5E93
2650
+ guide \u5982\u4F55\u628A\u4EFB\u4F55\u7F51\u7AD9\u53D8\u6210 adapter
2651
+ star \u2B50 Star bb-browser on GitHub
2652
+
2653
+ \u6D4F\u89C8\u5668\u64CD\u4F5C\uFF1A
2654
+ open <url> [--tab] \u6253\u5F00 URL
2655
+ snapshot [-i] [-c] [-d <n>] \u83B7\u53D6\u9875\u9762\u5FEB\u7167
2656
+ click <ref> \u70B9\u51FB\u5143\u7D20
2657
+ hover <ref> \u60AC\u505C\u5143\u7D20
2658
+ fill <ref> <text> \u586B\u5145\u8F93\u5165\u6846\uFF08\u6E05\u7A7A\u540E\u586B\u5165\uFF09
2659
+ type <ref> <text> \u9010\u5B57\u7B26\u8F93\u5165\uFF08\u4E0D\u6E05\u7A7A\uFF09
2660
+ check/uncheck <ref> \u52FE\u9009/\u53D6\u6D88\u590D\u9009\u6846
2661
+ select <ref> <val> \u4E0B\u62C9\u6846\u9009\u62E9
2662
+ press <key> \u53D1\u9001\u6309\u952E
2663
+ scroll <dir> [px] \u6EDA\u52A8\u9875\u9762
2664
+
2665
+ \u9875\u9762\u4FE1\u606F\uFF1A
2666
+ get text|url|title <ref> \u83B7\u53D6\u9875\u9762\u5185\u5BB9
2667
+ screenshot [path] \u622A\u56FE
2668
+ eval "<js>" \u6267\u884C JavaScript
2669
+ fetch <url> \u5E26\u767B\u5F55\u6001\u7684 HTTP \u8BF7\u6C42
2670
+
2671
+ \u6807\u7B7E\u9875\uFF1A
2672
+ tab [list|new|close|<n>] \u7BA1\u7406\u6807\u7B7E\u9875
2673
+ status \u67E5\u770B\u53D7\u7BA1\u6D4F\u89C8\u5668\u72B6\u6001
2674
+
2675
+ \u5BFC\u822A\uFF1A
2676
+ back / forward / refresh \u540E\u9000 / \u524D\u8FDB / \u5237\u65B0
2677
+
2678
+ \u8C03\u8BD5\uFF1A
2679
+ network requests [filter] \u67E5\u770B\u7F51\u7EDC\u8BF7\u6C42
2680
+ console [--clear] \u67E5\u770B/\u6E05\u7A7A\u63A7\u5236\u53F0
2681
+ errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
2682
+ trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
2683
+ history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
2684
+ daemon [start|status|stop] \u7BA1\u7406 daemon\uFF08start: \u540E\u53F0\u542F\u52A8\uFF09
2685
+
2686
+ \u9009\u9879\uFF1A
2687
+ --json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
2688
+ --port <n> \u6307\u5B9A Chrome CDP \u7AEF\u53E3
2689
+ --openclaw \u4F18\u5148\u590D\u7528 OpenClaw \u6D4F\u89C8\u5668\u5B9E\u4F8B
2690
+ --jq <expr> \u5BF9 JSON \u8F93\u51FA\u5E94\u7528 jq \u8FC7\u6EE4\uFF08\u76F4\u63A5\u4F5C\u7528\u4E8E\u6570\u636E\uFF0C\u8DF3\u8FC7 id/success \u4FE1\u5C01\uFF09
2691
+ -i, --interactive \u53EA\u8F93\u51FA\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08snapshot \u547D\u4EE4\uFF09
2692
+ -c, --compact \u79FB\u9664\u7A7A\u7ED3\u6784\u8282\u70B9\uFF08snapshot \u547D\u4EE4\uFF09
2693
+ -d, --depth <n> \u9650\u5236\u6811\u6DF1\u5EA6\uFF08snapshot \u547D\u4EE4\uFF09
2694
+ -s, --selector <sel> \u9650\u5B9A CSS \u9009\u62E9\u5668\u8303\u56F4\uFF08snapshot \u547D\u4EE4\uFF09
2695
+ --tab <tabId> \u6307\u5B9A\u64CD\u4F5C\u7684\u6807\u7B7E\u9875 ID
2696
+ --mcp \u542F\u52A8 MCP server\uFF08\u7528\u4E8E Claude Code / Cursor \u7B49 AI \u5DE5\u5177\uFF09
2697
+ --help, -h \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
2698
+ --version, -v \u663E\u793A\u7248\u672C\u53F7
2699
+ `.trim();
2700
+ function parseArgs(argv) {
2701
+ const args = argv.slice(2);
2702
+ const result = {
2703
+ command: null,
2704
+ args: [],
2705
+ flags: {
2706
+ json: false,
2707
+ help: false,
2708
+ version: false,
2709
+ interactive: false,
2710
+ compact: false
2711
+ }
2712
+ };
2713
+ let skipNext = false;
2714
+ for (const arg of args) {
2715
+ if (skipNext) {
2716
+ skipNext = false;
2717
+ continue;
2718
+ }
2719
+ if (arg === "--json") {
2720
+ result.flags.json = true;
2721
+ } else if (arg === "--jq") {
2722
+ skipNext = true;
2723
+ const nextIdx = args.indexOf(arg) + 1;
2724
+ if (nextIdx < args.length) {
2725
+ result.flags.jq = args[nextIdx];
2726
+ result.flags.json = true;
2727
+ }
2728
+ } else if (arg === "--openclaw") {
2729
+ result.flags.openclaw = true;
2730
+ } else if (arg === "--port") {
2731
+ skipNext = true;
2732
+ const nextIdx = args.indexOf(arg) + 1;
2733
+ if (nextIdx < args.length) {
2734
+ result.flags.port = parseInt(args[nextIdx], 10);
2735
+ }
2736
+ } else if (arg === "--help" || arg === "-h") {
2737
+ result.flags.help = true;
2738
+ } else if (arg === "--version" || arg === "-v") {
2739
+ result.flags.version = true;
2740
+ } else if (arg === "--interactive" || arg === "-i") {
2741
+ result.flags.interactive = true;
2742
+ } else if (arg === "--compact" || arg === "-c") {
2743
+ result.flags.compact = true;
2744
+ } else if (arg === "--depth" || arg === "-d") {
2745
+ skipNext = true;
2746
+ const nextIdx = args.indexOf(arg) + 1;
2747
+ if (nextIdx < args.length) {
2748
+ result.flags.depth = parseInt(args[nextIdx], 10);
2749
+ }
2750
+ } else if (arg === "--selector" || arg === "-s") {
2751
+ skipNext = true;
2752
+ const nextIdx = args.indexOf(arg) + 1;
2753
+ if (nextIdx < args.length) {
2754
+ result.flags.selector = args[nextIdx];
2755
+ }
2756
+ } else if (arg === "--days") {
2757
+ skipNext = true;
2758
+ const nextIdx = args.indexOf(arg) + 1;
2759
+ if (nextIdx < args.length) {
2760
+ result.flags.days = parseInt(args[nextIdx], 10);
2761
+ }
2762
+ } else if (arg === "--id") {
2763
+ skipNext = true;
2764
+ } else if (arg === "--tab") {
2765
+ skipNext = true;
2766
+ } else if (arg === "--since") {
2767
+ skipNext = true;
2768
+ } else if (arg === "--method") {
2769
+ skipNext = true;
2770
+ } else if (arg === "--status") {
2771
+ skipNext = true;
2772
+ } else if (arg.startsWith("-")) {
2773
+ } else if (result.command === null) {
2774
+ result.command = arg;
2775
+ } else {
2776
+ result.args.push(arg);
2777
+ }
2778
+ }
2779
+ return result;
2780
+ }
2781
+ async function main() {
2782
+ const parsed = parseArgs(process.argv);
2783
+ setJqExpression(parsed.flags.jq);
2784
+ const tabArgIdx = process.argv.indexOf("--tab");
2785
+ const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? process.argv[tabArgIdx + 1] : void 0;
2786
+ const sinceArgIdx = process.argv.indexOf("--since");
2787
+ const globalSince = sinceArgIdx >= 0 && process.argv[sinceArgIdx + 1] ? process.argv[sinceArgIdx + 1] : void 0;
2788
+ if (parsed.flags.version) {
2789
+ console.log(VERSION);
2790
+ return;
2791
+ }
2792
+ if (process.argv.includes("--mcp")) {
2793
+ const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
2794
+ const { spawn: spawn3 } = await import("child_process");
2795
+ const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
2796
+ child.on("exit", (code) => process.exit(code ?? 0));
2797
+ return;
2798
+ }
2799
+ if (!parsed.command) {
2800
+ console.log(HELP_TEXT);
2801
+ return;
2802
+ }
2803
+ if (parsed.flags.help && parsed.command !== "daemon") {
2804
+ console.log(HELP_TEXT);
2805
+ return;
2806
+ }
2807
+ try {
2808
+ switch (parsed.command) {
2809
+ case "open": {
2810
+ const url = parsed.args[0];
2811
+ if (!url) {
2812
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 URL \u53C2\u6570");
2813
+ console.error("\u7528\u6CD5\uFF1Abb-browser open <url> [--tab current|<tabId>]");
2814
+ process.exit(1);
2815
+ }
2816
+ const tabIndex = process.argv.findIndex((a) => a === "--tab");
2817
+ const tab = tabIndex >= 0 ? process.argv[tabIndex + 1] : void 0;
2818
+ await openCommand(url, { json: parsed.flags.json, tab });
2819
+ break;
2820
+ }
2821
+ case "snapshot": {
2822
+ await snapshotCommand({
2823
+ json: parsed.flags.json,
2824
+ interactive: parsed.flags.interactive,
2825
+ compact: parsed.flags.compact,
2826
+ maxDepth: parsed.flags.depth,
2827
+ selector: parsed.flags.selector,
2828
+ tabId: globalTabId
2829
+ });
2830
+ break;
2831
+ }
2832
+ case "click": {
2833
+ const ref = parsed.args[0];
2834
+ if (!ref) {
2835
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2836
+ console.error("\u7528\u6CD5\uFF1Abb-browser click <ref>");
2837
+ console.error("\u793A\u4F8B\uFF1Abb-browser click @5");
2838
+ process.exit(1);
2839
+ }
2840
+ await clickCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
2841
+ break;
2842
+ }
2843
+ case "hover": {
2844
+ const ref = parsed.args[0];
2845
+ if (!ref) {
2846
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2847
+ console.error("\u7528\u6CD5\uFF1Abb-browser hover <ref>");
2848
+ console.error("\u793A\u4F8B\uFF1Abb-browser hover @5");
2849
+ process.exit(1);
2850
+ }
2851
+ await hoverCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
2852
+ break;
2853
+ }
2854
+ case "check": {
2855
+ const ref = parsed.args[0];
2856
+ if (!ref) {
2857
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2858
+ console.error("\u7528\u6CD5\uFF1Abb-browser check <ref>");
2859
+ console.error("\u793A\u4F8B\uFF1Abb-browser check @5");
2860
+ process.exit(1);
2861
+ }
2862
+ await checkCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
2863
+ break;
2864
+ }
2865
+ case "uncheck": {
2866
+ const ref = parsed.args[0];
2867
+ if (!ref) {
2868
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2869
+ console.error("\u7528\u6CD5\uFF1Abb-browser uncheck <ref>");
2870
+ console.error("\u793A\u4F8B\uFF1Abb-browser uncheck @5");
2871
+ process.exit(1);
2872
+ }
2873
+ await uncheckCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
2874
+ break;
2875
+ }
2876
+ case "fill": {
2877
+ const ref = parsed.args[0];
2878
+ const text = parsed.args[1];
2879
+ if (!ref) {
2880
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2881
+ console.error("\u7528\u6CD5\uFF1Abb-browser fill <ref> <text>");
2882
+ console.error('\u793A\u4F8B\uFF1Abb-browser fill @3 "hello world"');
2883
+ process.exit(1);
2884
+ }
2885
+ if (text === void 0) {
2886
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 text \u53C2\u6570");
2887
+ console.error("\u7528\u6CD5\uFF1Abb-browser fill <ref> <text>");
2888
+ console.error('\u793A\u4F8B\uFF1Abb-browser fill @3 "hello world"');
2889
+ process.exit(1);
2890
+ }
2891
+ await fillCommand(ref, text, { json: parsed.flags.json, tabId: globalTabId });
2892
+ break;
2893
+ }
2894
+ case "type": {
2895
+ const ref = parsed.args[0];
2896
+ const text = parsed.args[1];
2897
+ if (!ref) {
2898
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2899
+ console.error("\u7528\u6CD5\uFF1Abb-browser type <ref> <text>");
2900
+ console.error('\u793A\u4F8B\uFF1Abb-browser type @3 "append text"');
2901
+ process.exit(1);
2902
+ }
2903
+ if (text === void 0) {
2904
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 text \u53C2\u6570");
2905
+ console.error("\u7528\u6CD5\uFF1Abb-browser type <ref> <text>");
2906
+ console.error('\u793A\u4F8B\uFF1Abb-browser type @3 "append text"');
2907
+ process.exit(1);
2908
+ }
2909
+ await typeCommand(ref, text, { json: parsed.flags.json, tabId: globalTabId });
2910
+ break;
2911
+ }
2912
+ case "select": {
2913
+ const ref = parsed.args[0];
2914
+ const value = parsed.args[1];
2915
+ if (!ref) {
2916
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
2917
+ console.error("\u7528\u6CD5\uFF1Abb-browser select <ref> <value>");
2918
+ console.error('\u793A\u4F8B\uFF1Abb-browser select @4 "option1"');
2919
+ process.exit(1);
2920
+ }
2921
+ if (value === void 0) {
2922
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 value \u53C2\u6570");
2923
+ console.error("\u7528\u6CD5\uFF1Abb-browser select <ref> <value>");
2924
+ console.error('\u793A\u4F8B\uFF1Abb-browser select @4 "option1"');
2925
+ process.exit(1);
2926
+ }
2927
+ await selectCommand(ref, value, { json: parsed.flags.json, tabId: globalTabId });
2928
+ break;
2929
+ }
2930
+ case "eval": {
2931
+ const script = parsed.args[0];
2932
+ if (!script) {
2933
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 script \u53C2\u6570");
2934
+ console.error("\u7528\u6CD5\uFF1Abb-browser eval <script>");
2935
+ console.error('\u793A\u4F8B\uFF1Abb-browser eval "document.title"');
2936
+ process.exit(1);
2937
+ }
2938
+ await evalCommand(script, { json: parsed.flags.json, tabId: globalTabId });
2939
+ break;
2940
+ }
2941
+ case "get": {
2942
+ const attribute = parsed.args[0];
2943
+ if (!attribute) {
2944
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u5C5E\u6027\u53C2\u6570");
2945
+ console.error("\u7528\u6CD5\uFF1Abb-browser get <text|url|title> [ref]");
2946
+ console.error("\u793A\u4F8B\uFF1Abb-browser get text @5");
2947
+ console.error(" bb-browser get url");
2948
+ process.exit(1);
2949
+ }
2950
+ if (!["text", "url", "title"].includes(attribute)) {
2951
+ console.error(`\u9519\u8BEF\uFF1A\u672A\u77E5\u5C5E\u6027 "${attribute}"`);
2952
+ console.error("\u652F\u6301\u7684\u5C5E\u6027\uFF1Atext, url, title");
2953
+ process.exit(1);
2954
+ }
2955
+ const ref = parsed.args[1];
2956
+ await getCommand(attribute, ref, { json: parsed.flags.json, tabId: globalTabId });
2957
+ break;
2958
+ }
2959
+ case "daemon": {
2960
+ const daemonSubcommand = parsed.args[0];
2961
+ if (daemonSubcommand === "status") {
2962
+ await statusCommand({ json: parsed.flags.json });
2963
+ break;
2964
+ }
2965
+ if (daemonSubcommand === "stop" || daemonSubcommand === "shutdown") {
2966
+ await shutdownCommand({ json: parsed.flags.json });
2967
+ break;
2968
+ }
2969
+ if (daemonSubcommand === "start") {
2970
+ await startCommand({ json: parsed.flags.json });
2971
+ break;
2972
+ }
2973
+ const daemonPath = getDaemonPath();
2974
+ const daemonArgs = process.argv.slice(3);
2975
+ const { spawn: spawn3 } = await import("child_process");
2976
+ const child = spawn3(process.execPath, [daemonPath, ...daemonArgs], {
2977
+ stdio: "inherit"
2978
+ });
2979
+ child.on("exit", (code, signal) => {
2980
+ if (signal) {
2981
+ process.kill(process.pid, signal);
2982
+ return;
2983
+ }
2984
+ process.exit(code ?? 0);
2985
+ });
2986
+ return;
2987
+ }
2988
+ case "close": {
2989
+ await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
2990
+ break;
2991
+ }
2992
+ case "back": {
2993
+ await backCommand({ json: parsed.flags.json, tabId: globalTabId });
2994
+ break;
2995
+ }
2996
+ case "forward": {
2997
+ await forwardCommand({ json: parsed.flags.json, tabId: globalTabId });
2998
+ break;
2999
+ }
3000
+ case "refresh": {
3001
+ await refreshCommand({ json: parsed.flags.json, tabId: globalTabId });
3002
+ break;
3003
+ }
3004
+ case "screenshot": {
3005
+ const outputPath = parsed.args[0];
3006
+ await screenshotCommand(outputPath, { json: parsed.flags.json, tabId: globalTabId });
3007
+ break;
3008
+ }
3009
+ case "wait": {
3010
+ const target = parsed.args[0];
3011
+ if (!target) {
3012
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
3013
+ console.error("\u7528\u6CD5\uFF1Abb-browser wait <ms|@ref>");
3014
+ console.error("\u793A\u4F8B\uFF1Abb-browser wait 2000");
3015
+ console.error(" bb-browser wait @5");
3016
+ process.exit(1);
3017
+ }
3018
+ await waitCommand(target, { json: parsed.flags.json, tabId: globalTabId });
3019
+ break;
3020
+ }
3021
+ case "press": {
3022
+ const key = parsed.args[0];
3023
+ if (!key) {
3024
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 key \u53C2\u6570");
3025
+ console.error("\u7528\u6CD5\uFF1Abb-browser press <key>");
3026
+ console.error("\u793A\u4F8B\uFF1Abb-browser press Enter");
3027
+ console.error(" bb-browser press Control+a");
3028
+ process.exit(1);
3029
+ }
3030
+ await pressCommand(key, { json: parsed.flags.json, tabId: globalTabId });
3031
+ break;
3032
+ }
3033
+ case "scroll": {
3034
+ const direction = parsed.args[0];
3035
+ const pixels = parsed.args[1];
3036
+ if (!direction) {
3037
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u65B9\u5411\u53C2\u6570");
3038
+ console.error("\u7528\u6CD5\uFF1Abb-browser scroll <up|down|left|right> [pixels]");
3039
+ console.error("\u793A\u4F8B\uFF1Abb-browser scroll down");
3040
+ console.error(" bb-browser scroll up 500");
3041
+ process.exit(1);
3042
+ }
3043
+ await scrollCommand(direction, pixels, { json: parsed.flags.json, tabId: globalTabId });
3044
+ break;
3045
+ }
3046
+ case "tab": {
3047
+ await tabCommand(parsed.args, { json: parsed.flags.json, globalTabId });
3048
+ break;
3049
+ }
3050
+ case "status": {
3051
+ await statusCommand({ json: parsed.flags.json });
3052
+ break;
3053
+ }
3054
+ case "frame": {
3055
+ const selectorOrMain = parsed.args[0];
3056
+ if (!selectorOrMain) {
3057
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 selector \u53C2\u6570");
3058
+ console.error("\u7528\u6CD5\uFF1Abb-browser frame <selector>");
3059
+ console.error('\u793A\u4F8B\uFF1Abb-browser frame "iframe#editor"');
3060
+ console.error(" bb-browser frame main");
3061
+ process.exit(1);
3062
+ }
3063
+ if (selectorOrMain === "main") {
3064
+ await frameMainCommand({ json: parsed.flags.json, tabId: globalTabId });
3065
+ } else {
3066
+ await frameCommand(selectorOrMain, { json: parsed.flags.json, tabId: globalTabId });
3067
+ }
3068
+ break;
3069
+ }
3070
+ case "dialog": {
3071
+ const subCommand = parsed.args[0];
3072
+ if (!subCommand) {
3073
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u5B50\u547D\u4EE4");
3074
+ console.error("\u7528\u6CD5\uFF1Abb-browser dialog <accept|dismiss> [text]");
3075
+ console.error("\u793A\u4F8B\uFF1Abb-browser dialog accept");
3076
+ console.error(' bb-browser dialog accept "my input"');
3077
+ console.error(" bb-browser dialog dismiss");
3078
+ process.exit(1);
3079
+ }
3080
+ const promptText = parsed.args[1];
3081
+ await dialogCommand(subCommand, promptText, { json: parsed.flags.json, tabId: globalTabId });
3082
+ break;
3083
+ }
3084
+ case "network": {
3085
+ const subCommand = parsed.args[0] || "requests";
3086
+ const urlOrFilter = parsed.args[1];
3087
+ const abort = process.argv.includes("--abort");
3088
+ const withBody = process.argv.includes("--with-body");
3089
+ const bodyIndex = process.argv.findIndex((a) => a === "--body");
3090
+ const body = bodyIndex >= 0 ? process.argv[bodyIndex + 1] : void 0;
3091
+ const methodIndex = process.argv.findIndex((a) => a === "--method");
3092
+ const method = methodIndex >= 0 ? process.argv[methodIndex + 1] : void 0;
3093
+ const statusIndex = process.argv.findIndex((a) => a === "--status");
3094
+ const statusFilter = statusIndex >= 0 ? process.argv[statusIndex + 1] : void 0;
3095
+ await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter });
3096
+ break;
3097
+ }
3098
+ case "console": {
3099
+ const clear = process.argv.includes("--clear");
3100
+ await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
3101
+ break;
3102
+ }
3103
+ case "errors": {
3104
+ const clear = process.argv.includes("--clear");
3105
+ await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
3106
+ break;
3107
+ }
3108
+ case "trace": {
3109
+ const subCmd = parsed.args[0];
3110
+ if (!subCmd || !["start", "stop", "status"].includes(subCmd)) {
3111
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u6216\u65E0\u6548\u7684\u5B50\u547D\u4EE4");
3112
+ console.error("\u7528\u6CD5\uFF1Abb-browser trace <start|stop|status>");
3113
+ console.error("\u793A\u4F8B\uFF1Abb-browser trace start");
3114
+ console.error(" bb-browser trace stop");
3115
+ console.error(" bb-browser trace status");
3116
+ process.exit(1);
3117
+ }
3118
+ await traceCommand(subCmd, { json: parsed.flags.json, tabId: globalTabId });
3119
+ break;
3120
+ }
3121
+ case "history": {
3122
+ const subCmd = parsed.args[0];
3123
+ if (!subCmd || !["search", "domains"].includes(subCmd)) {
3124
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u6216\u65E0\u6548\u7684\u5B50\u547D\u4EE4");
3125
+ console.error("\u7528\u6CD5\uFF1Abb-browser history <search|domains> [query] [--days <n>]");
3126
+ console.error("\u793A\u4F8B\uFF1Abb-browser history search github");
3127
+ console.error(" bb-browser history domains --days 7");
3128
+ process.exit(1);
3129
+ }
3130
+ const query = parsed.args.slice(1).join(" ");
3131
+ await historyCommand(subCmd, {
3132
+ json: parsed.flags.json,
3133
+ days: parsed.flags.days || 30,
3134
+ query
3135
+ });
3136
+ break;
3137
+ }
3138
+ case "fetch": {
3139
+ const fetchUrl = parsed.args[0];
3140
+ if (!fetchUrl) {
3141
+ console.error("[error] fetch: <url> is required.");
3142
+ console.error(" Usage: bb-browser fetch <url> [--json] [--method POST] [--body '{...}']");
3143
+ console.error(" Example: bb-browser fetch https://www.reddit.com/api/me.json --json");
3144
+ process.exit(1);
3145
+ }
3146
+ const methodIdx = process.argv.findIndex((a) => a === "--method");
3147
+ const fetchMethod = methodIdx >= 0 ? process.argv[methodIdx + 1] : void 0;
3148
+ const fetchBodyIdx = process.argv.findIndex((a) => a === "--body");
3149
+ const fetchBody = fetchBodyIdx >= 0 ? process.argv[fetchBodyIdx + 1] : void 0;
3150
+ const headersIdx = process.argv.findIndex((a) => a === "--headers");
3151
+ const fetchHeaders = headersIdx >= 0 ? process.argv[headersIdx + 1] : void 0;
3152
+ const outputIdx = process.argv.findIndex((a) => a === "--output");
3153
+ const fetchOutput = outputIdx >= 0 ? process.argv[outputIdx + 1] : void 0;
3154
+ await fetchCommand(fetchUrl, {
3155
+ json: parsed.flags.json,
3156
+ method: fetchMethod,
3157
+ body: fetchBody,
3158
+ headers: fetchHeaders,
3159
+ output: fetchOutput,
3160
+ tabId: globalTabId
3161
+ });
3162
+ break;
3163
+ }
3164
+ case "site": {
3165
+ await siteCommand(parsed.args, {
3166
+ json: parsed.flags.json,
3167
+ jq: parsed.flags.jq,
3168
+ days: parsed.flags.days,
3169
+ tabId: globalTabId,
3170
+ openclaw: parsed.flags.openclaw
3171
+ });
3172
+ break;
3173
+ }
3174
+ case "star": {
3175
+ const { execSync: execSync4 } = await import("child_process");
3176
+ try {
3177
+ execSync4("gh auth status", { stdio: "pipe" });
3178
+ } catch {
3179
+ console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
3180
+ console.error(" brew install gh && gh auth login");
3181
+ process.exit(1);
3182
+ }
3183
+ const repos = ["epiral/bb-browser", "epiral/bb-sites"];
3184
+ for (const repo of repos) {
3185
+ try {
3186
+ execSync4(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
3187
+ console.log(`\u2B50 Starred ${repo}`);
3188
+ } catch {
3189
+ console.log(`Already starred or failed: ${repo}`);
3190
+ }
3191
+ }
3192
+ console.log("\nThanks for your support! \u{1F64F}");
3193
+ break;
3194
+ }
3195
+ case "guide": {
3196
+ console.log(`How to turn any website into a bb-browser site adapter
3197
+ =======================================================
3198
+
3199
+ 1. REVERSE ENGINEER the API
3200
+ bb-browser network clear --tab <tabId>
3201
+ bb-browser refresh --tab <tabId>
3202
+ bb-browser network requests --filter "api" --with-body --json --tab <tabId>
3203
+
3204
+ 2. TEST if direct fetch works (Tier 1)
3205
+ bb-browser eval "fetch('/api/endpoint',{credentials:'include'}).then(r=>r.json())" --tab <tabId>
3206
+
3207
+ If it works \u2192 Tier 1 (Cookie auth, like Reddit/GitHub/Zhihu/Bilibili)
3208
+ If needs extra headers \u2192 Tier 2 (like Twitter: Bearer + CSRF token)
3209
+ If needs request signing \u2192 Tier 3 (like Xiaohongshu: Pinia store actions)
3210
+
3211
+ 3. WRITE the adapter (one JS file per operation)
3212
+
3213
+ /* @meta
3214
+ {
3215
+ "name": "platform/command",
3216
+ "description": "What it does",
3217
+ "domain": "www.example.com",
3218
+ "args": { "query": {"required": true, "description": "Search query"} },
3219
+ "readOnly": true,
3220
+ "example": "bb-browser site platform/command value"
3221
+ }
3222
+ */
3223
+ async function(args) {
3224
+ if (!args.query) return {error: 'Missing argument: query'};
3225
+ const resp = await fetch('/api/search?q=' + encodeURIComponent(args.query), {credentials: 'include'});
3226
+ if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};
3227
+ return await resp.json();
3228
+ }
3229
+
3230
+ 4. TEST it
3231
+ Save to ~/.bb-browser/sites/platform/command.js (private, takes priority)
3232
+ bb-browser site platform/command "test query" --json
3233
+
3234
+ 5. CONTRIBUTE
3235
+ Option A (with gh CLI):
3236
+ git clone https://github.com/epiral/bb-sites && cd bb-sites
3237
+ git checkout -b feat-platform
3238
+ # add adapter files
3239
+ git push -u origin feat-platform
3240
+ gh pr create --repo epiral/bb-sites
3241
+
3242
+ Option B (without gh CLI, using bb-browser itself):
3243
+ bb-browser site github/fork epiral/bb-sites
3244
+ git clone https://github.com/YOUR_USER/bb-sites && cd bb-sites
3245
+ git checkout -b feat-platform
3246
+ # add adapter files
3247
+ git push -u origin feat-platform
3248
+ bb-browser site github/pr-create epiral/bb-sites --title "feat(platform): add adapters" --head "YOUR_USER:feat-platform"
3249
+
3250
+ Private adapters: ~/.bb-browser/sites/<platform>/<command>.js
3251
+ Community: ~/.bb-browser/bb-sites/ (via bb-browser site update)
3252
+ Full guide: https://github.com/epiral/bb-sites/blob/main/SKILL.md`);
3253
+ break;
3254
+ }
3255
+ default: {
3256
+ console.error(`\u9519\u8BEF\uFF1A\u672A\u77E5\u547D\u4EE4 "${parsed.command}"`);
3257
+ console.error("\u8FD0\u884C bb-browser --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4");
3258
+ process.exit(1);
3259
+ }
3260
+ }
3261
+ } catch (error) {
3262
+ const message = error instanceof Error ? error.message : String(error);
3263
+ if (parsed.flags.json) {
3264
+ console.log(
3265
+ JSON.stringify({
3266
+ success: false,
3267
+ error: message
3268
+ })
3269
+ );
3270
+ } else {
3271
+ console.error(`\u9519\u8BEF\uFF1A${message}`);
3272
+ }
3273
+ process.exit(1);
3274
+ }
3275
+ }
3276
+ main().then(() => process.exit(0));
3277
+ //# sourceMappingURL=cli.js.map