bb-browser 0.10.1 → 0.11.1

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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  COMMAND_TIMEOUT,
4
4
  generateId
5
- } from "./chunk-XYKHDJST.js";
5
+ } from "./chunk-H2VEQBAU.js";
6
6
  import {
7
7
  applyJq
8
8
  } from "./chunk-AHGAQEFO.js";
@@ -12,16 +12,17 @@ import {
12
12
  import "./chunk-D4HDZEJT.js";
13
13
 
14
14
  // packages/cli/src/index.ts
15
- import { fileURLToPath as fileURLToPath4 } from "url";
15
+ import { fileURLToPath as fileURLToPath2 } from "url";
16
16
 
17
- // packages/cli/src/cdp-client.ts
18
- import { readFileSync, unlinkSync, writeFileSync } from "fs";
17
+ // packages/cli/src/daemon-manager.ts
18
+ import { spawn as spawn2 } from "child_process";
19
+ import { readFile as readFile2, unlink } from "fs/promises";
19
20
  import { request as httpRequest } from "http";
20
- import { request as httpsRequest } from "https";
21
+ import { fileURLToPath } from "url";
22
+ import { dirname, resolve } from "path";
23
+ import { existsSync as existsSync2 } from "fs";
21
24
  import os2 from "os";
22
25
  import path2 from "path";
23
- import { fileURLToPath } from "url";
24
- import WebSocket from "ws";
25
26
 
26
27
  // packages/cli/src/cdp-discovery.ts
27
28
  import { execFile, execSync, spawn } from "child_process";
@@ -33,14 +34,16 @@ var DEFAULT_CDP_PORT = 19825;
33
34
  var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
34
35
  var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
35
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;
36
39
  function execFileAsync(command, args, timeout) {
37
- return new Promise((resolve3, reject) => {
40
+ return new Promise((resolve2, reject) => {
38
41
  execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
39
42
  if (error) {
40
43
  reject(error);
41
44
  return;
42
45
  }
43
- resolve3(stdout.trim());
46
+ resolve2(stdout.trim());
44
47
  });
45
48
  });
46
49
  }
@@ -51,12 +54,33 @@ function getArgValue(flag) {
51
54
  }
52
55
  async function tryOpenClaw() {
53
56
  try {
54
- const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 5e3);
57
+ const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 3e4);
55
58
  const parsed = parseOpenClawJson(raw);
56
- const port = Number(parsed?.cdpPort);
57
- if (Number.isInteger(port) && port > 0) {
58
- return { host: "127.0.0.1", port };
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
+ }
59
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;
60
84
  } catch {
61
85
  }
62
86
  return null;
@@ -116,18 +140,6 @@ function findBrowserExecutable() {
116
140
  }
117
141
  return null;
118
142
  }
119
- async function isManagedBrowserRunning() {
120
- try {
121
- const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
122
- const port = Number.parseInt(rawPort.trim(), 10);
123
- if (!Number.isInteger(port) || port <= 0) {
124
- return false;
125
- }
126
- return await canConnect("127.0.0.1", port);
127
- } catch {
128
- return false;
129
- }
130
- }
131
143
  async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
132
144
  const executable = findBrowserExecutable();
133
145
  if (!executable) {
@@ -178,11 +190,22 @@ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
178
190
  if (await canConnect("127.0.0.1", port)) {
179
191
  return { host: "127.0.0.1", port };
180
192
  }
181
- await new Promise((resolve3) => setTimeout(resolve3, 250));
193
+ await new Promise((resolve2) => setTimeout(resolve2, 250));
182
194
  }
183
195
  return null;
184
196
  }
185
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
+ }
186
209
  const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
187
210
  if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
188
211
  return { host: "127.0.0.1", port: explicitPort };
@@ -195,6 +218,14 @@ async function discoverCdpPort() {
195
218
  }
196
219
  } catch {
197
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
+ }
198
229
  if (process.argv.includes("--openclaw")) {
199
230
  const viaOpenClaw = await tryOpenClaw();
200
231
  if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
@@ -214,1089 +245,36 @@ async function discoverCdpPort() {
214
245
  return null;
215
246
  }
216
247
 
217
- // packages/cli/src/cdp-client.ts
218
- var connectionState = null;
219
- var reconnecting = null;
220
- var networkRequests = /* @__PURE__ */ new Map();
221
- var networkEnabled = false;
222
- var consoleMessages = [];
223
- var consoleEnabled = false;
224
- var jsErrors = [];
225
- var errorsEnabled = false;
226
- var traceRecording = false;
227
- var traceEvents = [];
228
- function getContextFilePath(host, port) {
229
- const safeHost = host.replace(/[^a-zA-Z0-9_.-]/g, "_");
230
- return path2.join(os2.tmpdir(), `bb-browser-cdp-context-${safeHost}-${port}.json`);
231
- }
232
- function loadPersistedCurrentTargetId(host, port) {
233
- try {
234
- const data = JSON.parse(readFileSync(getContextFilePath(host, port), "utf-8"));
235
- return typeof data.currentTargetId === "string" && data.currentTargetId ? data.currentTargetId : void 0;
236
- } catch {
237
- return void 0;
238
- }
239
- }
240
- function persistCurrentTargetId(host, port, currentTargetId) {
241
- try {
242
- writeFileSync(getContextFilePath(host, port), JSON.stringify({ currentTargetId }));
243
- } catch {
244
- }
245
- }
246
- function setCurrentTargetId(targetId) {
247
- const state = connectionState;
248
- if (!state) return;
249
- state.currentTargetId = targetId;
250
- persistCurrentTargetId(state.host, state.port, targetId);
251
- }
252
- function buildRequestError(error) {
253
- return error instanceof Error ? error : new Error(String(error));
254
- }
255
- function fetchJson(url) {
256
- return new Promise((resolve3, reject) => {
257
- const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
258
- const req = requester(url, { method: "GET" }, (res) => {
259
- const chunks = [];
260
- res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
261
- res.on("end", () => {
262
- const raw = Buffer.concat(chunks).toString("utf8");
263
- if ((res.statusCode ?? 500) >= 400) {
264
- reject(new Error(`HTTP ${res.statusCode ?? 500}: ${raw}`));
265
- return;
266
- }
267
- try {
268
- resolve3(JSON.parse(raw));
269
- } catch (error) {
270
- reject(error);
271
- }
272
- });
273
- });
274
- req.on("error", reject);
275
- req.end();
276
- });
277
- }
278
- async function getJsonList(host, port) {
279
- const data = await fetchJson(`http://${host}:${port}/json/list`);
280
- return Array.isArray(data) ? data : [];
281
- }
282
- async function getJsonVersion(host, port) {
283
- const data = await fetchJson(`http://${host}:${port}/json/version`);
284
- const url = data.webSocketDebuggerUrl;
285
- if (typeof url !== "string" || !url) {
286
- throw new Error("CDP endpoint missing webSocketDebuggerUrl");
287
- }
288
- return { webSocketDebuggerUrl: url };
289
- }
290
- function connectWebSocket(url) {
291
- return new Promise((resolve3, reject) => {
292
- const ws = new WebSocket(url);
293
- ws.once("open", () => {
294
- const socket = ws._socket;
295
- if (socket && typeof socket.unref === "function") {
296
- socket.unref();
297
- }
298
- resolve3(ws);
299
- });
300
- ws.once("error", reject);
301
- });
302
- }
303
- function createState(host, port, browserWsUrl, browserSocket) {
304
- const state = {
305
- host,
306
- port,
307
- browserWsUrl,
308
- browserSocket,
309
- browserPending: /* @__PURE__ */ new Map(),
310
- nextMessageId: 1,
311
- sessions: /* @__PURE__ */ new Map(),
312
- attachedTargets: /* @__PURE__ */ new Map(),
313
- refsByTarget: /* @__PURE__ */ new Map(),
314
- currentTargetId: loadPersistedCurrentTargetId(host, port),
315
- activeFrameIdByTarget: /* @__PURE__ */ new Map(),
316
- dialogHandlers: /* @__PURE__ */ new Map()
317
- };
318
- browserSocket.on("message", (raw) => {
319
- const message = JSON.parse(raw.toString());
320
- if (typeof message.id === "number") {
321
- const pending = state.browserPending.get(message.id);
322
- if (!pending) return;
323
- state.browserPending.delete(message.id);
324
- if (message.error) {
325
- pending.reject(new Error(`${pending.method}: ${message.error.message ?? "Unknown CDP error"}`));
326
- } else {
327
- pending.resolve(message.result);
328
- }
329
- return;
330
- }
331
- if (message.method === "Target.attachedToTarget") {
332
- const params = message.params;
333
- const sessionId = params.sessionId;
334
- const targetInfo = params.targetInfo;
335
- if (typeof sessionId === "string" && typeof targetInfo?.targetId === "string") {
336
- state.sessions.set(targetInfo.targetId, sessionId);
337
- state.attachedTargets.set(sessionId, targetInfo.targetId);
338
- }
339
- return;
340
- }
341
- if (message.method === "Target.detachedFromTarget") {
342
- const params = message.params;
343
- const sessionId = params.sessionId;
344
- if (typeof sessionId === "string") {
345
- const targetId = state.attachedTargets.get(sessionId);
346
- if (targetId) {
347
- state.sessions.delete(targetId);
348
- state.attachedTargets.delete(sessionId);
349
- state.activeFrameIdByTarget.delete(targetId);
350
- state.dialogHandlers.delete(targetId);
351
- if (state.currentTargetId === targetId) {
352
- state.currentTargetId = void 0;
353
- persistCurrentTargetId(state.host, state.port, void 0);
354
- }
355
- }
356
- }
357
- return;
358
- }
359
- if (message.method === "Target.receivedMessageFromTarget") {
360
- const params = message.params;
361
- const sessionId = params.sessionId;
362
- const messageText = params.message;
363
- if (typeof sessionId === "string" && typeof messageText === "string") {
364
- const targetId = state.attachedTargets.get(sessionId);
365
- if (targetId) {
366
- handleSessionEvent(targetId, JSON.parse(messageText)).catch(() => {
367
- });
368
- }
369
- }
370
- return;
371
- }
372
- if (typeof message.sessionId === "string" && typeof message.method === "string") {
373
- const targetId = state.attachedTargets.get(message.sessionId);
374
- if (targetId) {
375
- handleSessionEvent(targetId, message).catch(() => {
376
- });
377
- }
378
- }
379
- });
380
- browserSocket.on("close", () => {
381
- if (connectionState === state) {
382
- connectionState = null;
383
- }
384
- for (const pending of state.browserPending.values()) {
385
- pending.reject(new Error("CDP connection closed"));
386
- }
387
- state.browserPending.clear();
388
- });
389
- browserSocket.on("error", () => {
390
- });
391
- return state;
392
- }
393
- async function browserCommand(method, params = {}) {
394
- const state = connectionState;
395
- if (!state) throw new Error("CDP connection not initialized");
396
- const id = state.nextMessageId++;
397
- const payload = JSON.stringify({ id, method, params });
398
- const promise = new Promise((resolve3, reject) => {
399
- state.browserPending.set(id, { resolve: resolve3, reject, method });
400
- });
401
- state.browserSocket.send(payload);
402
- return promise;
403
- }
404
- async function sessionCommand(targetId, method, params = {}) {
405
- const state = connectionState;
406
- if (!state) throw new Error("CDP connection not initialized");
407
- const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
408
- const id = state.nextMessageId++;
409
- const payload = JSON.stringify({ id, method, params, sessionId });
410
- return new Promise((resolve3, reject) => {
411
- const check = (raw) => {
412
- const msg = JSON.parse(raw.toString());
413
- if (msg.id === id && msg.sessionId === sessionId) {
414
- state.browserSocket.off("message", check);
415
- if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
416
- else resolve3(msg.result);
417
- }
418
- };
419
- state.browserSocket.on("message", check);
420
- state.browserSocket.send(payload);
421
- });
422
- }
423
- function getActiveFrameId(targetId) {
424
- const frameId = connectionState?.activeFrameIdByTarget.get(targetId);
425
- return frameId ?? void 0;
426
- }
427
- async function pageCommand(targetId, method, params = {}) {
428
- const frameId = getActiveFrameId(targetId);
429
- return sessionCommand(targetId, method, frameId ? { ...params, frameId } : params);
430
- }
431
- function normalizeHeaders(headers) {
432
- if (!headers || typeof headers !== "object") return void 0;
433
- return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
434
- }
435
- async function handleSessionEvent(targetId, event) {
436
- const method = event.method;
437
- const params = event.params ?? {};
438
- if (typeof method !== "string") return;
439
- if (method === "Page.javascriptDialogOpening") {
440
- const handler = connectionState?.dialogHandlers.get(targetId);
441
- if (handler) {
442
- await sessionCommand(targetId, "Page.handleJavaScriptDialog", {
443
- accept: handler.accept,
444
- ...handler.promptText !== void 0 ? { promptText: handler.promptText } : {}
445
- });
446
- }
447
- return;
448
- }
449
- if (method === "Network.requestWillBeSent") {
450
- const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
451
- const request = params.request;
452
- if (!requestId || !request) return;
453
- networkRequests.set(requestId, {
454
- requestId,
455
- url: String(request.url ?? ""),
456
- method: String(request.method ?? "GET"),
457
- type: String(params.type ?? "Other"),
458
- timestamp: Math.round(Number(params.timestamp ?? Date.now()) * 1e3),
459
- requestHeaders: normalizeHeaders(request.headers),
460
- requestBody: typeof request.postData === "string" ? request.postData : void 0
461
- });
462
- return;
463
- }
464
- if (method === "Network.responseReceived") {
465
- const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
466
- const response = params.response;
467
- if (!requestId || !response) return;
468
- const existing = networkRequests.get(requestId);
469
- if (!existing) return;
470
- existing.status = typeof response.status === "number" ? response.status : void 0;
471
- existing.statusText = typeof response.statusText === "string" ? response.statusText : void 0;
472
- existing.responseHeaders = normalizeHeaders(response.headers);
473
- existing.mimeType = typeof response.mimeType === "string" ? response.mimeType : void 0;
474
- networkRequests.set(requestId, existing);
475
- return;
476
- }
477
- if (method === "Network.loadingFailed") {
478
- const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
479
- if (!requestId) return;
480
- const existing = networkRequests.get(requestId);
481
- if (!existing) return;
482
- existing.failed = true;
483
- existing.failureReason = typeof params.errorText === "string" ? params.errorText : "Unknown error";
484
- networkRequests.set(requestId, existing);
485
- return;
486
- }
487
- if (method === "Runtime.consoleAPICalled") {
488
- const type = String(params.type ?? "log");
489
- const args = Array.isArray(params.args) ? params.args : [];
490
- const text = args.map((arg) => {
491
- if (typeof arg.value === "string") return arg.value;
492
- if (arg.value !== void 0) return String(arg.value);
493
- if (typeof arg.description === "string") return arg.description;
494
- return "";
495
- }).filter(Boolean).join(" ");
496
- const stack = params.stackTrace;
497
- const firstCallFrame = Array.isArray(stack?.callFrames) ? stack?.callFrames[0] : void 0;
498
- consoleMessages.push({
499
- type: ["log", "info", "warn", "error", "debug"].includes(type) ? type : "log",
500
- text,
501
- timestamp: Math.round(Number(params.timestamp ?? Date.now())),
502
- url: typeof firstCallFrame?.url === "string" ? firstCallFrame.url : void 0,
503
- lineNumber: typeof firstCallFrame?.lineNumber === "number" ? firstCallFrame.lineNumber : void 0
504
- });
505
- return;
506
- }
507
- if (method === "Runtime.exceptionThrown") {
508
- const details = params.exceptionDetails;
509
- if (!details) return;
510
- const exception = details.exception;
511
- const stackTrace = details.stackTrace;
512
- const callFrames = Array.isArray(stackTrace?.callFrames) ? stackTrace.callFrames : [];
513
- jsErrors.push({
514
- message: typeof exception?.description === "string" ? exception.description : String(details.text ?? "JavaScript exception"),
515
- url: typeof details.url === "string" ? details.url : typeof callFrames[0]?.url === "string" ? String(callFrames[0].url) : void 0,
516
- lineNumber: typeof details.lineNumber === "number" ? details.lineNumber : void 0,
517
- columnNumber: typeof details.columnNumber === "number" ? details.columnNumber : void 0,
518
- stackTrace: callFrames.length > 0 ? callFrames.map((frame) => `${String(frame.functionName ?? "<anonymous>")} (${String(frame.url ?? "")}:${String(frame.lineNumber ?? 0)}:${String(frame.columnNumber ?? 0)})`).join("\n") : void 0,
519
- timestamp: Date.now()
520
- });
521
- }
522
- }
523
- async function ensureNetworkMonitoring(targetId) {
524
- if (networkEnabled) return;
525
- await sessionCommand(targetId, "Network.enable");
526
- networkEnabled = true;
527
- }
528
- async function ensureConsoleMonitoring(targetId) {
529
- if (consoleEnabled && errorsEnabled) return;
530
- await sessionCommand(targetId, "Runtime.enable");
531
- consoleEnabled = true;
532
- errorsEnabled = true;
533
- }
534
- async function attachTarget(targetId) {
535
- const result = await browserCommand("Target.attachToTarget", {
536
- targetId,
537
- flatten: true
538
- });
539
- connectionState?.sessions.set(targetId, result.sessionId);
540
- connectionState?.attachedTargets.set(result.sessionId, targetId);
541
- connectionState?.activeFrameIdByTarget.set(targetId, connectionState?.activeFrameIdByTarget.get(targetId) ?? null);
542
- await sessionCommand(targetId, "Page.enable");
543
- await sessionCommand(targetId, "Runtime.enable");
544
- await sessionCommand(targetId, "DOM.enable");
545
- await sessionCommand(targetId, "Accessibility.enable");
546
- return result.sessionId;
547
- }
548
- async function getTargets() {
549
- const state = connectionState;
550
- if (!state) throw new Error("CDP connection not initialized");
551
- try {
552
- const result = await browserCommand("Target.getTargets");
553
- return (result.targetInfos || []).map((target) => ({
554
- id: target.targetId,
555
- type: target.type,
556
- title: target.title,
557
- url: target.url,
558
- webSocketDebuggerUrl: ""
559
- }));
560
- } catch {
561
- return getJsonList(state.host, state.port);
562
- }
563
- }
564
- async function ensurePageTarget(targetId) {
565
- const targets = (await getTargets()).filter((target2) => target2.type === "page");
566
- if (targets.length === 0) throw new Error("No page target found");
567
- const persistedTargetId = targetId === void 0 ? connectionState?.currentTargetId : void 0;
568
- let target;
569
- if (typeof targetId === "number") {
570
- target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
571
- } else if (typeof targetId === "string") {
572
- target = targets.find((item) => item.id === targetId);
573
- if (!target) {
574
- const numericTargetId = Number(targetId);
575
- if (!Number.isNaN(numericTargetId)) {
576
- target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
577
- }
578
- }
579
- } else if (persistedTargetId) {
580
- target = targets.find((item) => item.id === persistedTargetId);
581
- }
582
- target ??= targets[0];
583
- setCurrentTargetId(target.id);
584
- await attachTarget(target.id);
585
- return target;
586
- }
587
- async function resolveBackendNodeIdByXPath(targetId, xpath) {
588
- await sessionCommand(targetId, "DOM.getDocument", { depth: 0 });
589
- const search = await sessionCommand(targetId, "DOM.performSearch", {
590
- query: xpath,
591
- includeUserAgentShadowDOM: true
592
- });
593
- try {
594
- if (!search.resultCount) {
595
- throw new Error(`Unknown ref xpath: ${xpath}`);
596
- }
597
- const { nodeIds } = await sessionCommand(targetId, "DOM.getSearchResults", {
598
- searchId: search.searchId,
599
- fromIndex: 0,
600
- toIndex: search.resultCount
601
- });
602
- for (const nodeId of nodeIds) {
603
- const described = await sessionCommand(targetId, "DOM.describeNode", {
604
- nodeId
605
- });
606
- if (described.node.backendNodeId) {
607
- return described.node.backendNodeId;
608
- }
609
- }
610
- throw new Error(`XPath resolved but no backend node id found: ${xpath}`);
611
- } finally {
612
- await sessionCommand(targetId, "DOM.discardSearchResults", { searchId: search.searchId }).catch(() => {
613
- });
614
- }
615
- }
616
- async function parseRef(ref) {
617
- const targetId = connectionState?.currentTargetId ?? "";
618
- let refs = connectionState?.refsByTarget.get(targetId) ?? {};
619
- if (!refs[ref] && targetId) {
620
- const persistedRefs = loadPersistedRefs(targetId);
621
- if (persistedRefs) {
622
- connectionState?.refsByTarget.set(targetId, persistedRefs);
623
- refs = persistedRefs;
624
- }
625
- }
626
- const found = refs[ref];
627
- if (!found) {
628
- throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
629
- }
630
- if (found.backendDOMNodeId) {
631
- return found.backendDOMNodeId;
632
- }
633
- if (targetId && found.xpath) {
634
- const backendDOMNodeId = await resolveBackendNodeIdByXPath(targetId, found.xpath);
635
- found.backendDOMNodeId = backendDOMNodeId;
636
- connectionState?.refsByTarget.set(targetId, refs);
637
- const pageUrl = await evaluate(targetId, "location.href", true).catch(() => void 0);
638
- if (pageUrl) {
639
- persistRefs(targetId, pageUrl, refs);
640
- }
641
- return backendDOMNodeId;
642
- }
643
- throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
644
- }
645
- function getRefsFilePath(targetId) {
646
- return path2.join(os2.tmpdir(), `bb-browser-refs-${targetId}.json`);
647
- }
648
- function loadPersistedRefs(targetId, expectedUrl) {
649
- try {
650
- const data = JSON.parse(readFileSync(getRefsFilePath(targetId), "utf-8"));
651
- if (data.targetId !== targetId) return null;
652
- if (expectedUrl !== void 0 && data.url !== expectedUrl) return null;
653
- if (!data.refs || typeof data.refs !== "object") return null;
654
- return data.refs;
655
- } catch {
656
- return null;
657
- }
658
- }
659
- function persistRefs(targetId, url, refs) {
660
- try {
661
- writeFileSync(getRefsFilePath(targetId), JSON.stringify({ targetId, url, timestamp: Date.now(), refs }));
662
- } catch {
663
- }
664
- }
665
- function clearPersistedRefs(targetId) {
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;
252
+ var daemonReady = false;
253
+ function isProcessAlive(pid) {
666
254
  try {
667
- unlinkSync(getRefsFilePath(targetId));
255
+ process.kill(pid, 0);
256
+ return true;
668
257
  } catch {
258
+ return false;
669
259
  }
670
260
  }
671
- function loadBuildDomTreeScript() {
672
- const currentDir = path2.dirname(fileURLToPath(import.meta.url));
673
- const candidates = [
674
- path2.resolve(currentDir, "./extension/buildDomTree.js"),
675
- // npm installed: dist/cli.js → ../extension/buildDomTree.js
676
- path2.resolve(currentDir, "../extension/buildDomTree.js"),
677
- path2.resolve(currentDir, "../extension/dist/buildDomTree.js"),
678
- path2.resolve(currentDir, "../packages/extension/public/buildDomTree.js"),
679
- path2.resolve(currentDir, "../packages/extension/dist/buildDomTree.js"),
680
- // dev mode: packages/cli/dist/ → ../../../extension/
681
- path2.resolve(currentDir, "../../../extension/buildDomTree.js"),
682
- path2.resolve(currentDir, "../../../extension/dist/buildDomTree.js"),
683
- // dev mode: packages/cli/src/ → ../../extension/
684
- path2.resolve(currentDir, "../../extension/buildDomTree.js"),
685
- path2.resolve(currentDir, "../../../packages/extension/dist/buildDomTree.js"),
686
- path2.resolve(currentDir, "../../../packages/extension/public/buildDomTree.js")
687
- ];
688
- for (const candidate of candidates) {
689
- try {
690
- return readFileSync(candidate, "utf8");
691
- } catch {
692
- }
693
- }
694
- throw new Error("Cannot find buildDomTree.js");
695
- }
696
- async function evaluate(targetId, expression, returnByValue = true) {
697
- const result = await sessionCommand(targetId, "Runtime.evaluate", {
698
- expression,
699
- awaitPromise: true,
700
- returnByValue
701
- });
702
- if (result.exceptionDetails) {
703
- throw new Error(
704
- result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Runtime.evaluate failed"
705
- );
706
- }
707
- return result.result.value ?? result.result;
708
- }
709
- async function focusNode(targetId, backendNodeId) {
710
- await sessionCommand(targetId, "DOM.focus", { backendNodeId });
711
- }
712
- async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
713
- const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
714
- await sessionCommand(targetId, "Runtime.callFunctionOn", {
715
- objectId: resolved.object.objectId,
716
- functionDeclaration: `function(clearFirst) {
717
- if (typeof this.scrollIntoView === 'function') {
718
- this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
719
- }
720
- if (typeof this.focus === 'function') this.focus();
721
- if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement) {
722
- if (clearFirst) {
723
- this.value = '';
724
- this.dispatchEvent(new Event('input', { bubbles: true }));
725
- }
726
- if (typeof this.setSelectionRange === 'function') {
727
- const end = this.value.length;
728
- this.setSelectionRange(end, end);
729
- }
730
- return true;
731
- }
732
- if (this instanceof HTMLElement && this.isContentEditable) {
733
- if (clearFirst) {
734
- this.textContent = '';
735
- this.dispatchEvent(new Event('input', { bubbles: true }));
736
- }
737
- const selection = window.getSelection();
738
- if (selection) {
739
- const range = document.createRange();
740
- range.selectNodeContents(this);
741
- range.collapse(false);
742
- selection.removeAllRanges();
743
- selection.addRange(range);
744
- }
745
- return true;
746
- }
747
- return false;
748
- }`,
749
- arguments: [
750
- { value: clearFirst }
751
- ],
752
- returnByValue: true
753
- });
754
- if (text) {
755
- await focusNode(targetId, backendNodeId);
756
- await sessionCommand(targetId, "Input.insertText", { text });
757
- }
758
- }
759
- async function getInteractablePoint(targetId, backendNodeId) {
760
- const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
761
- const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
762
- objectId: resolved.object.objectId,
763
- functionDeclaration: `function() {
764
- if (!(this instanceof Element)) {
765
- throw new Error('Ref does not resolve to an element');
766
- }
767
- this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
768
- const rect = this.getBoundingClientRect();
769
- if (!rect || rect.width <= 0 || rect.height <= 0) {
770
- throw new Error('Element is not visible');
771
- }
772
- return {
773
- x: rect.left + rect.width / 2,
774
- y: rect.top + rect.height / 2,
775
- };
776
- }`,
777
- returnByValue: true
778
- });
779
- if (call.exceptionDetails) {
780
- throw new Error(call.exceptionDetails.text || "Failed to resolve element point");
781
- }
782
- const point = call.result.value;
783
- if (!point || typeof point.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
784
- throw new Error("Failed to resolve element point");
785
- }
786
- return point;
787
- }
788
- async function mouseClick(targetId, x, y) {
789
- await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
790
- await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
791
- await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
792
- }
793
- async function getAttributeValue(targetId, backendNodeId, attribute) {
794
- if (attribute === "text") {
795
- const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
796
- const call2 = await sessionCommand(targetId, "Runtime.callFunctionOn", {
797
- objectId: resolved.object.objectId,
798
- functionDeclaration: `function() { return (this instanceof HTMLElement ? this.innerText : this.textContent || '').trim(); }`,
799
- returnByValue: true
800
- });
801
- return String(call2.result.value ?? "");
802
- }
803
- const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
804
- const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
805
- objectId: result.object.objectId,
806
- functionDeclaration: `function() { if (${JSON.stringify(attribute)} === 'url') return this.href || this.src || location.href; if (${JSON.stringify(attribute)} === 'title') return document.title; return this.getAttribute(${JSON.stringify(attribute)}) || ''; }`,
807
- returnByValue: true
808
- });
809
- return String(call.result.value ?? "");
810
- }
811
- async function buildSnapshot(targetId, request) {
812
- const script = loadBuildDomTreeScript();
813
- const buildArgs = {
814
- showHighlightElements: true,
815
- focusHighlightIndex: -1,
816
- viewportExpansion: -1,
817
- debugMode: false,
818
- startId: 0,
819
- startHighlightIndex: 0
820
- };
821
- const expression = `(() => { ${script}; const fn = globalThis.buildDomTree ?? (typeof window !== 'undefined' ? window.buildDomTree : undefined); if (typeof fn !== 'function') { throw new Error('buildDomTree is not available after script injection'); } return fn(${JSON.stringify({
822
- ...buildArgs
823
- })}); })()`;
824
- const value = await evaluate(targetId, expression, true);
825
- if (!value || !value.map || !value.rootId) {
826
- const title = await evaluate(targetId, "document.title", true);
827
- const pageUrl2 = await evaluate(targetId, "location.href", true);
828
- const fallbackSnapshot = {
829
- title,
830
- url: pageUrl2,
831
- lines: [title || pageUrl2],
832
- refs: {}
833
- };
834
- connectionState?.refsByTarget.set(targetId, {});
835
- persistRefs(targetId, pageUrl2, {});
836
- return fallbackSnapshot;
837
- }
838
- const snapshot = convertBuildDomTreeResult(value, {
839
- interactiveOnly: !!request.interactive,
840
- compact: !!request.compact,
841
- maxDepth: request.maxDepth,
842
- selector: request.selector
843
- });
844
- const pageUrl = await evaluate(targetId, "location.href", true);
845
- connectionState?.refsByTarget.set(targetId, snapshot.refs || {});
846
- persistRefs(targetId, pageUrl, snapshot.refs || {});
847
- return snapshot;
848
- }
849
- function convertBuildDomTreeResult(result, options) {
850
- const { interactiveOnly, compact, maxDepth, selector } = options;
851
- const { rootId, map } = result;
852
- const refs = {};
853
- const lines = [];
854
- const getRole = (node) => {
855
- const tagName = node.tagName.toLowerCase();
856
- const role = node.attributes?.role;
857
- if (role) return role;
858
- const type = node.attributes?.type?.toLowerCase() || "text";
859
- const inputRoleMap = {
860
- text: "textbox",
861
- password: "textbox",
862
- email: "textbox",
863
- url: "textbox",
864
- tel: "textbox",
865
- search: "searchbox",
866
- number: "spinbutton",
867
- range: "slider",
868
- checkbox: "checkbox",
869
- radio: "radio",
870
- button: "button",
871
- submit: "button",
872
- reset: "button",
873
- file: "button"
874
- };
875
- const roleMap = {
876
- a: "link",
877
- button: "button",
878
- input: inputRoleMap[type] || "textbox",
879
- select: "combobox",
880
- textarea: "textbox",
881
- img: "image",
882
- nav: "navigation",
883
- main: "main",
884
- header: "banner",
885
- footer: "contentinfo",
886
- aside: "complementary",
887
- form: "form",
888
- table: "table",
889
- ul: "list",
890
- ol: "list",
891
- li: "listitem",
892
- h1: "heading",
893
- h2: "heading",
894
- h3: "heading",
895
- h4: "heading",
896
- h5: "heading",
897
- h6: "heading",
898
- dialog: "dialog",
899
- article: "article",
900
- section: "region",
901
- label: "label",
902
- details: "group",
903
- summary: "button"
904
- };
905
- return roleMap[tagName] || tagName;
906
- };
907
- const collectTextContent = (node, nodeMap, depthLimit = 5) => {
908
- const texts = [];
909
- const visit = (nodeId, depth) => {
910
- if (depth > depthLimit) return;
911
- const currentNode = nodeMap[nodeId];
912
- if (!currentNode) return;
913
- if ("type" in currentNode && currentNode.type === "TEXT_NODE") {
914
- const text = currentNode.text.trim();
915
- if (text) texts.push(text);
916
- return;
917
- }
918
- for (const childId of currentNode.children || []) visit(childId, depth + 1);
919
- };
920
- for (const childId of node.children || []) visit(childId, 0);
921
- return texts.join(" ").trim();
922
- };
923
- const getName = (node) => {
924
- const attrs = node.attributes || {};
925
- return attrs["aria-label"] || attrs.title || attrs.placeholder || attrs.alt || attrs.value || collectTextContent(node, map) || attrs.name || void 0;
926
- };
927
- const truncateText = (text, length = 50) => text.length <= length ? text : `${text.slice(0, length - 3)}...`;
928
- const selectorText = selector?.trim().toLowerCase();
929
- const matchesSelector = (node, role, name) => {
930
- if (!selectorText) return true;
931
- const haystack = [node.tagName, role, name, node.xpath || "", ...Object.values(node.attributes || {})].join(" ").toLowerCase();
932
- return haystack.includes(selectorText);
933
- };
934
- if (interactiveOnly) {
935
- const interactiveNodes = Object.entries(map).filter(([, node]) => !("type" in node) && node.highlightIndex !== void 0 && node.highlightIndex !== null).map(([id, node]) => ({ id, node })).sort((a, b) => (a.node.highlightIndex ?? 0) - (b.node.highlightIndex ?? 0));
936
- for (const { node } of interactiveNodes) {
937
- const refId = String(node.highlightIndex);
938
- const role = getRole(node);
939
- const name = getName(node);
940
- if (!matchesSelector(node, role, name)) continue;
941
- let line = `${role} [ref=${refId}]`;
942
- if (name) line += ` ${JSON.stringify(truncateText(name))}`;
943
- lines.push(line);
944
- refs[refId] = {
945
- xpath: node.xpath || "",
946
- role,
947
- name,
948
- tagName: node.tagName.toLowerCase()
949
- };
950
- }
951
- return { snapshot: lines.join("\n"), refs };
952
- }
953
- const walk = (nodeId, depth) => {
954
- if (maxDepth !== void 0 && depth > maxDepth) return;
955
- const node = map[nodeId];
956
- if (!node) return;
957
- if ("type" in node && node.type === "TEXT_NODE") {
958
- const text = node.text.trim();
959
- if (!text) return;
960
- lines.push(`${" ".repeat(depth)}- text ${JSON.stringify(truncateText(text, compact ? 80 : 120))}`);
961
- return;
962
- }
963
- const role = getRole(node);
964
- const name = getName(node);
965
- if (!matchesSelector(node, role, name)) {
966
- for (const childId of node.children || []) walk(childId, depth + 1);
967
- return;
968
- }
969
- const indent = " ".repeat(depth);
970
- const refId = node.highlightIndex !== void 0 && node.highlightIndex !== null ? String(node.highlightIndex) : null;
971
- let line = `${indent}- ${role}`;
972
- if (refId) line += ` [ref=${refId}]`;
973
- if (name) line += ` ${JSON.stringify(truncateText(name, compact ? 50 : 80))}`;
974
- if (!compact) line += ` <${node.tagName.toLowerCase()}>`;
975
- lines.push(line);
976
- if (refId) {
977
- refs[refId] = {
978
- xpath: node.xpath || "",
979
- role,
980
- name,
981
- tagName: node.tagName.toLowerCase()
982
- };
983
- }
984
- for (const childId of node.children || []) walk(childId, depth + 1);
985
- };
986
- walk(rootId, 0);
987
- return { snapshot: lines.join("\n"), refs };
988
- }
989
- function ok(id, data) {
990
- return { id, success: true, data };
991
- }
992
- function fail(id, error) {
993
- return { id, success: false, error: buildRequestError(error).message };
994
- }
995
- async function ensureCdpConnection() {
996
- if (connectionState) return;
997
- if (reconnecting) return reconnecting;
998
- reconnecting = (async () => {
999
- const discovered = await discoverCdpPort();
1000
- if (!discovered) {
1001
- throw new Error("No browser connection found");
1002
- }
1003
- const version = await getJsonVersion(discovered.host, discovered.port);
1004
- const wsUrl = version.webSocketDebuggerUrl;
1005
- const socket = await connectWebSocket(wsUrl);
1006
- connectionState = createState(discovered.host, discovered.port, wsUrl, socket);
1007
- })();
1008
- try {
1009
- await reconnecting;
1010
- } finally {
1011
- reconnecting = null;
1012
- }
1013
- }
1014
- async function sendCommand(request) {
1015
- try {
1016
- await ensureCdpConnection();
1017
- const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("\u8BF7\u6C42\u8D85\u65F6")), COMMAND_TIMEOUT));
1018
- return await Promise.race([dispatchRequest(request), timeout]);
1019
- } catch (error) {
1020
- return fail(request.id, error);
1021
- }
1022
- }
1023
- async function dispatchRequest(request) {
1024
- const target = await ensurePageTarget(request.tabId);
1025
- switch (request.action) {
1026
- case "open": {
1027
- if (!request.url) return fail(request.id, "Missing url parameter");
1028
- if (request.tabId === void 0) {
1029
- const created = await browserCommand("Target.createTarget", { url: request.url, background: true });
1030
- const newTarget = await ensurePageTarget(created.targetId);
1031
- return ok(request.id, { url: request.url, tabId: newTarget.id });
1032
- }
1033
- await pageCommand(target.id, "Page.navigate", { url: request.url });
1034
- connectionState?.refsByTarget.delete(target.id);
1035
- clearPersistedRefs(target.id);
1036
- return ok(request.id, { url: request.url, title: target.title, tabId: target.id });
1037
- }
1038
- case "snapshot": {
1039
- const snapshotData = await buildSnapshot(target.id, request);
1040
- return ok(request.id, { title: target.title, url: target.url, snapshotData });
1041
- }
1042
- case "click":
1043
- case "hover": {
1044
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1045
- const backendNodeId = await parseRef(request.ref);
1046
- const point = await getInteractablePoint(target.id, backendNodeId);
1047
- await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
1048
- if (request.action === "click") await mouseClick(target.id, point.x, point.y);
1049
- return ok(request.id, {});
1050
- }
1051
- case "fill":
1052
- case "type": {
1053
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1054
- if (request.text == null) return fail(request.id, "Missing text parameter");
1055
- const backendNodeId = await parseRef(request.ref);
1056
- await insertTextIntoNode(target.id, backendNodeId, request.text, request.action === "fill");
1057
- return ok(request.id, { value: request.text });
1058
- }
1059
- case "check":
1060
- case "uncheck": {
1061
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1062
- const backendNodeId = await parseRef(request.ref);
1063
- const desired = request.action === "check";
1064
- const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
1065
- await sessionCommand(target.id, "Runtime.callFunctionOn", {
1066
- objectId: resolved.object.objectId,
1067
- functionDeclaration: `function() { this.checked = ${desired}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
1068
- });
1069
- return ok(request.id, {});
1070
- }
1071
- case "select": {
1072
- if (!request.ref || request.value == null) return fail(request.id, "Missing ref or value parameter");
1073
- const backendNodeId = await parseRef(request.ref);
1074
- const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
1075
- await sessionCommand(target.id, "Runtime.callFunctionOn", {
1076
- objectId: resolved.object.objectId,
1077
- functionDeclaration: `function() { this.value = ${JSON.stringify(request.value)}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
1078
- });
1079
- return ok(request.id, { value: request.value });
1080
- }
1081
- case "get": {
1082
- if (!request.attribute) return fail(request.id, "Missing attribute parameter");
1083
- if (request.attribute === "url" && !request.ref) {
1084
- return ok(request.id, { value: await evaluate(target.id, "location.href", true) });
1085
- }
1086
- if (request.attribute === "title" && !request.ref) {
1087
- return ok(request.id, { value: await evaluate(target.id, "document.title", true) });
1088
- }
1089
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1090
- const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
1091
- return ok(request.id, { value });
1092
- }
1093
- case "screenshot": {
1094
- const result = await sessionCommand(target.id, "Page.captureScreenshot", { format: "png", fromSurface: true });
1095
- return ok(request.id, { dataUrl: `data:image/png;base64,${result.data}` });
1096
- }
1097
- case "close": {
1098
- await browserCommand("Target.closeTarget", { targetId: target.id });
1099
- connectionState?.refsByTarget.delete(target.id);
1100
- clearPersistedRefs(target.id);
1101
- return ok(request.id, {});
1102
- }
1103
- case "wait": {
1104
- await new Promise((resolve3) => setTimeout(resolve3, request.ms ?? 1e3));
1105
- return ok(request.id, {});
1106
- }
1107
- case "press": {
1108
- if (!request.key) return fail(request.id, "Missing key parameter");
1109
- await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyDown", key: request.key });
1110
- if (request.key.length === 1) {
1111
- await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "char", text: request.key, key: request.key });
1112
- }
1113
- await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyUp", key: request.key });
1114
- return ok(request.id, {});
1115
- }
1116
- case "scroll": {
1117
- const deltaY = request.direction === "up" ? -(request.pixels ?? 300) : request.pixels ?? 300;
1118
- await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseWheel", x: 0, y: 0, deltaX: 0, deltaY });
1119
- return ok(request.id, {});
1120
- }
1121
- case "back": {
1122
- await evaluate(target.id, "history.back(); undefined");
1123
- return ok(request.id, {});
1124
- }
1125
- case "forward": {
1126
- await evaluate(target.id, "history.forward(); undefined");
1127
- return ok(request.id, {});
1128
- }
1129
- case "refresh": {
1130
- await sessionCommand(target.id, "Page.reload", { ignoreCache: false });
1131
- return ok(request.id, {});
1132
- }
1133
- case "eval": {
1134
- if (!request.script) return fail(request.id, "Missing script parameter");
1135
- const result = await evaluate(target.id, request.script, true);
1136
- return ok(request.id, { result });
1137
- }
1138
- case "tab_list": {
1139
- const tabs = (await getTargets()).filter((item) => item.type === "page").map((item, index) => ({ index, url: item.url, title: item.title, active: item.id === connectionState?.currentTargetId || !connectionState?.currentTargetId && index === 0, tabId: item.id }));
1140
- return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
1141
- }
1142
- case "tab_new": {
1143
- const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank", background: true });
1144
- return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
1145
- }
1146
- case "tab_select": {
1147
- const tabs = (await getTargets()).filter((item) => item.type === "page");
1148
- const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
1149
- if (!selected) return fail(request.id, "Tab not found");
1150
- setCurrentTargetId(selected.id);
1151
- await attachTarget(selected.id);
1152
- return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
1153
- }
1154
- case "tab_close": {
1155
- const tabs = (await getTargets()).filter((item) => item.type === "page");
1156
- const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
1157
- if (!selected) return fail(request.id, "Tab not found");
1158
- await browserCommand("Target.closeTarget", { targetId: selected.id });
1159
- connectionState?.refsByTarget.delete(selected.id);
1160
- if (connectionState?.currentTargetId === selected.id) {
1161
- setCurrentTargetId(void 0);
1162
- }
1163
- clearPersistedRefs(selected.id);
1164
- return ok(request.id, { tabId: selected.id });
1165
- }
1166
- case "frame": {
1167
- if (!request.selector) return fail(request.id, "Missing selector parameter");
1168
- const document = await pageCommand(target.id, "DOM.getDocument", {});
1169
- const node = await pageCommand(target.id, "DOM.querySelector", { nodeId: document.root.nodeId, selector: request.selector });
1170
- if (!node.nodeId) return fail(request.id, `\u627E\u4E0D\u5230 iframe: ${request.selector}`);
1171
- const described = await pageCommand(target.id, "DOM.describeNode", { nodeId: node.nodeId });
1172
- const frameId = described.node.frameId;
1173
- const nodeName = String(described.node.nodeName ?? "").toLowerCase();
1174
- if (!frameId) return fail(request.id, `\u65E0\u6CD5\u83B7\u53D6 iframe frameId: ${request.selector}`);
1175
- if (nodeName && nodeName !== "iframe" && nodeName !== "frame") return fail(request.id, `\u5143\u7D20\u4E0D\u662F iframe: ${nodeName}`);
1176
- connectionState?.activeFrameIdByTarget.set(target.id, frameId);
1177
- const attributes = described.node.attributes ?? [];
1178
- const attrMap = {};
1179
- for (let i = 0; i < attributes.length; i += 2) attrMap[String(attributes[i])] = String(attributes[i + 1] ?? "");
1180
- return ok(request.id, { frameInfo: { selector: request.selector, name: attrMap.name ?? "", url: attrMap.src ?? "", frameId } });
1181
- }
1182
- case "frame_main": {
1183
- connectionState?.activeFrameIdByTarget.set(target.id, null);
1184
- return ok(request.id, { frameInfo: { frameId: 0 } });
1185
- }
1186
- case "dialog": {
1187
- connectionState?.dialogHandlers.set(target.id, { accept: request.dialogResponse !== "dismiss", ...request.promptText !== void 0 ? { promptText: request.promptText } : {} });
1188
- await sessionCommand(target.id, "Page.enable");
1189
- return ok(request.id, { dialog: { armed: true, response: request.dialogResponse ?? "accept" } });
1190
- }
1191
- case "network": {
1192
- const subCommand = request.networkCommand ?? "requests";
1193
- switch (subCommand) {
1194
- case "requests": {
1195
- await ensureNetworkMonitoring(target.id);
1196
- const requests = Array.from(networkRequests.values()).filter((item) => !request.filter || item.url.includes(request.filter));
1197
- if (request.withBody) {
1198
- await Promise.all(requests.map(async (item) => {
1199
- if (item.failed || item.responseBody !== void 0 || item.bodyError !== void 0) return;
1200
- try {
1201
- const body = await sessionCommand(target.id, "Network.getResponseBody", { requestId: item.requestId });
1202
- item.responseBody = body.body;
1203
- item.responseBodyBase64 = body.base64Encoded;
1204
- } catch (error) {
1205
- item.bodyError = error instanceof Error ? error.message : String(error);
1206
- }
1207
- }));
1208
- }
1209
- return ok(request.id, { networkRequests: requests });
1210
- }
1211
- case "route":
1212
- return ok(request.id, { routeCount: 0 });
1213
- case "unroute":
1214
- return ok(request.id, { routeCount: 0 });
1215
- case "clear":
1216
- networkRequests.clear();
1217
- return ok(request.id, {});
1218
- default:
1219
- return fail(request.id, `Unknown network subcommand: ${subCommand}`);
1220
- }
1221
- }
1222
- case "console": {
1223
- const subCommand = request.consoleCommand ?? "get";
1224
- await ensureConsoleMonitoring(target.id);
1225
- switch (subCommand) {
1226
- case "get":
1227
- return ok(request.id, { consoleMessages: consoleMessages.filter((item) => !request.filter || item.text.includes(request.filter)) });
1228
- case "clear":
1229
- consoleMessages.length = 0;
1230
- return ok(request.id, {});
1231
- default:
1232
- return fail(request.id, `Unknown console subcommand: ${subCommand}`);
1233
- }
1234
- }
1235
- case "errors": {
1236
- const subCommand = request.errorsCommand ?? "get";
1237
- await ensureConsoleMonitoring(target.id);
1238
- switch (subCommand) {
1239
- case "get":
1240
- return ok(request.id, { jsErrors: jsErrors.filter((item) => !request.filter || item.message.includes(request.filter) || item.url?.includes(request.filter)) });
1241
- case "clear":
1242
- jsErrors.length = 0;
1243
- return ok(request.id, {});
1244
- default:
1245
- return fail(request.id, `Unknown errors subcommand: ${subCommand}`);
1246
- }
1247
- }
1248
- case "trace": {
1249
- const subCommand = request.traceCommand ?? "status";
1250
- switch (subCommand) {
1251
- case "start":
1252
- traceRecording = true;
1253
- traceEvents.length = 0;
1254
- return ok(request.id, { traceStatus: { recording: true, eventCount: 0 } });
1255
- case "stop": {
1256
- traceRecording = false;
1257
- return ok(request.id, { traceEvents: [...traceEvents], traceStatus: { recording: false, eventCount: traceEvents.length } });
1258
- }
1259
- case "status":
1260
- return ok(request.id, { traceStatus: { recording: traceRecording, eventCount: traceEvents.length } });
1261
- default:
1262
- return fail(request.id, `Unknown trace subcommand: ${subCommand}`);
1263
- }
1264
- }
1265
- default:
1266
- return fail(request.id, `Action not yet supported in direct CDP mode: ${request.action}`);
1267
- }
1268
- }
1269
-
1270
- // packages/cli/src/monitor-manager.ts
1271
- import { spawn as spawn2 } from "child_process";
1272
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, unlink } from "fs/promises";
1273
- import { request as httpRequest2 } from "http";
1274
- import { randomBytes } from "crypto";
1275
- import { fileURLToPath as fileURLToPath2 } from "url";
1276
- import { dirname, resolve } from "path";
1277
- import { existsSync as existsSync2 } from "fs";
1278
- import os3 from "os";
1279
- import path3 from "path";
1280
- var MONITOR_DIR = path3.join(os3.homedir(), ".bb-browser");
1281
- var PID_FILE = path3.join(MONITOR_DIR, "monitor.pid");
1282
- var PORT_FILE = path3.join(MONITOR_DIR, "monitor.port");
1283
- var TOKEN_FILE = path3.join(MONITOR_DIR, "monitor.token");
1284
- var DEFAULT_MONITOR_PORT = 19826;
1285
- function httpJson(method, url, token, body) {
1286
- return new Promise((resolve3, reject) => {
1287
- const parsed = new URL(url);
261
+ function httpJson(method, urlPath, info, body, timeout = 5e3) {
262
+ return new Promise((resolve2, reject) => {
1288
263
  const payload = body !== void 0 ? JSON.stringify(body) : void 0;
1289
- const req = httpRequest2(
264
+ const req = httpRequest(
1290
265
  {
1291
- hostname: parsed.hostname,
1292
- port: parsed.port,
1293
- path: parsed.pathname,
266
+ hostname: info.host,
267
+ port: info.port,
268
+ path: urlPath,
1294
269
  method,
1295
270
  headers: {
1296
- Authorization: `Bearer ${token}`,
1297
- ...payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}
271
+ Authorization: `Bearer ${info.token}`,
272
+ ...payload ? {
273
+ "Content-Type": "application/json",
274
+ "Content-Length": Buffer.byteLength(payload)
275
+ } : {}
1298
276
  },
1299
- timeout: 5e3
277
+ timeout
1300
278
  },
1301
279
  (res) => {
1302
280
  const chunks = [];
@@ -1304,13 +282,13 @@ function httpJson(method, url, token, body) {
1304
282
  res.on("end", () => {
1305
283
  const raw = Buffer.concat(chunks).toString("utf8");
1306
284
  if ((res.statusCode ?? 500) >= 400) {
1307
- reject(new Error(`Monitor HTTP ${res.statusCode}: ${raw}`));
285
+ reject(new Error(`Daemon HTTP ${res.statusCode}: ${raw}`));
1308
286
  return;
1309
287
  }
1310
288
  try {
1311
- resolve3(JSON.parse(raw));
289
+ resolve2(JSON.parse(raw));
1312
290
  } catch {
1313
- reject(new Error(`Invalid JSON from monitor: ${raw}`));
291
+ reject(new Error(`Invalid JSON from daemon: ${raw}`));
1314
292
  }
1315
293
  });
1316
294
  }
@@ -1318,116 +296,130 @@ function httpJson(method, url, token, body) {
1318
296
  req.on("error", reject);
1319
297
  req.on("timeout", () => {
1320
298
  req.destroy();
1321
- reject(new Error("Monitor request timed out"));
299
+ reject(new Error("Daemon request timed out"));
1322
300
  });
1323
301
  if (payload) req.write(payload);
1324
302
  req.end();
1325
303
  });
1326
304
  }
1327
- async function readPortFile() {
305
+ async function readDaemonJson() {
1328
306
  try {
1329
- const raw = await readFile2(PORT_FILE, "utf8");
1330
- const port = Number.parseInt(raw.trim(), 10);
1331
- return Number.isInteger(port) && port > 0 ? port : null;
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;
1332
313
  } catch {
1333
314
  return null;
1334
315
  }
1335
316
  }
1336
- async function readTokenFile() {
317
+ async function deleteDaemonJson() {
1337
318
  try {
1338
- return (await readFile2(TOKEN_FILE, "utf8")).trim();
319
+ await unlink(DAEMON_JSON);
1339
320
  } catch {
1340
- return null;
1341
321
  }
1342
322
  }
1343
- async function ensureMonitorRunning() {
1344
- const existingPort = await readPortFile();
1345
- const existingToken = await readTokenFile();
1346
- if (existingPort && existingToken) {
323
+ function getDaemonPath() {
324
+ const currentFile = fileURLToPath(import.meta.url);
325
+ const currentDir = dirname(currentFile);
326
+ const sameDirPath = resolve(currentDir, "daemon.js");
327
+ if (existsSync2(sameDirPath)) {
328
+ return sameDirPath;
329
+ }
330
+ return resolve(currentDir, "../../daemon/dist/index.js");
331
+ }
332
+ async function ensureDaemon() {
333
+ if (daemonReady && cachedInfo) {
1347
334
  try {
1348
- const status = await httpJson(
1349
- "GET",
1350
- `http://127.0.0.1:${existingPort}/status`,
1351
- existingToken
1352
- );
1353
- if (status.running) {
1354
- return { port: existingPort, token: existingToken };
1355
- }
335
+ await httpJson("GET", "/status", cachedInfo, void 0, 2e3);
336
+ return;
1356
337
  } catch {
338
+ daemonReady = false;
339
+ cachedInfo = null;
1357
340
  }
1358
341
  }
1359
- const cdp = await discoverCdpPort();
1360
- if (!cdp) {
1361
- throw new Error("Cannot start monitor: no browser connection found");
1362
- }
1363
- const token = randomBytes(32).toString("hex");
1364
- const monitorPort = DEFAULT_MONITOR_PORT;
1365
- const monitorScript = findMonitorScript();
1366
- await mkdir2(MONITOR_DIR, { recursive: true });
1367
- await writeFile2(TOKEN_FILE, token, { mode: 384 });
1368
- const child = spawn2(process.execPath, [
1369
- monitorScript,
1370
- "--cdp-host",
1371
- cdp.host,
1372
- "--cdp-port",
1373
- String(cdp.port),
1374
- "--monitor-port",
1375
- String(monitorPort),
1376
- "--token",
1377
- token
1378
- ], {
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 {
356
+ }
357
+ }
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
+ }
365
+ const daemonPath = getDaemonPath();
366
+ const child = spawn2(process.execPath, [daemonPath, "--cdp-host", cdpInfo.host, "--cdp-port", String(cdpInfo.port)], {
1379
367
  detached: true,
1380
368
  stdio: "ignore"
1381
369
  });
1382
370
  child.unref();
1383
- const deadline = Date.now() + 5e3;
371
+ const deadline = Date.now() + 1e4;
1384
372
  while (Date.now() < deadline) {
1385
373
  await new Promise((r) => setTimeout(r, 200));
374
+ info = await readDaemonJson();
375
+ if (!info) continue;
1386
376
  try {
1387
- const status = await httpJson(
1388
- "GET",
1389
- `http://127.0.0.1:${monitorPort}/status`,
1390
- token
1391
- );
377
+ const status = await httpJson("GET", "/status", info, void 0, 2e3);
1392
378
  if (status.running) {
1393
- return { port: monitorPort, token };
379
+ cachedInfo = info;
380
+ daemonReady = true;
381
+ return;
1394
382
  }
1395
383
  } catch {
1396
384
  }
1397
385
  }
1398
- throw new Error("Monitor process did not start in time");
1399
- }
1400
- async function monitorCommand(request) {
1401
- const { port, token } = await ensureMonitorRunning();
1402
- return httpJson(
1403
- "POST",
1404
- `http://127.0.0.1:${port}/command`,
1405
- token,
1406
- request
386
+ throw new Error(
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"
1407
388
  );
1408
389
  }
1409
- function findMonitorScript() {
1410
- const currentFile = fileURLToPath2(import.meta.url);
1411
- const currentDir = dirname(currentFile);
1412
- const candidates = [
1413
- // Built output (tsup puts it next to cli.js)
1414
- resolve(currentDir, "cdp-monitor.js"),
1415
- // Development: packages/cli/src -> packages/cli/dist
1416
- resolve(currentDir, "../dist/cdp-monitor.js"),
1417
- // Monorepo root dist
1418
- resolve(currentDir, "../../dist/cdp-monitor.js"),
1419
- resolve(currentDir, "../../../dist/cdp-monitor.js")
1420
- ];
1421
- for (const candidate of candidates) {
1422
- if (existsSync2(candidate)) {
1423
- return candidate;
1424
- }
390
+ async function daemonCommand(request) {
391
+ if (!cachedInfo) {
392
+ cachedInfo = await readDaemonJson();
393
+ }
394
+ if (!cachedInfo) {
395
+ throw new Error("No daemon.json found. Is the daemon running?");
396
+ }
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;
409
+ }
410
+ }
411
+ async function getDaemonStatus() {
412
+ const info = cachedInfo ?? await readDaemonJson();
413
+ if (!info) return null;
414
+ try {
415
+ return await httpJson("GET", "/status", info, void 0, 2e3);
416
+ } catch {
417
+ return null;
1425
418
  }
1426
- return candidates[0];
1427
419
  }
420
+ var ensureDaemonRunning = ensureDaemon;
1428
421
 
1429
422
  // packages/cli/src/client.ts
1430
- var MONITOR_ACTIONS = /* @__PURE__ */ new Set(["network", "console", "errors", "trace"]);
1431
423
  var jqExpression;
1432
424
  function setJqExpression(expression) {
1433
425
  jqExpression = expression;
@@ -1445,42 +437,13 @@ function handleJqResponse(response) {
1445
437
  printJqResults(response);
1446
438
  }
1447
439
  }
1448
- async function sendCommand2(request) {
1449
- if (MONITOR_ACTIONS.has(request.action)) {
1450
- try {
1451
- return await monitorCommand(request);
1452
- } catch {
1453
- return sendCommand(request);
1454
- }
1455
- }
1456
- return sendCommand(request);
1457
- }
1458
-
1459
- // packages/cli/src/daemon-manager.ts
1460
- import { fileURLToPath as fileURLToPath3 } from "url";
1461
- import { dirname as dirname2, resolve as resolve2 } from "path";
1462
- import { existsSync as existsSync3 } from "fs";
1463
- async function isDaemonRunning() {
1464
- return await isManagedBrowserRunning();
1465
- }
1466
- async function ensureDaemonRunning() {
1467
- try {
1468
- await ensureCdpConnection();
1469
- } catch (error) {
1470
- if (error instanceof Error && error.message.includes("No browser connection found")) {
1471
- throw new Error([
1472
- "bb-browser: Could not start browser.",
1473
- "",
1474
- "Make sure Chrome is installed, then try again.",
1475
- "Or specify a CDP port manually: bb-browser --port 9222"
1476
- ].join("\n"));
1477
- }
1478
- throw error;
1479
- }
440
+ async function sendCommand(request) {
441
+ await ensureDaemon();
442
+ return daemonCommand(request);
1480
443
  }
1481
444
 
1482
445
  // packages/cli/src/history-sqlite.ts
1483
- import { copyFileSync, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "fs";
446
+ import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
1484
447
  import { execSync as execSync2 } from "child_process";
1485
448
  import { homedir, tmpdir } from "os";
1486
449
  import { join } from "path";
@@ -1504,7 +467,7 @@ function getHistoryPathCandidates() {
1504
467
  }
1505
468
  function findHistoryPath() {
1506
469
  for (const historyPath of getHistoryPathCandidates()) {
1507
- if (existsSync4(historyPath)) {
470
+ if (existsSync3(historyPath)) {
1508
471
  return historyPath;
1509
472
  }
1510
473
  }
@@ -1538,7 +501,7 @@ function runHistoryQuery(sql, mapRow) {
1538
501
  return [];
1539
502
  } finally {
1540
503
  try {
1541
- unlinkSync2(tmpPath);
504
+ unlinkSync(tmpPath);
1542
505
  } catch {
1543
506
  }
1544
507
  }
@@ -1624,7 +587,7 @@ function getHistoryDomains(days) {
1624
587
  }
1625
588
 
1626
589
  // packages/cli/src/commands/site.ts
1627
- import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync5, mkdirSync } from "fs";
590
+ import { readFileSync, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
1628
591
  import { join as join2, relative } from "path";
1629
592
  import { homedir as homedir2 } from "os";
1630
593
  import { execSync as execSync3 } from "child_process";
@@ -1650,7 +613,7 @@ function exitJsonError(error, extra = {}) {
1650
613
  function parseSiteMeta(filePath, source) {
1651
614
  let content;
1652
615
  try {
1653
- content = readFileSync2(filePath, "utf-8");
616
+ content = readFileSync(filePath, "utf-8");
1654
617
  } catch {
1655
618
  return null;
1656
619
  }
@@ -1710,7 +673,7 @@ function parseSiteMeta(filePath, source) {
1710
673
  return meta;
1711
674
  }
1712
675
  function scanSites(dir, source) {
1713
- if (!existsSync5(dir)) return [];
676
+ if (!existsSync4(dir)) return [];
1714
677
  const sites = [];
1715
678
  function walk(currentDir) {
1716
679
  let entries;
@@ -1832,7 +795,7 @@ function siteSearch(query, options) {
1832
795
  }
1833
796
  function siteUpdate(options = {}) {
1834
797
  mkdirSync(BB_DIR, { recursive: true });
1835
- const updateMode = existsSync5(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
798
+ const updateMode = existsSync4(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
1836
799
  if (updateMode === "pull") {
1837
800
  if (!options.json) {
1838
801
  console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
@@ -2068,12 +1031,12 @@ async function siteRun(name, args, options) {
2068
1031
  process.exit(1);
2069
1032
  }
2070
1033
  }
2071
- const jsContent = readFileSync2(site.filePath, "utf-8");
1034
+ const jsContent = readFileSync(site.filePath, "utf-8");
2072
1035
  const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
2073
1036
  const argsJson = JSON.stringify(argMap);
2074
1037
  const script = `(${jsBody})(${argsJson})`;
2075
1038
  if (options.openclaw) {
2076
- const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-7BW5M4YX.js");
1039
+ const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-4XJKWEQ3.js");
2077
1040
  let targetId;
2078
1041
  if (site.domain) {
2079
1042
  const tabs = ocGetTabs();
@@ -2082,7 +1045,7 @@ async function siteRun(name, args, options) {
2082
1045
  targetId = existing.targetId;
2083
1046
  } else {
2084
1047
  targetId = ocOpenTab(`https://${site.domain}`);
2085
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
1048
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2086
1049
  }
2087
1050
  } else {
2088
1051
  const tabs = ocGetTabs();
@@ -2128,7 +1091,7 @@ async function siteRun(name, args, options) {
2128
1091
  let targetTabId = options.tabId;
2129
1092
  if (!targetTabId && site.domain) {
2130
1093
  const listReq = { id: generateId(), action: "tab_list" };
2131
- const listResp = await sendCommand2(listReq);
1094
+ const listResp = await sendCommand(listReq);
2132
1095
  if (listResp.success && listResp.data?.tabs) {
2133
1096
  const matchingTab = listResp.data.tabs.find(
2134
1097
  (tab) => matchTabOrigin(tab.url, site.domain)
@@ -2138,17 +1101,17 @@ async function siteRun(name, args, options) {
2138
1101
  }
2139
1102
  }
2140
1103
  if (!targetTabId) {
2141
- const newResp = await sendCommand2({
1104
+ const newResp = await sendCommand({
2142
1105
  id: generateId(),
2143
1106
  action: "tab_new",
2144
1107
  url: `https://${site.domain}`
2145
1108
  });
2146
1109
  targetTabId = newResp.data?.tabId;
2147
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
1110
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2148
1111
  }
2149
1112
  }
2150
1113
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
2151
- const evalResp = await sendCommand2(evalReq);
1114
+ const evalResp = await sendCommand(evalReq);
2152
1115
  if (!evalResp.success) {
2153
1116
  const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
2154
1117
  if (options.json) {
@@ -2284,7 +1247,7 @@ async function siteCommand(args, options = {}) {
2284
1247
  }
2285
1248
  function silentUpdate() {
2286
1249
  const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
2287
- if (!existsSync5(gitDir)) return;
1250
+ if (!existsSync4(gitDir)) return;
2288
1251
  import("child_process").then(({ spawn: spawn3 }) => {
2289
1252
  const child = spawn3("git", ["pull", "--ff-only"], {
2290
1253
  cwd: COMMUNITY_SITES_DIR,
@@ -2322,7 +1285,7 @@ async function openCommand(url, options = {}) {
2322
1285
  request.tabId = tabId;
2323
1286
  }
2324
1287
  }
2325
- const response = await sendCommand2(request);
1288
+ const response = await sendCommand(request);
2326
1289
  if (options.json) {
2327
1290
  console.log(JSON.stringify(response, null, 2));
2328
1291
  } else {
@@ -2358,7 +1321,7 @@ async function snapshotCommand(options = {}) {
2358
1321
  selector: options.selector,
2359
1322
  tabId: options.tabId
2360
1323
  };
2361
- const response = await sendCommand2(request);
1324
+ const response = await sendCommand(request);
2362
1325
  if (options.json) {
2363
1326
  console.log(JSON.stringify(response, null, 2));
2364
1327
  } else {
@@ -2377,7 +1340,7 @@ async function snapshotCommand(options = {}) {
2377
1340
  }
2378
1341
 
2379
1342
  // packages/cli/src/commands/click.ts
2380
- function parseRef2(ref) {
1343
+ function parseRef(ref) {
2381
1344
  return ref.startsWith("@") ? ref.slice(1) : ref;
2382
1345
  }
2383
1346
  async function clickCommand(ref, options = {}) {
@@ -2385,14 +1348,14 @@ async function clickCommand(ref, options = {}) {
2385
1348
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2386
1349
  }
2387
1350
  await ensureDaemonRunning();
2388
- const parsedRef = parseRef2(ref);
1351
+ const parsedRef = parseRef(ref);
2389
1352
  const request = {
2390
1353
  id: generateId(),
2391
1354
  action: "click",
2392
1355
  ref: parsedRef,
2393
1356
  tabId: options.tabId
2394
1357
  };
2395
- const response = await sendCommand2(request);
1358
+ const response = await sendCommand(request);
2396
1359
  if (options.json) {
2397
1360
  console.log(JSON.stringify(response, null, 2));
2398
1361
  } else {
@@ -2412,7 +1375,7 @@ async function clickCommand(ref, options = {}) {
2412
1375
  }
2413
1376
 
2414
1377
  // packages/cli/src/commands/hover.ts
2415
- function parseRef3(ref) {
1378
+ function parseRef2(ref) {
2416
1379
  return ref.startsWith("@") ? ref.slice(1) : ref;
2417
1380
  }
2418
1381
  async function hoverCommand(ref, options = {}) {
@@ -2420,14 +1383,14 @@ async function hoverCommand(ref, options = {}) {
2420
1383
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2421
1384
  }
2422
1385
  await ensureDaemonRunning();
2423
- const parsedRef = parseRef3(ref);
1386
+ const parsedRef = parseRef2(ref);
2424
1387
  const request = {
2425
1388
  id: generateId(),
2426
1389
  action: "hover",
2427
1390
  ref: parsedRef,
2428
1391
  tabId: options.tabId
2429
1392
  };
2430
- const response = await sendCommand2(request);
1393
+ const response = await sendCommand(request);
2431
1394
  if (options.json) {
2432
1395
  console.log(JSON.stringify(response, null, 2));
2433
1396
  } else {
@@ -2447,7 +1410,7 @@ async function hoverCommand(ref, options = {}) {
2447
1410
  }
2448
1411
 
2449
1412
  // packages/cli/src/commands/fill.ts
2450
- function parseRef4(ref) {
1413
+ function parseRef3(ref) {
2451
1414
  return ref.startsWith("@") ? ref.slice(1) : ref;
2452
1415
  }
2453
1416
  async function fillCommand(ref, text, options = {}) {
@@ -2458,7 +1421,7 @@ async function fillCommand(ref, text, options = {}) {
2458
1421
  throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
2459
1422
  }
2460
1423
  await ensureDaemonRunning();
2461
- const parsedRef = parseRef4(ref);
1424
+ const parsedRef = parseRef3(ref);
2462
1425
  const request = {
2463
1426
  id: generateId(),
2464
1427
  action: "fill",
@@ -2466,7 +1429,7 @@ async function fillCommand(ref, text, options = {}) {
2466
1429
  text,
2467
1430
  tabId: options.tabId
2468
1431
  };
2469
- const response = await sendCommand2(request);
1432
+ const response = await sendCommand(request);
2470
1433
  if (options.json) {
2471
1434
  console.log(JSON.stringify(response, null, 2));
2472
1435
  } else {
@@ -2487,7 +1450,7 @@ async function fillCommand(ref, text, options = {}) {
2487
1450
  }
2488
1451
 
2489
1452
  // packages/cli/src/commands/type.ts
2490
- function parseRef5(ref) {
1453
+ function parseRef4(ref) {
2491
1454
  return ref.startsWith("@") ? ref.slice(1) : ref;
2492
1455
  }
2493
1456
  async function typeCommand(ref, text, options = {}) {
@@ -2498,7 +1461,7 @@ async function typeCommand(ref, text, options = {}) {
2498
1461
  throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
2499
1462
  }
2500
1463
  await ensureDaemonRunning();
2501
- const parsedRef = parseRef5(ref);
1464
+ const parsedRef = parseRef4(ref);
2502
1465
  const request = {
2503
1466
  id: generateId(),
2504
1467
  action: "type",
@@ -2506,7 +1469,7 @@ async function typeCommand(ref, text, options = {}) {
2506
1469
  text,
2507
1470
  tabId: options.tabId
2508
1471
  };
2509
- const response = await sendCommand2(request);
1472
+ const response = await sendCommand(request);
2510
1473
  if (options.json) {
2511
1474
  console.log(JSON.stringify(response, null, 2));
2512
1475
  } else {
@@ -2534,7 +1497,7 @@ async function closeCommand(options = {}) {
2534
1497
  action: "close",
2535
1498
  tabId: options.tabId
2536
1499
  };
2537
- const response = await sendCommand2(request);
1500
+ const response = await sendCommand(request);
2538
1501
  if (options.json) {
2539
1502
  console.log(JSON.stringify(response, null, 2));
2540
1503
  } else {
@@ -2553,7 +1516,7 @@ async function closeCommand(options = {}) {
2553
1516
  }
2554
1517
 
2555
1518
  // packages/cli/src/commands/get.ts
2556
- function parseRef6(ref) {
1519
+ function parseRef5(ref) {
2557
1520
  return ref.startsWith("@") ? ref.slice(1) : ref;
2558
1521
  }
2559
1522
  async function getCommand(attribute, ref, options = {}) {
@@ -2565,10 +1528,10 @@ async function getCommand(attribute, ref, options = {}) {
2565
1528
  id: generateId(),
2566
1529
  action: "get",
2567
1530
  attribute,
2568
- ref: ref ? parseRef6(ref) : void 0,
1531
+ ref: ref ? parseRef5(ref) : void 0,
2569
1532
  tabId: options.tabId
2570
1533
  };
2571
- const response = await sendCommand2(request);
1534
+ const response = await sendCommand(request);
2572
1535
  if (options.json) {
2573
1536
  console.log(JSON.stringify(response, null, 2));
2574
1537
  } else {
@@ -2584,17 +1547,17 @@ async function getCommand(attribute, ref, options = {}) {
2584
1547
 
2585
1548
  // packages/cli/src/commands/screenshot.ts
2586
1549
  import fs from "fs";
2587
- import path4 from "path";
2588
- import os4 from "os";
1550
+ import path3 from "path";
1551
+ import os3 from "os";
2589
1552
  function getDefaultPath() {
2590
1553
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2591
1554
  const filename = `bb-screenshot-${timestamp}.png`;
2592
- return path4.join(os4.tmpdir(), filename);
1555
+ return path3.join(os3.tmpdir(), filename);
2593
1556
  }
2594
1557
  function saveBase64Image(dataUrl, filePath) {
2595
1558
  const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
2596
1559
  const buffer = Buffer.from(base64Data, "base64");
2597
- const dir = path4.dirname(filePath);
1560
+ const dir = path3.dirname(filePath);
2598
1561
  if (!fs.existsSync(dir)) {
2599
1562
  fs.mkdirSync(dir, { recursive: true });
2600
1563
  }
@@ -2602,13 +1565,13 @@ function saveBase64Image(dataUrl, filePath) {
2602
1565
  }
2603
1566
  async function screenshotCommand(outputPath, options = {}) {
2604
1567
  await ensureDaemonRunning();
2605
- const filePath = outputPath ? path4.resolve(outputPath) : getDefaultPath();
1568
+ const filePath = outputPath ? path3.resolve(outputPath) : getDefaultPath();
2606
1569
  const request = {
2607
1570
  id: generateId(),
2608
1571
  action: "screenshot",
2609
1572
  tabId: options.tabId
2610
1573
  };
2611
- const response = await sendCommand2(request);
1574
+ const response = await sendCommand(request);
2612
1575
  if (response.success && response.data?.dataUrl) {
2613
1576
  const dataUrl = response.data.dataUrl;
2614
1577
  saveBase64Image(dataUrl, filePath);
@@ -2635,7 +1598,7 @@ async function screenshotCommand(outputPath, options = {}) {
2635
1598
  function isTimeWait(target) {
2636
1599
  return /^\d+$/.test(target);
2637
1600
  }
2638
- function parseRef7(ref) {
1601
+ function parseRef6(ref) {
2639
1602
  return ref.startsWith("@") ? ref.slice(1) : ref;
2640
1603
  }
2641
1604
  async function waitCommand(target, options = {}) {
@@ -2654,7 +1617,7 @@ async function waitCommand(target, options = {}) {
2654
1617
  tabId: options.tabId
2655
1618
  };
2656
1619
  } else {
2657
- const ref = parseRef7(target);
1620
+ const ref = parseRef6(target);
2658
1621
  request = {
2659
1622
  id: generateId(),
2660
1623
  action: "wait",
@@ -2663,7 +1626,7 @@ async function waitCommand(target, options = {}) {
2663
1626
  tabId: options.tabId
2664
1627
  };
2665
1628
  }
2666
- const response = await sendCommand2(request);
1629
+ const response = await sendCommand(request);
2667
1630
  if (options.json) {
2668
1631
  console.log(JSON.stringify(response, null, 2));
2669
1632
  } else {
@@ -2671,7 +1634,7 @@ async function waitCommand(target, options = {}) {
2671
1634
  if (isTimeWait(target)) {
2672
1635
  console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
2673
1636
  } else {
2674
- console.log(`\u5143\u7D20 @${parseRef7(target)} \u5DF2\u51FA\u73B0`);
1637
+ console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
2675
1638
  }
2676
1639
  } else {
2677
1640
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -2711,7 +1674,7 @@ async function pressCommand(keyString, options = {}) {
2711
1674
  modifiers,
2712
1675
  tabId: options.tabId
2713
1676
  };
2714
- const response = await sendCommand2(request);
1677
+ const response = await sendCommand(request);
2715
1678
  if (options.json) {
2716
1679
  console.log(JSON.stringify(response, null, 2));
2717
1680
  } else {
@@ -2752,7 +1715,7 @@ async function scrollCommand(direction, pixels, options = {}) {
2752
1715
  pixels: pixelValue,
2753
1716
  tabId: options.tabId
2754
1717
  };
2755
- const response = await sendCommand2(request);
1718
+ const response = await sendCommand(request);
2756
1719
  if (options.json) {
2757
1720
  console.log(JSON.stringify(response, null, 2));
2758
1721
  } else {
@@ -2773,7 +1736,7 @@ async function backCommand(options = {}) {
2773
1736
  action: "back",
2774
1737
  tabId: options.tabId
2775
1738
  };
2776
- const response = await sendCommand2(request);
1739
+ const response = await sendCommand(request);
2777
1740
  if (options.json) {
2778
1741
  console.log(JSON.stringify(response, null, 2));
2779
1742
  } else {
@@ -2797,7 +1760,7 @@ async function forwardCommand(options = {}) {
2797
1760
  action: "forward",
2798
1761
  tabId: options.tabId
2799
1762
  };
2800
- const response = await sendCommand2(request);
1763
+ const response = await sendCommand(request);
2801
1764
  if (options.json) {
2802
1765
  console.log(JSON.stringify(response, null, 2));
2803
1766
  } else {
@@ -2821,7 +1784,7 @@ async function refreshCommand(options = {}) {
2821
1784
  action: "refresh",
2822
1785
  tabId: options.tabId
2823
1786
  };
2824
- const response = await sendCommand2(request);
1787
+ const response = await sendCommand(request);
2825
1788
  if (options.json) {
2826
1789
  console.log(JSON.stringify(response, null, 2));
2827
1790
  } else {
@@ -2840,7 +1803,7 @@ async function refreshCommand(options = {}) {
2840
1803
  }
2841
1804
 
2842
1805
  // packages/cli/src/commands/check.ts
2843
- function parseRef8(ref) {
1806
+ function parseRef7(ref) {
2844
1807
  return ref.startsWith("@") ? ref.slice(1) : ref;
2845
1808
  }
2846
1809
  async function checkCommand(ref, options = {}) {
@@ -2848,14 +1811,14 @@ async function checkCommand(ref, options = {}) {
2848
1811
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2849
1812
  }
2850
1813
  await ensureDaemonRunning();
2851
- const parsedRef = parseRef8(ref);
1814
+ const parsedRef = parseRef7(ref);
2852
1815
  const request = {
2853
1816
  id: generateId(),
2854
1817
  action: "check",
2855
1818
  ref: parsedRef,
2856
1819
  tabId: options.tabId
2857
1820
  };
2858
- const response = await sendCommand2(request);
1821
+ const response = await sendCommand(request);
2859
1822
  if (options.json) {
2860
1823
  console.log(JSON.stringify(response, null, 2));
2861
1824
  } else {
@@ -2887,14 +1850,14 @@ async function uncheckCommand(ref, options = {}) {
2887
1850
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2888
1851
  }
2889
1852
  await ensureDaemonRunning();
2890
- const parsedRef = parseRef8(ref);
1853
+ const parsedRef = parseRef7(ref);
2891
1854
  const request = {
2892
1855
  id: generateId(),
2893
1856
  action: "uncheck",
2894
1857
  ref: parsedRef,
2895
1858
  tabId: options.tabId
2896
1859
  };
2897
- const response = await sendCommand2(request);
1860
+ const response = await sendCommand(request);
2898
1861
  if (options.json) {
2899
1862
  console.log(JSON.stringify(response, null, 2));
2900
1863
  } else {
@@ -2923,7 +1886,7 @@ async function uncheckCommand(ref, options = {}) {
2923
1886
  }
2924
1887
 
2925
1888
  // packages/cli/src/commands/select.ts
2926
- function parseRef9(ref) {
1889
+ function parseRef8(ref) {
2927
1890
  return ref.startsWith("@") ? ref.slice(1) : ref;
2928
1891
  }
2929
1892
  async function selectCommand(ref, value, options = {}) {
@@ -2934,7 +1897,7 @@ async function selectCommand(ref, value, options = {}) {
2934
1897
  throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
2935
1898
  }
2936
1899
  await ensureDaemonRunning();
2937
- const parsedRef = parseRef9(ref);
1900
+ const parsedRef = parseRef8(ref);
2938
1901
  const request = {
2939
1902
  id: generateId(),
2940
1903
  action: "select",
@@ -2942,7 +1905,7 @@ async function selectCommand(ref, value, options = {}) {
2942
1905
  value,
2943
1906
  tabId: options.tabId
2944
1907
  };
2945
- const response = await sendCommand2(request);
1908
+ const response = await sendCommand(request);
2946
1909
  if (options.json) {
2947
1910
  console.log(JSON.stringify(response, null, 2));
2948
1911
  } else {
@@ -2980,7 +1943,7 @@ async function evalCommand(script, options = {}) {
2980
1943
  script,
2981
1944
  tabId: options.tabId
2982
1945
  };
2983
- const response = await sendCommand2(request);
1946
+ const response = await sendCommand(request);
2984
1947
  if (options.json) {
2985
1948
  console.log(JSON.stringify(response, null, 2));
2986
1949
  } else {
@@ -3063,6 +2026,12 @@ function formatTabList(tabs, activeIndex) {
3063
2026
  async function tabCommand(args, options = {}) {
3064
2027
  await ensureDaemonRunning();
3065
2028
  const parsed = parseTabSubcommand(args, process.argv);
2029
+ if (options.globalTabId && parsed.tabId === void 0 && parsed.index === void 0) {
2030
+ if (parsed.action === "tab_close" || parsed.action === "tab_select") {
2031
+ const numId = parseInt(options.globalTabId, 10);
2032
+ parsed.tabId = isNaN(numId) ? options.globalTabId : numId;
2033
+ }
2034
+ }
3066
2035
  const request = {
3067
2036
  id: generateId(),
3068
2037
  action: parsed.action,
@@ -3070,7 +2039,7 @@ async function tabCommand(args, options = {}) {
3070
2039
  index: parsed.index,
3071
2040
  tabId: parsed.tabId
3072
2041
  };
3073
- const response = await sendCommand2(request);
2042
+ const response = await sendCommand(request);
3074
2043
  if (options.json) {
3075
2044
  console.log(JSON.stringify(response, null, 2));
3076
2045
  } else {
@@ -3119,7 +2088,7 @@ async function frameCommand(selector, options = {}) {
3119
2088
  selector,
3120
2089
  tabId: options.tabId
3121
2090
  };
3122
- const response = await sendCommand2(request);
2091
+ const response = await sendCommand(request);
3123
2092
  if (options.json) {
3124
2093
  console.log(JSON.stringify(response, null, 2));
3125
2094
  } else {
@@ -3143,7 +2112,7 @@ async function frameMainCommand(options = {}) {
3143
2112
  action: "frame_main",
3144
2113
  tabId: options.tabId
3145
2114
  };
3146
- const response = await sendCommand2(request);
2115
+ const response = await sendCommand(request);
3147
2116
  if (options.json) {
3148
2117
  console.log(JSON.stringify(response, null, 2));
3149
2118
  } else {
@@ -3169,7 +2138,7 @@ async function dialogCommand(subCommand, promptText, options = {}) {
3169
2138
  promptText: subCommand === "accept" ? promptText : void 0,
3170
2139
  tabId: options.tabId
3171
2140
  };
3172
- const response = await sendCommand2(request);
2141
+ const response = await sendCommand(request);
3173
2142
  if (options.json) {
3174
2143
  console.log(JSON.stringify(response, null, 2));
3175
2144
  } else {
@@ -3190,7 +2159,12 @@ async function dialogCommand(subCommand, promptText, options = {}) {
3190
2159
 
3191
2160
  // packages/cli/src/commands/network.ts
3192
2161
  async function networkCommand(subCommand, urlOrFilter, options = {}) {
3193
- const response = await sendCommand2({
2162
+ let since;
2163
+ if (subCommand === "requests" && options.since) {
2164
+ const num = parseInt(options.since, 10);
2165
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2166
+ }
2167
+ const request = {
3194
2168
  id: generateId(),
3195
2169
  action: "network",
3196
2170
  networkCommand: subCommand,
@@ -3201,8 +2175,12 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
3201
2175
  body: options.body
3202
2176
  } : void 0,
3203
2177
  withBody: subCommand === "requests" ? options.withBody : void 0,
2178
+ since,
2179
+ method: subCommand === "requests" ? options.method : void 0,
2180
+ status: subCommand === "requests" ? options.status : void 0,
3204
2181
  tabId: options.tabId
3205
- });
2182
+ };
2183
+ const response = await sendCommand(request);
3206
2184
  if (options.json) {
3207
2185
  console.log(JSON.stringify(response));
3208
2186
  return;
@@ -3277,12 +2255,19 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
3277
2255
 
3278
2256
  // packages/cli/src/commands/console.ts
3279
2257
  async function consoleCommand(options = {}) {
3280
- const response = await sendCommand2({
2258
+ let since;
2259
+ if (options.since) {
2260
+ const num = parseInt(options.since, 10);
2261
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2262
+ }
2263
+ const request = {
3281
2264
  id: generateId(),
3282
2265
  action: "console",
3283
2266
  consoleCommand: options.clear ? "clear" : "get",
3284
- tabId: options.tabId
3285
- });
2267
+ tabId: options.tabId,
2268
+ since
2269
+ };
2270
+ const response = await sendCommand(request);
3286
2271
  if (options.json) {
3287
2272
  console.log(JSON.stringify(response));
3288
2273
  return;
@@ -3322,12 +2307,19 @@ async function consoleCommand(options = {}) {
3322
2307
 
3323
2308
  // packages/cli/src/commands/errors.ts
3324
2309
  async function errorsCommand(options = {}) {
3325
- const response = await sendCommand2({
2310
+ let since;
2311
+ if (options.since) {
2312
+ const num = parseInt(options.since, 10);
2313
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2314
+ }
2315
+ const request = {
3326
2316
  id: generateId(),
3327
2317
  action: "errors",
3328
2318
  errorsCommand: options.clear ? "clear" : "get",
3329
- tabId: options.tabId
3330
- });
2319
+ tabId: options.tabId,
2320
+ since
2321
+ };
2322
+ const response = await sendCommand(request);
3331
2323
  if (options.json) {
3332
2324
  console.log(JSON.stringify(response));
3333
2325
  return;
@@ -3362,7 +2354,7 @@ async function errorsCommand(options = {}) {
3362
2354
 
3363
2355
  // packages/cli/src/commands/trace.ts
3364
2356
  async function traceCommand(subCommand, options = {}) {
3365
- const response = await sendCommand2({
2357
+ const response = await sendCommand({
3366
2358
  id: generateId(),
3367
2359
  action: "trace",
3368
2360
  traceCommand: subCommand,
@@ -3452,7 +2444,7 @@ function matchTabOrigin2(tabUrl, targetHostname) {
3452
2444
  }
3453
2445
  async function ensureTabForOrigin(origin, hostname) {
3454
2446
  const listReq = { id: generateId(), action: "tab_list" };
3455
- const listResp = await sendCommand2(listReq);
2447
+ const listResp = await sendCommand(listReq);
3456
2448
  if (listResp.success && listResp.data?.tabs) {
3457
2449
  const matchingTab = listResp.data.tabs.find(
3458
2450
  (tab) => matchTabOrigin2(tab.url, hostname)
@@ -3461,11 +2453,11 @@ async function ensureTabForOrigin(origin, hostname) {
3461
2453
  return matchingTab.tabId;
3462
2454
  }
3463
2455
  }
3464
- const newResp = await sendCommand2({ id: generateId(), action: "tab_new", url: origin });
2456
+ const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
3465
2457
  if (!newResp.success) {
3466
2458
  throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
3467
2459
  }
3468
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
2460
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
3469
2461
  return newResp.data?.tabId;
3470
2462
  }
3471
2463
  function buildFetchScript(url, options) {
@@ -3530,7 +2522,7 @@ async function fetchCommand(url, options = {}) {
3530
2522
  }
3531
2523
  const script = buildFetchScript(url, options);
3532
2524
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
3533
- const evalResp = await sendCommand2(evalReq);
2525
+ const evalResp = await sendCommand(evalReq);
3534
2526
  if (!evalResp.success) {
3535
2527
  throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
3536
2528
  }
@@ -3549,9 +2541,9 @@ async function fetchCommand(url, options = {}) {
3549
2541
  throw new Error(`Fetch error: ${result.error}`);
3550
2542
  }
3551
2543
  if (options.output) {
3552
- const { writeFileSync: writeFileSync2 } = await import("fs");
2544
+ const { writeFileSync } = await import("fs");
3553
2545
  const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
3554
- writeFileSync2(options.output, content, "utf-8");
2546
+ writeFileSync(options.output, content, "utf-8");
3555
2547
  console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
3556
2548
  return;
3557
2549
  }
@@ -3613,16 +2605,57 @@ async function historyCommand(subCommand, options = {}) {
3613
2605
 
3614
2606
  // packages/cli/src/commands/daemon.ts
3615
2607
  async function statusCommand(options = {}) {
3616
- const running = await isDaemonRunning();
2608
+ const status = await getDaemonStatus();
2609
+ if (!status) {
2610
+ if (options.json) {
2611
+ console.log(JSON.stringify({ running: false }));
2612
+ } else {
2613
+ console.log("Daemon not running");
2614
+ }
2615
+ return;
2616
+ }
2617
+ if (options.json) {
2618
+ console.log(JSON.stringify(status, null, 2));
2619
+ return;
2620
+ }
2621
+ console.log(`Daemon running: ${status.running ? "yes" : "no"}`);
2622
+ console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
2623
+ console.log(`Uptime: ${formatUptime(status.uptime)}`);
2624
+ console.log(`Global seq: ${status.currentSeq ?? "N/A"}`);
2625
+ const tabs = status.tabs;
2626
+ if (tabs && tabs.length > 0) {
2627
+ console.log(`
2628
+ Tabs (${tabs.length}):`);
2629
+ for (const tab of tabs) {
2630
+ const active = tab.targetId === status.currentTargetId ? " *" : "";
2631
+ console.log(
2632
+ ` ${tab.shortId}${active} net:${tab.networkRequests} console:${tab.consoleMessages} err:${tab.jsErrors} seq:${tab.lastActionSeq}`
2633
+ );
2634
+ }
2635
+ } else {
2636
+ console.log("\nNo tabs");
2637
+ }
2638
+ }
2639
+ async function shutdownCommand(options = {}) {
2640
+ const ok = await stopDaemon();
3617
2641
  if (options.json) {
3618
- console.log(JSON.stringify({ running }));
2642
+ console.log(JSON.stringify({ stopped: ok }));
3619
2643
  } else {
3620
- console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
2644
+ console.log(ok ? "Daemon stopped" : "Daemon was not running");
3621
2645
  }
3622
2646
  }
2647
+ function formatUptime(ms) {
2648
+ if (!ms || ms <= 0) return "0s";
2649
+ const s = Math.floor(ms / 1e3);
2650
+ if (s < 60) return `${s}s`;
2651
+ const m = Math.floor(s / 60);
2652
+ if (m < 60) return `${m}m ${s % 60}s`;
2653
+ const h = Math.floor(m / 60);
2654
+ return `${h}h ${m % 60}m`;
2655
+ }
3623
2656
 
3624
2657
  // packages/cli/src/index.ts
3625
- var VERSION = "0.10.1";
2658
+ var VERSION = "0.11.1";
3626
2659
  var HELP_TEXT = `
3627
2660
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
3628
2661
 
@@ -3677,6 +2710,7 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
3677
2710
  errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
3678
2711
  trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
3679
2712
  history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
2713
+ daemon [status|stop] [opts] \u524D\u53F0\u8FD0\u884C\u6216\u7BA1\u7406 daemon
3680
2714
 
3681
2715
  \u9009\u9879\uFF1A
3682
2716
  --json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
@@ -3758,6 +2792,12 @@ function parseArgs(argv) {
3758
2792
  skipNext = true;
3759
2793
  } else if (arg === "--tab") {
3760
2794
  skipNext = true;
2795
+ } else if (arg === "--since") {
2796
+ skipNext = true;
2797
+ } else if (arg === "--method") {
2798
+ skipNext = true;
2799
+ } else if (arg === "--status") {
2800
+ skipNext = true;
3761
2801
  } else if (arg.startsWith("-")) {
3762
2802
  } else if (result.command === null) {
3763
2803
  result.command = arg;
@@ -3771,19 +2811,25 @@ async function main() {
3771
2811
  const parsed = parseArgs(process.argv);
3772
2812
  setJqExpression(parsed.flags.jq);
3773
2813
  const tabArgIdx = process.argv.indexOf("--tab");
3774
- const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? parseInt(process.argv[tabArgIdx + 1], 10) : void 0;
2814
+ const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? process.argv[tabArgIdx + 1] : void 0;
2815
+ const sinceArgIdx = process.argv.indexOf("--since");
2816
+ const globalSince = sinceArgIdx >= 0 && process.argv[sinceArgIdx + 1] ? process.argv[sinceArgIdx + 1] : void 0;
3775
2817
  if (parsed.flags.version) {
3776
2818
  console.log(VERSION);
3777
2819
  return;
3778
2820
  }
3779
2821
  if (process.argv.includes("--mcp")) {
3780
- const mcpPath = fileURLToPath4(new URL("./mcp.js", import.meta.url));
2822
+ const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
3781
2823
  const { spawn: spawn3 } = await import("child_process");
3782
2824
  const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
3783
2825
  child.on("exit", (code) => process.exit(code ?? 0));
3784
2826
  return;
3785
2827
  }
3786
- 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") {
3787
2833
  console.log(HELP_TEXT);
3788
2834
  return;
3789
2835
  }
@@ -3939,7 +2985,31 @@ async function main() {
3939
2985
  await getCommand(attribute, ref, { json: parsed.flags.json, tabId: globalTabId });
3940
2986
  break;
3941
2987
  }
3942
- 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
+ }
3943
3013
  case "close": {
3944
3014
  await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
3945
3015
  break;
@@ -3999,7 +3069,7 @@ async function main() {
3999
3069
  break;
4000
3070
  }
4001
3071
  case "tab": {
4002
- await tabCommand(parsed.args, { json: parsed.flags.json });
3072
+ await tabCommand(parsed.args, { json: parsed.flags.json, globalTabId });
4003
3073
  break;
4004
3074
  }
4005
3075
  case "status": {
@@ -4043,17 +3113,21 @@ async function main() {
4043
3113
  const withBody = process.argv.includes("--with-body");
4044
3114
  const bodyIndex = process.argv.findIndex((a) => a === "--body");
4045
3115
  const body = bodyIndex >= 0 ? process.argv[bodyIndex + 1] : void 0;
4046
- await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId });
3116
+ const methodIndex = process.argv.findIndex((a) => a === "--method");
3117
+ const method = methodIndex >= 0 ? process.argv[methodIndex + 1] : void 0;
3118
+ const statusIndex = process.argv.findIndex((a) => a === "--status");
3119
+ const statusFilter = statusIndex >= 0 ? process.argv[statusIndex + 1] : void 0;
3120
+ await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter });
4047
3121
  break;
4048
3122
  }
4049
3123
  case "console": {
4050
3124
  const clear = process.argv.includes("--clear");
4051
- await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId });
3125
+ await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
4052
3126
  break;
4053
3127
  }
4054
3128
  case "errors": {
4055
3129
  const clear = process.argv.includes("--clear");
4056
- await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId });
3130
+ await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
4057
3131
  break;
4058
3132
  }
4059
3133
  case "trace": {