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