bb-browser 0.11.0 → 0.11.2

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 CHANGED
@@ -1,41 +1,274 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  COMMAND_TIMEOUT,
4
- DAEMON_PORT,
5
4
  generateId
6
- } from "./chunk-5WGFUZLM.js";
5
+ } from "./chunk-H2VEQBAU.js";
7
6
  import {
8
7
  applyJq
9
8
  } from "./chunk-AHGAQEFO.js";
9
+ import {
10
+ parseOpenClawJson
11
+ } from "./chunk-FSL4RNI6.js";
10
12
  import "./chunk-D4HDZEJT.js";
11
13
 
12
14
  // packages/cli/src/index.ts
13
15
  import { fileURLToPath as fileURLToPath2 } from "url";
14
16
 
15
17
  // packages/cli/src/daemon-manager.ts
16
- import { spawn } from "child_process";
17
- import { readFile } from "fs/promises";
18
+ import { spawn as spawn2 } from "child_process";
19
+ import { readFile as readFile2, unlink } from "fs/promises";
18
20
  import { request as httpRequest } from "http";
19
21
  import { fileURLToPath } from "url";
20
22
  import { dirname, resolve } from "path";
23
+ import { existsSync as existsSync2 } from "fs";
24
+ import os2 from "os";
25
+ import path2 from "path";
26
+
27
+ // packages/cli/src/cdp-discovery.ts
28
+ import { execFile, execSync, spawn } from "child_process";
21
29
  import { existsSync } from "fs";
30
+ import { mkdir, readFile, writeFile } from "fs/promises";
22
31
  import os from "os";
23
32
  import path from "path";
24
- var DAEMON_DIR = path.join(os.homedir(), ".bb-browser");
25
- var TOKEN_FILE = path.join(DAEMON_DIR, "daemon.token");
26
- var cachedToken = null;
33
+ var DEFAULT_CDP_PORT = 19825;
34
+ var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
35
+ var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
36
+ var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
37
+ var CDP_CACHE_FILE = path.join(os.tmpdir(), "bb-browser-cdp-cache.json");
38
+ var CACHE_TTL_MS = 3e4;
39
+ function execFileAsync(command, args, timeout) {
40
+ return new Promise((resolve2, reject) => {
41
+ execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
42
+ if (error) {
43
+ reject(error);
44
+ return;
45
+ }
46
+ resolve2(stdout.trim());
47
+ });
48
+ });
49
+ }
50
+ function getArgValue(flag) {
51
+ const index = process.argv.indexOf(flag);
52
+ if (index < 0) return void 0;
53
+ return process.argv[index + 1];
54
+ }
55
+ async function tryOpenClaw() {
56
+ try {
57
+ const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 3e4);
58
+ const parsed = parseOpenClawJson(raw);
59
+ let result = null;
60
+ if (parsed?.cdpUrl) {
61
+ try {
62
+ const url = new URL(parsed.cdpUrl);
63
+ const port = Number(url.port);
64
+ if (Number.isInteger(port) && port > 0) {
65
+ result = { host: url.hostname, port };
66
+ }
67
+ } catch {
68
+ }
69
+ }
70
+ if (!result) {
71
+ const port = Number(parsed?.cdpPort);
72
+ if (Number.isInteger(port) && port > 0) {
73
+ const host = parsed?.cdpHost || "127.0.0.1";
74
+ result = { host, port };
75
+ }
76
+ }
77
+ if (result) {
78
+ try {
79
+ await writeFile(CDP_CACHE_FILE, JSON.stringify({ ...result, timestamp: Date.now() }), "utf8");
80
+ } catch {
81
+ }
82
+ }
83
+ return result;
84
+ } catch {
85
+ }
86
+ return null;
87
+ }
88
+ async function canConnect(host, port) {
89
+ try {
90
+ const controller = new AbortController();
91
+ const timeout = setTimeout(() => controller.abort(), 1200);
92
+ const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
93
+ clearTimeout(timeout);
94
+ return response.ok;
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+ function findBrowserExecutable() {
100
+ if (process.platform === "darwin") {
101
+ const candidates = [
102
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
103
+ "/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
104
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
105
+ "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
106
+ "/Applications/Arc.app/Contents/MacOS/Arc",
107
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
108
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
109
+ ];
110
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
111
+ }
112
+ if (process.platform === "linux") {
113
+ const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
114
+ for (const candidate of candidates) {
115
+ try {
116
+ const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
117
+ if (resolved) {
118
+ return resolved;
119
+ }
120
+ } catch {
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+ if (process.platform === "win32") {
126
+ const localAppData = process.env.LOCALAPPDATA ?? "";
127
+ const candidates = [
128
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
129
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
130
+ ...localAppData ? [
131
+ `${localAppData}\\Google\\Chrome Dev\\Application\\chrome.exe`,
132
+ `${localAppData}\\Google\\Chrome SxS\\Application\\chrome.exe`,
133
+ `${localAppData}\\Google\\Chrome Beta\\Application\\chrome.exe`
134
+ ] : [],
135
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
136
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
137
+ "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
138
+ ];
139
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
140
+ }
141
+ return null;
142
+ }
143
+ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
144
+ const executable = findBrowserExecutable();
145
+ if (!executable) {
146
+ return null;
147
+ }
148
+ await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
149
+ const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
150
+ const prefsPath = path.join(defaultProfileDir, "Preferences");
151
+ await mkdir(defaultProfileDir, { recursive: true });
152
+ try {
153
+ let prefs = {};
154
+ try {
155
+ prefs = JSON.parse(await readFile(prefsPath, "utf8"));
156
+ } catch {
157
+ }
158
+ if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
159
+ prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
160
+ await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
161
+ }
162
+ } catch {
163
+ }
164
+ const args = [
165
+ `--remote-debugging-port=${port}`,
166
+ `--user-data-dir=${MANAGED_USER_DATA_DIR}`,
167
+ "--no-first-run",
168
+ "--no-default-browser-check",
169
+ "--disable-sync",
170
+ "--disable-background-networking",
171
+ "--disable-component-update",
172
+ "--disable-features=Translate,MediaRouter",
173
+ "--disable-session-crashed-bubble",
174
+ "--hide-crash-restore-bubble",
175
+ "about:blank"
176
+ ];
177
+ try {
178
+ const child = spawn(executable, args, {
179
+ detached: true,
180
+ stdio: "ignore"
181
+ });
182
+ child.unref();
183
+ } catch {
184
+ return null;
185
+ }
186
+ await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
187
+ await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
188
+ const deadline = Date.now() + 8e3;
189
+ while (Date.now() < deadline) {
190
+ if (await canConnect("127.0.0.1", port)) {
191
+ return { host: "127.0.0.1", port };
192
+ }
193
+ await new Promise((resolve2) => setTimeout(resolve2, 250));
194
+ }
195
+ return null;
196
+ }
197
+ async function discoverCdpPort() {
198
+ const envUrl = process.env.BB_BROWSER_CDP_URL;
199
+ if (envUrl) {
200
+ try {
201
+ const url = new URL(envUrl);
202
+ const port = Number(url.port);
203
+ if (Number.isInteger(port) && port > 0 && await canConnect(url.hostname, port)) {
204
+ return { host: url.hostname, port };
205
+ }
206
+ } catch {
207
+ }
208
+ }
209
+ const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
210
+ if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
211
+ return { host: "127.0.0.1", port: explicitPort };
212
+ }
213
+ try {
214
+ const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
215
+ const managedPort = Number.parseInt(rawPort.trim(), 10);
216
+ if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
217
+ return { host: "127.0.0.1", port: managedPort };
218
+ }
219
+ } catch {
220
+ }
221
+ try {
222
+ const cacheRaw = await readFile(CDP_CACHE_FILE, "utf8");
223
+ const cache = JSON.parse(cacheRaw);
224
+ if (Date.now() - cache.timestamp < CACHE_TTL_MS && await canConnect(cache.host, cache.port)) {
225
+ return { host: cache.host, port: cache.port };
226
+ }
227
+ } catch {
228
+ }
229
+ if (process.argv.includes("--openclaw")) {
230
+ const viaOpenClaw = await tryOpenClaw();
231
+ if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
232
+ return viaOpenClaw;
233
+ }
234
+ }
235
+ const launched = await launchManagedBrowser();
236
+ if (launched) {
237
+ return launched;
238
+ }
239
+ if (!process.argv.includes("--openclaw")) {
240
+ const detectedOpenClaw = await tryOpenClaw();
241
+ if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
242
+ return detectedOpenClaw;
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+
248
+ // packages/cli/src/daemon-manager.ts
249
+ var DAEMON_DIR = process.env.BB_BROWSER_HOME || path2.join(os2.homedir(), ".bb-browser");
250
+ var DAEMON_JSON = path2.join(DAEMON_DIR, "daemon.json");
251
+ var cachedInfo = null;
27
252
  var daemonReady = false;
28
- function httpJson(method, urlPath, token, body, timeout = 5e3) {
253
+ function isProcessAlive(pid) {
254
+ try {
255
+ process.kill(pid, 0);
256
+ return true;
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ function httpJson(method, urlPath, info, body, timeout = 5e3) {
29
262
  return new Promise((resolve2, reject) => {
30
263
  const payload = body !== void 0 ? JSON.stringify(body) : void 0;
31
264
  const req = httpRequest(
32
265
  {
33
- hostname: "127.0.0.1",
34
- port: DAEMON_PORT,
266
+ hostname: info.host,
267
+ port: info.port,
35
268
  path: urlPath,
36
269
  method,
37
270
  headers: {
38
- Authorization: `Bearer ${token}`,
271
+ Authorization: `Bearer ${info.token}`,
39
272
  ...payload ? {
40
273
  "Content-Type": "application/json",
41
274
  "Content-Length": Buffer.byteLength(payload)
@@ -69,59 +302,81 @@ function httpJson(method, urlPath, token, body, timeout = 5e3) {
69
302
  req.end();
70
303
  });
71
304
  }
72
- async function readToken() {
305
+ async function readDaemonJson() {
73
306
  try {
74
- return (await readFile(TOKEN_FILE, "utf8")).trim();
307
+ const raw = await readFile2(DAEMON_JSON, "utf8");
308
+ const info = JSON.parse(raw);
309
+ if (typeof info.pid === "number" && typeof info.host === "string" && typeof info.port === "number" && typeof info.token === "string") {
310
+ return info;
311
+ }
312
+ return null;
75
313
  } catch {
76
314
  return null;
77
315
  }
78
316
  }
317
+ async function deleteDaemonJson() {
318
+ try {
319
+ await unlink(DAEMON_JSON);
320
+ } catch {
321
+ }
322
+ }
79
323
  function getDaemonPath() {
80
324
  const currentFile = fileURLToPath(import.meta.url);
81
325
  const currentDir = dirname(currentFile);
82
326
  const sameDirPath = resolve(currentDir, "daemon.js");
83
- if (existsSync(sameDirPath)) {
327
+ if (existsSync2(sameDirPath)) {
84
328
  return sameDirPath;
85
329
  }
86
330
  return resolve(currentDir, "../../daemon/dist/index.js");
87
331
  }
88
332
  async function ensureDaemon() {
89
- if (daemonReady && cachedToken) {
333
+ if (daemonReady && cachedInfo) {
90
334
  try {
91
- await httpJson("GET", "/status", cachedToken, void 0, 2e3);
335
+ await httpJson("GET", "/status", cachedInfo, void 0, 2e3);
92
336
  return;
93
337
  } catch {
94
338
  daemonReady = false;
95
- cachedToken = null;
339
+ cachedInfo = null;
96
340
  }
97
341
  }
98
- let token = await readToken();
99
- if (token) {
100
- try {
101
- const status = await httpJson("GET", "/status", token, void 0, 2e3);
102
- if (status.running) {
103
- cachedToken = token;
104
- daemonReady = true;
105
- return;
342
+ let info = await readDaemonJson();
343
+ if (info) {
344
+ if (!isProcessAlive(info.pid)) {
345
+ await deleteDaemonJson();
346
+ info = null;
347
+ } else {
348
+ try {
349
+ const status = await httpJson("GET", "/status", info, void 0, 2e3);
350
+ if (status.running) {
351
+ cachedInfo = info;
352
+ daemonReady = true;
353
+ return;
354
+ }
355
+ } catch {
106
356
  }
107
- } catch {
108
357
  }
109
358
  }
359
+ const cdpInfo = await discoverCdpPort();
360
+ if (!cdpInfo) {
361
+ throw new Error(
362
+ "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"
363
+ );
364
+ }
110
365
  const daemonPath = getDaemonPath();
111
- const child = spawn(process.execPath, [daemonPath], {
366
+ const child = spawn2(process.execPath, [daemonPath, "--cdp-host", cdpInfo.host, "--cdp-port", String(cdpInfo.port)], {
112
367
  detached: true,
113
368
  stdio: "ignore"
114
369
  });
115
370
  child.unref();
116
- const deadline = Date.now() + 5e3;
371
+ const deadline = Date.now() + 1e4;
117
372
  while (Date.now() < deadline) {
118
373
  await new Promise((r) => setTimeout(r, 200));
119
- token = await readToken();
120
- if (!token) continue;
374
+ info = await readDaemonJson();
375
+ if (!info) continue;
121
376
  try {
122
- const status = await httpJson("GET", "/status", token, void 0, 2e3);
377
+ const status = await httpJson("GET", "/status", info, void 0, 2e3);
123
378
  if (status.running) {
124
- cachedToken = token;
379
+ cachedInfo = info;
125
380
  daemonReady = true;
126
381
  return;
127
382
  }
@@ -129,23 +384,35 @@ async function ensureDaemon() {
129
384
  }
130
385
  }
131
386
  throw new Error(
132
- "bb-browser: Daemon did not start in time.\n\nMake sure Chrome is installed, then try again."
387
+ "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"
133
388
  );
134
389
  }
135
390
  async function daemonCommand(request) {
136
- if (!cachedToken) {
137
- cachedToken = await readToken();
391
+ if (!cachedInfo) {
392
+ cachedInfo = await readDaemonJson();
393
+ }
394
+ if (!cachedInfo) {
395
+ throw new Error("No daemon.json found. Is the daemon running?");
138
396
  }
139
- if (!cachedToken) {
140
- throw new Error("No daemon token found. Is the daemon running?");
397
+ return httpJson("POST", "/command", cachedInfo, request, COMMAND_TIMEOUT);
398
+ }
399
+ async function stopDaemon() {
400
+ const info = cachedInfo ?? await readDaemonJson();
401
+ if (!info) return false;
402
+ try {
403
+ await httpJson("POST", "/shutdown", info);
404
+ daemonReady = false;
405
+ cachedInfo = null;
406
+ return true;
407
+ } catch {
408
+ return false;
141
409
  }
142
- return httpJson("POST", "/command", cachedToken, request, COMMAND_TIMEOUT);
143
410
  }
144
411
  async function getDaemonStatus() {
145
- const token = cachedToken ?? await readToken();
146
- if (!token) return null;
412
+ const info = cachedInfo ?? await readDaemonJson();
413
+ if (!info) return null;
147
414
  try {
148
- return await httpJson("GET", "/status", token, void 0, 2e3);
415
+ return await httpJson("GET", "/status", info, void 0, 2e3);
149
416
  } catch {
150
417
  return null;
151
418
  }
@@ -176,8 +443,8 @@ async function sendCommand(request) {
176
443
  }
177
444
 
178
445
  // packages/cli/src/history-sqlite.ts
179
- import { copyFileSync, existsSync as existsSync2, unlinkSync } from "fs";
180
- import { execSync } from "child_process";
446
+ import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
447
+ import { execSync as execSync2 } from "child_process";
181
448
  import { homedir, tmpdir } from "os";
182
449
  import { join } from "path";
183
450
  function getHistoryPathCandidates() {
@@ -200,7 +467,7 @@ function getHistoryPathCandidates() {
200
467
  }
201
468
  function findHistoryPath() {
202
469
  for (const historyPath of getHistoryPathCandidates()) {
203
- if (existsSync2(historyPath)) {
470
+ if (existsSync3(historyPath)) {
204
471
  return historyPath;
205
472
  }
206
473
  }
@@ -225,7 +492,7 @@ function runHistoryQuery(sql, mapRow) {
225
492
  copyFileSync(historyPath, tmpPath);
226
493
  const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
227
494
  const escapedSql = sql.replace(/"/g, '\\"');
228
- const output = execSync(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
495
+ const output = execSync2(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
229
496
  encoding: "utf-8",
230
497
  stdio: ["pipe", "pipe", "pipe"]
231
498
  });
@@ -320,18 +587,18 @@ function getHistoryDomains(days) {
320
587
  }
321
588
 
322
589
  // packages/cli/src/commands/site.ts
323
- import { readFileSync, readdirSync, existsSync as existsSync3, mkdirSync } from "fs";
590
+ import { readFileSync, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
324
591
  import { join as join2, relative } from "path";
325
592
  import { homedir as homedir2 } from "os";
326
- import { execSync as execSync2 } from "child_process";
593
+ import { execSync as execSync3 } from "child_process";
327
594
  var BB_DIR = join2(homedir2(), ".bb-browser");
328
595
  var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
329
596
  var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
330
597
  var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
331
598
  function checkCliUpdate() {
332
599
  try {
333
- const current = execSync2("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
334
- const latest = execSync2("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
600
+ const current = execSync3("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
601
+ const latest = execSync3("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
335
602
  if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
336
603
  console.log(`
337
604
  \u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
@@ -406,7 +673,7 @@ function parseSiteMeta(filePath, source) {
406
673
  return meta;
407
674
  }
408
675
  function scanSites(dir, source) {
409
- if (!existsSync3(dir)) return [];
676
+ if (!existsSync4(dir)) return [];
410
677
  const sites = [];
411
678
  function walk(currentDir) {
412
679
  let entries;
@@ -528,13 +795,13 @@ function siteSearch(query, options) {
528
795
  }
529
796
  function siteUpdate(options = {}) {
530
797
  mkdirSync(BB_DIR, { recursive: true });
531
- const updateMode = existsSync3(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
798
+ const updateMode = existsSync4(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
532
799
  if (updateMode === "pull") {
533
800
  if (!options.json) {
534
801
  console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
535
802
  }
536
803
  try {
537
- execSync2("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
804
+ execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
538
805
  if (!options.json) {
539
806
  console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
540
807
  console.log("");
@@ -555,7 +822,7 @@ function siteUpdate(options = {}) {
555
822
  console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
556
823
  }
557
824
  try {
558
- execSync2(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
825
+ execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
559
826
  if (!options.json) {
560
827
  console.log("\u514B\u9686\u5B8C\u6210\u3002");
561
828
  console.log("");
@@ -769,7 +1036,7 @@ async function siteRun(name, args, options) {
769
1036
  const argsJson = JSON.stringify(argMap);
770
1037
  const script = `(${jsBody})(${argsJson})`;
771
1038
  if (options.openclaw) {
772
- const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-Q6EFUQCH.js");
1039
+ const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-4XJKWEQ3.js");
773
1040
  let targetId;
774
1041
  if (site.domain) {
775
1042
  const tabs = ocGetTabs();
@@ -980,9 +1247,9 @@ async function siteCommand(args, options = {}) {
980
1247
  }
981
1248
  function silentUpdate() {
982
1249
  const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
983
- if (!existsSync3(gitDir)) return;
984
- import("child_process").then(({ spawn: spawn2 }) => {
985
- const child = spawn2("git", ["pull", "--ff-only"], {
1250
+ if (!existsSync4(gitDir)) return;
1251
+ import("child_process").then(({ spawn: spawn3 }) => {
1252
+ const child = spawn3("git", ["pull", "--ff-only"], {
986
1253
  cwd: COMMUNITY_SITES_DIR,
987
1254
  stdio: "ignore",
988
1255
  detached: true
@@ -1280,17 +1547,17 @@ async function getCommand(attribute, ref, options = {}) {
1280
1547
 
1281
1548
  // packages/cli/src/commands/screenshot.ts
1282
1549
  import fs from "fs";
1283
- import path2 from "path";
1284
- import os2 from "os";
1550
+ import path3 from "path";
1551
+ import os3 from "os";
1285
1552
  function getDefaultPath() {
1286
1553
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1287
1554
  const filename = `bb-screenshot-${timestamp}.png`;
1288
- return path2.join(os2.tmpdir(), filename);
1555
+ return path3.join(os3.tmpdir(), filename);
1289
1556
  }
1290
1557
  function saveBase64Image(dataUrl, filePath) {
1291
1558
  const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
1292
1559
  const buffer = Buffer.from(base64Data, "base64");
1293
- const dir = path2.dirname(filePath);
1560
+ const dir = path3.dirname(filePath);
1294
1561
  if (!fs.existsSync(dir)) {
1295
1562
  fs.mkdirSync(dir, { recursive: true });
1296
1563
  }
@@ -1298,7 +1565,7 @@ function saveBase64Image(dataUrl, filePath) {
1298
1565
  }
1299
1566
  async function screenshotCommand(outputPath, options = {}) {
1300
1567
  await ensureDaemonRunning();
1301
- const filePath = outputPath ? path2.resolve(outputPath) : getDefaultPath();
1568
+ const filePath = outputPath ? path3.resolve(outputPath) : getDefaultPath();
1302
1569
  const request = {
1303
1570
  id: generateId(),
1304
1571
  action: "screenshot",
@@ -2369,6 +2636,14 @@ Tabs (${tabs.length}):`);
2369
2636
  console.log("\nNo tabs");
2370
2637
  }
2371
2638
  }
2639
+ async function shutdownCommand(options = {}) {
2640
+ const ok = await stopDaemon();
2641
+ if (options.json) {
2642
+ console.log(JSON.stringify({ stopped: ok }));
2643
+ } else {
2644
+ console.log(ok ? "Daemon stopped" : "Daemon was not running");
2645
+ }
2646
+ }
2372
2647
  function formatUptime(ms) {
2373
2648
  if (!ms || ms <= 0) return "0s";
2374
2649
  const s = Math.floor(ms / 1e3);
@@ -2380,7 +2655,7 @@ function formatUptime(ms) {
2380
2655
  }
2381
2656
 
2382
2657
  // packages/cli/src/index.ts
2383
- var VERSION = "0.11.0";
2658
+ var VERSION = "0.11.2";
2384
2659
  var HELP_TEXT = `
2385
2660
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
2386
2661
 
@@ -2435,6 +2710,7 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
2435
2710
  errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
2436
2711
  trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
2437
2712
  history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
2713
+ daemon [status|stop] [opts] \u524D\u53F0\u8FD0\u884C\u6216\u7BA1\u7406 daemon
2438
2714
 
2439
2715
  \u9009\u9879\uFF1A
2440
2716
  --json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
@@ -2544,12 +2820,16 @@ async function main() {
2544
2820
  }
2545
2821
  if (process.argv.includes("--mcp")) {
2546
2822
  const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
2547
- const { spawn: spawn2 } = await import("child_process");
2548
- const child = spawn2(process.execPath, [mcpPath], { stdio: "inherit" });
2823
+ const { spawn: spawn3 } = await import("child_process");
2824
+ const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
2549
2825
  child.on("exit", (code) => process.exit(code ?? 0));
2550
2826
  return;
2551
2827
  }
2552
- if (parsed.flags.help || !parsed.command) {
2828
+ if (!parsed.command) {
2829
+ console.log(HELP_TEXT);
2830
+ return;
2831
+ }
2832
+ if (parsed.flags.help && parsed.command !== "daemon") {
2553
2833
  console.log(HELP_TEXT);
2554
2834
  return;
2555
2835
  }
@@ -2705,7 +2985,31 @@ async function main() {
2705
2985
  await getCommand(attribute, ref, { json: parsed.flags.json, tabId: globalTabId });
2706
2986
  break;
2707
2987
  }
2708
- case "daemon":
2988
+ case "daemon": {
2989
+ const daemonSubcommand = parsed.args[0];
2990
+ if (daemonSubcommand === "status") {
2991
+ await statusCommand({ json: parsed.flags.json });
2992
+ break;
2993
+ }
2994
+ if (daemonSubcommand === "stop" || daemonSubcommand === "shutdown") {
2995
+ await shutdownCommand({ json: parsed.flags.json });
2996
+ break;
2997
+ }
2998
+ const daemonPath = getDaemonPath();
2999
+ const daemonArgs = process.argv.slice(3);
3000
+ const { spawn: spawn3 } = await import("child_process");
3001
+ const child = spawn3(process.execPath, [daemonPath, ...daemonArgs], {
3002
+ stdio: "inherit"
3003
+ });
3004
+ child.on("exit", (code, signal) => {
3005
+ if (signal) {
3006
+ process.kill(process.pid, signal);
3007
+ return;
3008
+ }
3009
+ process.exit(code ?? 0);
3010
+ });
3011
+ return;
3012
+ }
2709
3013
  case "close": {
2710
3014
  await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
2711
3015
  break;
@@ -2893,9 +3197,9 @@ async function main() {
2893
3197
  break;
2894
3198
  }
2895
3199
  case "star": {
2896
- const { execSync: execSync3 } = await import("child_process");
3200
+ const { execSync: execSync4 } = await import("child_process");
2897
3201
  try {
2898
- execSync3("gh auth status", { stdio: "pipe" });
3202
+ execSync4("gh auth status", { stdio: "pipe" });
2899
3203
  } catch {
2900
3204
  console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
2901
3205
  console.error(" brew install gh && gh auth login");
@@ -2904,7 +3208,7 @@ async function main() {
2904
3208
  const repos = ["epiral/bb-browser", "epiral/bb-sites"];
2905
3209
  for (const repo of repos) {
2906
3210
  try {
2907
- execSync3(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
3211
+ execSync4(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
2908
3212
  console.log(`\u2B50 Starred ${repo}`);
2909
3213
  } catch {
2910
3214
  console.log(`Already starred or failed: ${repo}`);