bb-browser 0.8.2 → 0.9.0

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
@@ -6,10 +6,13 @@ import {
6
6
  import {
7
7
  applyJq
8
8
  } from "./chunk-AHGAQEFO.js";
9
+ import {
10
+ parseOpenClawJson
11
+ } from "./chunk-FSL4RNI6.js";
9
12
  import "./chunk-D4HDZEJT.js";
10
13
 
11
14
  // packages/cli/src/index.ts
12
- import { fileURLToPath as fileURLToPath3 } from "url";
15
+ import { fileURLToPath as fileURLToPath4 } from "url";
13
16
 
14
17
  // packages/cli/src/cdp-client.ts
15
18
  import { readFileSync, unlinkSync, writeFileSync } from "fs";
@@ -31,13 +34,13 @@ var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
31
34
  var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
32
35
  var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
33
36
  function execFileAsync(command, args, timeout) {
34
- return new Promise((resolve2, reject) => {
37
+ return new Promise((resolve3, reject) => {
35
38
  execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
36
39
  if (error) {
37
40
  reject(error);
38
41
  return;
39
42
  }
40
- resolve2(stdout.trim());
43
+ resolve3(stdout.trim());
41
44
  });
42
45
  });
43
46
  }
@@ -49,7 +52,7 @@ function getArgValue(flag) {
49
52
  async function tryOpenClaw() {
50
53
  try {
51
54
  const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 5e3);
52
- const parsed = JSON.parse(raw);
55
+ const parsed = parseOpenClawJson(raw);
53
56
  const port = Number(parsed?.cdpPort);
54
57
  if (Number.isInteger(port) && port > 0) {
55
58
  return { host: "127.0.0.1", port };
@@ -73,6 +76,10 @@ function findBrowserExecutable() {
73
76
  if (process.platform === "darwin") {
74
77
  const candidates = [
75
78
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
79
+ "/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
80
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
81
+ "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
82
+ "/Applications/Arc.app/Contents/MacOS/Arc",
76
83
  "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
77
84
  "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
78
85
  ];
@@ -101,6 +108,18 @@ function findBrowserExecutable() {
101
108
  }
102
109
  return null;
103
110
  }
111
+ async function isManagedBrowserRunning() {
112
+ try {
113
+ const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
114
+ const port = Number.parseInt(rawPort.trim(), 10);
115
+ if (!Number.isInteger(port) || port <= 0) {
116
+ return false;
117
+ }
118
+ return await canConnect("127.0.0.1", port);
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
104
123
  async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
105
124
  const executable = findBrowserExecutable();
106
125
  if (!executable) {
@@ -151,7 +170,7 @@ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
151
170
  if (await canConnect("127.0.0.1", port)) {
152
171
  return { host: "127.0.0.1", port };
153
172
  }
154
- await new Promise((resolve2) => setTimeout(resolve2, 250));
173
+ await new Promise((resolve3) => setTimeout(resolve3, 250));
155
174
  }
156
175
  return null;
157
176
  }
@@ -198,11 +217,35 @@ var jsErrors = [];
198
217
  var errorsEnabled = false;
199
218
  var traceRecording = false;
200
219
  var traceEvents = [];
220
+ function getContextFilePath(host, port) {
221
+ const safeHost = host.replace(/[^a-zA-Z0-9_.-]/g, "_");
222
+ return path2.join(os2.tmpdir(), `bb-browser-cdp-context-${safeHost}-${port}.json`);
223
+ }
224
+ function loadPersistedCurrentTargetId(host, port) {
225
+ try {
226
+ const data = JSON.parse(readFileSync(getContextFilePath(host, port), "utf-8"));
227
+ return typeof data.currentTargetId === "string" && data.currentTargetId ? data.currentTargetId : void 0;
228
+ } catch {
229
+ return void 0;
230
+ }
231
+ }
232
+ function persistCurrentTargetId(host, port, currentTargetId) {
233
+ try {
234
+ writeFileSync(getContextFilePath(host, port), JSON.stringify({ currentTargetId }));
235
+ } catch {
236
+ }
237
+ }
238
+ function setCurrentTargetId(targetId) {
239
+ const state = connectionState;
240
+ if (!state) return;
241
+ state.currentTargetId = targetId;
242
+ persistCurrentTargetId(state.host, state.port, targetId);
243
+ }
201
244
  function buildRequestError(error) {
202
245
  return error instanceof Error ? error : new Error(String(error));
203
246
  }
204
247
  function fetchJson(url) {
205
- return new Promise((resolve2, reject) => {
248
+ return new Promise((resolve3, reject) => {
206
249
  const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
207
250
  const req = requester(url, { method: "GET" }, (res) => {
208
251
  const chunks = [];
@@ -214,7 +257,7 @@ function fetchJson(url) {
214
257
  return;
215
258
  }
216
259
  try {
217
- resolve2(JSON.parse(raw));
260
+ resolve3(JSON.parse(raw));
218
261
  } catch (error) {
219
262
  reject(error);
220
263
  }
@@ -237,14 +280,14 @@ async function getJsonVersion(host, port) {
237
280
  return { webSocketDebuggerUrl: url };
238
281
  }
239
282
  function connectWebSocket(url) {
240
- return new Promise((resolve2, reject) => {
283
+ return new Promise((resolve3, reject) => {
241
284
  const ws = new WebSocket(url);
242
285
  ws.once("open", () => {
243
286
  const socket = ws._socket;
244
287
  if (socket && typeof socket.unref === "function") {
245
288
  socket.unref();
246
289
  }
247
- resolve2(ws);
290
+ resolve3(ws);
248
291
  });
249
292
  ws.once("error", reject);
250
293
  });
@@ -260,6 +303,7 @@ function createState(host, port, browserWsUrl, browserSocket) {
260
303
  sessions: /* @__PURE__ */ new Map(),
261
304
  attachedTargets: /* @__PURE__ */ new Map(),
262
305
  refsByTarget: /* @__PURE__ */ new Map(),
306
+ currentTargetId: loadPersistedCurrentTargetId(host, port),
263
307
  activeFrameIdByTarget: /* @__PURE__ */ new Map(),
264
308
  dialogHandlers: /* @__PURE__ */ new Map()
265
309
  };
@@ -296,6 +340,10 @@ function createState(host, port, browserWsUrl, browserSocket) {
296
340
  state.attachedTargets.delete(sessionId);
297
341
  state.activeFrameIdByTarget.delete(targetId);
298
342
  state.dialogHandlers.delete(targetId);
343
+ if (state.currentTargetId === targetId) {
344
+ state.currentTargetId = void 0;
345
+ persistCurrentTargetId(state.host, state.port, void 0);
346
+ }
299
347
  }
300
348
  }
301
349
  return;
@@ -339,8 +387,8 @@ async function browserCommand(method, params = {}) {
339
387
  if (!state) throw new Error("CDP connection not initialized");
340
388
  const id = state.nextMessageId++;
341
389
  const payload = JSON.stringify({ id, method, params });
342
- const promise = new Promise((resolve2, reject) => {
343
- state.browserPending.set(id, { resolve: resolve2, reject, method });
390
+ const promise = new Promise((resolve3, reject) => {
391
+ state.browserPending.set(id, { resolve: resolve3, reject, method });
344
392
  });
345
393
  state.browserSocket.send(payload);
346
394
  return promise;
@@ -351,13 +399,13 @@ async function sessionCommand(targetId, method, params = {}) {
351
399
  const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
352
400
  const id = state.nextMessageId++;
353
401
  const payload = JSON.stringify({ id, method, params, sessionId });
354
- return new Promise((resolve2, reject) => {
402
+ return new Promise((resolve3, reject) => {
355
403
  const check = (raw) => {
356
404
  const msg = JSON.parse(raw.toString());
357
405
  if (msg.id === id && msg.sessionId === sessionId) {
358
406
  state.browserSocket.off("message", check);
359
407
  if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
360
- else resolve2(msg.result);
408
+ else resolve3(msg.result);
361
409
  }
362
410
  };
363
411
  state.browserSocket.on("message", check);
@@ -508,6 +556,7 @@ async function getTargets() {
508
556
  async function ensurePageTarget(targetId) {
509
557
  const targets = (await getTargets()).filter((target2) => target2.type === "page");
510
558
  if (targets.length === 0) throw new Error("No page target found");
559
+ const persistedTargetId = targetId === void 0 ? connectionState?.currentTargetId : void 0;
511
560
  let target;
512
561
  if (typeof targetId === "number") {
513
562
  target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
@@ -519,9 +568,11 @@ async function ensurePageTarget(targetId) {
519
568
  target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
520
569
  }
521
570
  }
571
+ } else if (persistedTargetId) {
572
+ target = targets.find((item) => item.id === persistedTargetId);
522
573
  }
523
574
  target ??= targets[0];
524
- connectionState.currentTargetId = target.id;
575
+ setCurrentTargetId(target.id);
525
576
  await attachTarget(target.id);
526
577
  return target;
527
578
  }
@@ -638,19 +689,16 @@ async function evaluate(targetId, expression, returnByValue = true) {
638
689
  const result = await sessionCommand(targetId, "Runtime.evaluate", {
639
690
  expression,
640
691
  awaitPromise: true,
641
- returnByValue
692
+ returnByValue,
693
+ replMode: true
642
694
  });
643
695
  if (result.exceptionDetails) {
644
- throw new Error(result.exceptionDetails.text || "Runtime.evaluate failed");
696
+ throw new Error(
697
+ result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Runtime.evaluate failed"
698
+ );
645
699
  }
646
700
  return result.result.value ?? result.result;
647
701
  }
648
- async function resolveNode(targetId, backendNodeId) {
649
- const result = await sessionCommand(targetId, "DOM.pushNodesByBackendIdsToFrontend", {
650
- backendNodeIds: [backendNodeId]
651
- });
652
- return result.nodeId;
653
- }
654
702
  async function focusNode(targetId, backendNodeId) {
655
703
  await sessionCommand(targetId, "DOM.focus", { backendNodeId });
656
704
  }
@@ -658,40 +706,77 @@ async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
658
706
  const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
659
707
  await sessionCommand(targetId, "Runtime.callFunctionOn", {
660
708
  objectId: resolved.object.objectId,
661
- functionDeclaration: `function(value, clearFirst) {
709
+ functionDeclaration: `function(clearFirst) {
710
+ if (typeof this.scrollIntoView === 'function') {
711
+ this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
712
+ }
662
713
  if (typeof this.focus === 'function') this.focus();
663
- if (clearFirst && ('value' in this)) {
664
- this.value = '';
665
- this.dispatchEvent(new Event('input', { bubbles: true }));
666
- }
667
- if ('value' in this) {
668
- this.value = clearFirst ? value : String(this.value ?? '') + value;
669
- this.dispatchEvent(new Event('input', { bubbles: true }));
670
- this.dispatchEvent(new Event('change', { bubbles: true }));
714
+ if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement) {
715
+ if (clearFirst) {
716
+ this.value = '';
717
+ this.dispatchEvent(new Event('input', { bubbles: true }));
718
+ }
719
+ if (typeof this.setSelectionRange === 'function') {
720
+ const end = this.value.length;
721
+ this.setSelectionRange(end, end);
722
+ }
723
+ return true;
724
+ }
725
+ if (this instanceof HTMLElement && this.isContentEditable) {
726
+ if (clearFirst) {
727
+ this.textContent = '';
728
+ this.dispatchEvent(new Event('input', { bubbles: true }));
729
+ }
730
+ const selection = window.getSelection();
731
+ if (selection) {
732
+ const range = document.createRange();
733
+ range.selectNodeContents(this);
734
+ range.collapse(false);
735
+ selection.removeAllRanges();
736
+ selection.addRange(range);
737
+ }
671
738
  return true;
672
739
  }
673
740
  return false;
674
741
  }`,
675
742
  arguments: [
676
- { value: text },
677
743
  { value: clearFirst }
678
744
  ],
679
745
  returnByValue: true
680
746
  });
681
- await focusNode(targetId, backendNodeId);
682
- await sessionCommand(targetId, "Input.insertText", { text });
747
+ if (text) {
748
+ await focusNode(targetId, backendNodeId);
749
+ await sessionCommand(targetId, "Input.insertText", { text });
750
+ }
683
751
  }
684
- async function getNodeBox(targetId, backendNodeId) {
685
- const result = await sessionCommand(targetId, "DOM.getBoxModel", {
686
- backendNodeId
752
+ async function getInteractablePoint(targetId, backendNodeId) {
753
+ const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
754
+ const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
755
+ objectId: resolved.object.objectId,
756
+ functionDeclaration: `function() {
757
+ if (!(this instanceof Element)) {
758
+ throw new Error('Ref does not resolve to an element');
759
+ }
760
+ this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
761
+ const rect = this.getBoundingClientRect();
762
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
763
+ throw new Error('Element is not visible');
764
+ }
765
+ return {
766
+ x: rect.left + rect.width / 2,
767
+ y: rect.top + rect.height / 2,
768
+ };
769
+ }`,
770
+ returnByValue: true
687
771
  });
688
- const quad = result.model.content.length >= 8 ? result.model.content : result.model.border;
689
- const xs = [quad[0], quad[2], quad[4], quad[6]];
690
- const ys = [quad[1], quad[3], quad[5], quad[7]];
691
- return {
692
- x: xs.reduce((a, b) => a + b, 0) / xs.length,
693
- y: ys.reduce((a, b) => a + b, 0) / ys.length
694
- };
772
+ if (call.exceptionDetails) {
773
+ throw new Error(call.exceptionDetails.text || "Failed to resolve element point");
774
+ }
775
+ const point = call.result.value;
776
+ if (!point || typeof point.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
777
+ throw new Error("Failed to resolve element point");
778
+ }
779
+ return point;
695
780
  }
696
781
  async function mouseClick(targetId, x, y) {
697
782
  await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
@@ -699,9 +784,14 @@ async function mouseClick(targetId, x, y) {
699
784
  await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
700
785
  }
701
786
  async function getAttributeValue(targetId, backendNodeId, attribute) {
702
- const nodeId = await resolveNode(targetId, backendNodeId);
703
787
  if (attribute === "text") {
704
- return evaluate(targetId, `(() => { const n = this; return n.innerText ?? n.textContent ?? ''; }).call(document.querySelector('[data-bb-node-id="${nodeId}"]'))`);
788
+ const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
789
+ const call2 = await sessionCommand(targetId, "Runtime.callFunctionOn", {
790
+ objectId: resolved.object.objectId,
791
+ functionDeclaration: `function() { return (this instanceof HTMLElement ? this.innerText : this.textContent || '').trim(); }`,
792
+ returnByValue: true
793
+ });
794
+ return String(call2.result.value ?? "");
705
795
  }
706
796
  const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
707
797
  const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
@@ -929,7 +1019,7 @@ async function dispatchRequest(request) {
929
1019
  case "open": {
930
1020
  if (!request.url) return fail(request.id, "Missing url parameter");
931
1021
  if (request.tabId === void 0) {
932
- const created = await browserCommand("Target.createTarget", { url: request.url });
1022
+ const created = await browserCommand("Target.createTarget", { url: request.url, background: true });
933
1023
  const newTarget = await ensurePageTarget(created.targetId);
934
1024
  return ok(request.id, { url: request.url, tabId: newTarget.id });
935
1025
  }
@@ -946,7 +1036,7 @@ async function dispatchRequest(request) {
946
1036
  case "hover": {
947
1037
  if (!request.ref) return fail(request.id, "Missing ref parameter");
948
1038
  const backendNodeId = await parseRef(request.ref);
949
- const point = await getNodeBox(target.id, backendNodeId);
1039
+ const point = await getInteractablePoint(target.id, backendNodeId);
950
1040
  await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
951
1041
  if (request.action === "click") await mouseClick(target.id, point.x, point.y);
952
1042
  return ok(request.id, {});
@@ -982,7 +1072,14 @@ async function dispatchRequest(request) {
982
1072
  return ok(request.id, { value: request.value });
983
1073
  }
984
1074
  case "get": {
985
- if (!request.ref || !request.attribute) return fail(request.id, "Missing ref or attribute parameter");
1075
+ if (!request.attribute) return fail(request.id, "Missing attribute parameter");
1076
+ if (request.attribute === "url" && !request.ref) {
1077
+ return ok(request.id, { value: await evaluate(target.id, "location.href", true) });
1078
+ }
1079
+ if (request.attribute === "title" && !request.ref) {
1080
+ return ok(request.id, { value: await evaluate(target.id, "document.title", true) });
1081
+ }
1082
+ if (!request.ref) return fail(request.id, "Missing ref parameter");
986
1083
  const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
987
1084
  return ok(request.id, { value });
988
1085
  }
@@ -997,7 +1094,7 @@ async function dispatchRequest(request) {
997
1094
  return ok(request.id, {});
998
1095
  }
999
1096
  case "wait": {
1000
- await new Promise((resolve2) => setTimeout(resolve2, request.ms ?? 1e3));
1097
+ await new Promise((resolve3) => setTimeout(resolve3, request.ms ?? 1e3));
1001
1098
  return ok(request.id, {});
1002
1099
  }
1003
1100
  case "press": {
@@ -1036,14 +1133,14 @@ async function dispatchRequest(request) {
1036
1133
  return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
1037
1134
  }
1038
1135
  case "tab_new": {
1039
- const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank" });
1136
+ const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank", background: true });
1040
1137
  return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
1041
1138
  }
1042
1139
  case "tab_select": {
1043
1140
  const tabs = (await getTargets()).filter((item) => item.type === "page");
1044
1141
  const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
1045
1142
  if (!selected) return fail(request.id, "Tab not found");
1046
- connectionState.currentTargetId = selected.id;
1143
+ setCurrentTargetId(selected.id);
1047
1144
  await attachTarget(selected.id);
1048
1145
  return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
1049
1146
  }
@@ -1053,6 +1150,9 @@ async function dispatchRequest(request) {
1053
1150
  if (!selected) return fail(request.id, "Tab not found");
1054
1151
  await browserCommand("Target.closeTarget", { targetId: selected.id });
1055
1152
  connectionState?.refsByTarget.delete(selected.id);
1153
+ if (connectionState?.currentTargetId === selected.id) {
1154
+ setCurrentTargetId(void 0);
1155
+ }
1056
1156
  clearPersistedRefs(selected.id);
1057
1157
  return ok(request.id, { tabId: selected.id });
1058
1158
  }
@@ -1160,7 +1260,167 @@ async function dispatchRequest(request) {
1160
1260
  }
1161
1261
  }
1162
1262
 
1263
+ // packages/cli/src/monitor-manager.ts
1264
+ import { spawn as spawn2 } from "child_process";
1265
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, unlink } from "fs/promises";
1266
+ import { request as httpRequest2 } from "http";
1267
+ import { randomBytes } from "crypto";
1268
+ import { fileURLToPath as fileURLToPath2 } from "url";
1269
+ import { dirname, resolve } from "path";
1270
+ import { existsSync as existsSync2 } from "fs";
1271
+ import os3 from "os";
1272
+ import path3 from "path";
1273
+ var MONITOR_DIR = path3.join(os3.homedir(), ".bb-browser");
1274
+ var PID_FILE = path3.join(MONITOR_DIR, "monitor.pid");
1275
+ var PORT_FILE = path3.join(MONITOR_DIR, "monitor.port");
1276
+ var TOKEN_FILE = path3.join(MONITOR_DIR, "monitor.token");
1277
+ var DEFAULT_MONITOR_PORT = 19826;
1278
+ function httpJson(method, url, token, body) {
1279
+ return new Promise((resolve3, reject) => {
1280
+ const parsed = new URL(url);
1281
+ const payload = body !== void 0 ? JSON.stringify(body) : void 0;
1282
+ const req = httpRequest2(
1283
+ {
1284
+ hostname: parsed.hostname,
1285
+ port: parsed.port,
1286
+ path: parsed.pathname,
1287
+ method,
1288
+ headers: {
1289
+ Authorization: `Bearer ${token}`,
1290
+ ...payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}
1291
+ },
1292
+ timeout: 5e3
1293
+ },
1294
+ (res) => {
1295
+ const chunks = [];
1296
+ res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
1297
+ res.on("end", () => {
1298
+ const raw = Buffer.concat(chunks).toString("utf8");
1299
+ if ((res.statusCode ?? 500) >= 400) {
1300
+ reject(new Error(`Monitor HTTP ${res.statusCode}: ${raw}`));
1301
+ return;
1302
+ }
1303
+ try {
1304
+ resolve3(JSON.parse(raw));
1305
+ } catch {
1306
+ reject(new Error(`Invalid JSON from monitor: ${raw}`));
1307
+ }
1308
+ });
1309
+ }
1310
+ );
1311
+ req.on("error", reject);
1312
+ req.on("timeout", () => {
1313
+ req.destroy();
1314
+ reject(new Error("Monitor request timed out"));
1315
+ });
1316
+ if (payload) req.write(payload);
1317
+ req.end();
1318
+ });
1319
+ }
1320
+ async function readPortFile() {
1321
+ try {
1322
+ const raw = await readFile2(PORT_FILE, "utf8");
1323
+ const port = Number.parseInt(raw.trim(), 10);
1324
+ return Number.isInteger(port) && port > 0 ? port : null;
1325
+ } catch {
1326
+ return null;
1327
+ }
1328
+ }
1329
+ async function readTokenFile() {
1330
+ try {
1331
+ return (await readFile2(TOKEN_FILE, "utf8")).trim();
1332
+ } catch {
1333
+ return null;
1334
+ }
1335
+ }
1336
+ async function ensureMonitorRunning() {
1337
+ const existingPort = await readPortFile();
1338
+ const existingToken = await readTokenFile();
1339
+ if (existingPort && existingToken) {
1340
+ try {
1341
+ const status = await httpJson(
1342
+ "GET",
1343
+ `http://127.0.0.1:${existingPort}/status`,
1344
+ existingToken
1345
+ );
1346
+ if (status.running) {
1347
+ return { port: existingPort, token: existingToken };
1348
+ }
1349
+ } catch {
1350
+ }
1351
+ }
1352
+ const cdp = await discoverCdpPort();
1353
+ if (!cdp) {
1354
+ throw new Error("Cannot start monitor: no browser connection found");
1355
+ }
1356
+ const token = randomBytes(32).toString("hex");
1357
+ const monitorPort = DEFAULT_MONITOR_PORT;
1358
+ const monitorScript = findMonitorScript();
1359
+ await mkdir2(MONITOR_DIR, { recursive: true });
1360
+ await writeFile2(TOKEN_FILE, token, { mode: 384 });
1361
+ const child = spawn2(process.execPath, [
1362
+ monitorScript,
1363
+ "--cdp-host",
1364
+ cdp.host,
1365
+ "--cdp-port",
1366
+ String(cdp.port),
1367
+ "--monitor-port",
1368
+ String(monitorPort),
1369
+ "--token",
1370
+ token
1371
+ ], {
1372
+ detached: true,
1373
+ stdio: "ignore"
1374
+ });
1375
+ child.unref();
1376
+ const deadline = Date.now() + 5e3;
1377
+ while (Date.now() < deadline) {
1378
+ await new Promise((r) => setTimeout(r, 200));
1379
+ try {
1380
+ const status = await httpJson(
1381
+ "GET",
1382
+ `http://127.0.0.1:${monitorPort}/status`,
1383
+ token
1384
+ );
1385
+ if (status.running) {
1386
+ return { port: monitorPort, token };
1387
+ }
1388
+ } catch {
1389
+ }
1390
+ }
1391
+ throw new Error("Monitor process did not start in time");
1392
+ }
1393
+ async function monitorCommand(request) {
1394
+ const { port, token } = await ensureMonitorRunning();
1395
+ return httpJson(
1396
+ "POST",
1397
+ `http://127.0.0.1:${port}/command`,
1398
+ token,
1399
+ request
1400
+ );
1401
+ }
1402
+ function findMonitorScript() {
1403
+ const currentFile = fileURLToPath2(import.meta.url);
1404
+ const currentDir = dirname(currentFile);
1405
+ const candidates = [
1406
+ // Built output (tsup puts it next to cli.js)
1407
+ resolve(currentDir, "cdp-monitor.js"),
1408
+ // Development: packages/cli/src -> packages/cli/dist
1409
+ resolve(currentDir, "../dist/cdp-monitor.js"),
1410
+ // Monorepo root dist
1411
+ resolve(currentDir, "../../dist/cdp-monitor.js"),
1412
+ resolve(currentDir, "../../../dist/cdp-monitor.js")
1413
+ ];
1414
+ for (const candidate of candidates) {
1415
+ if (existsSync2(candidate)) {
1416
+ return candidate;
1417
+ }
1418
+ }
1419
+ return candidates[0];
1420
+ }
1421
+
1163
1422
  // packages/cli/src/client.ts
1423
+ var MONITOR_ACTIONS = /* @__PURE__ */ new Set(["network", "console", "errors", "trace"]);
1164
1424
  var jqExpression;
1165
1425
  function setJqExpression(expression) {
1166
1426
  jqExpression = expression;
@@ -1179,15 +1439,22 @@ function handleJqResponse(response) {
1179
1439
  }
1180
1440
  }
1181
1441
  async function sendCommand2(request) {
1442
+ if (MONITOR_ACTIONS.has(request.action)) {
1443
+ try {
1444
+ return await monitorCommand(request);
1445
+ } catch {
1446
+ return sendCommand(request);
1447
+ }
1448
+ }
1182
1449
  return sendCommand(request);
1183
1450
  }
1184
1451
 
1185
1452
  // packages/cli/src/daemon-manager.ts
1186
- import { fileURLToPath as fileURLToPath2 } from "url";
1187
- import { dirname, resolve } from "path";
1188
- import { existsSync as existsSync2 } from "fs";
1453
+ import { fileURLToPath as fileURLToPath3 } from "url";
1454
+ import { dirname as dirname2, resolve as resolve2 } from "path";
1455
+ import { existsSync as existsSync3 } from "fs";
1189
1456
  async function isDaemonRunning() {
1190
- return await discoverCdpPort() !== null;
1457
+ return await isManagedBrowserRunning();
1191
1458
  }
1192
1459
  async function ensureDaemonRunning() {
1193
1460
  try {
@@ -1206,7 +1473,7 @@ async function ensureDaemonRunning() {
1206
1473
  }
1207
1474
 
1208
1475
  // packages/cli/src/history-sqlite.ts
1209
- import { copyFileSync, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
1476
+ import { copyFileSync, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "fs";
1210
1477
  import { execSync as execSync2 } from "child_process";
1211
1478
  import { homedir, tmpdir } from "os";
1212
1479
  import { join } from "path";
@@ -1230,7 +1497,7 @@ function getHistoryPathCandidates() {
1230
1497
  }
1231
1498
  function findHistoryPath() {
1232
1499
  for (const historyPath of getHistoryPathCandidates()) {
1233
- if (existsSync3(historyPath)) {
1500
+ if (existsSync4(historyPath)) {
1234
1501
  return historyPath;
1235
1502
  }
1236
1503
  }
@@ -1350,7 +1617,7 @@ function getHistoryDomains(days) {
1350
1617
  }
1351
1618
 
1352
1619
  // packages/cli/src/commands/site.ts
1353
- import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
1620
+ import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync5, mkdirSync } from "fs";
1354
1621
  import { join as join2, relative } from "path";
1355
1622
  import { homedir as homedir2 } from "os";
1356
1623
  import { execSync as execSync3 } from "child_process";
@@ -1369,6 +1636,10 @@ function checkCliUpdate() {
1369
1636
  } catch {
1370
1637
  }
1371
1638
  }
1639
+ function exitJsonError(error, extra = {}) {
1640
+ console.log(JSON.stringify({ success: false, error, ...extra }, null, 2));
1641
+ process.exit(1);
1642
+ }
1372
1643
  function parseSiteMeta(filePath, source) {
1373
1644
  let content;
1374
1645
  try {
@@ -1432,7 +1703,7 @@ function parseSiteMeta(filePath, source) {
1432
1703
  return meta;
1433
1704
  }
1434
1705
  function scanSites(dir, source) {
1435
- if (!existsSync4(dir)) return [];
1706
+ if (!existsSync5(dir)) return [];
1436
1707
  const sites = [];
1437
1708
  function walk(currentDir) {
1438
1709
  let entries;
@@ -1486,6 +1757,10 @@ function matchTabOrigin(tabUrl, domain) {
1486
1757
  function siteList(options) {
1487
1758
  const sites = getAllSites();
1488
1759
  if (sites.length === 0) {
1760
+ if (options.json) {
1761
+ console.log("[]");
1762
+ return;
1763
+ }
1489
1764
  console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
1490
1765
  console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
1491
1766
  console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
@@ -1526,6 +1801,10 @@ function siteSearch(query, options) {
1526
1801
  (s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
1527
1802
  );
1528
1803
  if (matches.length === 0) {
1804
+ if (options.json) {
1805
+ console.log("[]");
1806
+ return;
1807
+ }
1529
1808
  console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
1530
1809
  console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
1531
1810
  return;
@@ -1544,34 +1823,63 @@ function siteSearch(query, options) {
1544
1823
  console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
1545
1824
  }
1546
1825
  }
1547
- function siteUpdate() {
1826
+ function siteUpdate(options = {}) {
1548
1827
  mkdirSync(BB_DIR, { recursive: true });
1549
- if (existsSync4(join2(COMMUNITY_SITES_DIR, ".git"))) {
1550
- console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
1828
+ const updateMode = existsSync5(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
1829
+ if (updateMode === "pull") {
1830
+ if (!options.json) {
1831
+ console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
1832
+ }
1551
1833
  try {
1552
1834
  execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
1553
- console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
1554
- console.log("");
1555
- console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
1835
+ if (!options.json) {
1836
+ console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
1837
+ console.log("");
1838
+ console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
1839
+ }
1556
1840
  } catch (e) {
1841
+ const message = e instanceof Error ? e.message : String(e);
1842
+ const manualAction = "cd ~/.bb-browser/bb-sites && git pull";
1843
+ if (options.json) {
1844
+ exitJsonError(`\u66F4\u65B0\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
1845
+ }
1557
1846
  console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
1558
1847
  console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
1559
1848
  process.exit(1);
1560
1849
  }
1561
1850
  } else {
1562
- console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
1851
+ if (!options.json) {
1852
+ console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
1853
+ }
1563
1854
  try {
1564
1855
  execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
1565
- console.log("\u514B\u9686\u5B8C\u6210\u3002");
1566
- console.log("");
1567
- console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
1856
+ if (!options.json) {
1857
+ console.log("\u514B\u9686\u5B8C\u6210\u3002");
1858
+ console.log("");
1859
+ console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
1860
+ }
1568
1861
  } catch (e) {
1862
+ const message = e instanceof Error ? e.message : String(e);
1863
+ const manualAction = `git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`;
1864
+ if (options.json) {
1865
+ exitJsonError(`\u514B\u9686\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
1866
+ }
1569
1867
  console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
1570
1868
  console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
1571
1869
  process.exit(1);
1572
1870
  }
1573
1871
  }
1574
1872
  const sites = scanSites(COMMUNITY_SITES_DIR, "community");
1873
+ if (options.json) {
1874
+ console.log(JSON.stringify({
1875
+ success: true,
1876
+ updateMode,
1877
+ communityRepo: COMMUNITY_REPO,
1878
+ communityDir: COMMUNITY_SITES_DIR,
1879
+ siteCount: sites.length
1880
+ }, null, 2));
1881
+ return;
1882
+ }
1575
1883
  console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
1576
1884
  console.log(`\u2B50 Like bb-browser? \u2192 bb-browser star`);
1577
1885
  checkCliUpdate();
@@ -1582,6 +1890,9 @@ function findSiteByName(name) {
1582
1890
  function siteInfo(name, options) {
1583
1891
  const site = findSiteByName(name);
1584
1892
  if (!site) {
1893
+ if (options.json) {
1894
+ exitJsonError(`adapter "${name}" not found`, { action: "bb-browser site list" });
1895
+ }
1585
1896
  console.error(`[error] site info: adapter "${name}" not found.`);
1586
1897
  console.error(" Try: bb-browser site list");
1587
1898
  process.exit(1);
@@ -1694,6 +2005,12 @@ async function siteRun(name, args, options) {
1694
2005
  const site = sites.find((s) => s.name === name);
1695
2006
  if (!site) {
1696
2007
  const fuzzy = sites.filter((s) => s.name.includes(name));
2008
+ if (options.json) {
2009
+ exitJsonError(`site "${name}" not found`, {
2010
+ suggestions: fuzzy.slice(0, 5).map((s) => s.name),
2011
+ action: fuzzy.length > 0 ? void 0 : "bb-browser site update"
2012
+ });
2013
+ }
1697
2014
  console.error(`[error] site: "${name}" not found.`);
1698
2015
  if (fuzzy.length > 0) {
1699
2016
  console.error(" Did you mean:");
@@ -1728,11 +2045,17 @@ async function siteRun(name, args, options) {
1728
2045
  }
1729
2046
  for (const [argName, argDef] of Object.entries(site.args)) {
1730
2047
  if (argDef.required && !argMap[argName]) {
1731
- console.error(`[error] site ${name}: missing required argument "${argName}".`);
1732
2048
  const usage = argNames.map((a) => {
1733
2049
  const def = site.args[a];
1734
2050
  return def.required ? `<${a}>` : `[${a}]`;
1735
2051
  }).join(" ");
2052
+ if (options.json) {
2053
+ exitJsonError(`missing required argument "${argName}"`, {
2054
+ usage: `bb-browser site ${name} ${usage}`,
2055
+ example: site.example
2056
+ });
2057
+ }
2058
+ console.error(`[error] site ${name}: missing required argument "${argName}".`);
1736
2059
  console.error(` Usage: bb-browser site ${name} ${usage}`);
1737
2060
  if (site.example) console.error(` Example: ${site.example}`);
1738
2061
  process.exit(1);
@@ -1743,7 +2066,7 @@ async function siteRun(name, args, options) {
1743
2066
  const argsJson = JSON.stringify(argMap);
1744
2067
  const script = `(${jsBody})(${argsJson})`;
1745
2068
  if (options.openclaw) {
1746
- const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-P3G4KGYM.js");
2069
+ const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-HBJH6UFO.js");
1747
2070
  let targetId;
1748
2071
  if (site.domain) {
1749
2072
  const tabs = ocGetTabs();
@@ -1752,7 +2075,7 @@ async function siteRun(name, args, options) {
1752
2075
  targetId = existing.targetId;
1753
2076
  } else {
1754
2077
  targetId = ocOpenTab(`https://${site.domain}`);
1755
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2078
+ await new Promise((resolve3) => setTimeout(resolve3, 3e3));
1756
2079
  }
1757
2080
  } else {
1758
2081
  const tabs = ocGetTabs();
@@ -1814,7 +2137,7 @@ async function siteRun(name, args, options) {
1814
2137
  url: `https://${site.domain}`
1815
2138
  });
1816
2139
  targetTabId = newResp.data?.tabId;
1817
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2140
+ await new Promise((resolve3) => setTimeout(resolve3, 3e3));
1818
2141
  }
1819
2142
  }
1820
2143
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
@@ -1928,7 +2251,7 @@ async function siteCommand(args, options = {}) {
1928
2251
  await siteRecommend(options);
1929
2252
  break;
1930
2253
  case "update":
1931
- siteUpdate();
2254
+ siteUpdate(options);
1932
2255
  break;
1933
2256
  case "run":
1934
2257
  if (!args[1]) {
@@ -1954,9 +2277,9 @@ async function siteCommand(args, options = {}) {
1954
2277
  }
1955
2278
  function silentUpdate() {
1956
2279
  const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
1957
- if (!existsSync4(gitDir)) return;
1958
- import("child_process").then(({ spawn: spawn2 }) => {
1959
- const child = spawn2("git", ["pull", "--ff-only"], {
2280
+ if (!existsSync5(gitDir)) return;
2281
+ import("child_process").then(({ spawn: spawn3 }) => {
2282
+ const child = spawn3("git", ["pull", "--ff-only"], {
1960
2283
  cwd: COMMUNITY_SITES_DIR,
1961
2284
  stdio: "ignore",
1962
2285
  detached: true
@@ -2254,17 +2577,17 @@ async function getCommand(attribute, ref, options = {}) {
2254
2577
 
2255
2578
  // packages/cli/src/commands/screenshot.ts
2256
2579
  import fs from "fs";
2257
- import path3 from "path";
2258
- import os3 from "os";
2580
+ import path4 from "path";
2581
+ import os4 from "os";
2259
2582
  function getDefaultPath() {
2260
2583
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2261
2584
  const filename = `bb-screenshot-${timestamp}.png`;
2262
- return path3.join(os3.tmpdir(), filename);
2585
+ return path4.join(os4.tmpdir(), filename);
2263
2586
  }
2264
2587
  function saveBase64Image(dataUrl, filePath) {
2265
2588
  const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
2266
2589
  const buffer = Buffer.from(base64Data, "base64");
2267
- const dir = path3.dirname(filePath);
2590
+ const dir = path4.dirname(filePath);
2268
2591
  if (!fs.existsSync(dir)) {
2269
2592
  fs.mkdirSync(dir, { recursive: true });
2270
2593
  }
@@ -2272,7 +2595,7 @@ function saveBase64Image(dataUrl, filePath) {
2272
2595
  }
2273
2596
  async function screenshotCommand(outputPath, options = {}) {
2274
2597
  await ensureDaemonRunning();
2275
- const filePath = outputPath ? path3.resolve(outputPath) : getDefaultPath();
2598
+ const filePath = outputPath ? path4.resolve(outputPath) : getDefaultPath();
2276
2599
  const request = {
2277
2600
  id: generateId(),
2278
2601
  action: "screenshot",
@@ -2435,123 +2758,6 @@ async function scrollCommand(direction, pixels, options = {}) {
2435
2758
  }
2436
2759
  }
2437
2760
 
2438
- // packages/cli/src/commands/daemon.ts
2439
- async function statusCommand(options = {}) {
2440
- const running = await isDaemonRunning();
2441
- if (options.json) {
2442
- console.log(JSON.stringify({ running }));
2443
- } else {
2444
- console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
2445
- }
2446
- }
2447
-
2448
- // packages/cli/src/commands/reload.ts
2449
- import WebSocket2 from "ws";
2450
- var EXTENSION_NAME = "bb-browser";
2451
- async function reloadCommand(options = {}) {
2452
- const port = options.port || 9222;
2453
- try {
2454
- const listRes = await fetch(`http://127.0.0.1:${port}/json/list`);
2455
- if (!listRes.ok) {
2456
- throw new Error(`CDP \u672A\u542F\u7528\u3002\u8BF7\u7528 --remote-debugging-port=${port} \u542F\u52A8 Chrome`);
2457
- }
2458
- const list = await listRes.json();
2459
- const extPage = list.find(
2460
- (t) => t.type === "page" && t.url.includes("chrome://extensions")
2461
- );
2462
- if (!extPage) {
2463
- throw new Error("\u8BF7\u5148\u6253\u5F00 chrome://extensions \u9875\u9762");
2464
- }
2465
- const result = await new Promise((resolve2, reject) => {
2466
- const ws = new WebSocket2(extPage.webSocketDebuggerUrl);
2467
- let resolved = false;
2468
- const timeout = setTimeout(() => {
2469
- if (!resolved) {
2470
- resolved = true;
2471
- ws.close();
2472
- reject(new Error("CDP \u8FDE\u63A5\u8D85\u65F6"));
2473
- }
2474
- }, 1e4);
2475
- ws.on("open", () => {
2476
- const script = `
2477
- (async function() {
2478
- if (!chrome || !chrome.developerPrivate) {
2479
- return { error: 'developerPrivate API not available' };
2480
- }
2481
-
2482
- try {
2483
- const exts = await chrome.developerPrivate.getExtensionsInfo();
2484
- const bbExt = exts.find(e => e.name === '${EXTENSION_NAME}');
2485
-
2486
- if (!bbExt) {
2487
- return { error: '${EXTENSION_NAME} \u6269\u5C55\u672A\u5B89\u88C5' };
2488
- }
2489
-
2490
- if (bbExt.state !== 'ENABLED') {
2491
- return { error: '${EXTENSION_NAME} \u6269\u5C55\u5DF2\u7981\u7528' };
2492
- }
2493
-
2494
- await chrome.developerPrivate.reload(bbExt.id, {failQuietly: true});
2495
- return { success: true, extensionId: bbExt.id };
2496
- } catch (e) {
2497
- return { error: e.message };
2498
- }
2499
- })()
2500
- `;
2501
- ws.send(JSON.stringify({
2502
- id: 1,
2503
- method: "Runtime.evaluate",
2504
- params: {
2505
- expression: script,
2506
- awaitPromise: true,
2507
- returnByValue: true
2508
- }
2509
- }));
2510
- });
2511
- ws.on("message", (data) => {
2512
- const msg = JSON.parse(data.toString());
2513
- if (msg.id === 1) {
2514
- clearTimeout(timeout);
2515
- resolved = true;
2516
- ws.close();
2517
- const value = msg.result?.result?.value;
2518
- if (value?.success) {
2519
- resolve2({
2520
- success: true,
2521
- message: "\u6269\u5C55\u5DF2\u91CD\u8F7D",
2522
- extensionId: value.extensionId
2523
- });
2524
- } else if (value?.error) {
2525
- reject(new Error(value.error));
2526
- } else {
2527
- reject(new Error(`\u91CD\u8F7D\u5931\u8D25: ${JSON.stringify(value)}`));
2528
- }
2529
- }
2530
- });
2531
- ws.on("error", (err) => {
2532
- clearTimeout(timeout);
2533
- if (!resolved) {
2534
- resolved = true;
2535
- reject(new Error(`CDP \u8FDE\u63A5\u5931\u8D25: ${err.message}`));
2536
- }
2537
- });
2538
- });
2539
- if (options.json) {
2540
- console.log(JSON.stringify(result));
2541
- } else {
2542
- console.log(`${result.message} (${result.extensionId})`);
2543
- }
2544
- } catch (error) {
2545
- const message = error instanceof Error ? error.message : String(error);
2546
- if (options.json) {
2547
- console.log(JSON.stringify({ success: false, error: message }));
2548
- } else {
2549
- console.error(`\u9519\u8BEF: ${message}`);
2550
- }
2551
- process.exit(1);
2552
- }
2553
- }
2554
-
2555
2761
  // packages/cli/src/commands/nav.ts
2556
2762
  async function backCommand(options = {}) {
2557
2763
  await ensureDaemonRunning();
@@ -3252,7 +3458,7 @@ async function ensureTabForOrigin(origin, hostname) {
3252
3458
  if (!newResp.success) {
3253
3459
  throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
3254
3460
  }
3255
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
3461
+ await new Promise((resolve3) => setTimeout(resolve3, 3e3));
3256
3462
  return newResp.data?.tabId;
3257
3463
  }
3258
3464
  function buildFetchScript(url, options) {
@@ -3398,8 +3604,18 @@ async function historyCommand(subCommand, options = {}) {
3398
3604
  }
3399
3605
  }
3400
3606
 
3607
+ // packages/cli/src/commands/daemon.ts
3608
+ async function statusCommand(options = {}) {
3609
+ const running = await isDaemonRunning();
3610
+ if (options.json) {
3611
+ console.log(JSON.stringify({ running }));
3612
+ } else {
3613
+ console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
3614
+ }
3615
+ }
3616
+
3401
3617
  // packages/cli/src/index.ts
3402
- var VERSION = "0.8.2";
3618
+ var VERSION = "0.9.0";
3403
3619
  var HELP_TEXT = `
3404
3620
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
3405
3621
 
@@ -3554,9 +3770,9 @@ async function main() {
3554
3770
  return;
3555
3771
  }
3556
3772
  if (process.argv.includes("--mcp")) {
3557
- const mcpPath = fileURLToPath3(new URL("./mcp.js", import.meta.url));
3558
- const { spawn: spawn2 } = await import("child_process");
3559
- const child = spawn2(process.execPath, [mcpPath], { stdio: "inherit" });
3773
+ const mcpPath = fileURLToPath4(new URL("./mcp.js", import.meta.url));
3774
+ const { spawn: spawn3 } = await import("child_process");
3775
+ const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
3560
3776
  child.on("exit", (code) => process.exit(code ?? 0));
3561
3777
  return;
3562
3778
  }
@@ -3717,24 +3933,6 @@ async function main() {
3717
3933
  break;
3718
3934
  }
3719
3935
  case "daemon":
3720
- case "start": {
3721
- const hostIdx = process.argv.findIndex((a) => a === "--host");
3722
- const host = hostIdx >= 0 ? process.argv[hostIdx + 1] : void 0;
3723
- await daemonCommand({ json: parsed.flags.json, host });
3724
- break;
3725
- }
3726
- case "stop": {
3727
- await stopCommand({ json: parsed.flags.json });
3728
- break;
3729
- }
3730
- case "status": {
3731
- await statusCommand({ json: parsed.flags.json });
3732
- break;
3733
- }
3734
- case "reload": {
3735
- await reloadCommand({ json: parsed.flags.json });
3736
- break;
3737
- }
3738
3936
  case "close": {
3739
3937
  await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
3740
3938
  break;
@@ -3797,6 +3995,10 @@ async function main() {
3797
3995
  await tabCommand(parsed.args, { json: parsed.flags.json });
3798
3996
  break;
3799
3997
  }
3998
+ case "status": {
3999
+ await statusCommand({ json: parsed.flags.json });
4000
+ break;
4001
+ }
3800
4002
  case "frame": {
3801
4003
  const selectorOrMain = parsed.args[0];
3802
4004
  if (!selectorOrMain) {