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