bb-browser 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,1295 +1,47 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  COMMAND_TIMEOUT,
4
+ DAEMON_PORT,
4
5
  generateId
5
- } from "./chunk-DBJBHYC7.js";
6
+ } from "./chunk-5WGFUZLM.js";
6
7
  import {
7
8
  applyJq
8
9
  } from "./chunk-AHGAQEFO.js";
9
- import {
10
- parseOpenClawJson
11
- } from "./chunk-FSL4RNI6.js";
12
10
  import "./chunk-D4HDZEJT.js";
13
11
 
14
12
  // packages/cli/src/index.ts
15
- import { fileURLToPath as fileURLToPath4 } from "url";
13
+ import { fileURLToPath as fileURLToPath2 } from "url";
16
14
 
17
- // packages/cli/src/cdp-client.ts
18
- import { readFileSync, unlinkSync, writeFileSync } from "fs";
15
+ // packages/cli/src/daemon-manager.ts
16
+ import { spawn } from "child_process";
17
+ import { readFile } from "fs/promises";
19
18
  import { request as httpRequest } from "http";
20
- import { request as httpsRequest } from "https";
21
- import os2 from "os";
22
- import path2 from "path";
23
19
  import { fileURLToPath } from "url";
24
- import WebSocket from "ws";
25
-
26
- // packages/cli/src/cdp-discovery.ts
27
- import { execFile, execSync, spawn } from "child_process";
20
+ import { dirname, resolve } from "path";
28
21
  import { existsSync } from "fs";
29
- import { mkdir, readFile, writeFile } from "fs/promises";
30
22
  import os from "os";
31
23
  import path from "path";
32
- var DEFAULT_CDP_PORT = 19825;
33
- var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
34
- var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
35
- var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
36
- function execFileAsync(command, args, timeout) {
37
- return new Promise((resolve3, reject) => {
38
- execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
39
- if (error) {
40
- reject(error);
41
- return;
42
- }
43
- resolve3(stdout.trim());
44
- });
45
- });
46
- }
47
- function getArgValue(flag) {
48
- const index = process.argv.indexOf(flag);
49
- if (index < 0) return void 0;
50
- return process.argv[index + 1];
51
- }
52
- async function tryOpenClaw() {
53
- try {
54
- const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 5e3);
55
- 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
- }
60
- } catch {
61
- }
62
- return null;
63
- }
64
- async function canConnect(host, port) {
65
- try {
66
- const controller = new AbortController();
67
- const timeout = setTimeout(() => controller.abort(), 1200);
68
- const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
69
- clearTimeout(timeout);
70
- return response.ok;
71
- } catch {
72
- return false;
73
- }
74
- }
75
- function findBrowserExecutable() {
76
- if (process.platform === "darwin") {
77
- const candidates = [
78
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
79
- "/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
80
- "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
81
- "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
82
- "/Applications/Arc.app/Contents/MacOS/Arc",
83
- "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
84
- "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
85
- ];
86
- return candidates.find((candidate) => existsSync(candidate)) ?? null;
87
- }
88
- if (process.platform === "linux") {
89
- const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
90
- for (const candidate of candidates) {
91
- try {
92
- const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
93
- if (resolved) {
94
- return resolved;
95
- }
96
- } catch {
97
- }
98
- }
99
- return null;
100
- }
101
- if (process.platform === "win32") {
102
- const candidates = [
103
- "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
104
- "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
105
- "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
106
- ];
107
- return candidates.find((candidate) => existsSync(candidate)) ?? null;
108
- }
109
- return null;
110
- }
111
- async function isManagedBrowserRunning() {
112
- try {
113
- const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
114
- const port = Number.parseInt(rawPort.trim(), 10);
115
- if (!Number.isInteger(port) || port <= 0) {
116
- return false;
117
- }
118
- return await canConnect("127.0.0.1", port);
119
- } catch {
120
- return false;
121
- }
122
- }
123
- async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
124
- const executable = findBrowserExecutable();
125
- if (!executable) {
126
- return null;
127
- }
128
- await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
129
- const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
130
- const prefsPath = path.join(defaultProfileDir, "Preferences");
131
- await mkdir(defaultProfileDir, { recursive: true });
132
- try {
133
- let prefs = {};
134
- try {
135
- prefs = JSON.parse(await readFile(prefsPath, "utf8"));
136
- } catch {
137
- }
138
- if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
139
- prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
140
- await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
141
- }
142
- } catch {
143
- }
144
- const args = [
145
- `--remote-debugging-port=${port}`,
146
- `--user-data-dir=${MANAGED_USER_DATA_DIR}`,
147
- "--no-first-run",
148
- "--no-default-browser-check",
149
- "--disable-sync",
150
- "--disable-background-networking",
151
- "--disable-component-update",
152
- "--disable-features=Translate,MediaRouter",
153
- "--disable-session-crashed-bubble",
154
- "--hide-crash-restore-bubble",
155
- "about:blank"
156
- ];
157
- try {
158
- const child = spawn(executable, args, {
159
- detached: true,
160
- stdio: "ignore"
161
- });
162
- child.unref();
163
- } catch {
164
- return null;
165
- }
166
- await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
167
- await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
168
- const deadline = Date.now() + 8e3;
169
- while (Date.now() < deadline) {
170
- if (await canConnect("127.0.0.1", port)) {
171
- return { host: "127.0.0.1", port };
172
- }
173
- await new Promise((resolve3) => setTimeout(resolve3, 250));
174
- }
175
- return null;
176
- }
177
- async function discoverCdpPort() {
178
- const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
179
- if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
180
- return { host: "127.0.0.1", port: explicitPort };
181
- }
182
- try {
183
- const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
184
- const managedPort = Number.parseInt(rawPort.trim(), 10);
185
- if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
186
- return { host: "127.0.0.1", port: managedPort };
187
- }
188
- } catch {
189
- }
190
- if (process.argv.includes("--openclaw")) {
191
- const viaOpenClaw = await tryOpenClaw();
192
- if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
193
- return viaOpenClaw;
194
- }
195
- }
196
- const launched = await launchManagedBrowser();
197
- if (launched) {
198
- return launched;
199
- }
200
- if (!process.argv.includes("--openclaw")) {
201
- const detectedOpenClaw = await tryOpenClaw();
202
- if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
203
- return detectedOpenClaw;
204
- }
205
- }
206
- return null;
207
- }
208
-
209
- // packages/cli/src/cdp-client.ts
210
- var connectionState = null;
211
- var reconnecting = null;
212
- var networkRequests = /* @__PURE__ */ new Map();
213
- var networkEnabled = false;
214
- var consoleMessages = [];
215
- var consoleEnabled = false;
216
- var jsErrors = [];
217
- var errorsEnabled = false;
218
- var traceRecording = false;
219
- var traceEvents = [];
220
- function getContextFilePath(host, port) {
221
- const safeHost = host.replace(/[^a-zA-Z0-9_.-]/g, "_");
222
- return path2.join(os2.tmpdir(), `bb-browser-cdp-context-${safeHost}-${port}.json`);
223
- }
224
- function loadPersistedCurrentTargetId(host, port) {
225
- try {
226
- const data = JSON.parse(readFileSync(getContextFilePath(host, port), "utf-8"));
227
- return typeof data.currentTargetId === "string" && data.currentTargetId ? data.currentTargetId : void 0;
228
- } catch {
229
- return void 0;
230
- }
231
- }
232
- function persistCurrentTargetId(host, port, currentTargetId) {
233
- try {
234
- writeFileSync(getContextFilePath(host, port), JSON.stringify({ currentTargetId }));
235
- } catch {
236
- }
237
- }
238
- function setCurrentTargetId(targetId) {
239
- const state = connectionState;
240
- if (!state) return;
241
- state.currentTargetId = targetId;
242
- persistCurrentTargetId(state.host, state.port, targetId);
243
- }
244
- function buildRequestError(error) {
245
- return error instanceof Error ? error : new Error(String(error));
246
- }
247
- function fetchJson(url) {
248
- return new Promise((resolve3, reject) => {
249
- const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
250
- const req = requester(url, { method: "GET" }, (res) => {
251
- const chunks = [];
252
- res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
253
- res.on("end", () => {
254
- const raw = Buffer.concat(chunks).toString("utf8");
255
- if ((res.statusCode ?? 500) >= 400) {
256
- reject(new Error(`HTTP ${res.statusCode ?? 500}: ${raw}`));
257
- return;
258
- }
259
- try {
260
- resolve3(JSON.parse(raw));
261
- } catch (error) {
262
- reject(error);
263
- }
264
- });
265
- });
266
- req.on("error", reject);
267
- req.end();
268
- });
269
- }
270
- async function getJsonList(host, port) {
271
- const data = await fetchJson(`http://${host}:${port}/json/list`);
272
- return Array.isArray(data) ? data : [];
273
- }
274
- async function getJsonVersion(host, port) {
275
- const data = await fetchJson(`http://${host}:${port}/json/version`);
276
- const url = data.webSocketDebuggerUrl;
277
- if (typeof url !== "string" || !url) {
278
- throw new Error("CDP endpoint missing webSocketDebuggerUrl");
279
- }
280
- return { webSocketDebuggerUrl: url };
281
- }
282
- function connectWebSocket(url) {
283
- return new Promise((resolve3, reject) => {
284
- const ws = new WebSocket(url);
285
- ws.once("open", () => {
286
- const socket = ws._socket;
287
- if (socket && typeof socket.unref === "function") {
288
- socket.unref();
289
- }
290
- resolve3(ws);
291
- });
292
- ws.once("error", reject);
293
- });
294
- }
295
- function createState(host, port, browserWsUrl, browserSocket) {
296
- const state = {
297
- host,
298
- port,
299
- browserWsUrl,
300
- browserSocket,
301
- browserPending: /* @__PURE__ */ new Map(),
302
- nextMessageId: 1,
303
- sessions: /* @__PURE__ */ new Map(),
304
- attachedTargets: /* @__PURE__ */ new Map(),
305
- refsByTarget: /* @__PURE__ */ new Map(),
306
- currentTargetId: loadPersistedCurrentTargetId(host, port),
307
- activeFrameIdByTarget: /* @__PURE__ */ new Map(),
308
- dialogHandlers: /* @__PURE__ */ new Map()
309
- };
310
- browserSocket.on("message", (raw) => {
311
- const message = JSON.parse(raw.toString());
312
- if (typeof message.id === "number") {
313
- const pending = state.browserPending.get(message.id);
314
- if (!pending) return;
315
- state.browserPending.delete(message.id);
316
- if (message.error) {
317
- pending.reject(new Error(`${pending.method}: ${message.error.message ?? "Unknown CDP error"}`));
318
- } else {
319
- pending.resolve(message.result);
320
- }
321
- return;
322
- }
323
- if (message.method === "Target.attachedToTarget") {
324
- const params = message.params;
325
- const sessionId = params.sessionId;
326
- const targetInfo = params.targetInfo;
327
- if (typeof sessionId === "string" && typeof targetInfo?.targetId === "string") {
328
- state.sessions.set(targetInfo.targetId, sessionId);
329
- state.attachedTargets.set(sessionId, targetInfo.targetId);
330
- }
331
- return;
332
- }
333
- if (message.method === "Target.detachedFromTarget") {
334
- const params = message.params;
335
- const sessionId = params.sessionId;
336
- if (typeof sessionId === "string") {
337
- const targetId = state.attachedTargets.get(sessionId);
338
- if (targetId) {
339
- state.sessions.delete(targetId);
340
- state.attachedTargets.delete(sessionId);
341
- state.activeFrameIdByTarget.delete(targetId);
342
- state.dialogHandlers.delete(targetId);
343
- if (state.currentTargetId === targetId) {
344
- state.currentTargetId = void 0;
345
- persistCurrentTargetId(state.host, state.port, void 0);
346
- }
347
- }
348
- }
349
- return;
350
- }
351
- if (message.method === "Target.receivedMessageFromTarget") {
352
- const params = message.params;
353
- const sessionId = params.sessionId;
354
- const messageText = params.message;
355
- if (typeof sessionId === "string" && typeof messageText === "string") {
356
- const targetId = state.attachedTargets.get(sessionId);
357
- if (targetId) {
358
- handleSessionEvent(targetId, JSON.parse(messageText)).catch(() => {
359
- });
360
- }
361
- }
362
- return;
363
- }
364
- if (typeof message.sessionId === "string" && typeof message.method === "string") {
365
- const targetId = state.attachedTargets.get(message.sessionId);
366
- if (targetId) {
367
- handleSessionEvent(targetId, message).catch(() => {
368
- });
369
- }
370
- }
371
- });
372
- browserSocket.on("close", () => {
373
- if (connectionState === state) {
374
- connectionState = null;
375
- }
376
- for (const pending of state.browserPending.values()) {
377
- pending.reject(new Error("CDP connection closed"));
378
- }
379
- state.browserPending.clear();
380
- });
381
- browserSocket.on("error", () => {
382
- });
383
- return state;
384
- }
385
- async function browserCommand(method, params = {}) {
386
- const state = connectionState;
387
- if (!state) throw new Error("CDP connection not initialized");
388
- const id = state.nextMessageId++;
389
- const payload = JSON.stringify({ id, method, params });
390
- const promise = new Promise((resolve3, reject) => {
391
- state.browserPending.set(id, { resolve: resolve3, reject, method });
392
- });
393
- state.browserSocket.send(payload);
394
- return promise;
395
- }
396
- async function sessionCommand(targetId, method, params = {}) {
397
- const state = connectionState;
398
- if (!state) throw new Error("CDP connection not initialized");
399
- const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
400
- const id = state.nextMessageId++;
401
- const payload = JSON.stringify({ id, method, params, sessionId });
402
- return new Promise((resolve3, reject) => {
403
- const check = (raw) => {
404
- const msg = JSON.parse(raw.toString());
405
- if (msg.id === id && msg.sessionId === sessionId) {
406
- state.browserSocket.off("message", check);
407
- if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
408
- else resolve3(msg.result);
409
- }
410
- };
411
- state.browserSocket.on("message", check);
412
- state.browserSocket.send(payload);
413
- });
414
- }
415
- function getActiveFrameId(targetId) {
416
- const frameId = connectionState?.activeFrameIdByTarget.get(targetId);
417
- return frameId ?? void 0;
418
- }
419
- async function pageCommand(targetId, method, params = {}) {
420
- const frameId = getActiveFrameId(targetId);
421
- return sessionCommand(targetId, method, frameId ? { ...params, frameId } : params);
422
- }
423
- function normalizeHeaders(headers) {
424
- if (!headers || typeof headers !== "object") return void 0;
425
- return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
426
- }
427
- async function handleSessionEvent(targetId, event) {
428
- const method = event.method;
429
- const params = event.params ?? {};
430
- if (typeof method !== "string") return;
431
- if (method === "Page.javascriptDialogOpening") {
432
- const handler = connectionState?.dialogHandlers.get(targetId);
433
- if (handler) {
434
- await sessionCommand(targetId, "Page.handleJavaScriptDialog", {
435
- accept: handler.accept,
436
- ...handler.promptText !== void 0 ? { promptText: handler.promptText } : {}
437
- });
438
- }
439
- return;
440
- }
441
- if (method === "Network.requestWillBeSent") {
442
- const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
443
- const request = params.request;
444
- if (!requestId || !request) return;
445
- networkRequests.set(requestId, {
446
- requestId,
447
- url: String(request.url ?? ""),
448
- method: String(request.method ?? "GET"),
449
- type: String(params.type ?? "Other"),
450
- timestamp: Math.round(Number(params.timestamp ?? Date.now()) * 1e3),
451
- requestHeaders: normalizeHeaders(request.headers),
452
- requestBody: typeof request.postData === "string" ? request.postData : void 0
453
- });
454
- return;
455
- }
456
- if (method === "Network.responseReceived") {
457
- const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
458
- const response = params.response;
459
- if (!requestId || !response) return;
460
- const existing = networkRequests.get(requestId);
461
- if (!existing) return;
462
- existing.status = typeof response.status === "number" ? response.status : void 0;
463
- existing.statusText = typeof response.statusText === "string" ? response.statusText : void 0;
464
- existing.responseHeaders = normalizeHeaders(response.headers);
465
- existing.mimeType = typeof response.mimeType === "string" ? response.mimeType : void 0;
466
- networkRequests.set(requestId, existing);
467
- return;
468
- }
469
- if (method === "Network.loadingFailed") {
470
- const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
471
- if (!requestId) return;
472
- const existing = networkRequests.get(requestId);
473
- if (!existing) return;
474
- existing.failed = true;
475
- existing.failureReason = typeof params.errorText === "string" ? params.errorText : "Unknown error";
476
- networkRequests.set(requestId, existing);
477
- return;
478
- }
479
- if (method === "Runtime.consoleAPICalled") {
480
- const type = String(params.type ?? "log");
481
- const args = Array.isArray(params.args) ? params.args : [];
482
- const text = args.map((arg) => {
483
- if (typeof arg.value === "string") return arg.value;
484
- if (arg.value !== void 0) return String(arg.value);
485
- if (typeof arg.description === "string") return arg.description;
486
- return "";
487
- }).filter(Boolean).join(" ");
488
- const stack = params.stackTrace;
489
- const firstCallFrame = Array.isArray(stack?.callFrames) ? stack?.callFrames[0] : void 0;
490
- consoleMessages.push({
491
- type: ["log", "info", "warn", "error", "debug"].includes(type) ? type : "log",
492
- text,
493
- timestamp: Math.round(Number(params.timestamp ?? Date.now())),
494
- url: typeof firstCallFrame?.url === "string" ? firstCallFrame.url : void 0,
495
- lineNumber: typeof firstCallFrame?.lineNumber === "number" ? firstCallFrame.lineNumber : void 0
496
- });
497
- return;
498
- }
499
- if (method === "Runtime.exceptionThrown") {
500
- const details = params.exceptionDetails;
501
- if (!details) return;
502
- const exception = details.exception;
503
- const stackTrace = details.stackTrace;
504
- const callFrames = Array.isArray(stackTrace?.callFrames) ? stackTrace.callFrames : [];
505
- jsErrors.push({
506
- message: typeof exception?.description === "string" ? exception.description : String(details.text ?? "JavaScript exception"),
507
- url: typeof details.url === "string" ? details.url : typeof callFrames[0]?.url === "string" ? String(callFrames[0].url) : void 0,
508
- lineNumber: typeof details.lineNumber === "number" ? details.lineNumber : void 0,
509
- columnNumber: typeof details.columnNumber === "number" ? details.columnNumber : void 0,
510
- 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,
511
- timestamp: Date.now()
512
- });
513
- }
514
- }
515
- async function ensureNetworkMonitoring(targetId) {
516
- if (networkEnabled) return;
517
- await sessionCommand(targetId, "Network.enable");
518
- networkEnabled = true;
519
- }
520
- async function ensureConsoleMonitoring(targetId) {
521
- if (consoleEnabled && errorsEnabled) return;
522
- await sessionCommand(targetId, "Runtime.enable");
523
- consoleEnabled = true;
524
- errorsEnabled = true;
525
- }
526
- async function attachTarget(targetId) {
527
- const result = await browserCommand("Target.attachToTarget", {
528
- targetId,
529
- flatten: true
530
- });
531
- connectionState?.sessions.set(targetId, result.sessionId);
532
- connectionState?.attachedTargets.set(result.sessionId, targetId);
533
- connectionState?.activeFrameIdByTarget.set(targetId, connectionState?.activeFrameIdByTarget.get(targetId) ?? null);
534
- await sessionCommand(targetId, "Page.enable");
535
- await sessionCommand(targetId, "Runtime.enable");
536
- await sessionCommand(targetId, "DOM.enable");
537
- await sessionCommand(targetId, "Accessibility.enable");
538
- return result.sessionId;
539
- }
540
- async function getTargets() {
541
- const state = connectionState;
542
- if (!state) throw new Error("CDP connection not initialized");
543
- try {
544
- const result = await browserCommand("Target.getTargets");
545
- return (result.targetInfos || []).map((target) => ({
546
- id: target.targetId,
547
- type: target.type,
548
- title: target.title,
549
- url: target.url,
550
- webSocketDebuggerUrl: ""
551
- }));
552
- } catch {
553
- return getJsonList(state.host, state.port);
554
- }
555
- }
556
- async function ensurePageTarget(targetId) {
557
- const targets = (await getTargets()).filter((target2) => target2.type === "page");
558
- if (targets.length === 0) throw new Error("No page target found");
559
- const persistedTargetId = targetId === void 0 ? connectionState?.currentTargetId : void 0;
560
- let target;
561
- if (typeof targetId === "number") {
562
- target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
563
- } else if (typeof targetId === "string") {
564
- target = targets.find((item) => item.id === targetId);
565
- if (!target) {
566
- const numericTargetId = Number(targetId);
567
- if (!Number.isNaN(numericTargetId)) {
568
- target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
569
- }
570
- }
571
- } else if (persistedTargetId) {
572
- target = targets.find((item) => item.id === persistedTargetId);
573
- }
574
- target ??= targets[0];
575
- setCurrentTargetId(target.id);
576
- await attachTarget(target.id);
577
- return target;
578
- }
579
- async function resolveBackendNodeIdByXPath(targetId, xpath) {
580
- await sessionCommand(targetId, "DOM.getDocument", { depth: 0 });
581
- const search = await sessionCommand(targetId, "DOM.performSearch", {
582
- query: xpath,
583
- includeUserAgentShadowDOM: true
584
- });
585
- try {
586
- if (!search.resultCount) {
587
- throw new Error(`Unknown ref xpath: ${xpath}`);
588
- }
589
- const { nodeIds } = await sessionCommand(targetId, "DOM.getSearchResults", {
590
- searchId: search.searchId,
591
- fromIndex: 0,
592
- toIndex: search.resultCount
593
- });
594
- for (const nodeId of nodeIds) {
595
- const described = await sessionCommand(targetId, "DOM.describeNode", {
596
- nodeId
597
- });
598
- if (described.node.backendNodeId) {
599
- return described.node.backendNodeId;
600
- }
601
- }
602
- throw new Error(`XPath resolved but no backend node id found: ${xpath}`);
603
- } finally {
604
- await sessionCommand(targetId, "DOM.discardSearchResults", { searchId: search.searchId }).catch(() => {
605
- });
606
- }
607
- }
608
- async function parseRef(ref) {
609
- const targetId = connectionState?.currentTargetId ?? "";
610
- let refs = connectionState?.refsByTarget.get(targetId) ?? {};
611
- if (!refs[ref] && targetId) {
612
- const persistedRefs = loadPersistedRefs(targetId);
613
- if (persistedRefs) {
614
- connectionState?.refsByTarget.set(targetId, persistedRefs);
615
- refs = persistedRefs;
616
- }
617
- }
618
- const found = refs[ref];
619
- if (!found) {
620
- throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
621
- }
622
- if (found.backendDOMNodeId) {
623
- return found.backendDOMNodeId;
624
- }
625
- if (targetId && found.xpath) {
626
- const backendDOMNodeId = await resolveBackendNodeIdByXPath(targetId, found.xpath);
627
- found.backendDOMNodeId = backendDOMNodeId;
628
- connectionState?.refsByTarget.set(targetId, refs);
629
- const pageUrl = await evaluate(targetId, "location.href", true).catch(() => void 0);
630
- if (pageUrl) {
631
- persistRefs(targetId, pageUrl, refs);
632
- }
633
- return backendDOMNodeId;
634
- }
635
- throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
636
- }
637
- function getRefsFilePath(targetId) {
638
- return path2.join(os2.tmpdir(), `bb-browser-refs-${targetId}.json`);
639
- }
640
- function loadPersistedRefs(targetId, expectedUrl) {
641
- try {
642
- const data = JSON.parse(readFileSync(getRefsFilePath(targetId), "utf-8"));
643
- if (data.targetId !== targetId) return null;
644
- if (expectedUrl !== void 0 && data.url !== expectedUrl) return null;
645
- if (!data.refs || typeof data.refs !== "object") return null;
646
- return data.refs;
647
- } catch {
648
- return null;
649
- }
650
- }
651
- function persistRefs(targetId, url, refs) {
652
- try {
653
- writeFileSync(getRefsFilePath(targetId), JSON.stringify({ targetId, url, timestamp: Date.now(), refs }));
654
- } catch {
655
- }
656
- }
657
- function clearPersistedRefs(targetId) {
658
- try {
659
- unlinkSync(getRefsFilePath(targetId));
660
- } catch {
661
- }
662
- }
663
- function loadBuildDomTreeScript() {
664
- const currentDir = path2.dirname(fileURLToPath(import.meta.url));
665
- const candidates = [
666
- path2.resolve(currentDir, "./extension/buildDomTree.js"),
667
- // npm installed: dist/cli.js → ../extension/buildDomTree.js
668
- path2.resolve(currentDir, "../extension/buildDomTree.js"),
669
- path2.resolve(currentDir, "../extension/dist/buildDomTree.js"),
670
- path2.resolve(currentDir, "../packages/extension/public/buildDomTree.js"),
671
- path2.resolve(currentDir, "../packages/extension/dist/buildDomTree.js"),
672
- // dev mode: packages/cli/dist/ → ../../../extension/
673
- path2.resolve(currentDir, "../../../extension/buildDomTree.js"),
674
- path2.resolve(currentDir, "../../../extension/dist/buildDomTree.js"),
675
- // dev mode: packages/cli/src/ → ../../extension/
676
- path2.resolve(currentDir, "../../extension/buildDomTree.js"),
677
- path2.resolve(currentDir, "../../../packages/extension/dist/buildDomTree.js"),
678
- path2.resolve(currentDir, "../../../packages/extension/public/buildDomTree.js")
679
- ];
680
- for (const candidate of candidates) {
681
- try {
682
- return readFileSync(candidate, "utf8");
683
- } catch {
684
- }
685
- }
686
- throw new Error("Cannot find buildDomTree.js");
687
- }
688
- async function evaluate(targetId, expression, returnByValue = true) {
689
- const result = await sessionCommand(targetId, "Runtime.evaluate", {
690
- expression,
691
- awaitPromise: true,
692
- returnByValue,
693
- replMode: true
694
- });
695
- if (result.exceptionDetails) {
696
- throw new Error(
697
- result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Runtime.evaluate failed"
698
- );
699
- }
700
- return result.result.value ?? result.result;
701
- }
702
- async function focusNode(targetId, backendNodeId) {
703
- await sessionCommand(targetId, "DOM.focus", { backendNodeId });
704
- }
705
- async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
706
- const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
707
- await sessionCommand(targetId, "Runtime.callFunctionOn", {
708
- objectId: resolved.object.objectId,
709
- functionDeclaration: `function(clearFirst) {
710
- if (typeof this.scrollIntoView === 'function') {
711
- this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
712
- }
713
- if (typeof this.focus === 'function') this.focus();
714
- if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement) {
715
- if (clearFirst) {
716
- this.value = '';
717
- this.dispatchEvent(new Event('input', { bubbles: true }));
718
- }
719
- if (typeof this.setSelectionRange === 'function') {
720
- const end = this.value.length;
721
- this.setSelectionRange(end, end);
722
- }
723
- return true;
724
- }
725
- if (this instanceof HTMLElement && this.isContentEditable) {
726
- if (clearFirst) {
727
- this.textContent = '';
728
- this.dispatchEvent(new Event('input', { bubbles: true }));
729
- }
730
- const selection = window.getSelection();
731
- if (selection) {
732
- const range = document.createRange();
733
- range.selectNodeContents(this);
734
- range.collapse(false);
735
- selection.removeAllRanges();
736
- selection.addRange(range);
737
- }
738
- return true;
739
- }
740
- return false;
741
- }`,
742
- arguments: [
743
- { value: clearFirst }
744
- ],
745
- returnByValue: true
746
- });
747
- if (text) {
748
- await focusNode(targetId, backendNodeId);
749
- await sessionCommand(targetId, "Input.insertText", { text });
750
- }
751
- }
752
- async function getInteractablePoint(targetId, backendNodeId) {
753
- const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
754
- const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
755
- objectId: resolved.object.objectId,
756
- functionDeclaration: `function() {
757
- if (!(this instanceof Element)) {
758
- throw new Error('Ref does not resolve to an element');
759
- }
760
- this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
761
- const rect = this.getBoundingClientRect();
762
- if (!rect || rect.width <= 0 || rect.height <= 0) {
763
- throw new Error('Element is not visible');
764
- }
765
- return {
766
- x: rect.left + rect.width / 2,
767
- y: rect.top + rect.height / 2,
768
- };
769
- }`,
770
- returnByValue: true
771
- });
772
- if (call.exceptionDetails) {
773
- throw new Error(call.exceptionDetails.text || "Failed to resolve element point");
774
- }
775
- const point = call.result.value;
776
- if (!point || typeof point.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
777
- throw new Error("Failed to resolve element point");
778
- }
779
- return point;
780
- }
781
- async function mouseClick(targetId, x, y) {
782
- await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
783
- await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
784
- await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
785
- }
786
- async function getAttributeValue(targetId, backendNodeId, attribute) {
787
- if (attribute === "text") {
788
- const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
789
- const call2 = await sessionCommand(targetId, "Runtime.callFunctionOn", {
790
- objectId: resolved.object.objectId,
791
- functionDeclaration: `function() { return (this instanceof HTMLElement ? this.innerText : this.textContent || '').trim(); }`,
792
- returnByValue: true
793
- });
794
- return String(call2.result.value ?? "");
795
- }
796
- const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
797
- const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
798
- objectId: result.object.objectId,
799
- 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)}) || ''; }`,
800
- returnByValue: true
801
- });
802
- return String(call.result.value ?? "");
803
- }
804
- async function buildSnapshot(targetId, request) {
805
- const script = loadBuildDomTreeScript();
806
- const buildArgs = {
807
- showHighlightElements: true,
808
- focusHighlightIndex: -1,
809
- viewportExpansion: -1,
810
- debugMode: false,
811
- startId: 0,
812
- startHighlightIndex: 0
813
- };
814
- 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({
815
- ...buildArgs
816
- })}); })()`;
817
- const value = await evaluate(targetId, expression, true);
818
- if (!value || !value.map || !value.rootId) {
819
- const title = await evaluate(targetId, "document.title", true);
820
- const pageUrl2 = await evaluate(targetId, "location.href", true);
821
- const fallbackSnapshot = {
822
- title,
823
- url: pageUrl2,
824
- lines: [title || pageUrl2],
825
- refs: {}
826
- };
827
- connectionState?.refsByTarget.set(targetId, {});
828
- persistRefs(targetId, pageUrl2, {});
829
- return fallbackSnapshot;
830
- }
831
- const snapshot = convertBuildDomTreeResult(value, {
832
- interactiveOnly: !!request.interactive,
833
- compact: !!request.compact,
834
- maxDepth: request.maxDepth,
835
- selector: request.selector
836
- });
837
- const pageUrl = await evaluate(targetId, "location.href", true);
838
- connectionState?.refsByTarget.set(targetId, snapshot.refs || {});
839
- persistRefs(targetId, pageUrl, snapshot.refs || {});
840
- return snapshot;
841
- }
842
- function convertBuildDomTreeResult(result, options) {
843
- const { interactiveOnly, compact, maxDepth, selector } = options;
844
- const { rootId, map } = result;
845
- const refs = {};
846
- const lines = [];
847
- const getRole = (node) => {
848
- const tagName = node.tagName.toLowerCase();
849
- const role = node.attributes?.role;
850
- if (role) return role;
851
- const type = node.attributes?.type?.toLowerCase() || "text";
852
- const inputRoleMap = {
853
- text: "textbox",
854
- password: "textbox",
855
- email: "textbox",
856
- url: "textbox",
857
- tel: "textbox",
858
- search: "searchbox",
859
- number: "spinbutton",
860
- range: "slider",
861
- checkbox: "checkbox",
862
- radio: "radio",
863
- button: "button",
864
- submit: "button",
865
- reset: "button",
866
- file: "button"
867
- };
868
- const roleMap = {
869
- a: "link",
870
- button: "button",
871
- input: inputRoleMap[type] || "textbox",
872
- select: "combobox",
873
- textarea: "textbox",
874
- img: "image",
875
- nav: "navigation",
876
- main: "main",
877
- header: "banner",
878
- footer: "contentinfo",
879
- aside: "complementary",
880
- form: "form",
881
- table: "table",
882
- ul: "list",
883
- ol: "list",
884
- li: "listitem",
885
- h1: "heading",
886
- h2: "heading",
887
- h3: "heading",
888
- h4: "heading",
889
- h5: "heading",
890
- h6: "heading",
891
- dialog: "dialog",
892
- article: "article",
893
- section: "region",
894
- label: "label",
895
- details: "group",
896
- summary: "button"
897
- };
898
- return roleMap[tagName] || tagName;
899
- };
900
- const collectTextContent = (node, nodeMap, depthLimit = 5) => {
901
- const texts = [];
902
- const visit = (nodeId, depth) => {
903
- if (depth > depthLimit) return;
904
- const currentNode = nodeMap[nodeId];
905
- if (!currentNode) return;
906
- if ("type" in currentNode && currentNode.type === "TEXT_NODE") {
907
- const text = currentNode.text.trim();
908
- if (text) texts.push(text);
909
- return;
910
- }
911
- for (const childId of currentNode.children || []) visit(childId, depth + 1);
912
- };
913
- for (const childId of node.children || []) visit(childId, 0);
914
- return texts.join(" ").trim();
915
- };
916
- const getName = (node) => {
917
- const attrs = node.attributes || {};
918
- return attrs["aria-label"] || attrs.title || attrs.placeholder || attrs.alt || attrs.value || collectTextContent(node, map) || attrs.name || void 0;
919
- };
920
- const truncateText = (text, length = 50) => text.length <= length ? text : `${text.slice(0, length - 3)}...`;
921
- const selectorText = selector?.trim().toLowerCase();
922
- const matchesSelector = (node, role, name) => {
923
- if (!selectorText) return true;
924
- const haystack = [node.tagName, role, name, node.xpath || "", ...Object.values(node.attributes || {})].join(" ").toLowerCase();
925
- return haystack.includes(selectorText);
926
- };
927
- if (interactiveOnly) {
928
- 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));
929
- for (const { node } of interactiveNodes) {
930
- const refId = String(node.highlightIndex);
931
- const role = getRole(node);
932
- const name = getName(node);
933
- if (!matchesSelector(node, role, name)) continue;
934
- let line = `${role} [ref=${refId}]`;
935
- if (name) line += ` ${JSON.stringify(truncateText(name))}`;
936
- lines.push(line);
937
- refs[refId] = {
938
- xpath: node.xpath || "",
939
- role,
940
- name,
941
- tagName: node.tagName.toLowerCase()
942
- };
943
- }
944
- return { snapshot: lines.join("\n"), refs };
945
- }
946
- const walk = (nodeId, depth) => {
947
- if (maxDepth !== void 0 && depth > maxDepth) return;
948
- const node = map[nodeId];
949
- if (!node) return;
950
- if ("type" in node && node.type === "TEXT_NODE") {
951
- const text = node.text.trim();
952
- if (!text) return;
953
- lines.push(`${" ".repeat(depth)}- text ${JSON.stringify(truncateText(text, compact ? 80 : 120))}`);
954
- return;
955
- }
956
- const role = getRole(node);
957
- const name = getName(node);
958
- if (!matchesSelector(node, role, name)) {
959
- for (const childId of node.children || []) walk(childId, depth + 1);
960
- return;
961
- }
962
- const indent = " ".repeat(depth);
963
- const refId = node.highlightIndex !== void 0 && node.highlightIndex !== null ? String(node.highlightIndex) : null;
964
- let line = `${indent}- ${role}`;
965
- if (refId) line += ` [ref=${refId}]`;
966
- if (name) line += ` ${JSON.stringify(truncateText(name, compact ? 50 : 80))}`;
967
- if (!compact) line += ` <${node.tagName.toLowerCase()}>`;
968
- lines.push(line);
969
- if (refId) {
970
- refs[refId] = {
971
- xpath: node.xpath || "",
972
- role,
973
- name,
974
- tagName: node.tagName.toLowerCase()
975
- };
976
- }
977
- for (const childId of node.children || []) walk(childId, depth + 1);
978
- };
979
- walk(rootId, 0);
980
- return { snapshot: lines.join("\n"), refs };
981
- }
982
- function ok(id, data) {
983
- return { id, success: true, data };
984
- }
985
- function fail(id, error) {
986
- return { id, success: false, error: buildRequestError(error).message };
987
- }
988
- async function ensureCdpConnection() {
989
- if (connectionState) return;
990
- if (reconnecting) return reconnecting;
991
- reconnecting = (async () => {
992
- const discovered = await discoverCdpPort();
993
- if (!discovered) {
994
- throw new Error("No browser connection found");
995
- }
996
- const version = await getJsonVersion(discovered.host, discovered.port);
997
- const wsUrl = version.webSocketDebuggerUrl;
998
- const socket = await connectWebSocket(wsUrl);
999
- connectionState = createState(discovered.host, discovered.port, wsUrl, socket);
1000
- })();
1001
- try {
1002
- await reconnecting;
1003
- } finally {
1004
- reconnecting = null;
1005
- }
1006
- }
1007
- async function sendCommand(request) {
1008
- try {
1009
- await ensureCdpConnection();
1010
- const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("\u8BF7\u6C42\u8D85\u65F6")), COMMAND_TIMEOUT));
1011
- return await Promise.race([dispatchRequest(request), timeout]);
1012
- } catch (error) {
1013
- return fail(request.id, error);
1014
- }
1015
- }
1016
- async function dispatchRequest(request) {
1017
- const target = await ensurePageTarget(request.tabId);
1018
- switch (request.action) {
1019
- case "open": {
1020
- if (!request.url) return fail(request.id, "Missing url parameter");
1021
- if (request.tabId === void 0) {
1022
- const created = await browserCommand("Target.createTarget", { url: request.url, background: true });
1023
- const newTarget = await ensurePageTarget(created.targetId);
1024
- return ok(request.id, { url: request.url, tabId: newTarget.id });
1025
- }
1026
- await pageCommand(target.id, "Page.navigate", { url: request.url });
1027
- connectionState?.refsByTarget.delete(target.id);
1028
- clearPersistedRefs(target.id);
1029
- return ok(request.id, { url: request.url, title: target.title, tabId: target.id });
1030
- }
1031
- case "snapshot": {
1032
- const snapshotData = await buildSnapshot(target.id, request);
1033
- return ok(request.id, { title: target.title, url: target.url, snapshotData });
1034
- }
1035
- case "click":
1036
- case "hover": {
1037
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1038
- const backendNodeId = await parseRef(request.ref);
1039
- const point = await getInteractablePoint(target.id, backendNodeId);
1040
- await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
1041
- if (request.action === "click") await mouseClick(target.id, point.x, point.y);
1042
- return ok(request.id, {});
1043
- }
1044
- case "fill":
1045
- case "type": {
1046
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1047
- if (request.text == null) return fail(request.id, "Missing text parameter");
1048
- const backendNodeId = await parseRef(request.ref);
1049
- await insertTextIntoNode(target.id, backendNodeId, request.text, request.action === "fill");
1050
- return ok(request.id, { value: request.text });
1051
- }
1052
- case "check":
1053
- case "uncheck": {
1054
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1055
- const backendNodeId = await parseRef(request.ref);
1056
- const desired = request.action === "check";
1057
- const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
1058
- await sessionCommand(target.id, "Runtime.callFunctionOn", {
1059
- objectId: resolved.object.objectId,
1060
- functionDeclaration: `function() { this.checked = ${desired}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
1061
- });
1062
- return ok(request.id, {});
1063
- }
1064
- case "select": {
1065
- if (!request.ref || request.value == null) return fail(request.id, "Missing ref or value parameter");
1066
- const backendNodeId = await parseRef(request.ref);
1067
- const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
1068
- await sessionCommand(target.id, "Runtime.callFunctionOn", {
1069
- objectId: resolved.object.objectId,
1070
- functionDeclaration: `function() { this.value = ${JSON.stringify(request.value)}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
1071
- });
1072
- return ok(request.id, { value: request.value });
1073
- }
1074
- case "get": {
1075
- if (!request.attribute) return fail(request.id, "Missing attribute parameter");
1076
- if (request.attribute === "url" && !request.ref) {
1077
- return ok(request.id, { value: await evaluate(target.id, "location.href", true) });
1078
- }
1079
- if (request.attribute === "title" && !request.ref) {
1080
- return ok(request.id, { value: await evaluate(target.id, "document.title", true) });
1081
- }
1082
- if (!request.ref) return fail(request.id, "Missing ref parameter");
1083
- const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
1084
- return ok(request.id, { value });
1085
- }
1086
- case "screenshot": {
1087
- const result = await sessionCommand(target.id, "Page.captureScreenshot", { format: "png", fromSurface: true });
1088
- return ok(request.id, { dataUrl: `data:image/png;base64,${result.data}` });
1089
- }
1090
- case "close": {
1091
- await browserCommand("Target.closeTarget", { targetId: target.id });
1092
- connectionState?.refsByTarget.delete(target.id);
1093
- clearPersistedRefs(target.id);
1094
- return ok(request.id, {});
1095
- }
1096
- case "wait": {
1097
- await new Promise((resolve3) => setTimeout(resolve3, request.ms ?? 1e3));
1098
- return ok(request.id, {});
1099
- }
1100
- case "press": {
1101
- if (!request.key) return fail(request.id, "Missing key parameter");
1102
- await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyDown", key: request.key });
1103
- if (request.key.length === 1) {
1104
- await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "char", text: request.key, key: request.key });
1105
- }
1106
- await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyUp", key: request.key });
1107
- return ok(request.id, {});
1108
- }
1109
- case "scroll": {
1110
- const deltaY = request.direction === "up" ? -(request.pixels ?? 300) : request.pixels ?? 300;
1111
- await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseWheel", x: 0, y: 0, deltaX: 0, deltaY });
1112
- return ok(request.id, {});
1113
- }
1114
- case "back": {
1115
- await evaluate(target.id, "history.back(); undefined");
1116
- return ok(request.id, {});
1117
- }
1118
- case "forward": {
1119
- await evaluate(target.id, "history.forward(); undefined");
1120
- return ok(request.id, {});
1121
- }
1122
- case "refresh": {
1123
- await sessionCommand(target.id, "Page.reload", { ignoreCache: false });
1124
- return ok(request.id, {});
1125
- }
1126
- case "eval": {
1127
- if (!request.script) return fail(request.id, "Missing script parameter");
1128
- const result = await evaluate(target.id, request.script, true);
1129
- return ok(request.id, { result });
1130
- }
1131
- case "tab_list": {
1132
- 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 }));
1133
- return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
1134
- }
1135
- case "tab_new": {
1136
- const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank", background: true });
1137
- return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
1138
- }
1139
- case "tab_select": {
1140
- const tabs = (await getTargets()).filter((item) => item.type === "page");
1141
- const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
1142
- if (!selected) return fail(request.id, "Tab not found");
1143
- setCurrentTargetId(selected.id);
1144
- await attachTarget(selected.id);
1145
- return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
1146
- }
1147
- case "tab_close": {
1148
- const tabs = (await getTargets()).filter((item) => item.type === "page");
1149
- const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
1150
- if (!selected) return fail(request.id, "Tab not found");
1151
- await browserCommand("Target.closeTarget", { targetId: selected.id });
1152
- connectionState?.refsByTarget.delete(selected.id);
1153
- if (connectionState?.currentTargetId === selected.id) {
1154
- setCurrentTargetId(void 0);
1155
- }
1156
- clearPersistedRefs(selected.id);
1157
- return ok(request.id, { tabId: selected.id });
1158
- }
1159
- case "frame": {
1160
- if (!request.selector) return fail(request.id, "Missing selector parameter");
1161
- const document = await pageCommand(target.id, "DOM.getDocument", {});
1162
- const node = await pageCommand(target.id, "DOM.querySelector", { nodeId: document.root.nodeId, selector: request.selector });
1163
- if (!node.nodeId) return fail(request.id, `\u627E\u4E0D\u5230 iframe: ${request.selector}`);
1164
- const described = await pageCommand(target.id, "DOM.describeNode", { nodeId: node.nodeId });
1165
- const frameId = described.node.frameId;
1166
- const nodeName = String(described.node.nodeName ?? "").toLowerCase();
1167
- if (!frameId) return fail(request.id, `\u65E0\u6CD5\u83B7\u53D6 iframe frameId: ${request.selector}`);
1168
- if (nodeName && nodeName !== "iframe" && nodeName !== "frame") return fail(request.id, `\u5143\u7D20\u4E0D\u662F iframe: ${nodeName}`);
1169
- connectionState?.activeFrameIdByTarget.set(target.id, frameId);
1170
- const attributes = described.node.attributes ?? [];
1171
- const attrMap = {};
1172
- for (let i = 0; i < attributes.length; i += 2) attrMap[String(attributes[i])] = String(attributes[i + 1] ?? "");
1173
- return ok(request.id, { frameInfo: { selector: request.selector, name: attrMap.name ?? "", url: attrMap.src ?? "", frameId } });
1174
- }
1175
- case "frame_main": {
1176
- connectionState?.activeFrameIdByTarget.set(target.id, null);
1177
- return ok(request.id, { frameInfo: { frameId: 0 } });
1178
- }
1179
- case "dialog": {
1180
- connectionState?.dialogHandlers.set(target.id, { accept: request.dialogResponse !== "dismiss", ...request.promptText !== void 0 ? { promptText: request.promptText } : {} });
1181
- await sessionCommand(target.id, "Page.enable");
1182
- return ok(request.id, { dialog: { armed: true, response: request.dialogResponse ?? "accept" } });
1183
- }
1184
- case "network": {
1185
- const subCommand = request.networkCommand ?? "requests";
1186
- switch (subCommand) {
1187
- case "requests": {
1188
- await ensureNetworkMonitoring(target.id);
1189
- const requests = Array.from(networkRequests.values()).filter((item) => !request.filter || item.url.includes(request.filter));
1190
- if (request.withBody) {
1191
- await Promise.all(requests.map(async (item) => {
1192
- if (item.failed || item.responseBody !== void 0 || item.bodyError !== void 0) return;
1193
- try {
1194
- const body = await sessionCommand(target.id, "Network.getResponseBody", { requestId: item.requestId });
1195
- item.responseBody = body.body;
1196
- item.responseBodyBase64 = body.base64Encoded;
1197
- } catch (error) {
1198
- item.bodyError = error instanceof Error ? error.message : String(error);
1199
- }
1200
- }));
1201
- }
1202
- return ok(request.id, { networkRequests: requests });
1203
- }
1204
- case "route":
1205
- return ok(request.id, { routeCount: 0 });
1206
- case "unroute":
1207
- return ok(request.id, { routeCount: 0 });
1208
- case "clear":
1209
- networkRequests.clear();
1210
- return ok(request.id, {});
1211
- default:
1212
- return fail(request.id, `Unknown network subcommand: ${subCommand}`);
1213
- }
1214
- }
1215
- case "console": {
1216
- const subCommand = request.consoleCommand ?? "get";
1217
- await ensureConsoleMonitoring(target.id);
1218
- switch (subCommand) {
1219
- case "get":
1220
- return ok(request.id, { consoleMessages: consoleMessages.filter((item) => !request.filter || item.text.includes(request.filter)) });
1221
- case "clear":
1222
- consoleMessages.length = 0;
1223
- return ok(request.id, {});
1224
- default:
1225
- return fail(request.id, `Unknown console subcommand: ${subCommand}`);
1226
- }
1227
- }
1228
- case "errors": {
1229
- const subCommand = request.errorsCommand ?? "get";
1230
- await ensureConsoleMonitoring(target.id);
1231
- switch (subCommand) {
1232
- case "get":
1233
- return ok(request.id, { jsErrors: jsErrors.filter((item) => !request.filter || item.message.includes(request.filter) || item.url?.includes(request.filter)) });
1234
- case "clear":
1235
- jsErrors.length = 0;
1236
- return ok(request.id, {});
1237
- default:
1238
- return fail(request.id, `Unknown errors subcommand: ${subCommand}`);
1239
- }
1240
- }
1241
- case "trace": {
1242
- const subCommand = request.traceCommand ?? "status";
1243
- switch (subCommand) {
1244
- case "start":
1245
- traceRecording = true;
1246
- traceEvents.length = 0;
1247
- return ok(request.id, { traceStatus: { recording: true, eventCount: 0 } });
1248
- case "stop": {
1249
- traceRecording = false;
1250
- return ok(request.id, { traceEvents: [...traceEvents], traceStatus: { recording: false, eventCount: traceEvents.length } });
1251
- }
1252
- case "status":
1253
- return ok(request.id, { traceStatus: { recording: traceRecording, eventCount: traceEvents.length } });
1254
- default:
1255
- return fail(request.id, `Unknown trace subcommand: ${subCommand}`);
1256
- }
1257
- }
1258
- default:
1259
- return fail(request.id, `Action not yet supported in direct CDP mode: ${request.action}`);
1260
- }
1261
- }
1262
-
1263
- // packages/cli/src/monitor-manager.ts
1264
- import { spawn as spawn2 } from "child_process";
1265
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, unlink } from "fs/promises";
1266
- import { request as httpRequest2 } from "http";
1267
- import { randomBytes } from "crypto";
1268
- import { fileURLToPath as fileURLToPath2 } from "url";
1269
- import { dirname, resolve } from "path";
1270
- import { existsSync as existsSync2 } from "fs";
1271
- import os3 from "os";
1272
- import path3 from "path";
1273
- var MONITOR_DIR = path3.join(os3.homedir(), ".bb-browser");
1274
- var PID_FILE = path3.join(MONITOR_DIR, "monitor.pid");
1275
- var PORT_FILE = path3.join(MONITOR_DIR, "monitor.port");
1276
- var TOKEN_FILE = path3.join(MONITOR_DIR, "monitor.token");
1277
- var DEFAULT_MONITOR_PORT = 19826;
1278
- function httpJson(method, url, token, body) {
1279
- return new Promise((resolve3, reject) => {
1280
- const parsed = new URL(url);
24
+ var DAEMON_DIR = path.join(os.homedir(), ".bb-browser");
25
+ var TOKEN_FILE = path.join(DAEMON_DIR, "daemon.token");
26
+ var cachedToken = null;
27
+ var daemonReady = false;
28
+ function httpJson(method, urlPath, token, body, timeout = 5e3) {
29
+ return new Promise((resolve2, reject) => {
1281
30
  const payload = body !== void 0 ? JSON.stringify(body) : void 0;
1282
- const req = httpRequest2(
31
+ const req = httpRequest(
1283
32
  {
1284
- hostname: parsed.hostname,
1285
- port: parsed.port,
1286
- path: parsed.pathname,
33
+ hostname: "127.0.0.1",
34
+ port: DAEMON_PORT,
35
+ path: urlPath,
1287
36
  method,
1288
37
  headers: {
1289
38
  Authorization: `Bearer ${token}`,
1290
- ...payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}
39
+ ...payload ? {
40
+ "Content-Type": "application/json",
41
+ "Content-Length": Buffer.byteLength(payload)
42
+ } : {}
1291
43
  },
1292
- timeout: 5e3
44
+ timeout
1293
45
  },
1294
46
  (res) => {
1295
47
  const chunks = [];
@@ -1297,13 +49,13 @@ function httpJson(method, url, token, body) {
1297
49
  res.on("end", () => {
1298
50
  const raw = Buffer.concat(chunks).toString("utf8");
1299
51
  if ((res.statusCode ?? 500) >= 400) {
1300
- reject(new Error(`Monitor HTTP ${res.statusCode}: ${raw}`));
52
+ reject(new Error(`Daemon HTTP ${res.statusCode}: ${raw}`));
1301
53
  return;
1302
54
  }
1303
55
  try {
1304
- resolve3(JSON.parse(raw));
56
+ resolve2(JSON.parse(raw));
1305
57
  } catch {
1306
- reject(new Error(`Invalid JSON from monitor: ${raw}`));
58
+ reject(new Error(`Invalid JSON from daemon: ${raw}`));
1307
59
  }
1308
60
  });
1309
61
  }
@@ -1311,64 +63,52 @@ function httpJson(method, url, token, body) {
1311
63
  req.on("error", reject);
1312
64
  req.on("timeout", () => {
1313
65
  req.destroy();
1314
- reject(new Error("Monitor request timed out"));
66
+ reject(new Error("Daemon request timed out"));
1315
67
  });
1316
68
  if (payload) req.write(payload);
1317
69
  req.end();
1318
70
  });
1319
71
  }
1320
- async function readPortFile() {
72
+ async function readToken() {
1321
73
  try {
1322
- const raw = await readFile2(PORT_FILE, "utf8");
1323
- const port = Number.parseInt(raw.trim(), 10);
1324
- return Number.isInteger(port) && port > 0 ? port : null;
74
+ return (await readFile(TOKEN_FILE, "utf8")).trim();
1325
75
  } catch {
1326
76
  return null;
1327
77
  }
1328
78
  }
1329
- async function readTokenFile() {
1330
- try {
1331
- return (await readFile2(TOKEN_FILE, "utf8")).trim();
1332
- } catch {
1333
- return null;
79
+ function getDaemonPath() {
80
+ const currentFile = fileURLToPath(import.meta.url);
81
+ const currentDir = dirname(currentFile);
82
+ const sameDirPath = resolve(currentDir, "daemon.js");
83
+ if (existsSync(sameDirPath)) {
84
+ return sameDirPath;
1334
85
  }
86
+ return resolve(currentDir, "../../daemon/dist/index.js");
1335
87
  }
1336
- async function ensureMonitorRunning() {
1337
- const existingPort = await readPortFile();
1338
- const existingToken = await readTokenFile();
1339
- if (existingPort && existingToken) {
88
+ async function ensureDaemon() {
89
+ if (daemonReady && cachedToken) {
1340
90
  try {
1341
- const status = await httpJson(
1342
- "GET",
1343
- `http://127.0.0.1:${existingPort}/status`,
1344
- existingToken
1345
- );
91
+ await httpJson("GET", "/status", cachedToken, void 0, 2e3);
92
+ return;
93
+ } catch {
94
+ daemonReady = false;
95
+ cachedToken = null;
96
+ }
97
+ }
98
+ let token = await readToken();
99
+ if (token) {
100
+ try {
101
+ const status = await httpJson("GET", "/status", token, void 0, 2e3);
1346
102
  if (status.running) {
1347
- return { port: existingPort, token: existingToken };
103
+ cachedToken = token;
104
+ daemonReady = true;
105
+ return;
1348
106
  }
1349
107
  } catch {
1350
108
  }
1351
109
  }
1352
- const cdp = await discoverCdpPort();
1353
- if (!cdp) {
1354
- throw new Error("Cannot start monitor: no browser connection found");
1355
- }
1356
- const token = randomBytes(32).toString("hex");
1357
- const monitorPort = DEFAULT_MONITOR_PORT;
1358
- const monitorScript = findMonitorScript();
1359
- await mkdir2(MONITOR_DIR, { recursive: true });
1360
- await writeFile2(TOKEN_FILE, token, { mode: 384 });
1361
- const child = spawn2(process.execPath, [
1362
- monitorScript,
1363
- "--cdp-host",
1364
- cdp.host,
1365
- "--cdp-port",
1366
- String(cdp.port),
1367
- "--monitor-port",
1368
- String(monitorPort),
1369
- "--token",
1370
- token
1371
- ], {
110
+ const daemonPath = getDaemonPath();
111
+ const child = spawn(process.execPath, [daemonPath], {
1372
112
  detached: true,
1373
113
  stdio: "ignore"
1374
114
  });
@@ -1376,51 +116,43 @@ async function ensureMonitorRunning() {
1376
116
  const deadline = Date.now() + 5e3;
1377
117
  while (Date.now() < deadline) {
1378
118
  await new Promise((r) => setTimeout(r, 200));
119
+ token = await readToken();
120
+ if (!token) continue;
1379
121
  try {
1380
- const status = await httpJson(
1381
- "GET",
1382
- `http://127.0.0.1:${monitorPort}/status`,
1383
- token
1384
- );
122
+ const status = await httpJson("GET", "/status", token, void 0, 2e3);
1385
123
  if (status.running) {
1386
- return { port: monitorPort, token };
124
+ cachedToken = token;
125
+ daemonReady = true;
126
+ return;
1387
127
  }
1388
128
  } catch {
1389
129
  }
1390
130
  }
1391
- throw new Error("Monitor process did not start in time");
1392
- }
1393
- async function monitorCommand(request) {
1394
- const { port, token } = await ensureMonitorRunning();
1395
- return httpJson(
1396
- "POST",
1397
- `http://127.0.0.1:${port}/command`,
1398
- token,
1399
- request
131
+ throw new Error(
132
+ "bb-browser: Daemon did not start in time.\n\nMake sure Chrome is installed, then try again."
1400
133
  );
1401
134
  }
1402
- function findMonitorScript() {
1403
- const currentFile = fileURLToPath2(import.meta.url);
1404
- const currentDir = dirname(currentFile);
1405
- const candidates = [
1406
- // Built output (tsup puts it next to cli.js)
1407
- resolve(currentDir, "cdp-monitor.js"),
1408
- // Development: packages/cli/src -> packages/cli/dist
1409
- resolve(currentDir, "../dist/cdp-monitor.js"),
1410
- // Monorepo root dist
1411
- resolve(currentDir, "../../dist/cdp-monitor.js"),
1412
- resolve(currentDir, "../../../dist/cdp-monitor.js")
1413
- ];
1414
- for (const candidate of candidates) {
1415
- if (existsSync2(candidate)) {
1416
- return candidate;
1417
- }
135
+ async function daemonCommand(request) {
136
+ if (!cachedToken) {
137
+ cachedToken = await readToken();
138
+ }
139
+ if (!cachedToken) {
140
+ throw new Error("No daemon token found. Is the daemon running?");
141
+ }
142
+ return httpJson("POST", "/command", cachedToken, request, COMMAND_TIMEOUT);
143
+ }
144
+ async function getDaemonStatus() {
145
+ const token = cachedToken ?? await readToken();
146
+ if (!token) return null;
147
+ try {
148
+ return await httpJson("GET", "/status", token, void 0, 2e3);
149
+ } catch {
150
+ return null;
1418
151
  }
1419
- return candidates[0];
1420
152
  }
153
+ var ensureDaemonRunning = ensureDaemon;
1421
154
 
1422
155
  // packages/cli/src/client.ts
1423
- var MONITOR_ACTIONS = /* @__PURE__ */ new Set(["network", "console", "errors", "trace"]);
1424
156
  var jqExpression;
1425
157
  function setJqExpression(expression) {
1426
158
  jqExpression = expression;
@@ -1438,43 +170,14 @@ function handleJqResponse(response) {
1438
170
  printJqResults(response);
1439
171
  }
1440
172
  }
1441
- async function sendCommand2(request) {
1442
- if (MONITOR_ACTIONS.has(request.action)) {
1443
- try {
1444
- return await monitorCommand(request);
1445
- } catch {
1446
- return sendCommand(request);
1447
- }
1448
- }
1449
- return sendCommand(request);
1450
- }
1451
-
1452
- // packages/cli/src/daemon-manager.ts
1453
- import { fileURLToPath as fileURLToPath3 } from "url";
1454
- import { dirname as dirname2, resolve as resolve2 } from "path";
1455
- import { existsSync as existsSync3 } from "fs";
1456
- async function isDaemonRunning() {
1457
- return await isManagedBrowserRunning();
1458
- }
1459
- async function ensureDaemonRunning() {
1460
- try {
1461
- await ensureCdpConnection();
1462
- } catch (error) {
1463
- if (error instanceof Error && error.message.includes("No browser connection found")) {
1464
- throw new Error([
1465
- "bb-browser: Could not start browser.",
1466
- "",
1467
- "Make sure Chrome is installed, then try again.",
1468
- "Or specify a CDP port manually: bb-browser --port 9222"
1469
- ].join("\n"));
1470
- }
1471
- throw error;
1472
- }
173
+ async function sendCommand(request) {
174
+ await ensureDaemon();
175
+ return daemonCommand(request);
1473
176
  }
1474
177
 
1475
178
  // packages/cli/src/history-sqlite.ts
1476
- import { copyFileSync, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "fs";
1477
- import { execSync as execSync2 } from "child_process";
179
+ import { copyFileSync, existsSync as existsSync2, unlinkSync } from "fs";
180
+ import { execSync } from "child_process";
1478
181
  import { homedir, tmpdir } from "os";
1479
182
  import { join } from "path";
1480
183
  function getHistoryPathCandidates() {
@@ -1497,7 +200,7 @@ function getHistoryPathCandidates() {
1497
200
  }
1498
201
  function findHistoryPath() {
1499
202
  for (const historyPath of getHistoryPathCandidates()) {
1500
- if (existsSync4(historyPath)) {
203
+ if (existsSync2(historyPath)) {
1501
204
  return historyPath;
1502
205
  }
1503
206
  }
@@ -1522,7 +225,7 @@ function runHistoryQuery(sql, mapRow) {
1522
225
  copyFileSync(historyPath, tmpPath);
1523
226
  const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
1524
227
  const escapedSql = sql.replace(/"/g, '\\"');
1525
- const output = execSync2(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
228
+ const output = execSync(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
1526
229
  encoding: "utf-8",
1527
230
  stdio: ["pipe", "pipe", "pipe"]
1528
231
  });
@@ -1531,7 +234,7 @@ function runHistoryQuery(sql, mapRow) {
1531
234
  return [];
1532
235
  } finally {
1533
236
  try {
1534
- unlinkSync2(tmpPath);
237
+ unlinkSync(tmpPath);
1535
238
  } catch {
1536
239
  }
1537
240
  }
@@ -1617,18 +320,18 @@ function getHistoryDomains(days) {
1617
320
  }
1618
321
 
1619
322
  // packages/cli/src/commands/site.ts
1620
- import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync5, mkdirSync } from "fs";
323
+ import { readFileSync, readdirSync, existsSync as existsSync3, mkdirSync } from "fs";
1621
324
  import { join as join2, relative } from "path";
1622
325
  import { homedir as homedir2 } from "os";
1623
- import { execSync as execSync3 } from "child_process";
326
+ import { execSync as execSync2 } from "child_process";
1624
327
  var BB_DIR = join2(homedir2(), ".bb-browser");
1625
328
  var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
1626
329
  var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
1627
330
  var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
1628
331
  function checkCliUpdate() {
1629
332
  try {
1630
- const current = execSync3("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1631
- const latest = execSync3("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
333
+ const current = execSync2("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
334
+ const latest = execSync2("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1632
335
  if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
1633
336
  console.log(`
1634
337
  \u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
@@ -1643,7 +346,7 @@ function exitJsonError(error, extra = {}) {
1643
346
  function parseSiteMeta(filePath, source) {
1644
347
  let content;
1645
348
  try {
1646
- content = readFileSync2(filePath, "utf-8");
349
+ content = readFileSync(filePath, "utf-8");
1647
350
  } catch {
1648
351
  return null;
1649
352
  }
@@ -1703,7 +406,7 @@ function parseSiteMeta(filePath, source) {
1703
406
  return meta;
1704
407
  }
1705
408
  function scanSites(dir, source) {
1706
- if (!existsSync5(dir)) return [];
409
+ if (!existsSync3(dir)) return [];
1707
410
  const sites = [];
1708
411
  function walk(currentDir) {
1709
412
  let entries;
@@ -1825,13 +528,13 @@ function siteSearch(query, options) {
1825
528
  }
1826
529
  function siteUpdate(options = {}) {
1827
530
  mkdirSync(BB_DIR, { recursive: true });
1828
- const updateMode = existsSync5(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
531
+ const updateMode = existsSync3(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
1829
532
  if (updateMode === "pull") {
1830
533
  if (!options.json) {
1831
534
  console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
1832
535
  }
1833
536
  try {
1834
- execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
537
+ execSync2("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
1835
538
  if (!options.json) {
1836
539
  console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
1837
540
  console.log("");
@@ -1852,7 +555,7 @@ function siteUpdate(options = {}) {
1852
555
  console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
1853
556
  }
1854
557
  try {
1855
- execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
558
+ execSync2(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
1856
559
  if (!options.json) {
1857
560
  console.log("\u514B\u9686\u5B8C\u6210\u3002");
1858
561
  console.log("");
@@ -2061,12 +764,12 @@ async function siteRun(name, args, options) {
2061
764
  process.exit(1);
2062
765
  }
2063
766
  }
2064
- const jsContent = readFileSync2(site.filePath, "utf-8");
767
+ const jsContent = readFileSync(site.filePath, "utf-8");
2065
768
  const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
2066
769
  const argsJson = JSON.stringify(argMap);
2067
770
  const script = `(${jsBody})(${argsJson})`;
2068
771
  if (options.openclaw) {
2069
- const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-HBJH6UFO.js");
772
+ const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-Q6EFUQCH.js");
2070
773
  let targetId;
2071
774
  if (site.domain) {
2072
775
  const tabs = ocGetTabs();
@@ -2075,7 +778,7 @@ async function siteRun(name, args, options) {
2075
778
  targetId = existing.targetId;
2076
779
  } else {
2077
780
  targetId = ocOpenTab(`https://${site.domain}`);
2078
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
781
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2079
782
  }
2080
783
  } else {
2081
784
  const tabs = ocGetTabs();
@@ -2121,7 +824,7 @@ async function siteRun(name, args, options) {
2121
824
  let targetTabId = options.tabId;
2122
825
  if (!targetTabId && site.domain) {
2123
826
  const listReq = { id: generateId(), action: "tab_list" };
2124
- const listResp = await sendCommand2(listReq);
827
+ const listResp = await sendCommand(listReq);
2125
828
  if (listResp.success && listResp.data?.tabs) {
2126
829
  const matchingTab = listResp.data.tabs.find(
2127
830
  (tab) => matchTabOrigin(tab.url, site.domain)
@@ -2131,17 +834,17 @@ async function siteRun(name, args, options) {
2131
834
  }
2132
835
  }
2133
836
  if (!targetTabId) {
2134
- const newResp = await sendCommand2({
837
+ const newResp = await sendCommand({
2135
838
  id: generateId(),
2136
839
  action: "tab_new",
2137
840
  url: `https://${site.domain}`
2138
841
  });
2139
842
  targetTabId = newResp.data?.tabId;
2140
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
843
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2141
844
  }
2142
845
  }
2143
846
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
2144
- const evalResp = await sendCommand2(evalReq);
847
+ const evalResp = await sendCommand(evalReq);
2145
848
  if (!evalResp.success) {
2146
849
  const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
2147
850
  if (options.json) {
@@ -2277,9 +980,9 @@ async function siteCommand(args, options = {}) {
2277
980
  }
2278
981
  function silentUpdate() {
2279
982
  const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
2280
- if (!existsSync5(gitDir)) return;
2281
- import("child_process").then(({ spawn: spawn3 }) => {
2282
- const child = spawn3("git", ["pull", "--ff-only"], {
983
+ if (!existsSync3(gitDir)) return;
984
+ import("child_process").then(({ spawn: spawn2 }) => {
985
+ const child = spawn2("git", ["pull", "--ff-only"], {
2283
986
  cwd: COMMUNITY_SITES_DIR,
2284
987
  stdio: "ignore",
2285
988
  detached: true
@@ -2315,7 +1018,7 @@ async function openCommand(url, options = {}) {
2315
1018
  request.tabId = tabId;
2316
1019
  }
2317
1020
  }
2318
- const response = await sendCommand2(request);
1021
+ const response = await sendCommand(request);
2319
1022
  if (options.json) {
2320
1023
  console.log(JSON.stringify(response, null, 2));
2321
1024
  } else {
@@ -2351,7 +1054,7 @@ async function snapshotCommand(options = {}) {
2351
1054
  selector: options.selector,
2352
1055
  tabId: options.tabId
2353
1056
  };
2354
- const response = await sendCommand2(request);
1057
+ const response = await sendCommand(request);
2355
1058
  if (options.json) {
2356
1059
  console.log(JSON.stringify(response, null, 2));
2357
1060
  } else {
@@ -2370,7 +1073,7 @@ async function snapshotCommand(options = {}) {
2370
1073
  }
2371
1074
 
2372
1075
  // packages/cli/src/commands/click.ts
2373
- function parseRef2(ref) {
1076
+ function parseRef(ref) {
2374
1077
  return ref.startsWith("@") ? ref.slice(1) : ref;
2375
1078
  }
2376
1079
  async function clickCommand(ref, options = {}) {
@@ -2378,14 +1081,14 @@ async function clickCommand(ref, options = {}) {
2378
1081
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2379
1082
  }
2380
1083
  await ensureDaemonRunning();
2381
- const parsedRef = parseRef2(ref);
1084
+ const parsedRef = parseRef(ref);
2382
1085
  const request = {
2383
1086
  id: generateId(),
2384
1087
  action: "click",
2385
1088
  ref: parsedRef,
2386
1089
  tabId: options.tabId
2387
1090
  };
2388
- const response = await sendCommand2(request);
1091
+ const response = await sendCommand(request);
2389
1092
  if (options.json) {
2390
1093
  console.log(JSON.stringify(response, null, 2));
2391
1094
  } else {
@@ -2405,7 +1108,7 @@ async function clickCommand(ref, options = {}) {
2405
1108
  }
2406
1109
 
2407
1110
  // packages/cli/src/commands/hover.ts
2408
- function parseRef3(ref) {
1111
+ function parseRef2(ref) {
2409
1112
  return ref.startsWith("@") ? ref.slice(1) : ref;
2410
1113
  }
2411
1114
  async function hoverCommand(ref, options = {}) {
@@ -2413,14 +1116,14 @@ async function hoverCommand(ref, options = {}) {
2413
1116
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2414
1117
  }
2415
1118
  await ensureDaemonRunning();
2416
- const parsedRef = parseRef3(ref);
1119
+ const parsedRef = parseRef2(ref);
2417
1120
  const request = {
2418
1121
  id: generateId(),
2419
1122
  action: "hover",
2420
1123
  ref: parsedRef,
2421
1124
  tabId: options.tabId
2422
1125
  };
2423
- const response = await sendCommand2(request);
1126
+ const response = await sendCommand(request);
2424
1127
  if (options.json) {
2425
1128
  console.log(JSON.stringify(response, null, 2));
2426
1129
  } else {
@@ -2440,7 +1143,7 @@ async function hoverCommand(ref, options = {}) {
2440
1143
  }
2441
1144
 
2442
1145
  // packages/cli/src/commands/fill.ts
2443
- function parseRef4(ref) {
1146
+ function parseRef3(ref) {
2444
1147
  return ref.startsWith("@") ? ref.slice(1) : ref;
2445
1148
  }
2446
1149
  async function fillCommand(ref, text, options = {}) {
@@ -2451,7 +1154,7 @@ async function fillCommand(ref, text, options = {}) {
2451
1154
  throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
2452
1155
  }
2453
1156
  await ensureDaemonRunning();
2454
- const parsedRef = parseRef4(ref);
1157
+ const parsedRef = parseRef3(ref);
2455
1158
  const request = {
2456
1159
  id: generateId(),
2457
1160
  action: "fill",
@@ -2459,7 +1162,7 @@ async function fillCommand(ref, text, options = {}) {
2459
1162
  text,
2460
1163
  tabId: options.tabId
2461
1164
  };
2462
- const response = await sendCommand2(request);
1165
+ const response = await sendCommand(request);
2463
1166
  if (options.json) {
2464
1167
  console.log(JSON.stringify(response, null, 2));
2465
1168
  } else {
@@ -2480,7 +1183,7 @@ async function fillCommand(ref, text, options = {}) {
2480
1183
  }
2481
1184
 
2482
1185
  // packages/cli/src/commands/type.ts
2483
- function parseRef5(ref) {
1186
+ function parseRef4(ref) {
2484
1187
  return ref.startsWith("@") ? ref.slice(1) : ref;
2485
1188
  }
2486
1189
  async function typeCommand(ref, text, options = {}) {
@@ -2491,7 +1194,7 @@ async function typeCommand(ref, text, options = {}) {
2491
1194
  throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
2492
1195
  }
2493
1196
  await ensureDaemonRunning();
2494
- const parsedRef = parseRef5(ref);
1197
+ const parsedRef = parseRef4(ref);
2495
1198
  const request = {
2496
1199
  id: generateId(),
2497
1200
  action: "type",
@@ -2499,7 +1202,7 @@ async function typeCommand(ref, text, options = {}) {
2499
1202
  text,
2500
1203
  tabId: options.tabId
2501
1204
  };
2502
- const response = await sendCommand2(request);
1205
+ const response = await sendCommand(request);
2503
1206
  if (options.json) {
2504
1207
  console.log(JSON.stringify(response, null, 2));
2505
1208
  } else {
@@ -2527,7 +1230,7 @@ async function closeCommand(options = {}) {
2527
1230
  action: "close",
2528
1231
  tabId: options.tabId
2529
1232
  };
2530
- const response = await sendCommand2(request);
1233
+ const response = await sendCommand(request);
2531
1234
  if (options.json) {
2532
1235
  console.log(JSON.stringify(response, null, 2));
2533
1236
  } else {
@@ -2546,7 +1249,7 @@ async function closeCommand(options = {}) {
2546
1249
  }
2547
1250
 
2548
1251
  // packages/cli/src/commands/get.ts
2549
- function parseRef6(ref) {
1252
+ function parseRef5(ref) {
2550
1253
  return ref.startsWith("@") ? ref.slice(1) : ref;
2551
1254
  }
2552
1255
  async function getCommand(attribute, ref, options = {}) {
@@ -2558,10 +1261,10 @@ async function getCommand(attribute, ref, options = {}) {
2558
1261
  id: generateId(),
2559
1262
  action: "get",
2560
1263
  attribute,
2561
- ref: ref ? parseRef6(ref) : void 0,
1264
+ ref: ref ? parseRef5(ref) : void 0,
2562
1265
  tabId: options.tabId
2563
1266
  };
2564
- const response = await sendCommand2(request);
1267
+ const response = await sendCommand(request);
2565
1268
  if (options.json) {
2566
1269
  console.log(JSON.stringify(response, null, 2));
2567
1270
  } else {
@@ -2577,17 +1280,17 @@ async function getCommand(attribute, ref, options = {}) {
2577
1280
 
2578
1281
  // packages/cli/src/commands/screenshot.ts
2579
1282
  import fs from "fs";
2580
- import path4 from "path";
2581
- import os4 from "os";
1283
+ import path2 from "path";
1284
+ import os2 from "os";
2582
1285
  function getDefaultPath() {
2583
1286
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2584
1287
  const filename = `bb-screenshot-${timestamp}.png`;
2585
- return path4.join(os4.tmpdir(), filename);
1288
+ return path2.join(os2.tmpdir(), filename);
2586
1289
  }
2587
1290
  function saveBase64Image(dataUrl, filePath) {
2588
1291
  const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
2589
1292
  const buffer = Buffer.from(base64Data, "base64");
2590
- const dir = path4.dirname(filePath);
1293
+ const dir = path2.dirname(filePath);
2591
1294
  if (!fs.existsSync(dir)) {
2592
1295
  fs.mkdirSync(dir, { recursive: true });
2593
1296
  }
@@ -2595,13 +1298,13 @@ function saveBase64Image(dataUrl, filePath) {
2595
1298
  }
2596
1299
  async function screenshotCommand(outputPath, options = {}) {
2597
1300
  await ensureDaemonRunning();
2598
- const filePath = outputPath ? path4.resolve(outputPath) : getDefaultPath();
1301
+ const filePath = outputPath ? path2.resolve(outputPath) : getDefaultPath();
2599
1302
  const request = {
2600
1303
  id: generateId(),
2601
1304
  action: "screenshot",
2602
1305
  tabId: options.tabId
2603
1306
  };
2604
- const response = await sendCommand2(request);
1307
+ const response = await sendCommand(request);
2605
1308
  if (response.success && response.data?.dataUrl) {
2606
1309
  const dataUrl = response.data.dataUrl;
2607
1310
  saveBase64Image(dataUrl, filePath);
@@ -2628,7 +1331,7 @@ async function screenshotCommand(outputPath, options = {}) {
2628
1331
  function isTimeWait(target) {
2629
1332
  return /^\d+$/.test(target);
2630
1333
  }
2631
- function parseRef7(ref) {
1334
+ function parseRef6(ref) {
2632
1335
  return ref.startsWith("@") ? ref.slice(1) : ref;
2633
1336
  }
2634
1337
  async function waitCommand(target, options = {}) {
@@ -2647,7 +1350,7 @@ async function waitCommand(target, options = {}) {
2647
1350
  tabId: options.tabId
2648
1351
  };
2649
1352
  } else {
2650
- const ref = parseRef7(target);
1353
+ const ref = parseRef6(target);
2651
1354
  request = {
2652
1355
  id: generateId(),
2653
1356
  action: "wait",
@@ -2656,7 +1359,7 @@ async function waitCommand(target, options = {}) {
2656
1359
  tabId: options.tabId
2657
1360
  };
2658
1361
  }
2659
- const response = await sendCommand2(request);
1362
+ const response = await sendCommand(request);
2660
1363
  if (options.json) {
2661
1364
  console.log(JSON.stringify(response, null, 2));
2662
1365
  } else {
@@ -2664,7 +1367,7 @@ async function waitCommand(target, options = {}) {
2664
1367
  if (isTimeWait(target)) {
2665
1368
  console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
2666
1369
  } else {
2667
- console.log(`\u5143\u7D20 @${parseRef7(target)} \u5DF2\u51FA\u73B0`);
1370
+ console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
2668
1371
  }
2669
1372
  } else {
2670
1373
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -2704,7 +1407,7 @@ async function pressCommand(keyString, options = {}) {
2704
1407
  modifiers,
2705
1408
  tabId: options.tabId
2706
1409
  };
2707
- const response = await sendCommand2(request);
1410
+ const response = await sendCommand(request);
2708
1411
  if (options.json) {
2709
1412
  console.log(JSON.stringify(response, null, 2));
2710
1413
  } else {
@@ -2745,7 +1448,7 @@ async function scrollCommand(direction, pixels, options = {}) {
2745
1448
  pixels: pixelValue,
2746
1449
  tabId: options.tabId
2747
1450
  };
2748
- const response = await sendCommand2(request);
1451
+ const response = await sendCommand(request);
2749
1452
  if (options.json) {
2750
1453
  console.log(JSON.stringify(response, null, 2));
2751
1454
  } else {
@@ -2766,7 +1469,7 @@ async function backCommand(options = {}) {
2766
1469
  action: "back",
2767
1470
  tabId: options.tabId
2768
1471
  };
2769
- const response = await sendCommand2(request);
1472
+ const response = await sendCommand(request);
2770
1473
  if (options.json) {
2771
1474
  console.log(JSON.stringify(response, null, 2));
2772
1475
  } else {
@@ -2790,7 +1493,7 @@ async function forwardCommand(options = {}) {
2790
1493
  action: "forward",
2791
1494
  tabId: options.tabId
2792
1495
  };
2793
- const response = await sendCommand2(request);
1496
+ const response = await sendCommand(request);
2794
1497
  if (options.json) {
2795
1498
  console.log(JSON.stringify(response, null, 2));
2796
1499
  } else {
@@ -2814,7 +1517,7 @@ async function refreshCommand(options = {}) {
2814
1517
  action: "refresh",
2815
1518
  tabId: options.tabId
2816
1519
  };
2817
- const response = await sendCommand2(request);
1520
+ const response = await sendCommand(request);
2818
1521
  if (options.json) {
2819
1522
  console.log(JSON.stringify(response, null, 2));
2820
1523
  } else {
@@ -2833,7 +1536,7 @@ async function refreshCommand(options = {}) {
2833
1536
  }
2834
1537
 
2835
1538
  // packages/cli/src/commands/check.ts
2836
- function parseRef8(ref) {
1539
+ function parseRef7(ref) {
2837
1540
  return ref.startsWith("@") ? ref.slice(1) : ref;
2838
1541
  }
2839
1542
  async function checkCommand(ref, options = {}) {
@@ -2841,14 +1544,14 @@ async function checkCommand(ref, options = {}) {
2841
1544
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2842
1545
  }
2843
1546
  await ensureDaemonRunning();
2844
- const parsedRef = parseRef8(ref);
1547
+ const parsedRef = parseRef7(ref);
2845
1548
  const request = {
2846
1549
  id: generateId(),
2847
1550
  action: "check",
2848
1551
  ref: parsedRef,
2849
1552
  tabId: options.tabId
2850
1553
  };
2851
- const response = await sendCommand2(request);
1554
+ const response = await sendCommand(request);
2852
1555
  if (options.json) {
2853
1556
  console.log(JSON.stringify(response, null, 2));
2854
1557
  } else {
@@ -2880,14 +1583,14 @@ async function uncheckCommand(ref, options = {}) {
2880
1583
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
2881
1584
  }
2882
1585
  await ensureDaemonRunning();
2883
- const parsedRef = parseRef8(ref);
1586
+ const parsedRef = parseRef7(ref);
2884
1587
  const request = {
2885
1588
  id: generateId(),
2886
1589
  action: "uncheck",
2887
1590
  ref: parsedRef,
2888
1591
  tabId: options.tabId
2889
1592
  };
2890
- const response = await sendCommand2(request);
1593
+ const response = await sendCommand(request);
2891
1594
  if (options.json) {
2892
1595
  console.log(JSON.stringify(response, null, 2));
2893
1596
  } else {
@@ -2916,7 +1619,7 @@ async function uncheckCommand(ref, options = {}) {
2916
1619
  }
2917
1620
 
2918
1621
  // packages/cli/src/commands/select.ts
2919
- function parseRef9(ref) {
1622
+ function parseRef8(ref) {
2920
1623
  return ref.startsWith("@") ? ref.slice(1) : ref;
2921
1624
  }
2922
1625
  async function selectCommand(ref, value, options = {}) {
@@ -2927,7 +1630,7 @@ async function selectCommand(ref, value, options = {}) {
2927
1630
  throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
2928
1631
  }
2929
1632
  await ensureDaemonRunning();
2930
- const parsedRef = parseRef9(ref);
1633
+ const parsedRef = parseRef8(ref);
2931
1634
  const request = {
2932
1635
  id: generateId(),
2933
1636
  action: "select",
@@ -2935,7 +1638,7 @@ async function selectCommand(ref, value, options = {}) {
2935
1638
  value,
2936
1639
  tabId: options.tabId
2937
1640
  };
2938
- const response = await sendCommand2(request);
1641
+ const response = await sendCommand(request);
2939
1642
  if (options.json) {
2940
1643
  console.log(JSON.stringify(response, null, 2));
2941
1644
  } else {
@@ -2973,7 +1676,7 @@ async function evalCommand(script, options = {}) {
2973
1676
  script,
2974
1677
  tabId: options.tabId
2975
1678
  };
2976
- const response = await sendCommand2(request);
1679
+ const response = await sendCommand(request);
2977
1680
  if (options.json) {
2978
1681
  console.log(JSON.stringify(response, null, 2));
2979
1682
  } else {
@@ -3056,6 +1759,12 @@ function formatTabList(tabs, activeIndex) {
3056
1759
  async function tabCommand(args, options = {}) {
3057
1760
  await ensureDaemonRunning();
3058
1761
  const parsed = parseTabSubcommand(args, process.argv);
1762
+ if (options.globalTabId && parsed.tabId === void 0 && parsed.index === void 0) {
1763
+ if (parsed.action === "tab_close" || parsed.action === "tab_select") {
1764
+ const numId = parseInt(options.globalTabId, 10);
1765
+ parsed.tabId = isNaN(numId) ? options.globalTabId : numId;
1766
+ }
1767
+ }
3059
1768
  const request = {
3060
1769
  id: generateId(),
3061
1770
  action: parsed.action,
@@ -3063,7 +1772,7 @@ async function tabCommand(args, options = {}) {
3063
1772
  index: parsed.index,
3064
1773
  tabId: parsed.tabId
3065
1774
  };
3066
- const response = await sendCommand2(request);
1775
+ const response = await sendCommand(request);
3067
1776
  if (options.json) {
3068
1777
  console.log(JSON.stringify(response, null, 2));
3069
1778
  } else {
@@ -3112,7 +1821,7 @@ async function frameCommand(selector, options = {}) {
3112
1821
  selector,
3113
1822
  tabId: options.tabId
3114
1823
  };
3115
- const response = await sendCommand2(request);
1824
+ const response = await sendCommand(request);
3116
1825
  if (options.json) {
3117
1826
  console.log(JSON.stringify(response, null, 2));
3118
1827
  } else {
@@ -3136,7 +1845,7 @@ async function frameMainCommand(options = {}) {
3136
1845
  action: "frame_main",
3137
1846
  tabId: options.tabId
3138
1847
  };
3139
- const response = await sendCommand2(request);
1848
+ const response = await sendCommand(request);
3140
1849
  if (options.json) {
3141
1850
  console.log(JSON.stringify(response, null, 2));
3142
1851
  } else {
@@ -3162,7 +1871,7 @@ async function dialogCommand(subCommand, promptText, options = {}) {
3162
1871
  promptText: subCommand === "accept" ? promptText : void 0,
3163
1872
  tabId: options.tabId
3164
1873
  };
3165
- const response = await sendCommand2(request);
1874
+ const response = await sendCommand(request);
3166
1875
  if (options.json) {
3167
1876
  console.log(JSON.stringify(response, null, 2));
3168
1877
  } else {
@@ -3183,8 +1892,13 @@ async function dialogCommand(subCommand, promptText, options = {}) {
3183
1892
 
3184
1893
  // packages/cli/src/commands/network.ts
3185
1894
  async function networkCommand(subCommand, urlOrFilter, options = {}) {
3186
- const response = await sendCommand2({
3187
- id: crypto.randomUUID(),
1895
+ let since;
1896
+ if (subCommand === "requests" && options.since) {
1897
+ const num = parseInt(options.since, 10);
1898
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
1899
+ }
1900
+ const request = {
1901
+ id: generateId(),
3188
1902
  action: "network",
3189
1903
  networkCommand: subCommand,
3190
1904
  url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
@@ -3194,8 +1908,12 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
3194
1908
  body: options.body
3195
1909
  } : void 0,
3196
1910
  withBody: subCommand === "requests" ? options.withBody : void 0,
1911
+ since,
1912
+ method: subCommand === "requests" ? options.method : void 0,
1913
+ status: subCommand === "requests" ? options.status : void 0,
3197
1914
  tabId: options.tabId
3198
- });
1915
+ };
1916
+ const response = await sendCommand(request);
3199
1917
  if (options.json) {
3200
1918
  console.log(JSON.stringify(response));
3201
1919
  return;
@@ -3270,12 +1988,19 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
3270
1988
 
3271
1989
  // packages/cli/src/commands/console.ts
3272
1990
  async function consoleCommand(options = {}) {
3273
- const response = await sendCommand2({
3274
- id: crypto.randomUUID(),
1991
+ let since;
1992
+ if (options.since) {
1993
+ const num = parseInt(options.since, 10);
1994
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
1995
+ }
1996
+ const request = {
1997
+ id: generateId(),
3275
1998
  action: "console",
3276
1999
  consoleCommand: options.clear ? "clear" : "get",
3277
- tabId: options.tabId
3278
- });
2000
+ tabId: options.tabId,
2001
+ since
2002
+ };
2003
+ const response = await sendCommand(request);
3279
2004
  if (options.json) {
3280
2005
  console.log(JSON.stringify(response));
3281
2006
  return;
@@ -3315,12 +2040,19 @@ async function consoleCommand(options = {}) {
3315
2040
 
3316
2041
  // packages/cli/src/commands/errors.ts
3317
2042
  async function errorsCommand(options = {}) {
3318
- const response = await sendCommand2({
3319
- id: crypto.randomUUID(),
2043
+ let since;
2044
+ if (options.since) {
2045
+ const num = parseInt(options.since, 10);
2046
+ since = !isNaN(num) && String(num) === options.since ? num : options.since;
2047
+ }
2048
+ const request = {
2049
+ id: generateId(),
3320
2050
  action: "errors",
3321
2051
  errorsCommand: options.clear ? "clear" : "get",
3322
- tabId: options.tabId
3323
- });
2052
+ tabId: options.tabId,
2053
+ since
2054
+ };
2055
+ const response = await sendCommand(request);
3324
2056
  if (options.json) {
3325
2057
  console.log(JSON.stringify(response));
3326
2058
  return;
@@ -3355,8 +2087,8 @@ async function errorsCommand(options = {}) {
3355
2087
 
3356
2088
  // packages/cli/src/commands/trace.ts
3357
2089
  async function traceCommand(subCommand, options = {}) {
3358
- const response = await sendCommand2({
3359
- id: crypto.randomUUID(),
2090
+ const response = await sendCommand({
2091
+ id: generateId(),
3360
2092
  action: "trace",
3361
2093
  traceCommand: subCommand,
3362
2094
  tabId: options.tabId
@@ -3445,7 +2177,7 @@ function matchTabOrigin2(tabUrl, targetHostname) {
3445
2177
  }
3446
2178
  async function ensureTabForOrigin(origin, hostname) {
3447
2179
  const listReq = { id: generateId(), action: "tab_list" };
3448
- const listResp = await sendCommand2(listReq);
2180
+ const listResp = await sendCommand(listReq);
3449
2181
  if (listResp.success && listResp.data?.tabs) {
3450
2182
  const matchingTab = listResp.data.tabs.find(
3451
2183
  (tab) => matchTabOrigin2(tab.url, hostname)
@@ -3454,11 +2186,11 @@ async function ensureTabForOrigin(origin, hostname) {
3454
2186
  return matchingTab.tabId;
3455
2187
  }
3456
2188
  }
3457
- const newResp = await sendCommand2({ id: generateId(), action: "tab_new", url: origin });
2189
+ const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
3458
2190
  if (!newResp.success) {
3459
2191
  throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
3460
2192
  }
3461
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
2193
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
3462
2194
  return newResp.data?.tabId;
3463
2195
  }
3464
2196
  function buildFetchScript(url, options) {
@@ -3523,7 +2255,7 @@ async function fetchCommand(url, options = {}) {
3523
2255
  }
3524
2256
  const script = buildFetchScript(url, options);
3525
2257
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
3526
- const evalResp = await sendCommand2(evalReq);
2258
+ const evalResp = await sendCommand(evalReq);
3527
2259
  if (!evalResp.success) {
3528
2260
  throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
3529
2261
  }
@@ -3542,9 +2274,9 @@ async function fetchCommand(url, options = {}) {
3542
2274
  throw new Error(`Fetch error: ${result.error}`);
3543
2275
  }
3544
2276
  if (options.output) {
3545
- const { writeFileSync: writeFileSync2 } = await import("fs");
2277
+ const { writeFileSync } = await import("fs");
3546
2278
  const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
3547
- writeFileSync2(options.output, content, "utf-8");
2279
+ writeFileSync(options.output, content, "utf-8");
3548
2280
  console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
3549
2281
  return;
3550
2282
  }
@@ -3561,7 +2293,7 @@ async function historyCommand(subCommand, options = {}) {
3561
2293
  const data = subCommand === "search" ? { historyItems: searchHistory(options.query, days) } : { historyDomains: getHistoryDomains(days) };
3562
2294
  if (options.json) {
3563
2295
  console.log(JSON.stringify({
3564
- id: crypto.randomUUID(),
2296
+ id: generateId(),
3565
2297
  success: true,
3566
2298
  data
3567
2299
  }));
@@ -3606,16 +2338,49 @@ async function historyCommand(subCommand, options = {}) {
3606
2338
 
3607
2339
  // packages/cli/src/commands/daemon.ts
3608
2340
  async function statusCommand(options = {}) {
3609
- const running = await isDaemonRunning();
2341
+ const status = await getDaemonStatus();
2342
+ if (!status) {
2343
+ if (options.json) {
2344
+ console.log(JSON.stringify({ running: false }));
2345
+ } else {
2346
+ console.log("Daemon not running");
2347
+ }
2348
+ return;
2349
+ }
3610
2350
  if (options.json) {
3611
- console.log(JSON.stringify({ running }));
2351
+ console.log(JSON.stringify(status, null, 2));
2352
+ return;
2353
+ }
2354
+ console.log(`Daemon running: ${status.running ? "yes" : "no"}`);
2355
+ console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
2356
+ console.log(`Uptime: ${formatUptime(status.uptime)}`);
2357
+ console.log(`Global seq: ${status.currentSeq ?? "N/A"}`);
2358
+ const tabs = status.tabs;
2359
+ if (tabs && tabs.length > 0) {
2360
+ console.log(`
2361
+ Tabs (${tabs.length}):`);
2362
+ for (const tab of tabs) {
2363
+ const active = tab.targetId === status.currentTargetId ? " *" : "";
2364
+ console.log(
2365
+ ` ${tab.shortId}${active} net:${tab.networkRequests} console:${tab.consoleMessages} err:${tab.jsErrors} seq:${tab.lastActionSeq}`
2366
+ );
2367
+ }
3612
2368
  } else {
3613
- console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
2369
+ console.log("\nNo tabs");
3614
2370
  }
3615
2371
  }
2372
+ function formatUptime(ms) {
2373
+ if (!ms || ms <= 0) return "0s";
2374
+ const s = Math.floor(ms / 1e3);
2375
+ if (s < 60) return `${s}s`;
2376
+ const m = Math.floor(s / 60);
2377
+ if (m < 60) return `${m}m ${s % 60}s`;
2378
+ const h = Math.floor(m / 60);
2379
+ return `${h}h ${m % 60}m`;
2380
+ }
3616
2381
 
3617
2382
  // packages/cli/src/index.ts
3618
- var VERSION = "0.10.0";
2383
+ var VERSION = "0.11.0";
3619
2384
  var HELP_TEXT = `
3620
2385
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
3621
2386
 
@@ -3751,6 +2516,12 @@ function parseArgs(argv) {
3751
2516
  skipNext = true;
3752
2517
  } else if (arg === "--tab") {
3753
2518
  skipNext = true;
2519
+ } else if (arg === "--since") {
2520
+ skipNext = true;
2521
+ } else if (arg === "--method") {
2522
+ skipNext = true;
2523
+ } else if (arg === "--status") {
2524
+ skipNext = true;
3754
2525
  } else if (arg.startsWith("-")) {
3755
2526
  } else if (result.command === null) {
3756
2527
  result.command = arg;
@@ -3764,15 +2535,17 @@ async function main() {
3764
2535
  const parsed = parseArgs(process.argv);
3765
2536
  setJqExpression(parsed.flags.jq);
3766
2537
  const tabArgIdx = process.argv.indexOf("--tab");
3767
- const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? parseInt(process.argv[tabArgIdx + 1], 10) : void 0;
2538
+ const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? process.argv[tabArgIdx + 1] : void 0;
2539
+ const sinceArgIdx = process.argv.indexOf("--since");
2540
+ const globalSince = sinceArgIdx >= 0 && process.argv[sinceArgIdx + 1] ? process.argv[sinceArgIdx + 1] : void 0;
3768
2541
  if (parsed.flags.version) {
3769
2542
  console.log(VERSION);
3770
2543
  return;
3771
2544
  }
3772
2545
  if (process.argv.includes("--mcp")) {
3773
- const mcpPath = fileURLToPath4(new URL("./mcp.js", import.meta.url));
3774
- const { spawn: spawn3 } = await import("child_process");
3775
- const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
2546
+ const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
2547
+ const { spawn: spawn2 } = await import("child_process");
2548
+ const child = spawn2(process.execPath, [mcpPath], { stdio: "inherit" });
3776
2549
  child.on("exit", (code) => process.exit(code ?? 0));
3777
2550
  return;
3778
2551
  }
@@ -3992,7 +2765,7 @@ async function main() {
3992
2765
  break;
3993
2766
  }
3994
2767
  case "tab": {
3995
- await tabCommand(parsed.args, { json: parsed.flags.json });
2768
+ await tabCommand(parsed.args, { json: parsed.flags.json, globalTabId });
3996
2769
  break;
3997
2770
  }
3998
2771
  case "status": {
@@ -4036,17 +2809,21 @@ async function main() {
4036
2809
  const withBody = process.argv.includes("--with-body");
4037
2810
  const bodyIndex = process.argv.findIndex((a) => a === "--body");
4038
2811
  const body = bodyIndex >= 0 ? process.argv[bodyIndex + 1] : void 0;
4039
- await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId });
2812
+ const methodIndex = process.argv.findIndex((a) => a === "--method");
2813
+ const method = methodIndex >= 0 ? process.argv[methodIndex + 1] : void 0;
2814
+ const statusIndex = process.argv.findIndex((a) => a === "--status");
2815
+ const statusFilter = statusIndex >= 0 ? process.argv[statusIndex + 1] : void 0;
2816
+ await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter });
4040
2817
  break;
4041
2818
  }
4042
2819
  case "console": {
4043
2820
  const clear = process.argv.includes("--clear");
4044
- await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId });
2821
+ await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
4045
2822
  break;
4046
2823
  }
4047
2824
  case "errors": {
4048
2825
  const clear = process.argv.includes("--clear");
4049
- await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId });
2826
+ await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
4050
2827
  break;
4051
2828
  }
4052
2829
  case "trace": {
@@ -4116,9 +2893,9 @@ async function main() {
4116
2893
  break;
4117
2894
  }
4118
2895
  case "star": {
4119
- const { execSync: execSync4 } = await import("child_process");
2896
+ const { execSync: execSync3 } = await import("child_process");
4120
2897
  try {
4121
- execSync4("gh auth status", { stdio: "pipe" });
2898
+ execSync3("gh auth status", { stdio: "pipe" });
4122
2899
  } catch {
4123
2900
  console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
4124
2901
  console.error(" brew install gh && gh auth login");
@@ -4127,7 +2904,7 @@ async function main() {
4127
2904
  const repos = ["epiral/bb-browser", "epiral/bb-sites"];
4128
2905
  for (const repo of repos) {
4129
2906
  try {
4130
- execSync4(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
2907
+ execSync3(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
4131
2908
  console.log(`\u2B50 Starred ${repo}`);
4132
2909
  } catch {
4133
2910
  console.log(`Already starred or failed: ${repo}`);