bb-browser 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1111 -313
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  COMMAND_TIMEOUT,
4
- DAEMON_BASE_URL,
5
4
  generateId
6
5
  } from "./chunk-DBJBHYC7.js";
7
6
  import {
@@ -10,7 +9,861 @@ import {
10
9
  import "./chunk-D4HDZEJT.js";
11
10
 
12
11
  // packages/cli/src/index.ts
13
- import { fileURLToPath as fileURLToPath2 } from "url";
12
+ import { fileURLToPath as fileURLToPath3 } from "url";
13
+
14
+ // packages/cli/src/cdp-client.ts
15
+ import { readFileSync } from "fs";
16
+ import { request as httpRequest } from "http";
17
+ import { request as httpsRequest } from "https";
18
+ import path2 from "path";
19
+ import { fileURLToPath } from "url";
20
+ import WebSocket from "ws";
21
+
22
+ // packages/cli/src/cdp-discovery.ts
23
+ import { execFile, execSync, spawn } from "child_process";
24
+ import { existsSync } from "fs";
25
+ import { mkdir, readFile, writeFile } from "fs/promises";
26
+ import os from "os";
27
+ import path from "path";
28
+ var DEFAULT_CDP_PORT = 19825;
29
+ var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
30
+ var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
31
+ var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
32
+ function execFileAsync(command, args, timeout) {
33
+ return new Promise((resolve2, reject) => {
34
+ execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
35
+ if (error) {
36
+ reject(error);
37
+ return;
38
+ }
39
+ resolve2(stdout.trim());
40
+ });
41
+ });
42
+ }
43
+ function getArgValue(flag) {
44
+ const index = process.argv.indexOf(flag);
45
+ if (index < 0) return void 0;
46
+ return process.argv[index + 1];
47
+ }
48
+ async function tryOpenClaw() {
49
+ try {
50
+ const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 5e3);
51
+ const parsed = JSON.parse(raw);
52
+ const port = Number(parsed?.cdpPort);
53
+ if (Number.isInteger(port) && port > 0) {
54
+ return { host: "127.0.0.1", port };
55
+ }
56
+ } catch {
57
+ }
58
+ return null;
59
+ }
60
+ async function canConnect(host, port) {
61
+ try {
62
+ const controller = new AbortController();
63
+ const timeout = setTimeout(() => controller.abort(), 1200);
64
+ const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
65
+ clearTimeout(timeout);
66
+ return response.ok;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ function findBrowserExecutable() {
72
+ if (process.platform === "darwin") {
73
+ const candidates = [
74
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
75
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
76
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
77
+ ];
78
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
79
+ }
80
+ if (process.platform === "linux") {
81
+ const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
82
+ for (const candidate of candidates) {
83
+ try {
84
+ const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
85
+ if (resolved) {
86
+ return resolved;
87
+ }
88
+ } catch {
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ if (process.platform === "win32") {
94
+ const candidates = [
95
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
96
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
97
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
98
+ ];
99
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
100
+ }
101
+ return null;
102
+ }
103
+ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
104
+ const executable = findBrowserExecutable();
105
+ if (!executable) {
106
+ return null;
107
+ }
108
+ await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
109
+ const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
110
+ const prefsPath = path.join(defaultProfileDir, "Preferences");
111
+ await mkdir(defaultProfileDir, { recursive: true });
112
+ try {
113
+ let prefs = {};
114
+ try {
115
+ prefs = JSON.parse(await readFile(prefsPath, "utf8"));
116
+ } catch {
117
+ }
118
+ if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
119
+ prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
120
+ await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
121
+ }
122
+ } catch {
123
+ }
124
+ const args = [
125
+ `--remote-debugging-port=${port}`,
126
+ `--user-data-dir=${MANAGED_USER_DATA_DIR}`,
127
+ "--no-first-run",
128
+ "--no-default-browser-check",
129
+ "--disable-sync",
130
+ "--disable-background-networking",
131
+ "--disable-component-update",
132
+ "--disable-features=Translate,MediaRouter",
133
+ "--disable-session-crashed-bubble",
134
+ "--hide-crash-restore-bubble",
135
+ "about:blank"
136
+ ];
137
+ try {
138
+ const child = spawn(executable, args, {
139
+ detached: true,
140
+ stdio: "ignore"
141
+ });
142
+ child.unref();
143
+ } catch {
144
+ return null;
145
+ }
146
+ await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
147
+ await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
148
+ const deadline = Date.now() + 8e3;
149
+ while (Date.now() < deadline) {
150
+ if (await canConnect("127.0.0.1", port)) {
151
+ return { host: "127.0.0.1", port };
152
+ }
153
+ await new Promise((resolve2) => setTimeout(resolve2, 250));
154
+ }
155
+ return null;
156
+ }
157
+ async function discoverCdpPort() {
158
+ const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
159
+ if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
160
+ return { host: "127.0.0.1", port: explicitPort };
161
+ }
162
+ if (process.argv.includes("--openclaw")) {
163
+ const viaOpenClaw = await tryOpenClaw();
164
+ if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
165
+ return viaOpenClaw;
166
+ }
167
+ }
168
+ const detectedOpenClaw = await tryOpenClaw();
169
+ if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
170
+ return detectedOpenClaw;
171
+ }
172
+ try {
173
+ const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
174
+ const managedPort = Number.parseInt(rawPort.trim(), 10);
175
+ if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
176
+ return { host: "127.0.0.1", port: managedPort };
177
+ }
178
+ } catch {
179
+ }
180
+ return launchManagedBrowser();
181
+ }
182
+
183
+ // packages/cli/src/cdp-client.ts
184
+ var connectionState = null;
185
+ var reconnecting = null;
186
+ var networkRequests = /* @__PURE__ */ new Map();
187
+ var networkEnabled = false;
188
+ var consoleMessages = [];
189
+ var consoleEnabled = false;
190
+ var jsErrors = [];
191
+ var errorsEnabled = false;
192
+ var traceRecording = false;
193
+ var traceEvents = [];
194
+ function buildRequestError(error) {
195
+ return error instanceof Error ? error : new Error(String(error));
196
+ }
197
+ function fetchJson(url) {
198
+ return new Promise((resolve2, reject) => {
199
+ const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
200
+ const req = requester(url, { method: "GET" }, (res) => {
201
+ const chunks = [];
202
+ res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
203
+ res.on("end", () => {
204
+ const raw = Buffer.concat(chunks).toString("utf8");
205
+ if ((res.statusCode ?? 500) >= 400) {
206
+ reject(new Error(`HTTP ${res.statusCode ?? 500}: ${raw}`));
207
+ return;
208
+ }
209
+ try {
210
+ resolve2(JSON.parse(raw));
211
+ } catch (error) {
212
+ reject(error);
213
+ }
214
+ });
215
+ });
216
+ req.on("error", reject);
217
+ req.end();
218
+ });
219
+ }
220
+ async function getJsonList(host, port) {
221
+ const data = await fetchJson(`http://${host}:${port}/json/list`);
222
+ return Array.isArray(data) ? data : [];
223
+ }
224
+ async function getJsonVersion(host, port) {
225
+ const data = await fetchJson(`http://${host}:${port}/json/version`);
226
+ const url = data.webSocketDebuggerUrl;
227
+ if (typeof url !== "string" || !url) {
228
+ throw new Error("CDP endpoint missing webSocketDebuggerUrl");
229
+ }
230
+ return { webSocketDebuggerUrl: url };
231
+ }
232
+ function connectWebSocket(url) {
233
+ return new Promise((resolve2, reject) => {
234
+ const ws = new WebSocket(url);
235
+ ws.once("open", () => {
236
+ ws._socket?.unref?.();
237
+ resolve2(ws);
238
+ });
239
+ ws.once("error", reject);
240
+ });
241
+ }
242
+ function createState(host, port, browserWsUrl, browserSocket) {
243
+ const state = {
244
+ host,
245
+ port,
246
+ browserWsUrl,
247
+ browserSocket,
248
+ browserPending: /* @__PURE__ */ new Map(),
249
+ nextMessageId: 1,
250
+ sessions: /* @__PURE__ */ new Map(),
251
+ attachedTargets: /* @__PURE__ */ new Map(),
252
+ refsByTarget: /* @__PURE__ */ new Map(),
253
+ activeFrameIdByTarget: /* @__PURE__ */ new Map(),
254
+ dialogHandlers: /* @__PURE__ */ new Map()
255
+ };
256
+ browserSocket.on("message", (raw) => {
257
+ const message = JSON.parse(raw.toString());
258
+ if (typeof message.id === "number") {
259
+ const pending = state.browserPending.get(message.id);
260
+ if (!pending) return;
261
+ state.browserPending.delete(message.id);
262
+ if (message.error) {
263
+ pending.reject(new Error(`${pending.method}: ${message.error.message ?? "Unknown CDP error"}`));
264
+ } else {
265
+ pending.resolve(message.result);
266
+ }
267
+ return;
268
+ }
269
+ if (message.method === "Target.attachedToTarget") {
270
+ const params = message.params;
271
+ const sessionId = params.sessionId;
272
+ const targetInfo = params.targetInfo;
273
+ if (typeof sessionId === "string" && typeof targetInfo?.targetId === "string") {
274
+ state.sessions.set(targetInfo.targetId, sessionId);
275
+ state.attachedTargets.set(sessionId, targetInfo.targetId);
276
+ }
277
+ return;
278
+ }
279
+ if (message.method === "Target.detachedFromTarget") {
280
+ const params = message.params;
281
+ const sessionId = params.sessionId;
282
+ if (typeof sessionId === "string") {
283
+ const targetId = state.attachedTargets.get(sessionId);
284
+ if (targetId) {
285
+ state.sessions.delete(targetId);
286
+ state.attachedTargets.delete(sessionId);
287
+ state.activeFrameIdByTarget.delete(targetId);
288
+ state.dialogHandlers.delete(targetId);
289
+ }
290
+ }
291
+ return;
292
+ }
293
+ if (message.method === "Target.receivedMessageFromTarget") {
294
+ const params = message.params;
295
+ const sessionId = params.sessionId;
296
+ const messageText = params.message;
297
+ if (typeof sessionId === "string" && typeof messageText === "string") {
298
+ const targetId = state.attachedTargets.get(sessionId);
299
+ if (targetId) {
300
+ handleSessionEvent(targetId, JSON.parse(messageText)).catch(() => {
301
+ });
302
+ }
303
+ }
304
+ return;
305
+ }
306
+ if (typeof message.sessionId === "string" && typeof message.method === "string") {
307
+ const targetId = state.attachedTargets.get(message.sessionId);
308
+ if (targetId) {
309
+ handleSessionEvent(targetId, message).catch(() => {
310
+ });
311
+ }
312
+ }
313
+ });
314
+ browserSocket.on("close", () => {
315
+ if (connectionState === state) {
316
+ connectionState = null;
317
+ }
318
+ for (const pending of state.browserPending.values()) {
319
+ pending.reject(new Error("CDP connection closed"));
320
+ }
321
+ state.browserPending.clear();
322
+ });
323
+ browserSocket.on("error", () => {
324
+ });
325
+ return state;
326
+ }
327
+ async function browserCommand(method, params = {}) {
328
+ const state = connectionState;
329
+ if (!state) throw new Error("CDP connection not initialized");
330
+ const id = state.nextMessageId++;
331
+ const payload = JSON.stringify({ id, method, params });
332
+ const promise = new Promise((resolve2, reject) => {
333
+ state.browserPending.set(id, { resolve: resolve2, reject, method });
334
+ });
335
+ state.browserSocket.send(payload);
336
+ return promise;
337
+ }
338
+ async function sessionCommand(targetId, method, params = {}) {
339
+ const state = connectionState;
340
+ if (!state) throw new Error("CDP connection not initialized");
341
+ const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
342
+ const id = state.nextMessageId++;
343
+ const payload = JSON.stringify({ id, method, params, sessionId });
344
+ return new Promise((resolve2, reject) => {
345
+ const check = (raw) => {
346
+ const msg = JSON.parse(raw.toString());
347
+ if (msg.id === id && msg.sessionId === sessionId) {
348
+ state.browserSocket.off("message", check);
349
+ if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
350
+ else resolve2(msg.result);
351
+ }
352
+ };
353
+ state.browserSocket.on("message", check);
354
+ state.browserSocket.send(payload);
355
+ });
356
+ }
357
+ function getActiveFrameId(targetId) {
358
+ const frameId = connectionState?.activeFrameIdByTarget.get(targetId);
359
+ return frameId ?? void 0;
360
+ }
361
+ async function pageCommand(targetId, method, params = {}) {
362
+ const frameId = getActiveFrameId(targetId);
363
+ return sessionCommand(targetId, method, frameId ? { ...params, frameId } : params);
364
+ }
365
+ function normalizeHeaders(headers) {
366
+ if (!headers || typeof headers !== "object") return void 0;
367
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
368
+ }
369
+ async function handleSessionEvent(targetId, event) {
370
+ const method = event.method;
371
+ const params = event.params ?? {};
372
+ if (typeof method !== "string") return;
373
+ if (method === "Page.javascriptDialogOpening") {
374
+ const handler = connectionState?.dialogHandlers.get(targetId);
375
+ if (handler) {
376
+ await sessionCommand(targetId, "Page.handleJavaScriptDialog", {
377
+ accept: handler.accept,
378
+ ...handler.promptText !== void 0 ? { promptText: handler.promptText } : {}
379
+ });
380
+ }
381
+ return;
382
+ }
383
+ if (method === "Network.requestWillBeSent") {
384
+ const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
385
+ const request = params.request;
386
+ if (!requestId || !request) return;
387
+ networkRequests.set(requestId, {
388
+ requestId,
389
+ url: String(request.url ?? ""),
390
+ method: String(request.method ?? "GET"),
391
+ type: String(params.type ?? "Other"),
392
+ timestamp: Math.round(Number(params.timestamp ?? Date.now()) * 1e3),
393
+ requestHeaders: normalizeHeaders(request.headers),
394
+ requestBody: typeof request.postData === "string" ? request.postData : void 0
395
+ });
396
+ return;
397
+ }
398
+ if (method === "Network.responseReceived") {
399
+ const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
400
+ const response = params.response;
401
+ if (!requestId || !response) return;
402
+ const existing = networkRequests.get(requestId);
403
+ if (!existing) return;
404
+ existing.status = typeof response.status === "number" ? response.status : void 0;
405
+ existing.statusText = typeof response.statusText === "string" ? response.statusText : void 0;
406
+ existing.responseHeaders = normalizeHeaders(response.headers);
407
+ existing.mimeType = typeof response.mimeType === "string" ? response.mimeType : void 0;
408
+ networkRequests.set(requestId, existing);
409
+ return;
410
+ }
411
+ if (method === "Network.loadingFailed") {
412
+ const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
413
+ if (!requestId) return;
414
+ const existing = networkRequests.get(requestId);
415
+ if (!existing) return;
416
+ existing.failed = true;
417
+ existing.failureReason = typeof params.errorText === "string" ? params.errorText : "Unknown error";
418
+ networkRequests.set(requestId, existing);
419
+ return;
420
+ }
421
+ if (method === "Runtime.consoleAPICalled") {
422
+ const type = String(params.type ?? "log");
423
+ const args = Array.isArray(params.args) ? params.args : [];
424
+ const text = args.map((arg) => {
425
+ if (typeof arg.value === "string") return arg.value;
426
+ if (arg.value !== void 0) return String(arg.value);
427
+ if (typeof arg.description === "string") return arg.description;
428
+ return "";
429
+ }).filter(Boolean).join(" ");
430
+ const stack = params.stackTrace;
431
+ const firstCallFrame = Array.isArray(stack?.callFrames) ? stack?.callFrames[0] : void 0;
432
+ consoleMessages.push({
433
+ type: ["log", "info", "warn", "error", "debug"].includes(type) ? type : "log",
434
+ text,
435
+ timestamp: Math.round(Number(params.timestamp ?? Date.now())),
436
+ url: typeof firstCallFrame?.url === "string" ? firstCallFrame.url : void 0,
437
+ lineNumber: typeof firstCallFrame?.lineNumber === "number" ? firstCallFrame.lineNumber : void 0
438
+ });
439
+ return;
440
+ }
441
+ if (method === "Runtime.exceptionThrown") {
442
+ const details = params.exceptionDetails;
443
+ if (!details) return;
444
+ const exception = details.exception;
445
+ const stackTrace = details.stackTrace;
446
+ const callFrames = Array.isArray(stackTrace?.callFrames) ? stackTrace.callFrames : [];
447
+ jsErrors.push({
448
+ message: typeof exception?.description === "string" ? exception.description : String(details.text ?? "JavaScript exception"),
449
+ url: typeof details.url === "string" ? details.url : typeof callFrames[0]?.url === "string" ? String(callFrames[0].url) : void 0,
450
+ lineNumber: typeof details.lineNumber === "number" ? details.lineNumber : void 0,
451
+ columnNumber: typeof details.columnNumber === "number" ? details.columnNumber : void 0,
452
+ 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,
453
+ timestamp: Date.now()
454
+ });
455
+ }
456
+ }
457
+ async function ensureNetworkMonitoring(targetId) {
458
+ if (networkEnabled) return;
459
+ await sessionCommand(targetId, "Network.enable");
460
+ networkEnabled = true;
461
+ }
462
+ async function ensureConsoleMonitoring(targetId) {
463
+ if (consoleEnabled && errorsEnabled) return;
464
+ await sessionCommand(targetId, "Runtime.enable");
465
+ consoleEnabled = true;
466
+ errorsEnabled = true;
467
+ }
468
+ async function attachTarget(targetId) {
469
+ const result = await browserCommand("Target.attachToTarget", {
470
+ targetId,
471
+ flatten: true
472
+ });
473
+ connectionState?.sessions.set(targetId, result.sessionId);
474
+ connectionState?.attachedTargets.set(result.sessionId, targetId);
475
+ connectionState?.activeFrameIdByTarget.set(targetId, connectionState?.activeFrameIdByTarget.get(targetId) ?? null);
476
+ await sessionCommand(targetId, "Page.enable");
477
+ await sessionCommand(targetId, "Runtime.enable");
478
+ await sessionCommand(targetId, "DOM.enable");
479
+ await sessionCommand(targetId, "Accessibility.enable");
480
+ return result.sessionId;
481
+ }
482
+ async function getTargets() {
483
+ const state = connectionState;
484
+ if (!state) throw new Error("CDP connection not initialized");
485
+ try {
486
+ const result = await browserCommand("Target.getTargets");
487
+ return (result.targetInfos || []).map((target) => ({
488
+ id: target.targetId,
489
+ type: target.type,
490
+ title: target.title,
491
+ url: target.url,
492
+ webSocketDebuggerUrl: ""
493
+ }));
494
+ } catch {
495
+ return getJsonList(state.host, state.port);
496
+ }
497
+ }
498
+ async function ensurePageTarget(targetId) {
499
+ const targets = (await getTargets()).filter((target2) => target2.type === "page");
500
+ if (targets.length === 0) throw new Error("No page target found");
501
+ let target;
502
+ if (typeof targetId === "number") {
503
+ target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
504
+ } else if (typeof targetId === "string") {
505
+ target = targets.find((item) => item.id === targetId);
506
+ }
507
+ target ??= targets[0];
508
+ connectionState.currentTargetId = target.id;
509
+ await attachTarget(target.id);
510
+ return target;
511
+ }
512
+ function parseRef(ref) {
513
+ const refs = connectionState?.refsByTarget.get(connectionState.currentTargetId ?? "") ?? {};
514
+ const found = refs[ref];
515
+ if (!found?.backendDOMNodeId) {
516
+ throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
517
+ }
518
+ return found.backendDOMNodeId;
519
+ }
520
+ function loadBuildDomTreeScript() {
521
+ const currentDir = path2.dirname(fileURLToPath(import.meta.url));
522
+ const candidates = [
523
+ path2.resolve(currentDir, "../../../extension/buildDomTree.js"),
524
+ path2.resolve(currentDir, "../../extension/buildDomTree.js"),
525
+ path2.resolve(currentDir, "../../../packages/extension/buildDomTree.js"),
526
+ path2.resolve(currentDir, "../../../extension/dist/buildDomTree.js")
527
+ ];
528
+ for (const candidate of candidates) {
529
+ try {
530
+ return readFileSync(candidate, "utf8");
531
+ } catch {
532
+ }
533
+ }
534
+ throw new Error("Cannot find buildDomTree.js");
535
+ }
536
+ async function evaluate(targetId, expression, returnByValue = true) {
537
+ const result = await sessionCommand(targetId, "Runtime.evaluate", {
538
+ expression,
539
+ awaitPromise: true,
540
+ returnByValue
541
+ });
542
+ if (result.exceptionDetails) {
543
+ throw new Error(result.exceptionDetails.text || "Runtime.evaluate failed");
544
+ }
545
+ return result.result.value ?? result.result;
546
+ }
547
+ async function resolveNode(targetId, backendNodeId) {
548
+ const result = await sessionCommand(targetId, "DOM.pushNodesByBackendIdsToFrontend", {
549
+ backendNodeIds: [backendNodeId]
550
+ });
551
+ return result.nodeId;
552
+ }
553
+ async function focusNode(targetId, backendNodeId) {
554
+ const nodeId = await resolveNode(targetId, backendNodeId);
555
+ await sessionCommand(targetId, "DOM.focus", { nodeId });
556
+ }
557
+ async function getNodeBox(targetId, backendNodeId) {
558
+ const result = await sessionCommand(targetId, "DOM.getBoxModel", {
559
+ backendNodeId
560
+ });
561
+ const quad = result.model.content.length >= 8 ? result.model.content : result.model.border;
562
+ const xs = [quad[0], quad[2], quad[4], quad[6]];
563
+ const ys = [quad[1], quad[3], quad[5], quad[7]];
564
+ return {
565
+ x: xs.reduce((a, b) => a + b, 0) / xs.length,
566
+ y: ys.reduce((a, b) => a + b, 0) / ys.length
567
+ };
568
+ }
569
+ async function mouseClick(targetId, x, y) {
570
+ await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
571
+ await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
572
+ await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
573
+ }
574
+ async function getAttributeValue(targetId, backendNodeId, attribute) {
575
+ const nodeId = await resolveNode(targetId, backendNodeId);
576
+ if (attribute === "text") {
577
+ return evaluate(targetId, `(() => { const n = this; return n.innerText ?? n.textContent ?? ''; }).call(document.querySelector('[data-bb-node-id="${nodeId}"]'))`);
578
+ }
579
+ const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
580
+ const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
581
+ objectId: result.object.objectId,
582
+ 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)}) || ''; }`,
583
+ returnByValue: true
584
+ });
585
+ return String(call.result.value ?? "");
586
+ }
587
+ async function buildSnapshot(targetId, request) {
588
+ const script = loadBuildDomTreeScript();
589
+ const expression = `(() => { ${script}; return (typeof buildDomTree === 'function' ? buildDomTree : globalThis.buildDomTree)(${JSON.stringify({
590
+ interactiveOnly: !!request.interactive,
591
+ compact: !!request.compact,
592
+ maxDepth: request.maxDepth,
593
+ selector: request.selector
594
+ })}); })()`;
595
+ const value = await evaluate(targetId, expression, true);
596
+ connectionState?.refsByTarget.set(targetId, value.refs || {});
597
+ return value;
598
+ }
599
+ function ok(id, data) {
600
+ return { id, success: true, data };
601
+ }
602
+ function fail(id, error) {
603
+ return { id, success: false, error: buildRequestError(error).message };
604
+ }
605
+ async function ensureCdpConnection() {
606
+ if (connectionState) return;
607
+ if (reconnecting) return reconnecting;
608
+ reconnecting = (async () => {
609
+ const discovered = await discoverCdpPort();
610
+ if (!discovered) {
611
+ throw new Error("No browser connection found");
612
+ }
613
+ const version = await getJsonVersion(discovered.host, discovered.port);
614
+ const wsUrl = version.webSocketDebuggerUrl;
615
+ const socket = await connectWebSocket(wsUrl);
616
+ connectionState = createState(discovered.host, discovered.port, wsUrl, socket);
617
+ })();
618
+ try {
619
+ await reconnecting;
620
+ } finally {
621
+ reconnecting = null;
622
+ }
623
+ }
624
+ async function sendCommand(request) {
625
+ try {
626
+ await ensureCdpConnection();
627
+ const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("\u8BF7\u6C42\u8D85\u65F6")), COMMAND_TIMEOUT));
628
+ return await Promise.race([dispatchRequest(request), timeout]);
629
+ } catch (error) {
630
+ return fail(request.id, error);
631
+ }
632
+ }
633
+ async function dispatchRequest(request) {
634
+ const target = await ensurePageTarget(request.tabId);
635
+ switch (request.action) {
636
+ case "open": {
637
+ if (!request.url) return fail(request.id, "Missing url parameter");
638
+ if (request.tabId === void 0) {
639
+ const created = await browserCommand("Target.createTarget", { url: request.url });
640
+ const newTarget = await ensurePageTarget(created.targetId);
641
+ return ok(request.id, { url: request.url, tabId: Number(newTarget.id) || void 0 });
642
+ }
643
+ await pageCommand(target.id, "Page.navigate", { url: request.url });
644
+ return ok(request.id, { url: request.url, title: target.title, tabId: Number(target.id) || void 0 });
645
+ }
646
+ case "snapshot": {
647
+ const snapshotData = await buildSnapshot(target.id, request);
648
+ return ok(request.id, { title: target.title, url: target.url, snapshotData });
649
+ }
650
+ case "click":
651
+ case "hover": {
652
+ if (!request.ref) return fail(request.id, "Missing ref parameter");
653
+ const backendNodeId = parseRef(request.ref);
654
+ const point = await getNodeBox(target.id, backendNodeId);
655
+ await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
656
+ if (request.action === "click") await mouseClick(target.id, point.x, point.y);
657
+ return ok(request.id, {});
658
+ }
659
+ case "fill":
660
+ case "type": {
661
+ if (!request.ref) return fail(request.id, "Missing ref parameter");
662
+ if (request.text == null) return fail(request.id, "Missing text parameter");
663
+ const backendNodeId = parseRef(request.ref);
664
+ await focusNode(target.id, backendNodeId);
665
+ if (request.action === "fill") {
666
+ await evaluate(target.id, `document.activeElement && ((document.activeElement.value = ''), document.activeElement.dispatchEvent(new Event('input', { bubbles: true })))`);
667
+ }
668
+ await sessionCommand(target.id, "Input.insertText", { text: request.text });
669
+ return ok(request.id, { value: request.text });
670
+ }
671
+ case "check":
672
+ case "uncheck": {
673
+ if (!request.ref) return fail(request.id, "Missing ref parameter");
674
+ const backendNodeId = parseRef(request.ref);
675
+ const desired = request.action === "check";
676
+ const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
677
+ await sessionCommand(target.id, "Runtime.callFunctionOn", {
678
+ objectId: resolved.object.objectId,
679
+ functionDeclaration: `function() { this.checked = ${desired}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
680
+ });
681
+ return ok(request.id, {});
682
+ }
683
+ case "select": {
684
+ if (!request.ref || request.value == null) return fail(request.id, "Missing ref or value parameter");
685
+ const backendNodeId = parseRef(request.ref);
686
+ const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
687
+ await sessionCommand(target.id, "Runtime.callFunctionOn", {
688
+ objectId: resolved.object.objectId,
689
+ functionDeclaration: `function() { this.value = ${JSON.stringify(request.value)}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
690
+ });
691
+ return ok(request.id, { value: request.value });
692
+ }
693
+ case "get": {
694
+ if (!request.ref || !request.attribute) return fail(request.id, "Missing ref or attribute parameter");
695
+ const value = await getAttributeValue(target.id, parseRef(request.ref), request.attribute);
696
+ return ok(request.id, { value });
697
+ }
698
+ case "screenshot": {
699
+ const result = await sessionCommand(target.id, "Page.captureScreenshot", { format: "png", fromSurface: true });
700
+ return ok(request.id, { dataUrl: `data:image/png;base64,${result.data}` });
701
+ }
702
+ case "close": {
703
+ await browserCommand("Target.closeTarget", { targetId: target.id });
704
+ return ok(request.id, {});
705
+ }
706
+ case "wait": {
707
+ await new Promise((resolve2) => setTimeout(resolve2, request.ms ?? 1e3));
708
+ return ok(request.id, {});
709
+ }
710
+ case "press": {
711
+ if (!request.key) return fail(request.id, "Missing key parameter");
712
+ await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyDown", key: request.key });
713
+ if (request.key.length === 1) {
714
+ await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "char", text: request.key, key: request.key });
715
+ }
716
+ await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyUp", key: request.key });
717
+ return ok(request.id, {});
718
+ }
719
+ case "scroll": {
720
+ const deltaY = request.direction === "up" ? -(request.pixels ?? 300) : request.pixels ?? 300;
721
+ await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseWheel", x: 0, y: 0, deltaX: 0, deltaY });
722
+ return ok(request.id, {});
723
+ }
724
+ case "back": {
725
+ await evaluate(target.id, "history.back(); undefined");
726
+ return ok(request.id, {});
727
+ }
728
+ case "forward": {
729
+ await evaluate(target.id, "history.forward(); undefined");
730
+ return ok(request.id, {});
731
+ }
732
+ case "refresh": {
733
+ await sessionCommand(target.id, "Page.reload", { ignoreCache: false });
734
+ return ok(request.id, {});
735
+ }
736
+ case "eval": {
737
+ if (!request.script) return fail(request.id, "Missing script parameter");
738
+ const result = await evaluate(target.id, request.script, true);
739
+ return ok(request.id, { result });
740
+ }
741
+ case "tab_list": {
742
+ 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: Number(item.id) || index }));
743
+ return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
744
+ }
745
+ case "tab_new": {
746
+ const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank" });
747
+ return ok(request.id, { tabId: Number(created.targetId) || void 0, url: request.url ?? "about:blank" });
748
+ }
749
+ case "tab_select": {
750
+ const tabs = (await getTargets()).filter((item) => item.type === "page");
751
+ const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
752
+ if (!selected) return fail(request.id, "Tab not found");
753
+ connectionState.currentTargetId = selected.id;
754
+ await attachTarget(selected.id);
755
+ return ok(request.id, { tabId: Number(selected.id) || void 0, url: selected.url, title: selected.title });
756
+ }
757
+ case "tab_close": {
758
+ const tabs = (await getTargets()).filter((item) => item.type === "page");
759
+ const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
760
+ if (!selected) return fail(request.id, "Tab not found");
761
+ await browserCommand("Target.closeTarget", { targetId: selected.id });
762
+ return ok(request.id, { tabId: Number(selected.id) || void 0 });
763
+ }
764
+ case "frame": {
765
+ if (!request.selector) return fail(request.id, "Missing selector parameter");
766
+ const document = await pageCommand(target.id, "DOM.getDocument", {});
767
+ const node = await pageCommand(target.id, "DOM.querySelector", { nodeId: document.root.nodeId, selector: request.selector });
768
+ if (!node.nodeId) return fail(request.id, `\u627E\u4E0D\u5230 iframe: ${request.selector}`);
769
+ const described = await pageCommand(target.id, "DOM.describeNode", { nodeId: node.nodeId });
770
+ const frameId = described.node.frameId;
771
+ const nodeName = String(described.node.nodeName ?? "").toLowerCase();
772
+ if (!frameId) return fail(request.id, `\u65E0\u6CD5\u83B7\u53D6 iframe frameId: ${request.selector}`);
773
+ if (nodeName && nodeName !== "iframe" && nodeName !== "frame") return fail(request.id, `\u5143\u7D20\u4E0D\u662F iframe: ${nodeName}`);
774
+ connectionState?.activeFrameIdByTarget.set(target.id, frameId);
775
+ const attributes = described.node.attributes ?? [];
776
+ const attrMap = {};
777
+ for (let i = 0; i < attributes.length; i += 2) attrMap[String(attributes[i])] = String(attributes[i + 1] ?? "");
778
+ return ok(request.id, { frameInfo: { selector: request.selector, name: attrMap.name ?? "", url: attrMap.src ?? "", frameId } });
779
+ }
780
+ case "frame_main": {
781
+ connectionState?.activeFrameIdByTarget.set(target.id, null);
782
+ return ok(request.id, { frameInfo: { frameId: 0 } });
783
+ }
784
+ case "dialog": {
785
+ connectionState?.dialogHandlers.set(target.id, { accept: request.dialogResponse !== "dismiss", ...request.promptText !== void 0 ? { promptText: request.promptText } : {} });
786
+ await sessionCommand(target.id, "Page.enable");
787
+ return ok(request.id, { dialog: { armed: true, response: request.dialogResponse ?? "accept" } });
788
+ }
789
+ case "network": {
790
+ const subCommand = request.networkCommand ?? "requests";
791
+ switch (subCommand) {
792
+ case "requests": {
793
+ await ensureNetworkMonitoring(target.id);
794
+ const requests = Array.from(networkRequests.values()).filter((item) => !request.filter || item.url.includes(request.filter));
795
+ if (request.withBody) {
796
+ await Promise.all(requests.map(async (item) => {
797
+ if (item.failed || item.responseBody !== void 0 || item.bodyError !== void 0) return;
798
+ try {
799
+ const body = await sessionCommand(target.id, "Network.getResponseBody", { requestId: item.requestId });
800
+ item.responseBody = body.body;
801
+ item.responseBodyBase64 = body.base64Encoded;
802
+ } catch (error) {
803
+ item.bodyError = error instanceof Error ? error.message : String(error);
804
+ }
805
+ }));
806
+ }
807
+ return ok(request.id, { networkRequests: requests });
808
+ }
809
+ case "route":
810
+ return ok(request.id, { routeCount: 0 });
811
+ case "unroute":
812
+ return ok(request.id, { routeCount: 0 });
813
+ case "clear":
814
+ networkRequests.clear();
815
+ return ok(request.id, {});
816
+ default:
817
+ return fail(request.id, `Unknown network subcommand: ${subCommand}`);
818
+ }
819
+ }
820
+ case "console": {
821
+ const subCommand = request.consoleCommand ?? "get";
822
+ await ensureConsoleMonitoring(target.id);
823
+ switch (subCommand) {
824
+ case "get":
825
+ return ok(request.id, { consoleMessages: consoleMessages.filter((item) => !request.filter || item.text.includes(request.filter)) });
826
+ case "clear":
827
+ consoleMessages.length = 0;
828
+ return ok(request.id, {});
829
+ default:
830
+ return fail(request.id, `Unknown console subcommand: ${subCommand}`);
831
+ }
832
+ }
833
+ case "errors": {
834
+ const subCommand = request.errorsCommand ?? "get";
835
+ await ensureConsoleMonitoring(target.id);
836
+ switch (subCommand) {
837
+ case "get":
838
+ return ok(request.id, { jsErrors: jsErrors.filter((item) => !request.filter || item.message.includes(request.filter) || item.url?.includes(request.filter)) });
839
+ case "clear":
840
+ jsErrors.length = 0;
841
+ return ok(request.id, {});
842
+ default:
843
+ return fail(request.id, `Unknown errors subcommand: ${subCommand}`);
844
+ }
845
+ }
846
+ case "trace": {
847
+ const subCommand = request.traceCommand ?? "status";
848
+ switch (subCommand) {
849
+ case "start":
850
+ traceRecording = true;
851
+ traceEvents.length = 0;
852
+ return ok(request.id, { traceStatus: { recording: true, eventCount: 0 } });
853
+ case "stop": {
854
+ traceRecording = false;
855
+ return ok(request.id, { traceEvents: [...traceEvents], traceStatus: { recording: false, eventCount: traceEvents.length } });
856
+ }
857
+ case "status":
858
+ return ok(request.id, { traceStatus: { recording: traceRecording, eventCount: traceEvents.length } });
859
+ default:
860
+ return fail(request.id, `Unknown trace subcommand: ${subCommand}`);
861
+ }
862
+ }
863
+ default:
864
+ return fail(request.id, `Action not yet supported in direct CDP mode: ${request.action}`);
865
+ }
866
+ }
14
867
 
15
868
  // packages/cli/src/client.ts
16
869
  var jqExpression;
@@ -30,184 +883,190 @@ function handleJqResponse(response) {
30
883
  printJqResults(response);
31
884
  }
32
885
  }
33
- async function sendCommand(request) {
34
- const controller = new AbortController();
35
- const timeoutId = setTimeout(() => controller.abort(), COMMAND_TIMEOUT);
36
- try {
37
- const res = await fetch(`${DAEMON_BASE_URL}/command`, {
38
- method: "POST",
39
- headers: {
40
- "Content-Type": "application/json"
41
- },
42
- body: JSON.stringify(request),
43
- signal: controller.signal
44
- });
45
- clearTimeout(timeoutId);
46
- if (!res.ok) {
47
- if (res.status === 408) {
48
- return {
49
- id: request.id,
50
- success: false,
51
- error: "\u547D\u4EE4\u6267\u884C\u8D85\u65F6"
52
- };
53
- }
54
- if (res.status === 503) {
55
- return {
56
- id: request.id,
57
- success: false,
58
- error: [
59
- "Chrome extension not connected.",
60
- "",
61
- "1. Download extension: https://github.com/epiral/bb-browser/releases/latest",
62
- "2. Unzip the downloaded file",
63
- "3. Open chrome://extensions/ \u2192 Enable Developer Mode",
64
- '4. Click "Load unpacked" \u2192 select the unzipped folder'
65
- ].join("\n")
66
- };
67
- }
68
- return {
69
- id: request.id,
70
- success: false,
71
- error: `HTTP \u9519\u8BEF: ${res.status} ${res.statusText}`
72
- };
73
- }
74
- const response = await res.json();
75
- return response;
76
- } catch (error) {
77
- clearTimeout(timeoutId);
78
- if (error instanceof Error) {
79
- if (error.name === "AbortError") {
80
- return {
81
- id: request.id,
82
- success: false,
83
- error: "\u8BF7\u6C42\u8D85\u65F6"
84
- };
85
- }
86
- if (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED")) {
87
- throw new Error([
88
- "Cannot connect to daemon.",
89
- "",
90
- "Start the daemon first:",
91
- " bb-browser daemon",
92
- "",
93
- "Then load the Chrome extension:",
94
- " chrome://extensions/ \u2192 Developer Mode \u2192 Load unpacked \u2192 node_modules/bb-browser/extension/"
95
- ].join("\n"));
96
- }
97
- throw error;
98
- }
99
- throw error;
100
- }
886
+ async function sendCommand2(request) {
887
+ return sendCommand(request);
101
888
  }
102
889
 
103
890
  // packages/cli/src/daemon-manager.ts
104
- import { spawn } from "child_process";
105
- import { existsSync } from "fs";
106
- import { fileURLToPath } from "url";
891
+ import { fileURLToPath as fileURLToPath2 } from "url";
107
892
  import { dirname, resolve } from "path";
108
- function getDaemonPath() {
109
- const currentFile = fileURLToPath(import.meta.url);
110
- const currentDir = dirname(currentFile);
111
- const sameDirPath = resolve(currentDir, "daemon.js");
112
- if (existsSync(sameDirPath)) {
113
- return sameDirPath;
114
- }
115
- return resolve(currentDir, "../../daemon/dist/index.js");
116
- }
117
- var DAEMON_START_TIMEOUT = 5e3;
118
- var POLL_INTERVAL = 200;
893
+ import { existsSync as existsSync2 } from "fs";
119
894
  async function isDaemonRunning() {
895
+ return await discoverCdpPort() !== null;
896
+ }
897
+ async function ensureDaemonRunning() {
120
898
  try {
121
- const controller = new AbortController();
122
- const timeoutId = setTimeout(() => controller.abort(), 2e3);
123
- const response = await fetch(`${DAEMON_BASE_URL}/status`, {
124
- signal: controller.signal
125
- });
126
- clearTimeout(timeoutId);
127
- return response.ok;
128
- } catch {
129
- return false;
899
+ await ensureCdpConnection();
900
+ } catch (error) {
901
+ if (error instanceof Error && error.message.includes("No browser connection found")) {
902
+ throw new Error([
903
+ "bb-browser: Could not start browser.",
904
+ "",
905
+ "Make sure Chrome is installed, then try again.",
906
+ "Or specify a CDP port manually: bb-browser --port 9222"
907
+ ].join("\n"));
908
+ }
909
+ throw error;
130
910
  }
131
911
  }
132
- async function isExtensionConnected() {
133
- try {
134
- const controller = new AbortController();
135
- const timeoutId = setTimeout(() => controller.abort(), 2e3);
136
- const response = await fetch(`${DAEMON_BASE_URL}/status`, {
137
- signal: controller.signal
138
- });
139
- clearTimeout(timeoutId);
140
- if (!response.ok) return false;
141
- const data = await response.json();
142
- return !!data.extensionConnected;
143
- } catch {
144
- return false;
912
+
913
+ // packages/cli/src/history-sqlite.ts
914
+ import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
915
+ import { execSync as execSync2 } from "child_process";
916
+ import { homedir, tmpdir } from "os";
917
+ import { join } from "path";
918
+ function getHistoryPathCandidates() {
919
+ const home = homedir();
920
+ const localAppData = process.env.LOCALAPPDATA || "";
921
+ const candidates = [
922
+ join(home, "Library/Application Support/Google/Chrome/Default/History"),
923
+ join(home, "Library/Application Support/Microsoft Edge/Default/History"),
924
+ join(home, "Library/Application Support/BraveSoftware/Brave-Browser/Default/History"),
925
+ join(home, "Library/Application Support/Arc/User Data/Default/History"),
926
+ join(home, ".config/google-chrome/Default/History")
927
+ ];
928
+ if (localAppData) {
929
+ candidates.push(
930
+ join(localAppData, "Google/Chrome/User Data/Default/History"),
931
+ join(localAppData, "Microsoft/Edge/User Data/Default/History")
932
+ );
145
933
  }
934
+ return candidates;
146
935
  }
147
- async function waitForDaemon(timeoutMs) {
148
- const startTime = Date.now();
149
- while (Date.now() - startTime < timeoutMs) {
150
- if (await isDaemonRunning()) {
151
- return true;
936
+ function findHistoryPath() {
937
+ for (const historyPath of getHistoryPathCandidates()) {
938
+ if (existsSync3(historyPath)) {
939
+ return historyPath;
152
940
  }
153
- await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
154
941
  }
155
- return false;
942
+ return null;
156
943
  }
157
- function spawnDaemon() {
158
- const daemonPath = getDaemonPath();
159
- const daemonProcess = spawn(process.execPath, [daemonPath], {
160
- detached: true,
161
- stdio: "ignore",
162
- env: { ...process.env }
163
- });
164
- daemonProcess.unref();
944
+ function sqlEscape(value) {
945
+ return value.replace(/'/g, "''");
165
946
  }
166
- async function ensureDaemonRunning() {
167
- if (await isDaemonRunning()) {
168
- return;
169
- }
170
- spawnDaemon();
171
- const ready = await waitForDaemon(DAEMON_START_TIMEOUT);
172
- if (!ready) {
173
- throw new Error(
174
- "\u65E0\u6CD5\u542F\u52A8 Daemon\u3002\u8BF7\u624B\u52A8\u8FD0\u884C bb-browser daemon \u6216 bb-daemon \u542F\u52A8\u670D\u52A1"
175
- );
176
- }
177
- const extStart = Date.now();
178
- while (Date.now() - extStart < 1e4) {
179
- if (await isExtensionConnected()) return;
180
- await new Promise((r) => setTimeout(r, POLL_INTERVAL));
947
+ function buildTimeWhere(days) {
948
+ if (!days || days <= 0) {
949
+ return "";
181
950
  }
951
+ return `last_visit_time > (strftime('%s', 'now') - ${Math.floor(days)}*86400) * 1000000 + 11644473600000000`;
182
952
  }
183
- async function stopDaemon() {
953
+ function runHistoryQuery(sql, mapRow) {
954
+ const historyPath = findHistoryPath();
955
+ if (!historyPath) {
956
+ return [];
957
+ }
958
+ const tmpPath = join(tmpdir(), `bb-history-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
184
959
  try {
185
- const controller = new AbortController();
186
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
187
- const response = await fetch(`${DAEMON_BASE_URL}/shutdown`, {
188
- method: "POST",
189
- signal: controller.signal
960
+ copyFileSync(historyPath, tmpPath);
961
+ const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
962
+ const escapedSql = sql.replace(/"/g, '\\"');
963
+ const output = execSync2(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
964
+ encoding: "utf-8",
965
+ stdio: ["pipe", "pipe", "pipe"]
190
966
  });
191
- clearTimeout(timeoutId);
192
- return response.ok;
967
+ return output.split("\n").filter(Boolean).map((line) => mapRow(line.split(" "))).filter((item) => item !== null);
193
968
  } catch {
194
- return false;
969
+ return [];
970
+ } finally {
971
+ try {
972
+ unlinkSync(tmpPath);
973
+ } catch {
974
+ }
195
975
  }
196
976
  }
977
+ function searchHistory(query, days) {
978
+ const conditions = [];
979
+ const trimmedQuery = query?.trim();
980
+ if (trimmedQuery) {
981
+ const escapedQuery = sqlEscape(trimmedQuery);
982
+ conditions.push(`(url LIKE '%${escapedQuery}%' OR title LIKE '%${escapedQuery}%')`);
983
+ }
984
+ const timeWhere = buildTimeWhere(days);
985
+ if (timeWhere) {
986
+ conditions.push(timeWhere);
987
+ }
988
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
989
+ const sql = `
990
+ SELECT
991
+ url,
992
+ REPLACE(IFNULL(title, ''), char(9), ' '),
993
+ IFNULL(visit_count, 0),
994
+ IFNULL(last_visit_time, 0)
995
+ FROM urls
996
+ ${whereClause}
997
+ ORDER BY last_visit_time DESC
998
+ LIMIT 100;
999
+ `.trim();
1000
+ return runHistoryQuery(sql, (row) => {
1001
+ if (row.length < 4) {
1002
+ return null;
1003
+ }
1004
+ const chromeTimestamp = Number(row[3]) || 0;
1005
+ return {
1006
+ url: row[0] || "",
1007
+ title: row[1] || "",
1008
+ visitCount: Number(row[2]) || 0,
1009
+ lastVisitTime: chromeTimestamp > 0 ? chromeTimestamp / 1e6 - 11644473600 : 0
1010
+ };
1011
+ });
1012
+ }
1013
+ function getHistoryDomains(days) {
1014
+ const timeWhere = buildTimeWhere(days);
1015
+ const whereClause = timeWhere ? `WHERE ${timeWhere}` : "";
1016
+ const sql = `
1017
+ SELECT
1018
+ domain,
1019
+ SUM(visit_count) AS visits,
1020
+ GROUP_CONCAT(title, char(31)) AS titles
1021
+ FROM (
1022
+ SELECT
1023
+ CASE
1024
+ WHEN instr(url, '//') > 0 AND instr(substr(url, instr(url, '//') + 2), '/') > 0
1025
+ THEN substr(
1026
+ substr(url, instr(url, '//') + 2),
1027
+ 1,
1028
+ instr(substr(url, instr(url, '//') + 2), '/') - 1
1029
+ )
1030
+ WHEN instr(url, '//') > 0 THEN substr(url, instr(url, '//') + 2)
1031
+ WHEN instr(url, '/') > 0 THEN substr(url, 1, instr(url, '/') - 1)
1032
+ ELSE url
1033
+ END AS domain,
1034
+ IFNULL(visit_count, 0) AS visit_count,
1035
+ REPLACE(IFNULL(title, ''), char(31), ' ') AS title
1036
+ FROM urls
1037
+ ${whereClause}
1038
+ )
1039
+ WHERE domain != ''
1040
+ GROUP BY domain
1041
+ ORDER BY visits DESC
1042
+ LIMIT 50;
1043
+ `.trim();
1044
+ return runHistoryQuery(sql, (row) => {
1045
+ if (row.length < 3) {
1046
+ return null;
1047
+ }
1048
+ const titles = row[2] ? Array.from(new Set(row[2].split(String.fromCharCode(31)).map((title) => title.trim()).filter(Boolean))).slice(0, 10) : [];
1049
+ return {
1050
+ domain: row[0] || "",
1051
+ visits: Number(row[1]) || 0,
1052
+ titles
1053
+ };
1054
+ });
1055
+ }
197
1056
 
198
1057
  // packages/cli/src/commands/site.ts
199
- import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync } from "fs";
200
- import { join, relative } from "path";
201
- import { homedir } from "os";
202
- import { execSync } from "child_process";
203
- var BB_DIR = join(homedir(), ".bb-browser");
204
- var LOCAL_SITES_DIR = join(BB_DIR, "sites");
205
- var COMMUNITY_SITES_DIR = join(BB_DIR, "bb-sites");
1058
+ import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
1059
+ import { join as join2, relative } from "path";
1060
+ import { homedir as homedir2 } from "os";
1061
+ import { execSync as execSync3 } from "child_process";
1062
+ var BB_DIR = join2(homedir2(), ".bb-browser");
1063
+ var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
1064
+ var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
206
1065
  var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
207
1066
  function checkCliUpdate() {
208
1067
  try {
209
- const current = execSync("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
210
- const latest = execSync("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1068
+ const current = execSync3("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1069
+ const latest = execSync3("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
211
1070
  if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
212
1071
  console.log(`
213
1072
  \u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
@@ -218,7 +1077,7 @@ function checkCliUpdate() {
218
1077
  function parseSiteMeta(filePath, source) {
219
1078
  let content;
220
1079
  try {
221
- content = readFileSync(filePath, "utf-8");
1080
+ content = readFileSync2(filePath, "utf-8");
222
1081
  } catch {
223
1082
  return null;
224
1083
  }
@@ -278,7 +1137,7 @@ function parseSiteMeta(filePath, source) {
278
1137
  return meta;
279
1138
  }
280
1139
  function scanSites(dir, source) {
281
- if (!existsSync2(dir)) return [];
1140
+ if (!existsSync4(dir)) return [];
282
1141
  const sites = [];
283
1142
  function walk(currentDir) {
284
1143
  let entries;
@@ -288,7 +1147,7 @@ function scanSites(dir, source) {
288
1147
  return;
289
1148
  }
290
1149
  for (const entry of entries) {
291
- const fullPath = join(currentDir, entry.name);
1150
+ const fullPath = join2(currentDir, entry.name);
292
1151
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
293
1152
  walk(fullPath);
294
1153
  } else if (entry.isFile() && entry.name.endsWith(".js")) {
@@ -392,10 +1251,10 @@ function siteSearch(query, options) {
392
1251
  }
393
1252
  function siteUpdate() {
394
1253
  mkdirSync(BB_DIR, { recursive: true });
395
- if (existsSync2(join(COMMUNITY_SITES_DIR, ".git"))) {
1254
+ if (existsSync4(join2(COMMUNITY_SITES_DIR, ".git"))) {
396
1255
  console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
397
1256
  try {
398
- execSync("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
1257
+ execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
399
1258
  console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
400
1259
  console.log("");
401
1260
  console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
@@ -407,7 +1266,7 @@ function siteUpdate() {
407
1266
  } else {
408
1267
  console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
409
1268
  try {
410
- execSync(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
1269
+ execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
411
1270
  console.log("\u514B\u9686\u5B8C\u6210\u3002");
412
1271
  console.log("");
413
1272
  console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
@@ -466,16 +1325,7 @@ function siteInfo(name, options) {
466
1325
  }
467
1326
  async function siteRecommend(options) {
468
1327
  const days = options.days ?? 30;
469
- const response = await sendCommand({
470
- id: generateId(),
471
- action: "history",
472
- historyCommand: "domains",
473
- ms: days
474
- });
475
- if (!response.success) {
476
- throw new Error(response.error || "History command failed");
477
- }
478
- const historyDomains = response.data?.historyDomains || [];
1328
+ const historyDomains = getHistoryDomains(days);
479
1329
  const sites = getAllSites();
480
1330
  const sitesByDomain = /* @__PURE__ */ new Map();
481
1331
  for (const site of sites) {
@@ -593,7 +1443,7 @@ async function siteRun(name, args, options) {
593
1443
  process.exit(1);
594
1444
  }
595
1445
  }
596
- const jsContent = readFileSync(site.filePath, "utf-8");
1446
+ const jsContent = readFileSync2(site.filePath, "utf-8");
597
1447
  const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
598
1448
  const argsJson = JSON.stringify(argMap);
599
1449
  const script = `(${jsBody})(${argsJson})`;
@@ -653,7 +1503,7 @@ async function siteRun(name, args, options) {
653
1503
  let targetTabId = options.tabId;
654
1504
  if (!targetTabId && site.domain) {
655
1505
  const listReq = { id: generateId(), action: "tab_list" };
656
- const listResp = await sendCommand(listReq);
1506
+ const listResp = await sendCommand2(listReq);
657
1507
  if (listResp.success && listResp.data?.tabs) {
658
1508
  const matchingTab = listResp.data.tabs.find(
659
1509
  (tab) => matchTabOrigin(tab.url, site.domain)
@@ -663,7 +1513,7 @@ async function siteRun(name, args, options) {
663
1513
  }
664
1514
  }
665
1515
  if (!targetTabId) {
666
- const newResp = await sendCommand({
1516
+ const newResp = await sendCommand2({
667
1517
  id: generateId(),
668
1518
  action: "tab_new",
669
1519
  url: `https://${site.domain}`
@@ -673,7 +1523,7 @@ async function siteRun(name, args, options) {
673
1523
  }
674
1524
  }
675
1525
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
676
- const evalResp = await sendCommand(evalReq);
1526
+ const evalResp = await sendCommand2(evalReq);
677
1527
  if (!evalResp.success) {
678
1528
  const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
679
1529
  if (options.json) {
@@ -808,10 +1658,10 @@ async function siteCommand(args, options = {}) {
808
1658
  silentUpdate();
809
1659
  }
810
1660
  function silentUpdate() {
811
- const gitDir = join(COMMUNITY_SITES_DIR, ".git");
812
- if (!existsSync2(gitDir)) return;
813
- import("child_process").then(({ spawn: spawn3 }) => {
814
- const child = spawn3("git", ["pull", "--ff-only"], {
1661
+ const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
1662
+ if (!existsSync4(gitDir)) return;
1663
+ import("child_process").then(({ spawn: spawn2 }) => {
1664
+ const child = spawn2("git", ["pull", "--ff-only"], {
815
1665
  cwd: COMMUNITY_SITES_DIR,
816
1666
  stdio: "ignore",
817
1667
  detached: true
@@ -847,7 +1697,7 @@ async function openCommand(url, options = {}) {
847
1697
  request.tabId = tabId;
848
1698
  }
849
1699
  }
850
- const response = await sendCommand(request);
1700
+ const response = await sendCommand2(request);
851
1701
  if (options.json) {
852
1702
  console.log(JSON.stringify(response, null, 2));
853
1703
  } else {
@@ -883,7 +1733,7 @@ async function snapshotCommand(options = {}) {
883
1733
  selector: options.selector,
884
1734
  tabId: options.tabId
885
1735
  };
886
- const response = await sendCommand(request);
1736
+ const response = await sendCommand2(request);
887
1737
  if (options.json) {
888
1738
  console.log(JSON.stringify(response, null, 2));
889
1739
  } else {
@@ -902,7 +1752,7 @@ async function snapshotCommand(options = {}) {
902
1752
  }
903
1753
 
904
1754
  // packages/cli/src/commands/click.ts
905
- function parseRef(ref) {
1755
+ function parseRef2(ref) {
906
1756
  return ref.startsWith("@") ? ref.slice(1) : ref;
907
1757
  }
908
1758
  async function clickCommand(ref, options = {}) {
@@ -910,14 +1760,14 @@ async function clickCommand(ref, options = {}) {
910
1760
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
911
1761
  }
912
1762
  await ensureDaemonRunning();
913
- const parsedRef = parseRef(ref);
1763
+ const parsedRef = parseRef2(ref);
914
1764
  const request = {
915
1765
  id: generateId(),
916
1766
  action: "click",
917
1767
  ref: parsedRef,
918
1768
  tabId: options.tabId
919
1769
  };
920
- const response = await sendCommand(request);
1770
+ const response = await sendCommand2(request);
921
1771
  if (options.json) {
922
1772
  console.log(JSON.stringify(response, null, 2));
923
1773
  } else {
@@ -937,7 +1787,7 @@ async function clickCommand(ref, options = {}) {
937
1787
  }
938
1788
 
939
1789
  // packages/cli/src/commands/hover.ts
940
- function parseRef2(ref) {
1790
+ function parseRef3(ref) {
941
1791
  return ref.startsWith("@") ? ref.slice(1) : ref;
942
1792
  }
943
1793
  async function hoverCommand(ref, options = {}) {
@@ -945,14 +1795,14 @@ async function hoverCommand(ref, options = {}) {
945
1795
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
946
1796
  }
947
1797
  await ensureDaemonRunning();
948
- const parsedRef = parseRef2(ref);
1798
+ const parsedRef = parseRef3(ref);
949
1799
  const request = {
950
1800
  id: generateId(),
951
1801
  action: "hover",
952
1802
  ref: parsedRef,
953
1803
  tabId: options.tabId
954
1804
  };
955
- const response = await sendCommand(request);
1805
+ const response = await sendCommand2(request);
956
1806
  if (options.json) {
957
1807
  console.log(JSON.stringify(response, null, 2));
958
1808
  } else {
@@ -972,7 +1822,7 @@ async function hoverCommand(ref, options = {}) {
972
1822
  }
973
1823
 
974
1824
  // packages/cli/src/commands/fill.ts
975
- function parseRef3(ref) {
1825
+ function parseRef4(ref) {
976
1826
  return ref.startsWith("@") ? ref.slice(1) : ref;
977
1827
  }
978
1828
  async function fillCommand(ref, text, options = {}) {
@@ -983,7 +1833,7 @@ async function fillCommand(ref, text, options = {}) {
983
1833
  throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
984
1834
  }
985
1835
  await ensureDaemonRunning();
986
- const parsedRef = parseRef3(ref);
1836
+ const parsedRef = parseRef4(ref);
987
1837
  const request = {
988
1838
  id: generateId(),
989
1839
  action: "fill",
@@ -991,7 +1841,7 @@ async function fillCommand(ref, text, options = {}) {
991
1841
  text,
992
1842
  tabId: options.tabId
993
1843
  };
994
- const response = await sendCommand(request);
1844
+ const response = await sendCommand2(request);
995
1845
  if (options.json) {
996
1846
  console.log(JSON.stringify(response, null, 2));
997
1847
  } else {
@@ -1012,7 +1862,7 @@ async function fillCommand(ref, text, options = {}) {
1012
1862
  }
1013
1863
 
1014
1864
  // packages/cli/src/commands/type.ts
1015
- function parseRef4(ref) {
1865
+ function parseRef5(ref) {
1016
1866
  return ref.startsWith("@") ? ref.slice(1) : ref;
1017
1867
  }
1018
1868
  async function typeCommand(ref, text, options = {}) {
@@ -1023,7 +1873,7 @@ async function typeCommand(ref, text, options = {}) {
1023
1873
  throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
1024
1874
  }
1025
1875
  await ensureDaemonRunning();
1026
- const parsedRef = parseRef4(ref);
1876
+ const parsedRef = parseRef5(ref);
1027
1877
  const request = {
1028
1878
  id: generateId(),
1029
1879
  action: "type",
@@ -1031,7 +1881,7 @@ async function typeCommand(ref, text, options = {}) {
1031
1881
  text,
1032
1882
  tabId: options.tabId
1033
1883
  };
1034
- const response = await sendCommand(request);
1884
+ const response = await sendCommand2(request);
1035
1885
  if (options.json) {
1036
1886
  console.log(JSON.stringify(response, null, 2));
1037
1887
  } else {
@@ -1059,7 +1909,7 @@ async function closeCommand(options = {}) {
1059
1909
  action: "close",
1060
1910
  tabId: options.tabId
1061
1911
  };
1062
- const response = await sendCommand(request);
1912
+ const response = await sendCommand2(request);
1063
1913
  if (options.json) {
1064
1914
  console.log(JSON.stringify(response, null, 2));
1065
1915
  } else {
@@ -1078,7 +1928,7 @@ async function closeCommand(options = {}) {
1078
1928
  }
1079
1929
 
1080
1930
  // packages/cli/src/commands/get.ts
1081
- function parseRef5(ref) {
1931
+ function parseRef6(ref) {
1082
1932
  return ref.startsWith("@") ? ref.slice(1) : ref;
1083
1933
  }
1084
1934
  async function getCommand(attribute, ref, options = {}) {
@@ -1090,10 +1940,10 @@ async function getCommand(attribute, ref, options = {}) {
1090
1940
  id: generateId(),
1091
1941
  action: "get",
1092
1942
  attribute,
1093
- ref: ref ? parseRef5(ref) : void 0,
1943
+ ref: ref ? parseRef6(ref) : void 0,
1094
1944
  tabId: options.tabId
1095
1945
  };
1096
- const response = await sendCommand(request);
1946
+ const response = await sendCommand2(request);
1097
1947
  if (options.json) {
1098
1948
  console.log(JSON.stringify(response, null, 2));
1099
1949
  } else {
@@ -1109,17 +1959,17 @@ async function getCommand(attribute, ref, options = {}) {
1109
1959
 
1110
1960
  // packages/cli/src/commands/screenshot.ts
1111
1961
  import fs from "fs";
1112
- import path from "path";
1113
- import os from "os";
1962
+ import path3 from "path";
1963
+ import os2 from "os";
1114
1964
  function getDefaultPath() {
1115
1965
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1116
1966
  const filename = `bb-screenshot-${timestamp}.png`;
1117
- return path.join(os.tmpdir(), filename);
1967
+ return path3.join(os2.tmpdir(), filename);
1118
1968
  }
1119
1969
  function saveBase64Image(dataUrl, filePath) {
1120
1970
  const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
1121
1971
  const buffer = Buffer.from(base64Data, "base64");
1122
- const dir = path.dirname(filePath);
1972
+ const dir = path3.dirname(filePath);
1123
1973
  if (!fs.existsSync(dir)) {
1124
1974
  fs.mkdirSync(dir, { recursive: true });
1125
1975
  }
@@ -1127,13 +1977,13 @@ function saveBase64Image(dataUrl, filePath) {
1127
1977
  }
1128
1978
  async function screenshotCommand(outputPath, options = {}) {
1129
1979
  await ensureDaemonRunning();
1130
- const filePath = outputPath ? path.resolve(outputPath) : getDefaultPath();
1980
+ const filePath = outputPath ? path3.resolve(outputPath) : getDefaultPath();
1131
1981
  const request = {
1132
1982
  id: generateId(),
1133
1983
  action: "screenshot",
1134
1984
  tabId: options.tabId
1135
1985
  };
1136
- const response = await sendCommand(request);
1986
+ const response = await sendCommand2(request);
1137
1987
  if (response.success && response.data?.dataUrl) {
1138
1988
  const dataUrl = response.data.dataUrl;
1139
1989
  saveBase64Image(dataUrl, filePath);
@@ -1160,7 +2010,7 @@ async function screenshotCommand(outputPath, options = {}) {
1160
2010
  function isTimeWait(target) {
1161
2011
  return /^\d+$/.test(target);
1162
2012
  }
1163
- function parseRef6(ref) {
2013
+ function parseRef7(ref) {
1164
2014
  return ref.startsWith("@") ? ref.slice(1) : ref;
1165
2015
  }
1166
2016
  async function waitCommand(target, options = {}) {
@@ -1179,7 +2029,7 @@ async function waitCommand(target, options = {}) {
1179
2029
  tabId: options.tabId
1180
2030
  };
1181
2031
  } else {
1182
- const ref = parseRef6(target);
2032
+ const ref = parseRef7(target);
1183
2033
  request = {
1184
2034
  id: generateId(),
1185
2035
  action: "wait",
@@ -1188,7 +2038,7 @@ async function waitCommand(target, options = {}) {
1188
2038
  tabId: options.tabId
1189
2039
  };
1190
2040
  }
1191
- const response = await sendCommand(request);
2041
+ const response = await sendCommand2(request);
1192
2042
  if (options.json) {
1193
2043
  console.log(JSON.stringify(response, null, 2));
1194
2044
  } else {
@@ -1196,7 +2046,7 @@ async function waitCommand(target, options = {}) {
1196
2046
  if (isTimeWait(target)) {
1197
2047
  console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
1198
2048
  } else {
1199
- console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
2049
+ console.log(`\u5143\u7D20 @${parseRef7(target)} \u5DF2\u51FA\u73B0`);
1200
2050
  }
1201
2051
  } else {
1202
2052
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -1236,7 +2086,7 @@ async function pressCommand(keyString, options = {}) {
1236
2086
  modifiers,
1237
2087
  tabId: options.tabId
1238
2088
  };
1239
- const response = await sendCommand(request);
2089
+ const response = await sendCommand2(request);
1240
2090
  if (options.json) {
1241
2091
  console.log(JSON.stringify(response, null, 2));
1242
2092
  } else {
@@ -1277,7 +2127,7 @@ async function scrollCommand(direction, pixels, options = {}) {
1277
2127
  pixels: pixelValue,
1278
2128
  tabId: options.tabId
1279
2129
  };
1280
- const response = await sendCommand(request);
2130
+ const response = await sendCommand2(request);
1281
2131
  if (options.json) {
1282
2132
  console.log(JSON.stringify(response, null, 2));
1283
2133
  } else {
@@ -1291,76 +2141,17 @@ async function scrollCommand(direction, pixels, options = {}) {
1291
2141
  }
1292
2142
 
1293
2143
  // packages/cli/src/commands/daemon.ts
1294
- import { spawn as spawn2 } from "child_process";
1295
- async function daemonCommand(options = {}) {
1296
- if (await isDaemonRunning()) {
1297
- if (options.json) {
1298
- console.log(JSON.stringify({ success: false, error: "Daemon \u5DF2\u5728\u8FD0\u884C" }));
1299
- } else {
1300
- console.log("Daemon \u5DF2\u5728\u8FD0\u884C");
1301
- }
1302
- return;
1303
- }
1304
- const daemonPath = getDaemonPath();
1305
- const args = [daemonPath];
1306
- if (options.host) {
1307
- args.push("--host", options.host);
1308
- }
1309
- if (options.json) {
1310
- console.log(JSON.stringify({ success: true, message: "Daemon \u542F\u52A8\u4E2D..." }));
1311
- } else {
1312
- console.log("Daemon \u542F\u52A8\u4E2D...");
1313
- }
1314
- await new Promise((resolve2, reject) => {
1315
- const child = spawn2(process.execPath, args, {
1316
- stdio: "inherit"
1317
- });
1318
- child.on("exit", (code) => {
1319
- if (code && code !== 0) {
1320
- reject(new Error(`Daemon exited with code ${code}`));
1321
- } else {
1322
- resolve2();
1323
- }
1324
- });
1325
- child.on("error", reject);
1326
- });
1327
- }
1328
- async function stopCommand(options = {}) {
1329
- if (!await isDaemonRunning()) {
1330
- if (options.json) {
1331
- console.log(JSON.stringify({ success: false, error: "Daemon \u672A\u8FD0\u884C" }));
1332
- } else {
1333
- console.log("Daemon \u672A\u8FD0\u884C");
1334
- }
1335
- return;
1336
- }
1337
- const stopped = await stopDaemon();
1338
- if (stopped) {
1339
- if (options.json) {
1340
- console.log(JSON.stringify({ success: true, message: "Daemon \u5DF2\u505C\u6B62" }));
1341
- } else {
1342
- console.log("Daemon \u5DF2\u505C\u6B62");
1343
- }
1344
- } else {
1345
- if (options.json) {
1346
- console.log(JSON.stringify({ success: false, error: "\u65E0\u6CD5\u505C\u6B62 Daemon" }));
1347
- } else {
1348
- console.error("\u65E0\u6CD5\u505C\u6B62 Daemon");
1349
- }
1350
- process.exit(1);
1351
- }
1352
- }
1353
2144
  async function statusCommand(options = {}) {
1354
2145
  const running = await isDaemonRunning();
1355
2146
  if (options.json) {
1356
2147
  console.log(JSON.stringify({ running }));
1357
2148
  } else {
1358
- console.log(running ? "Daemon \u8FD0\u884C\u4E2D" : "Daemon \u672A\u8FD0\u884C");
2149
+ console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
1359
2150
  }
1360
2151
  }
1361
2152
 
1362
2153
  // packages/cli/src/commands/reload.ts
1363
- import WebSocket from "ws";
2154
+ import WebSocket2 from "ws";
1364
2155
  var EXTENSION_NAME = "bb-browser";
1365
2156
  async function reloadCommand(options = {}) {
1366
2157
  const port = options.port || 9222;
@@ -1377,7 +2168,7 @@ async function reloadCommand(options = {}) {
1377
2168
  throw new Error("\u8BF7\u5148\u6253\u5F00 chrome://extensions \u9875\u9762");
1378
2169
  }
1379
2170
  const result = await new Promise((resolve2, reject) => {
1380
- const ws = new WebSocket(extPage.webSocketDebuggerUrl);
2171
+ const ws = new WebSocket2(extPage.webSocketDebuggerUrl);
1381
2172
  let resolved = false;
1382
2173
  const timeout = setTimeout(() => {
1383
2174
  if (!resolved) {
@@ -1474,7 +2265,7 @@ async function backCommand(options = {}) {
1474
2265
  action: "back",
1475
2266
  tabId: options.tabId
1476
2267
  };
1477
- const response = await sendCommand(request);
2268
+ const response = await sendCommand2(request);
1478
2269
  if (options.json) {
1479
2270
  console.log(JSON.stringify(response, null, 2));
1480
2271
  } else {
@@ -1498,7 +2289,7 @@ async function forwardCommand(options = {}) {
1498
2289
  action: "forward",
1499
2290
  tabId: options.tabId
1500
2291
  };
1501
- const response = await sendCommand(request);
2292
+ const response = await sendCommand2(request);
1502
2293
  if (options.json) {
1503
2294
  console.log(JSON.stringify(response, null, 2));
1504
2295
  } else {
@@ -1522,7 +2313,7 @@ async function refreshCommand(options = {}) {
1522
2313
  action: "refresh",
1523
2314
  tabId: options.tabId
1524
2315
  };
1525
- const response = await sendCommand(request);
2316
+ const response = await sendCommand2(request);
1526
2317
  if (options.json) {
1527
2318
  console.log(JSON.stringify(response, null, 2));
1528
2319
  } else {
@@ -1541,7 +2332,7 @@ async function refreshCommand(options = {}) {
1541
2332
  }
1542
2333
 
1543
2334
  // packages/cli/src/commands/check.ts
1544
- function parseRef7(ref) {
2335
+ function parseRef8(ref) {
1545
2336
  return ref.startsWith("@") ? ref.slice(1) : ref;
1546
2337
  }
1547
2338
  async function checkCommand(ref, options = {}) {
@@ -1549,14 +2340,14 @@ async function checkCommand(ref, options = {}) {
1549
2340
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1550
2341
  }
1551
2342
  await ensureDaemonRunning();
1552
- const parsedRef = parseRef7(ref);
2343
+ const parsedRef = parseRef8(ref);
1553
2344
  const request = {
1554
2345
  id: generateId(),
1555
2346
  action: "check",
1556
2347
  ref: parsedRef,
1557
2348
  tabId: options.tabId
1558
2349
  };
1559
- const response = await sendCommand(request);
2350
+ const response = await sendCommand2(request);
1560
2351
  if (options.json) {
1561
2352
  console.log(JSON.stringify(response, null, 2));
1562
2353
  } else {
@@ -1588,14 +2379,14 @@ async function uncheckCommand(ref, options = {}) {
1588
2379
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1589
2380
  }
1590
2381
  await ensureDaemonRunning();
1591
- const parsedRef = parseRef7(ref);
2382
+ const parsedRef = parseRef8(ref);
1592
2383
  const request = {
1593
2384
  id: generateId(),
1594
2385
  action: "uncheck",
1595
2386
  ref: parsedRef,
1596
2387
  tabId: options.tabId
1597
2388
  };
1598
- const response = await sendCommand(request);
2389
+ const response = await sendCommand2(request);
1599
2390
  if (options.json) {
1600
2391
  console.log(JSON.stringify(response, null, 2));
1601
2392
  } else {
@@ -1624,7 +2415,7 @@ async function uncheckCommand(ref, options = {}) {
1624
2415
  }
1625
2416
 
1626
2417
  // packages/cli/src/commands/select.ts
1627
- function parseRef8(ref) {
2418
+ function parseRef9(ref) {
1628
2419
  return ref.startsWith("@") ? ref.slice(1) : ref;
1629
2420
  }
1630
2421
  async function selectCommand(ref, value, options = {}) {
@@ -1635,7 +2426,7 @@ async function selectCommand(ref, value, options = {}) {
1635
2426
  throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
1636
2427
  }
1637
2428
  await ensureDaemonRunning();
1638
- const parsedRef = parseRef8(ref);
2429
+ const parsedRef = parseRef9(ref);
1639
2430
  const request = {
1640
2431
  id: generateId(),
1641
2432
  action: "select",
@@ -1643,7 +2434,7 @@ async function selectCommand(ref, value, options = {}) {
1643
2434
  value,
1644
2435
  tabId: options.tabId
1645
2436
  };
1646
- const response = await sendCommand(request);
2437
+ const response = await sendCommand2(request);
1647
2438
  if (options.json) {
1648
2439
  console.log(JSON.stringify(response, null, 2));
1649
2440
  } else {
@@ -1681,7 +2472,7 @@ async function evalCommand(script, options = {}) {
1681
2472
  script,
1682
2473
  tabId: options.tabId
1683
2474
  };
1684
- const response = await sendCommand(request);
2475
+ const response = await sendCommand2(request);
1685
2476
  if (options.json) {
1686
2477
  console.log(JSON.stringify(response, null, 2));
1687
2478
  } else {
@@ -1768,7 +2559,7 @@ async function tabCommand(args, options = {}) {
1768
2559
  index: parsed.index,
1769
2560
  tabId: parsed.tabId
1770
2561
  };
1771
- const response = await sendCommand(request);
2562
+ const response = await sendCommand2(request);
1772
2563
  if (options.json) {
1773
2564
  console.log(JSON.stringify(response, null, 2));
1774
2565
  } else {
@@ -1817,7 +2608,7 @@ async function frameCommand(selector, options = {}) {
1817
2608
  selector,
1818
2609
  tabId: options.tabId
1819
2610
  };
1820
- const response = await sendCommand(request);
2611
+ const response = await sendCommand2(request);
1821
2612
  if (options.json) {
1822
2613
  console.log(JSON.stringify(response, null, 2));
1823
2614
  } else {
@@ -1841,7 +2632,7 @@ async function frameMainCommand(options = {}) {
1841
2632
  action: "frame_main",
1842
2633
  tabId: options.tabId
1843
2634
  };
1844
- const response = await sendCommand(request);
2635
+ const response = await sendCommand2(request);
1845
2636
  if (options.json) {
1846
2637
  console.log(JSON.stringify(response, null, 2));
1847
2638
  } else {
@@ -1867,7 +2658,7 @@ async function dialogCommand(subCommand, promptText, options = {}) {
1867
2658
  promptText: subCommand === "accept" ? promptText : void 0,
1868
2659
  tabId: options.tabId
1869
2660
  };
1870
- const response = await sendCommand(request);
2661
+ const response = await sendCommand2(request);
1871
2662
  if (options.json) {
1872
2663
  console.log(JSON.stringify(response, null, 2));
1873
2664
  } else {
@@ -1888,7 +2679,7 @@ async function dialogCommand(subCommand, promptText, options = {}) {
1888
2679
 
1889
2680
  // packages/cli/src/commands/network.ts
1890
2681
  async function networkCommand(subCommand, urlOrFilter, options = {}) {
1891
- const response = await sendCommand({
2682
+ const response = await sendCommand2({
1892
2683
  id: crypto.randomUUID(),
1893
2684
  action: "network",
1894
2685
  networkCommand: subCommand,
@@ -1975,7 +2766,7 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
1975
2766
 
1976
2767
  // packages/cli/src/commands/console.ts
1977
2768
  async function consoleCommand(options = {}) {
1978
- const response = await sendCommand({
2769
+ const response = await sendCommand2({
1979
2770
  id: crypto.randomUUID(),
1980
2771
  action: "console",
1981
2772
  consoleCommand: options.clear ? "clear" : "get",
@@ -2020,7 +2811,7 @@ async function consoleCommand(options = {}) {
2020
2811
 
2021
2812
  // packages/cli/src/commands/errors.ts
2022
2813
  async function errorsCommand(options = {}) {
2023
- const response = await sendCommand({
2814
+ const response = await sendCommand2({
2024
2815
  id: crypto.randomUUID(),
2025
2816
  action: "errors",
2026
2817
  errorsCommand: options.clear ? "clear" : "get",
@@ -2060,7 +2851,7 @@ async function errorsCommand(options = {}) {
2060
2851
 
2061
2852
  // packages/cli/src/commands/trace.ts
2062
2853
  async function traceCommand(subCommand, options = {}) {
2063
- const response = await sendCommand({
2854
+ const response = await sendCommand2({
2064
2855
  id: crypto.randomUUID(),
2065
2856
  action: "trace",
2066
2857
  traceCommand: subCommand,
@@ -2150,7 +2941,7 @@ function matchTabOrigin2(tabUrl, targetHostname) {
2150
2941
  }
2151
2942
  async function ensureTabForOrigin(origin, hostname) {
2152
2943
  const listReq = { id: generateId(), action: "tab_list" };
2153
- const listResp = await sendCommand(listReq);
2944
+ const listResp = await sendCommand2(listReq);
2154
2945
  if (listResp.success && listResp.data?.tabs) {
2155
2946
  const matchingTab = listResp.data.tabs.find(
2156
2947
  (tab) => matchTabOrigin2(tab.url, hostname)
@@ -2159,7 +2950,7 @@ async function ensureTabForOrigin(origin, hostname) {
2159
2950
  return matchingTab.tabId;
2160
2951
  }
2161
2952
  }
2162
- const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
2953
+ const newResp = await sendCommand2({ id: generateId(), action: "tab_new", url: origin });
2163
2954
  if (!newResp.success) {
2164
2955
  throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
2165
2956
  }
@@ -2228,7 +3019,7 @@ async function fetchCommand(url, options = {}) {
2228
3019
  }
2229
3020
  const script = buildFetchScript(url, options);
2230
3021
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
2231
- const evalResp = await sendCommand(evalReq);
3022
+ const evalResp = await sendCommand2(evalReq);
2232
3023
  if (!evalResp.success) {
2233
3024
  throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
2234
3025
  }
@@ -2262,21 +3053,16 @@ async function fetchCommand(url, options = {}) {
2262
3053
 
2263
3054
  // packages/cli/src/commands/history.ts
2264
3055
  async function historyCommand(subCommand, options = {}) {
2265
- const response = await sendCommand({
2266
- id: crypto.randomUUID(),
2267
- action: "history",
2268
- historyCommand: subCommand,
2269
- text: options.query,
2270
- ms: options.days
2271
- });
3056
+ const days = options.days || 30;
3057
+ const data = subCommand === "search" ? { historyItems: searchHistory(options.query, days) } : { historyDomains: getHistoryDomains(days) };
2272
3058
  if (options.json) {
2273
- console.log(JSON.stringify(response));
3059
+ console.log(JSON.stringify({
3060
+ id: crypto.randomUUID(),
3061
+ success: true,
3062
+ data
3063
+ }));
2274
3064
  return;
2275
3065
  }
2276
- if (!response.success) {
2277
- throw new Error(response.error || "History command failed");
2278
- }
2279
- const data = response.data;
2280
3066
  switch (subCommand) {
2281
3067
  case "search": {
2282
3068
  const items = data?.historyItems || [];
@@ -2315,10 +3101,13 @@ async function historyCommand(subCommand, options = {}) {
2315
3101
  }
2316
3102
 
2317
3103
  // packages/cli/src/index.ts
2318
- var VERSION = "0.7.0";
3104
+ var VERSION = "0.8.0";
2319
3105
  var HELP_TEXT = `
2320
3106
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
2321
3107
 
3108
+ \u5B89\u88C5\uFF1A
3109
+ npm install -g bb-browser
3110
+
2322
3111
  \u63D0\u793A\uFF1A\u5927\u591A\u6570\u6570\u636E\u83B7\u53D6\u4EFB\u52A1\u8BF7\u76F4\u63A5\u4F7F\u7528 site \u547D\u4EE4\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u6D4F\u89C8\u5668\uFF1A
2323
3112
  bb-browser site list \u67E5\u770B\u6240\u6709\u53EF\u7528\u547D\u4EE4
2324
3113
  bb-browser site twitter/search "AI" \u793A\u4F8B\uFF1A\u641C\u7D22\u63A8\u6587
@@ -2356,6 +3145,7 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
2356
3145
 
2357
3146
  \u6807\u7B7E\u9875\uFF1A
2358
3147
  tab [list|new|close|<n>] \u7BA1\u7406\u6807\u7B7E\u9875
3148
+ status \u67E5\u770B\u53D7\u7BA1\u6D4F\u89C8\u5668\u72B6\u6001
2359
3149
 
2360
3150
  \u5BFC\u822A\uFF1A
2361
3151
  back / forward / refresh \u540E\u9000 / \u524D\u8FDB / \u5237\u65B0
@@ -2369,6 +3159,8 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
2369
3159
 
2370
3160
  \u9009\u9879\uFF1A
2371
3161
  --json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
3162
+ --port <n> \u6307\u5B9A Chrome CDP \u7AEF\u53E3
3163
+ --openclaw \u4F18\u5148\u590D\u7528 OpenClaw \u6D4F\u89C8\u5668\u5B9E\u4F8B
2372
3164
  --jq <expr> \u5BF9 JSON \u8F93\u51FA\u5E94\u7528 jq \u8FC7\u6EE4\uFF08\u76F4\u63A5\u4F5C\u7528\u4E8E\u6570\u636E\uFF0C\u8DF3\u8FC7 id/success \u4FE1\u5C01\uFF09
2373
3165
  -i, --interactive \u53EA\u8F93\u51FA\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08snapshot \u547D\u4EE4\uFF09
2374
3166
  -c, --compact \u79FB\u9664\u7A7A\u7ED3\u6784\u8282\u70B9\uFF08snapshot \u547D\u4EE4\uFF09
@@ -2409,6 +3201,12 @@ function parseArgs(argv) {
2409
3201
  }
2410
3202
  } else if (arg === "--openclaw") {
2411
3203
  result.flags.openclaw = true;
3204
+ } else if (arg === "--port") {
3205
+ skipNext = true;
3206
+ const nextIdx = args.indexOf(arg) + 1;
3207
+ if (nextIdx < args.length) {
3208
+ result.flags.port = parseInt(args[nextIdx], 10);
3209
+ }
2412
3210
  } else if (arg === "--help" || arg === "-h") {
2413
3211
  result.flags.help = true;
2414
3212
  } else if (arg === "--version" || arg === "-v") {
@@ -2458,9 +3256,9 @@ async function main() {
2458
3256
  return;
2459
3257
  }
2460
3258
  if (process.argv.includes("--mcp")) {
2461
- const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
2462
- const { spawn: spawn3 } = await import("child_process");
2463
- const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
3259
+ const mcpPath = fileURLToPath3(new URL("./mcp.js", import.meta.url));
3260
+ const { spawn: spawn2 } = await import("child_process");
3261
+ const child = spawn2(process.execPath, [mcpPath], { stdio: "inherit" });
2464
3262
  child.on("exit", (code) => process.exit(code ?? 0));
2465
3263
  return;
2466
3264
  }
@@ -2818,9 +3616,9 @@ async function main() {
2818
3616
  break;
2819
3617
  }
2820
3618
  case "star": {
2821
- const { execSync: execSync2 } = await import("child_process");
3619
+ const { execSync: execSync4 } = await import("child_process");
2822
3620
  try {
2823
- execSync2("gh auth status", { stdio: "pipe" });
3621
+ execSync4("gh auth status", { stdio: "pipe" });
2824
3622
  } catch {
2825
3623
  console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
2826
3624
  console.error(" brew install gh && gh auth login");
@@ -2829,7 +3627,7 @@ async function main() {
2829
3627
  const repos = ["epiral/bb-browser", "epiral/bb-sites"];
2830
3628
  for (const repo of repos) {
2831
3629
  try {
2832
- execSync2(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
3630
+ execSync4(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
2833
3631
  console.log(`\u2B50 Starred ${repo}`);
2834
3632
  } catch {
2835
3633
  console.log(`Already starred or failed: ${repo}`);
@@ -2919,5 +3717,5 @@ Full guide: https://github.com/epiral/bb-sites/blob/main/SKILL.md`);
2919
3717
  process.exit(1);
2920
3718
  }
2921
3719
  }
2922
- main();
3720
+ main().then(() => process.exit(0));
2923
3721
  //# sourceMappingURL=cli.js.map