bb-browser 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdp-monitor.js +535 -0
- package/dist/cdp-monitor.js.map +1 -0
- package/dist/chunk-FSL4RNI6.js +53 -0
- package/dist/chunk-FSL4RNI6.js.map +1 -0
- package/dist/cli.js +425 -223
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/daemon.js.map +1 -1
- package/dist/mcp.js +194 -5
- package/dist/mcp.js.map +1 -1
- package/dist/{openclaw-bridge-P3G4KGYM.js → openclaw-bridge-HBJH6UFO.js} +7 -4
- package/dist/openclaw-bridge-HBJH6UFO.js.map +1 -0
- package/extension/background.js +2 -1
- package/extension/background.js.map +1 -1
- package/extension/manifest.json +26 -7
- package/package.json +12 -5
- package/dist/openclaw-bridge-P3G4KGYM.js.map +0 -1
- package/extension/dist/background.js +0 -3257
- package/extension/dist/background.js.map +0 -1
- package/extension/dist/buildDomTree.js +0 -1505
- package/extension/dist/content/trace.js +0 -339
- package/extension/dist/content/trace.js.map +0 -1
- package/extension/dist/manifest.json +0 -26
- package/extension/dist/options.html +0 -26
- package/extension/dist/options.js +0 -19
- package/extension/dist/options.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -6,10 +6,13 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
applyJq
|
|
8
8
|
} from "./chunk-AHGAQEFO.js";
|
|
9
|
+
import {
|
|
10
|
+
parseOpenClawJson
|
|
11
|
+
} from "./chunk-FSL4RNI6.js";
|
|
9
12
|
import "./chunk-D4HDZEJT.js";
|
|
10
13
|
|
|
11
14
|
// packages/cli/src/index.ts
|
|
12
|
-
import { fileURLToPath as
|
|
15
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13
16
|
|
|
14
17
|
// packages/cli/src/cdp-client.ts
|
|
15
18
|
import { readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -31,13 +34,13 @@ var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
|
|
|
31
34
|
var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
|
|
32
35
|
var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
|
|
33
36
|
function execFileAsync(command, args, timeout) {
|
|
34
|
-
return new Promise((
|
|
37
|
+
return new Promise((resolve3, reject) => {
|
|
35
38
|
execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
|
|
36
39
|
if (error) {
|
|
37
40
|
reject(error);
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
|
-
|
|
43
|
+
resolve3(stdout.trim());
|
|
41
44
|
});
|
|
42
45
|
});
|
|
43
46
|
}
|
|
@@ -49,7 +52,7 @@ function getArgValue(flag) {
|
|
|
49
52
|
async function tryOpenClaw() {
|
|
50
53
|
try {
|
|
51
54
|
const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 5e3);
|
|
52
|
-
const parsed =
|
|
55
|
+
const parsed = parseOpenClawJson(raw);
|
|
53
56
|
const port = Number(parsed?.cdpPort);
|
|
54
57
|
if (Number.isInteger(port) && port > 0) {
|
|
55
58
|
return { host: "127.0.0.1", port };
|
|
@@ -73,6 +76,10 @@ function findBrowserExecutable() {
|
|
|
73
76
|
if (process.platform === "darwin") {
|
|
74
77
|
const candidates = [
|
|
75
78
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
79
|
+
"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
|
|
80
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
81
|
+
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
|
|
82
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
76
83
|
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
77
84
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
78
85
|
];
|
|
@@ -101,6 +108,18 @@ function findBrowserExecutable() {
|
|
|
101
108
|
}
|
|
102
109
|
return null;
|
|
103
110
|
}
|
|
111
|
+
async function isManagedBrowserRunning() {
|
|
112
|
+
try {
|
|
113
|
+
const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
|
|
114
|
+
const port = Number.parseInt(rawPort.trim(), 10);
|
|
115
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return await canConnect("127.0.0.1", port);
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
104
123
|
async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
105
124
|
const executable = findBrowserExecutable();
|
|
106
125
|
if (!executable) {
|
|
@@ -151,7 +170,7 @@ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
|
151
170
|
if (await canConnect("127.0.0.1", port)) {
|
|
152
171
|
return { host: "127.0.0.1", port };
|
|
153
172
|
}
|
|
154
|
-
await new Promise((
|
|
173
|
+
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
155
174
|
}
|
|
156
175
|
return null;
|
|
157
176
|
}
|
|
@@ -198,11 +217,35 @@ var jsErrors = [];
|
|
|
198
217
|
var errorsEnabled = false;
|
|
199
218
|
var traceRecording = false;
|
|
200
219
|
var traceEvents = [];
|
|
220
|
+
function getContextFilePath(host, port) {
|
|
221
|
+
const safeHost = host.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
222
|
+
return path2.join(os2.tmpdir(), `bb-browser-cdp-context-${safeHost}-${port}.json`);
|
|
223
|
+
}
|
|
224
|
+
function loadPersistedCurrentTargetId(host, port) {
|
|
225
|
+
try {
|
|
226
|
+
const data = JSON.parse(readFileSync(getContextFilePath(host, port), "utf-8"));
|
|
227
|
+
return typeof data.currentTargetId === "string" && data.currentTargetId ? data.currentTargetId : void 0;
|
|
228
|
+
} catch {
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function persistCurrentTargetId(host, port, currentTargetId) {
|
|
233
|
+
try {
|
|
234
|
+
writeFileSync(getContextFilePath(host, port), JSON.stringify({ currentTargetId }));
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function setCurrentTargetId(targetId) {
|
|
239
|
+
const state = connectionState;
|
|
240
|
+
if (!state) return;
|
|
241
|
+
state.currentTargetId = targetId;
|
|
242
|
+
persistCurrentTargetId(state.host, state.port, targetId);
|
|
243
|
+
}
|
|
201
244
|
function buildRequestError(error) {
|
|
202
245
|
return error instanceof Error ? error : new Error(String(error));
|
|
203
246
|
}
|
|
204
247
|
function fetchJson(url) {
|
|
205
|
-
return new Promise((
|
|
248
|
+
return new Promise((resolve3, reject) => {
|
|
206
249
|
const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
|
|
207
250
|
const req = requester(url, { method: "GET" }, (res) => {
|
|
208
251
|
const chunks = [];
|
|
@@ -214,7 +257,7 @@ function fetchJson(url) {
|
|
|
214
257
|
return;
|
|
215
258
|
}
|
|
216
259
|
try {
|
|
217
|
-
|
|
260
|
+
resolve3(JSON.parse(raw));
|
|
218
261
|
} catch (error) {
|
|
219
262
|
reject(error);
|
|
220
263
|
}
|
|
@@ -237,14 +280,14 @@ async function getJsonVersion(host, port) {
|
|
|
237
280
|
return { webSocketDebuggerUrl: url };
|
|
238
281
|
}
|
|
239
282
|
function connectWebSocket(url) {
|
|
240
|
-
return new Promise((
|
|
283
|
+
return new Promise((resolve3, reject) => {
|
|
241
284
|
const ws = new WebSocket(url);
|
|
242
285
|
ws.once("open", () => {
|
|
243
286
|
const socket = ws._socket;
|
|
244
287
|
if (socket && typeof socket.unref === "function") {
|
|
245
288
|
socket.unref();
|
|
246
289
|
}
|
|
247
|
-
|
|
290
|
+
resolve3(ws);
|
|
248
291
|
});
|
|
249
292
|
ws.once("error", reject);
|
|
250
293
|
});
|
|
@@ -260,6 +303,7 @@ function createState(host, port, browserWsUrl, browserSocket) {
|
|
|
260
303
|
sessions: /* @__PURE__ */ new Map(),
|
|
261
304
|
attachedTargets: /* @__PURE__ */ new Map(),
|
|
262
305
|
refsByTarget: /* @__PURE__ */ new Map(),
|
|
306
|
+
currentTargetId: loadPersistedCurrentTargetId(host, port),
|
|
263
307
|
activeFrameIdByTarget: /* @__PURE__ */ new Map(),
|
|
264
308
|
dialogHandlers: /* @__PURE__ */ new Map()
|
|
265
309
|
};
|
|
@@ -296,6 +340,10 @@ function createState(host, port, browserWsUrl, browserSocket) {
|
|
|
296
340
|
state.attachedTargets.delete(sessionId);
|
|
297
341
|
state.activeFrameIdByTarget.delete(targetId);
|
|
298
342
|
state.dialogHandlers.delete(targetId);
|
|
343
|
+
if (state.currentTargetId === targetId) {
|
|
344
|
+
state.currentTargetId = void 0;
|
|
345
|
+
persistCurrentTargetId(state.host, state.port, void 0);
|
|
346
|
+
}
|
|
299
347
|
}
|
|
300
348
|
}
|
|
301
349
|
return;
|
|
@@ -339,8 +387,8 @@ async function browserCommand(method, params = {}) {
|
|
|
339
387
|
if (!state) throw new Error("CDP connection not initialized");
|
|
340
388
|
const id = state.nextMessageId++;
|
|
341
389
|
const payload = JSON.stringify({ id, method, params });
|
|
342
|
-
const promise = new Promise((
|
|
343
|
-
state.browserPending.set(id, { resolve:
|
|
390
|
+
const promise = new Promise((resolve3, reject) => {
|
|
391
|
+
state.browserPending.set(id, { resolve: resolve3, reject, method });
|
|
344
392
|
});
|
|
345
393
|
state.browserSocket.send(payload);
|
|
346
394
|
return promise;
|
|
@@ -351,13 +399,13 @@ async function sessionCommand(targetId, method, params = {}) {
|
|
|
351
399
|
const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
|
|
352
400
|
const id = state.nextMessageId++;
|
|
353
401
|
const payload = JSON.stringify({ id, method, params, sessionId });
|
|
354
|
-
return new Promise((
|
|
402
|
+
return new Promise((resolve3, reject) => {
|
|
355
403
|
const check = (raw) => {
|
|
356
404
|
const msg = JSON.parse(raw.toString());
|
|
357
405
|
if (msg.id === id && msg.sessionId === sessionId) {
|
|
358
406
|
state.browserSocket.off("message", check);
|
|
359
407
|
if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
|
|
360
|
-
else
|
|
408
|
+
else resolve3(msg.result);
|
|
361
409
|
}
|
|
362
410
|
};
|
|
363
411
|
state.browserSocket.on("message", check);
|
|
@@ -508,6 +556,7 @@ async function getTargets() {
|
|
|
508
556
|
async function ensurePageTarget(targetId) {
|
|
509
557
|
const targets = (await getTargets()).filter((target2) => target2.type === "page");
|
|
510
558
|
if (targets.length === 0) throw new Error("No page target found");
|
|
559
|
+
const persistedTargetId = targetId === void 0 ? connectionState?.currentTargetId : void 0;
|
|
511
560
|
let target;
|
|
512
561
|
if (typeof targetId === "number") {
|
|
513
562
|
target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
|
|
@@ -519,9 +568,11 @@ async function ensurePageTarget(targetId) {
|
|
|
519
568
|
target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
|
|
520
569
|
}
|
|
521
570
|
}
|
|
571
|
+
} else if (persistedTargetId) {
|
|
572
|
+
target = targets.find((item) => item.id === persistedTargetId);
|
|
522
573
|
}
|
|
523
574
|
target ??= targets[0];
|
|
524
|
-
|
|
575
|
+
setCurrentTargetId(target.id);
|
|
525
576
|
await attachTarget(target.id);
|
|
526
577
|
return target;
|
|
527
578
|
}
|
|
@@ -638,19 +689,16 @@ async function evaluate(targetId, expression, returnByValue = true) {
|
|
|
638
689
|
const result = await sessionCommand(targetId, "Runtime.evaluate", {
|
|
639
690
|
expression,
|
|
640
691
|
awaitPromise: true,
|
|
641
|
-
returnByValue
|
|
692
|
+
returnByValue,
|
|
693
|
+
replMode: true
|
|
642
694
|
});
|
|
643
695
|
if (result.exceptionDetails) {
|
|
644
|
-
throw new Error(
|
|
696
|
+
throw new Error(
|
|
697
|
+
result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Runtime.evaluate failed"
|
|
698
|
+
);
|
|
645
699
|
}
|
|
646
700
|
return result.result.value ?? result.result;
|
|
647
701
|
}
|
|
648
|
-
async function resolveNode(targetId, backendNodeId) {
|
|
649
|
-
const result = await sessionCommand(targetId, "DOM.pushNodesByBackendIdsToFrontend", {
|
|
650
|
-
backendNodeIds: [backendNodeId]
|
|
651
|
-
});
|
|
652
|
-
return result.nodeId;
|
|
653
|
-
}
|
|
654
702
|
async function focusNode(targetId, backendNodeId) {
|
|
655
703
|
await sessionCommand(targetId, "DOM.focus", { backendNodeId });
|
|
656
704
|
}
|
|
@@ -658,40 +706,77 @@ async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
|
|
|
658
706
|
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
659
707
|
await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
660
708
|
objectId: resolved.object.objectId,
|
|
661
|
-
functionDeclaration: `function(
|
|
709
|
+
functionDeclaration: `function(clearFirst) {
|
|
710
|
+
if (typeof this.scrollIntoView === 'function') {
|
|
711
|
+
this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
|
|
712
|
+
}
|
|
662
713
|
if (typeof this.focus === 'function') this.focus();
|
|
663
|
-
if (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
714
|
+
if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement) {
|
|
715
|
+
if (clearFirst) {
|
|
716
|
+
this.value = '';
|
|
717
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
718
|
+
}
|
|
719
|
+
if (typeof this.setSelectionRange === 'function') {
|
|
720
|
+
const end = this.value.length;
|
|
721
|
+
this.setSelectionRange(end, end);
|
|
722
|
+
}
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
if (this instanceof HTMLElement && this.isContentEditable) {
|
|
726
|
+
if (clearFirst) {
|
|
727
|
+
this.textContent = '';
|
|
728
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
729
|
+
}
|
|
730
|
+
const selection = window.getSelection();
|
|
731
|
+
if (selection) {
|
|
732
|
+
const range = document.createRange();
|
|
733
|
+
range.selectNodeContents(this);
|
|
734
|
+
range.collapse(false);
|
|
735
|
+
selection.removeAllRanges();
|
|
736
|
+
selection.addRange(range);
|
|
737
|
+
}
|
|
671
738
|
return true;
|
|
672
739
|
}
|
|
673
740
|
return false;
|
|
674
741
|
}`,
|
|
675
742
|
arguments: [
|
|
676
|
-
{ value: text },
|
|
677
743
|
{ value: clearFirst }
|
|
678
744
|
],
|
|
679
745
|
returnByValue: true
|
|
680
746
|
});
|
|
681
|
-
|
|
682
|
-
|
|
747
|
+
if (text) {
|
|
748
|
+
await focusNode(targetId, backendNodeId);
|
|
749
|
+
await sessionCommand(targetId, "Input.insertText", { text });
|
|
750
|
+
}
|
|
683
751
|
}
|
|
684
|
-
async function
|
|
685
|
-
const
|
|
686
|
-
|
|
752
|
+
async function getInteractablePoint(targetId, backendNodeId) {
|
|
753
|
+
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
754
|
+
const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
755
|
+
objectId: resolved.object.objectId,
|
|
756
|
+
functionDeclaration: `function() {
|
|
757
|
+
if (!(this instanceof Element)) {
|
|
758
|
+
throw new Error('Ref does not resolve to an element');
|
|
759
|
+
}
|
|
760
|
+
this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
|
|
761
|
+
const rect = this.getBoundingClientRect();
|
|
762
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
763
|
+
throw new Error('Element is not visible');
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
x: rect.left + rect.width / 2,
|
|
767
|
+
y: rect.top + rect.height / 2,
|
|
768
|
+
};
|
|
769
|
+
}`,
|
|
770
|
+
returnByValue: true
|
|
687
771
|
});
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
772
|
+
if (call.exceptionDetails) {
|
|
773
|
+
throw new Error(call.exceptionDetails.text || "Failed to resolve element point");
|
|
774
|
+
}
|
|
775
|
+
const point = call.result.value;
|
|
776
|
+
if (!point || typeof point.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
|
|
777
|
+
throw new Error("Failed to resolve element point");
|
|
778
|
+
}
|
|
779
|
+
return point;
|
|
695
780
|
}
|
|
696
781
|
async function mouseClick(targetId, x, y) {
|
|
697
782
|
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
|
|
@@ -699,9 +784,14 @@ async function mouseClick(targetId, x, y) {
|
|
|
699
784
|
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
700
785
|
}
|
|
701
786
|
async function getAttributeValue(targetId, backendNodeId, attribute) {
|
|
702
|
-
const nodeId = await resolveNode(targetId, backendNodeId);
|
|
703
787
|
if (attribute === "text") {
|
|
704
|
-
|
|
788
|
+
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
789
|
+
const call2 = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
790
|
+
objectId: resolved.object.objectId,
|
|
791
|
+
functionDeclaration: `function() { return (this instanceof HTMLElement ? this.innerText : this.textContent || '').trim(); }`,
|
|
792
|
+
returnByValue: true
|
|
793
|
+
});
|
|
794
|
+
return String(call2.result.value ?? "");
|
|
705
795
|
}
|
|
706
796
|
const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
707
797
|
const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
@@ -929,7 +1019,7 @@ async function dispatchRequest(request) {
|
|
|
929
1019
|
case "open": {
|
|
930
1020
|
if (!request.url) return fail(request.id, "Missing url parameter");
|
|
931
1021
|
if (request.tabId === void 0) {
|
|
932
|
-
const created = await browserCommand("Target.createTarget", { url: request.url });
|
|
1022
|
+
const created = await browserCommand("Target.createTarget", { url: request.url, background: true });
|
|
933
1023
|
const newTarget = await ensurePageTarget(created.targetId);
|
|
934
1024
|
return ok(request.id, { url: request.url, tabId: newTarget.id });
|
|
935
1025
|
}
|
|
@@ -946,7 +1036,7 @@ async function dispatchRequest(request) {
|
|
|
946
1036
|
case "hover": {
|
|
947
1037
|
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
948
1038
|
const backendNodeId = await parseRef(request.ref);
|
|
949
|
-
const point = await
|
|
1039
|
+
const point = await getInteractablePoint(target.id, backendNodeId);
|
|
950
1040
|
await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
|
|
951
1041
|
if (request.action === "click") await mouseClick(target.id, point.x, point.y);
|
|
952
1042
|
return ok(request.id, {});
|
|
@@ -982,7 +1072,14 @@ async function dispatchRequest(request) {
|
|
|
982
1072
|
return ok(request.id, { value: request.value });
|
|
983
1073
|
}
|
|
984
1074
|
case "get": {
|
|
985
|
-
if (!request.
|
|
1075
|
+
if (!request.attribute) return fail(request.id, "Missing attribute parameter");
|
|
1076
|
+
if (request.attribute === "url" && !request.ref) {
|
|
1077
|
+
return ok(request.id, { value: await evaluate(target.id, "location.href", true) });
|
|
1078
|
+
}
|
|
1079
|
+
if (request.attribute === "title" && !request.ref) {
|
|
1080
|
+
return ok(request.id, { value: await evaluate(target.id, "document.title", true) });
|
|
1081
|
+
}
|
|
1082
|
+
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
986
1083
|
const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
|
|
987
1084
|
return ok(request.id, { value });
|
|
988
1085
|
}
|
|
@@ -997,7 +1094,7 @@ async function dispatchRequest(request) {
|
|
|
997
1094
|
return ok(request.id, {});
|
|
998
1095
|
}
|
|
999
1096
|
case "wait": {
|
|
1000
|
-
await new Promise((
|
|
1097
|
+
await new Promise((resolve3) => setTimeout(resolve3, request.ms ?? 1e3));
|
|
1001
1098
|
return ok(request.id, {});
|
|
1002
1099
|
}
|
|
1003
1100
|
case "press": {
|
|
@@ -1036,14 +1133,14 @@ async function dispatchRequest(request) {
|
|
|
1036
1133
|
return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
|
|
1037
1134
|
}
|
|
1038
1135
|
case "tab_new": {
|
|
1039
|
-
const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank" });
|
|
1136
|
+
const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank", background: true });
|
|
1040
1137
|
return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
|
|
1041
1138
|
}
|
|
1042
1139
|
case "tab_select": {
|
|
1043
1140
|
const tabs = (await getTargets()).filter((item) => item.type === "page");
|
|
1044
1141
|
const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
|
|
1045
1142
|
if (!selected) return fail(request.id, "Tab not found");
|
|
1046
|
-
|
|
1143
|
+
setCurrentTargetId(selected.id);
|
|
1047
1144
|
await attachTarget(selected.id);
|
|
1048
1145
|
return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
|
|
1049
1146
|
}
|
|
@@ -1053,6 +1150,9 @@ async function dispatchRequest(request) {
|
|
|
1053
1150
|
if (!selected) return fail(request.id, "Tab not found");
|
|
1054
1151
|
await browserCommand("Target.closeTarget", { targetId: selected.id });
|
|
1055
1152
|
connectionState?.refsByTarget.delete(selected.id);
|
|
1153
|
+
if (connectionState?.currentTargetId === selected.id) {
|
|
1154
|
+
setCurrentTargetId(void 0);
|
|
1155
|
+
}
|
|
1056
1156
|
clearPersistedRefs(selected.id);
|
|
1057
1157
|
return ok(request.id, { tabId: selected.id });
|
|
1058
1158
|
}
|
|
@@ -1160,7 +1260,167 @@ async function dispatchRequest(request) {
|
|
|
1160
1260
|
}
|
|
1161
1261
|
}
|
|
1162
1262
|
|
|
1263
|
+
// packages/cli/src/monitor-manager.ts
|
|
1264
|
+
import { spawn as spawn2 } from "child_process";
|
|
1265
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, unlink } from "fs/promises";
|
|
1266
|
+
import { request as httpRequest2 } from "http";
|
|
1267
|
+
import { randomBytes } from "crypto";
|
|
1268
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1269
|
+
import { dirname, resolve } from "path";
|
|
1270
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1271
|
+
import os3 from "os";
|
|
1272
|
+
import path3 from "path";
|
|
1273
|
+
var MONITOR_DIR = path3.join(os3.homedir(), ".bb-browser");
|
|
1274
|
+
var PID_FILE = path3.join(MONITOR_DIR, "monitor.pid");
|
|
1275
|
+
var PORT_FILE = path3.join(MONITOR_DIR, "monitor.port");
|
|
1276
|
+
var TOKEN_FILE = path3.join(MONITOR_DIR, "monitor.token");
|
|
1277
|
+
var DEFAULT_MONITOR_PORT = 19826;
|
|
1278
|
+
function httpJson(method, url, token, body) {
|
|
1279
|
+
return new Promise((resolve3, reject) => {
|
|
1280
|
+
const parsed = new URL(url);
|
|
1281
|
+
const payload = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
1282
|
+
const req = httpRequest2(
|
|
1283
|
+
{
|
|
1284
|
+
hostname: parsed.hostname,
|
|
1285
|
+
port: parsed.port,
|
|
1286
|
+
path: parsed.pathname,
|
|
1287
|
+
method,
|
|
1288
|
+
headers: {
|
|
1289
|
+
Authorization: `Bearer ${token}`,
|
|
1290
|
+
...payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}
|
|
1291
|
+
},
|
|
1292
|
+
timeout: 5e3
|
|
1293
|
+
},
|
|
1294
|
+
(res) => {
|
|
1295
|
+
const chunks = [];
|
|
1296
|
+
res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
1297
|
+
res.on("end", () => {
|
|
1298
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1299
|
+
if ((res.statusCode ?? 500) >= 400) {
|
|
1300
|
+
reject(new Error(`Monitor HTTP ${res.statusCode}: ${raw}`));
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
try {
|
|
1304
|
+
resolve3(JSON.parse(raw));
|
|
1305
|
+
} catch {
|
|
1306
|
+
reject(new Error(`Invalid JSON from monitor: ${raw}`));
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
req.on("error", reject);
|
|
1312
|
+
req.on("timeout", () => {
|
|
1313
|
+
req.destroy();
|
|
1314
|
+
reject(new Error("Monitor request timed out"));
|
|
1315
|
+
});
|
|
1316
|
+
if (payload) req.write(payload);
|
|
1317
|
+
req.end();
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
async function readPortFile() {
|
|
1321
|
+
try {
|
|
1322
|
+
const raw = await readFile2(PORT_FILE, "utf8");
|
|
1323
|
+
const port = Number.parseInt(raw.trim(), 10);
|
|
1324
|
+
return Number.isInteger(port) && port > 0 ? port : null;
|
|
1325
|
+
} catch {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
async function readTokenFile() {
|
|
1330
|
+
try {
|
|
1331
|
+
return (await readFile2(TOKEN_FILE, "utf8")).trim();
|
|
1332
|
+
} catch {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
async function ensureMonitorRunning() {
|
|
1337
|
+
const existingPort = await readPortFile();
|
|
1338
|
+
const existingToken = await readTokenFile();
|
|
1339
|
+
if (existingPort && existingToken) {
|
|
1340
|
+
try {
|
|
1341
|
+
const status = await httpJson(
|
|
1342
|
+
"GET",
|
|
1343
|
+
`http://127.0.0.1:${existingPort}/status`,
|
|
1344
|
+
existingToken
|
|
1345
|
+
);
|
|
1346
|
+
if (status.running) {
|
|
1347
|
+
return { port: existingPort, token: existingToken };
|
|
1348
|
+
}
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
const cdp = await discoverCdpPort();
|
|
1353
|
+
if (!cdp) {
|
|
1354
|
+
throw new Error("Cannot start monitor: no browser connection found");
|
|
1355
|
+
}
|
|
1356
|
+
const token = randomBytes(32).toString("hex");
|
|
1357
|
+
const monitorPort = DEFAULT_MONITOR_PORT;
|
|
1358
|
+
const monitorScript = findMonitorScript();
|
|
1359
|
+
await mkdir2(MONITOR_DIR, { recursive: true });
|
|
1360
|
+
await writeFile2(TOKEN_FILE, token, { mode: 384 });
|
|
1361
|
+
const child = spawn2(process.execPath, [
|
|
1362
|
+
monitorScript,
|
|
1363
|
+
"--cdp-host",
|
|
1364
|
+
cdp.host,
|
|
1365
|
+
"--cdp-port",
|
|
1366
|
+
String(cdp.port),
|
|
1367
|
+
"--monitor-port",
|
|
1368
|
+
String(monitorPort),
|
|
1369
|
+
"--token",
|
|
1370
|
+
token
|
|
1371
|
+
], {
|
|
1372
|
+
detached: true,
|
|
1373
|
+
stdio: "ignore"
|
|
1374
|
+
});
|
|
1375
|
+
child.unref();
|
|
1376
|
+
const deadline = Date.now() + 5e3;
|
|
1377
|
+
while (Date.now() < deadline) {
|
|
1378
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
1379
|
+
try {
|
|
1380
|
+
const status = await httpJson(
|
|
1381
|
+
"GET",
|
|
1382
|
+
`http://127.0.0.1:${monitorPort}/status`,
|
|
1383
|
+
token
|
|
1384
|
+
);
|
|
1385
|
+
if (status.running) {
|
|
1386
|
+
return { port: monitorPort, token };
|
|
1387
|
+
}
|
|
1388
|
+
} catch {
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
throw new Error("Monitor process did not start in time");
|
|
1392
|
+
}
|
|
1393
|
+
async function monitorCommand(request) {
|
|
1394
|
+
const { port, token } = await ensureMonitorRunning();
|
|
1395
|
+
return httpJson(
|
|
1396
|
+
"POST",
|
|
1397
|
+
`http://127.0.0.1:${port}/command`,
|
|
1398
|
+
token,
|
|
1399
|
+
request
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
function findMonitorScript() {
|
|
1403
|
+
const currentFile = fileURLToPath2(import.meta.url);
|
|
1404
|
+
const currentDir = dirname(currentFile);
|
|
1405
|
+
const candidates = [
|
|
1406
|
+
// Built output (tsup puts it next to cli.js)
|
|
1407
|
+
resolve(currentDir, "cdp-monitor.js"),
|
|
1408
|
+
// Development: packages/cli/src -> packages/cli/dist
|
|
1409
|
+
resolve(currentDir, "../dist/cdp-monitor.js"),
|
|
1410
|
+
// Monorepo root dist
|
|
1411
|
+
resolve(currentDir, "../../dist/cdp-monitor.js"),
|
|
1412
|
+
resolve(currentDir, "../../../dist/cdp-monitor.js")
|
|
1413
|
+
];
|
|
1414
|
+
for (const candidate of candidates) {
|
|
1415
|
+
if (existsSync2(candidate)) {
|
|
1416
|
+
return candidate;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return candidates[0];
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1163
1422
|
// packages/cli/src/client.ts
|
|
1423
|
+
var MONITOR_ACTIONS = /* @__PURE__ */ new Set(["network", "console", "errors", "trace"]);
|
|
1164
1424
|
var jqExpression;
|
|
1165
1425
|
function setJqExpression(expression) {
|
|
1166
1426
|
jqExpression = expression;
|
|
@@ -1179,15 +1439,22 @@ function handleJqResponse(response) {
|
|
|
1179
1439
|
}
|
|
1180
1440
|
}
|
|
1181
1441
|
async function sendCommand2(request) {
|
|
1442
|
+
if (MONITOR_ACTIONS.has(request.action)) {
|
|
1443
|
+
try {
|
|
1444
|
+
return await monitorCommand(request);
|
|
1445
|
+
} catch {
|
|
1446
|
+
return sendCommand(request);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1182
1449
|
return sendCommand(request);
|
|
1183
1450
|
}
|
|
1184
1451
|
|
|
1185
1452
|
// packages/cli/src/daemon-manager.ts
|
|
1186
|
-
import { fileURLToPath as
|
|
1187
|
-
import { dirname, resolve } from "path";
|
|
1188
|
-
import { existsSync as
|
|
1453
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1454
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
1455
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1189
1456
|
async function isDaemonRunning() {
|
|
1190
|
-
return await
|
|
1457
|
+
return await isManagedBrowserRunning();
|
|
1191
1458
|
}
|
|
1192
1459
|
async function ensureDaemonRunning() {
|
|
1193
1460
|
try {
|
|
@@ -1206,7 +1473,7 @@ async function ensureDaemonRunning() {
|
|
|
1206
1473
|
}
|
|
1207
1474
|
|
|
1208
1475
|
// packages/cli/src/history-sqlite.ts
|
|
1209
|
-
import { copyFileSync, existsSync as
|
|
1476
|
+
import { copyFileSync, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
1210
1477
|
import { execSync as execSync2 } from "child_process";
|
|
1211
1478
|
import { homedir, tmpdir } from "os";
|
|
1212
1479
|
import { join } from "path";
|
|
@@ -1230,7 +1497,7 @@ function getHistoryPathCandidates() {
|
|
|
1230
1497
|
}
|
|
1231
1498
|
function findHistoryPath() {
|
|
1232
1499
|
for (const historyPath of getHistoryPathCandidates()) {
|
|
1233
|
-
if (
|
|
1500
|
+
if (existsSync4(historyPath)) {
|
|
1234
1501
|
return historyPath;
|
|
1235
1502
|
}
|
|
1236
1503
|
}
|
|
@@ -1350,7 +1617,7 @@ function getHistoryDomains(days) {
|
|
|
1350
1617
|
}
|
|
1351
1618
|
|
|
1352
1619
|
// packages/cli/src/commands/site.ts
|
|
1353
|
-
import { readFileSync as readFileSync2, readdirSync, existsSync as
|
|
1620
|
+
import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync5, mkdirSync } from "fs";
|
|
1354
1621
|
import { join as join2, relative } from "path";
|
|
1355
1622
|
import { homedir as homedir2 } from "os";
|
|
1356
1623
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -1369,6 +1636,10 @@ function checkCliUpdate() {
|
|
|
1369
1636
|
} catch {
|
|
1370
1637
|
}
|
|
1371
1638
|
}
|
|
1639
|
+
function exitJsonError(error, extra = {}) {
|
|
1640
|
+
console.log(JSON.stringify({ success: false, error, ...extra }, null, 2));
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1372
1643
|
function parseSiteMeta(filePath, source) {
|
|
1373
1644
|
let content;
|
|
1374
1645
|
try {
|
|
@@ -1432,7 +1703,7 @@ function parseSiteMeta(filePath, source) {
|
|
|
1432
1703
|
return meta;
|
|
1433
1704
|
}
|
|
1434
1705
|
function scanSites(dir, source) {
|
|
1435
|
-
if (!
|
|
1706
|
+
if (!existsSync5(dir)) return [];
|
|
1436
1707
|
const sites = [];
|
|
1437
1708
|
function walk(currentDir) {
|
|
1438
1709
|
let entries;
|
|
@@ -1486,6 +1757,10 @@ function matchTabOrigin(tabUrl, domain) {
|
|
|
1486
1757
|
function siteList(options) {
|
|
1487
1758
|
const sites = getAllSites();
|
|
1488
1759
|
if (sites.length === 0) {
|
|
1760
|
+
if (options.json) {
|
|
1761
|
+
console.log("[]");
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1489
1764
|
console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
|
|
1490
1765
|
console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
|
|
1491
1766
|
console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
|
|
@@ -1526,6 +1801,10 @@ function siteSearch(query, options) {
|
|
|
1526
1801
|
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
|
|
1527
1802
|
);
|
|
1528
1803
|
if (matches.length === 0) {
|
|
1804
|
+
if (options.json) {
|
|
1805
|
+
console.log("[]");
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1529
1808
|
console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
|
|
1530
1809
|
console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
|
|
1531
1810
|
return;
|
|
@@ -1544,34 +1823,63 @@ function siteSearch(query, options) {
|
|
|
1544
1823
|
console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
|
|
1545
1824
|
}
|
|
1546
1825
|
}
|
|
1547
|
-
function siteUpdate() {
|
|
1826
|
+
function siteUpdate(options = {}) {
|
|
1548
1827
|
mkdirSync(BB_DIR, { recursive: true });
|
|
1549
|
-
|
|
1550
|
-
|
|
1828
|
+
const updateMode = existsSync5(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
|
|
1829
|
+
if (updateMode === "pull") {
|
|
1830
|
+
if (!options.json) {
|
|
1831
|
+
console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
|
|
1832
|
+
}
|
|
1551
1833
|
try {
|
|
1552
1834
|
execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1835
|
+
if (!options.json) {
|
|
1836
|
+
console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
|
|
1837
|
+
console.log("");
|
|
1838
|
+
console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
|
|
1839
|
+
}
|
|
1556
1840
|
} catch (e) {
|
|
1841
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1842
|
+
const manualAction = "cd ~/.bb-browser/bb-sites && git pull";
|
|
1843
|
+
if (options.json) {
|
|
1844
|
+
exitJsonError(`\u66F4\u65B0\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
|
|
1845
|
+
}
|
|
1557
1846
|
console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
1558
1847
|
console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
|
|
1559
1848
|
process.exit(1);
|
|
1560
1849
|
}
|
|
1561
1850
|
} else {
|
|
1562
|
-
|
|
1851
|
+
if (!options.json) {
|
|
1852
|
+
console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
|
|
1853
|
+
}
|
|
1563
1854
|
try {
|
|
1564
1855
|
execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1856
|
+
if (!options.json) {
|
|
1857
|
+
console.log("\u514B\u9686\u5B8C\u6210\u3002");
|
|
1858
|
+
console.log("");
|
|
1859
|
+
console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
|
|
1860
|
+
}
|
|
1568
1861
|
} catch (e) {
|
|
1862
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1863
|
+
const manualAction = `git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`;
|
|
1864
|
+
if (options.json) {
|
|
1865
|
+
exitJsonError(`\u514B\u9686\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
|
|
1866
|
+
}
|
|
1569
1867
|
console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
1570
1868
|
console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
|
|
1571
1869
|
process.exit(1);
|
|
1572
1870
|
}
|
|
1573
1871
|
}
|
|
1574
1872
|
const sites = scanSites(COMMUNITY_SITES_DIR, "community");
|
|
1873
|
+
if (options.json) {
|
|
1874
|
+
console.log(JSON.stringify({
|
|
1875
|
+
success: true,
|
|
1876
|
+
updateMode,
|
|
1877
|
+
communityRepo: COMMUNITY_REPO,
|
|
1878
|
+
communityDir: COMMUNITY_SITES_DIR,
|
|
1879
|
+
siteCount: sites.length
|
|
1880
|
+
}, null, 2));
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1575
1883
|
console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
|
|
1576
1884
|
console.log(`\u2B50 Like bb-browser? \u2192 bb-browser star`);
|
|
1577
1885
|
checkCliUpdate();
|
|
@@ -1582,6 +1890,9 @@ function findSiteByName(name) {
|
|
|
1582
1890
|
function siteInfo(name, options) {
|
|
1583
1891
|
const site = findSiteByName(name);
|
|
1584
1892
|
if (!site) {
|
|
1893
|
+
if (options.json) {
|
|
1894
|
+
exitJsonError(`adapter "${name}" not found`, { action: "bb-browser site list" });
|
|
1895
|
+
}
|
|
1585
1896
|
console.error(`[error] site info: adapter "${name}" not found.`);
|
|
1586
1897
|
console.error(" Try: bb-browser site list");
|
|
1587
1898
|
process.exit(1);
|
|
@@ -1694,6 +2005,12 @@ async function siteRun(name, args, options) {
|
|
|
1694
2005
|
const site = sites.find((s) => s.name === name);
|
|
1695
2006
|
if (!site) {
|
|
1696
2007
|
const fuzzy = sites.filter((s) => s.name.includes(name));
|
|
2008
|
+
if (options.json) {
|
|
2009
|
+
exitJsonError(`site "${name}" not found`, {
|
|
2010
|
+
suggestions: fuzzy.slice(0, 5).map((s) => s.name),
|
|
2011
|
+
action: fuzzy.length > 0 ? void 0 : "bb-browser site update"
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
1697
2014
|
console.error(`[error] site: "${name}" not found.`);
|
|
1698
2015
|
if (fuzzy.length > 0) {
|
|
1699
2016
|
console.error(" Did you mean:");
|
|
@@ -1728,11 +2045,17 @@ async function siteRun(name, args, options) {
|
|
|
1728
2045
|
}
|
|
1729
2046
|
for (const [argName, argDef] of Object.entries(site.args)) {
|
|
1730
2047
|
if (argDef.required && !argMap[argName]) {
|
|
1731
|
-
console.error(`[error] site ${name}: missing required argument "${argName}".`);
|
|
1732
2048
|
const usage = argNames.map((a) => {
|
|
1733
2049
|
const def = site.args[a];
|
|
1734
2050
|
return def.required ? `<${a}>` : `[${a}]`;
|
|
1735
2051
|
}).join(" ");
|
|
2052
|
+
if (options.json) {
|
|
2053
|
+
exitJsonError(`missing required argument "${argName}"`, {
|
|
2054
|
+
usage: `bb-browser site ${name} ${usage}`,
|
|
2055
|
+
example: site.example
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
console.error(`[error] site ${name}: missing required argument "${argName}".`);
|
|
1736
2059
|
console.error(` Usage: bb-browser site ${name} ${usage}`);
|
|
1737
2060
|
if (site.example) console.error(` Example: ${site.example}`);
|
|
1738
2061
|
process.exit(1);
|
|
@@ -1743,7 +2066,7 @@ async function siteRun(name, args, options) {
|
|
|
1743
2066
|
const argsJson = JSON.stringify(argMap);
|
|
1744
2067
|
const script = `(${jsBody})(${argsJson})`;
|
|
1745
2068
|
if (options.openclaw) {
|
|
1746
|
-
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-
|
|
2069
|
+
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-HBJH6UFO.js");
|
|
1747
2070
|
let targetId;
|
|
1748
2071
|
if (site.domain) {
|
|
1749
2072
|
const tabs = ocGetTabs();
|
|
@@ -1752,7 +2075,7 @@ async function siteRun(name, args, options) {
|
|
|
1752
2075
|
targetId = existing.targetId;
|
|
1753
2076
|
} else {
|
|
1754
2077
|
targetId = ocOpenTab(`https://${site.domain}`);
|
|
1755
|
-
await new Promise((
|
|
2078
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
1756
2079
|
}
|
|
1757
2080
|
} else {
|
|
1758
2081
|
const tabs = ocGetTabs();
|
|
@@ -1814,7 +2137,7 @@ async function siteRun(name, args, options) {
|
|
|
1814
2137
|
url: `https://${site.domain}`
|
|
1815
2138
|
});
|
|
1816
2139
|
targetTabId = newResp.data?.tabId;
|
|
1817
|
-
await new Promise((
|
|
2140
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
1818
2141
|
}
|
|
1819
2142
|
}
|
|
1820
2143
|
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
@@ -1928,7 +2251,7 @@ async function siteCommand(args, options = {}) {
|
|
|
1928
2251
|
await siteRecommend(options);
|
|
1929
2252
|
break;
|
|
1930
2253
|
case "update":
|
|
1931
|
-
siteUpdate();
|
|
2254
|
+
siteUpdate(options);
|
|
1932
2255
|
break;
|
|
1933
2256
|
case "run":
|
|
1934
2257
|
if (!args[1]) {
|
|
@@ -1954,9 +2277,9 @@ async function siteCommand(args, options = {}) {
|
|
|
1954
2277
|
}
|
|
1955
2278
|
function silentUpdate() {
|
|
1956
2279
|
const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
|
|
1957
|
-
if (!
|
|
1958
|
-
import("child_process").then(({ spawn:
|
|
1959
|
-
const child =
|
|
2280
|
+
if (!existsSync5(gitDir)) return;
|
|
2281
|
+
import("child_process").then(({ spawn: spawn3 }) => {
|
|
2282
|
+
const child = spawn3("git", ["pull", "--ff-only"], {
|
|
1960
2283
|
cwd: COMMUNITY_SITES_DIR,
|
|
1961
2284
|
stdio: "ignore",
|
|
1962
2285
|
detached: true
|
|
@@ -2254,17 +2577,17 @@ async function getCommand(attribute, ref, options = {}) {
|
|
|
2254
2577
|
|
|
2255
2578
|
// packages/cli/src/commands/screenshot.ts
|
|
2256
2579
|
import fs from "fs";
|
|
2257
|
-
import
|
|
2258
|
-
import
|
|
2580
|
+
import path4 from "path";
|
|
2581
|
+
import os4 from "os";
|
|
2259
2582
|
function getDefaultPath() {
|
|
2260
2583
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2261
2584
|
const filename = `bb-screenshot-${timestamp}.png`;
|
|
2262
|
-
return
|
|
2585
|
+
return path4.join(os4.tmpdir(), filename);
|
|
2263
2586
|
}
|
|
2264
2587
|
function saveBase64Image(dataUrl, filePath) {
|
|
2265
2588
|
const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
2266
2589
|
const buffer = Buffer.from(base64Data, "base64");
|
|
2267
|
-
const dir =
|
|
2590
|
+
const dir = path4.dirname(filePath);
|
|
2268
2591
|
if (!fs.existsSync(dir)) {
|
|
2269
2592
|
fs.mkdirSync(dir, { recursive: true });
|
|
2270
2593
|
}
|
|
@@ -2272,7 +2595,7 @@ function saveBase64Image(dataUrl, filePath) {
|
|
|
2272
2595
|
}
|
|
2273
2596
|
async function screenshotCommand(outputPath, options = {}) {
|
|
2274
2597
|
await ensureDaemonRunning();
|
|
2275
|
-
const filePath = outputPath ?
|
|
2598
|
+
const filePath = outputPath ? path4.resolve(outputPath) : getDefaultPath();
|
|
2276
2599
|
const request = {
|
|
2277
2600
|
id: generateId(),
|
|
2278
2601
|
action: "screenshot",
|
|
@@ -2435,123 +2758,6 @@ async function scrollCommand(direction, pixels, options = {}) {
|
|
|
2435
2758
|
}
|
|
2436
2759
|
}
|
|
2437
2760
|
|
|
2438
|
-
// packages/cli/src/commands/daemon.ts
|
|
2439
|
-
async function statusCommand(options = {}) {
|
|
2440
|
-
const running = await isDaemonRunning();
|
|
2441
|
-
if (options.json) {
|
|
2442
|
-
console.log(JSON.stringify({ running }));
|
|
2443
|
-
} else {
|
|
2444
|
-
console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
// packages/cli/src/commands/reload.ts
|
|
2449
|
-
import WebSocket2 from "ws";
|
|
2450
|
-
var EXTENSION_NAME = "bb-browser";
|
|
2451
|
-
async function reloadCommand(options = {}) {
|
|
2452
|
-
const port = options.port || 9222;
|
|
2453
|
-
try {
|
|
2454
|
-
const listRes = await fetch(`http://127.0.0.1:${port}/json/list`);
|
|
2455
|
-
if (!listRes.ok) {
|
|
2456
|
-
throw new Error(`CDP \u672A\u542F\u7528\u3002\u8BF7\u7528 --remote-debugging-port=${port} \u542F\u52A8 Chrome`);
|
|
2457
|
-
}
|
|
2458
|
-
const list = await listRes.json();
|
|
2459
|
-
const extPage = list.find(
|
|
2460
|
-
(t) => t.type === "page" && t.url.includes("chrome://extensions")
|
|
2461
|
-
);
|
|
2462
|
-
if (!extPage) {
|
|
2463
|
-
throw new Error("\u8BF7\u5148\u6253\u5F00 chrome://extensions \u9875\u9762");
|
|
2464
|
-
}
|
|
2465
|
-
const result = await new Promise((resolve2, reject) => {
|
|
2466
|
-
const ws = new WebSocket2(extPage.webSocketDebuggerUrl);
|
|
2467
|
-
let resolved = false;
|
|
2468
|
-
const timeout = setTimeout(() => {
|
|
2469
|
-
if (!resolved) {
|
|
2470
|
-
resolved = true;
|
|
2471
|
-
ws.close();
|
|
2472
|
-
reject(new Error("CDP \u8FDE\u63A5\u8D85\u65F6"));
|
|
2473
|
-
}
|
|
2474
|
-
}, 1e4);
|
|
2475
|
-
ws.on("open", () => {
|
|
2476
|
-
const script = `
|
|
2477
|
-
(async function() {
|
|
2478
|
-
if (!chrome || !chrome.developerPrivate) {
|
|
2479
|
-
return { error: 'developerPrivate API not available' };
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
try {
|
|
2483
|
-
const exts = await chrome.developerPrivate.getExtensionsInfo();
|
|
2484
|
-
const bbExt = exts.find(e => e.name === '${EXTENSION_NAME}');
|
|
2485
|
-
|
|
2486
|
-
if (!bbExt) {
|
|
2487
|
-
return { error: '${EXTENSION_NAME} \u6269\u5C55\u672A\u5B89\u88C5' };
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
if (bbExt.state !== 'ENABLED') {
|
|
2491
|
-
return { error: '${EXTENSION_NAME} \u6269\u5C55\u5DF2\u7981\u7528' };
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
await chrome.developerPrivate.reload(bbExt.id, {failQuietly: true});
|
|
2495
|
-
return { success: true, extensionId: bbExt.id };
|
|
2496
|
-
} catch (e) {
|
|
2497
|
-
return { error: e.message };
|
|
2498
|
-
}
|
|
2499
|
-
})()
|
|
2500
|
-
`;
|
|
2501
|
-
ws.send(JSON.stringify({
|
|
2502
|
-
id: 1,
|
|
2503
|
-
method: "Runtime.evaluate",
|
|
2504
|
-
params: {
|
|
2505
|
-
expression: script,
|
|
2506
|
-
awaitPromise: true,
|
|
2507
|
-
returnByValue: true
|
|
2508
|
-
}
|
|
2509
|
-
}));
|
|
2510
|
-
});
|
|
2511
|
-
ws.on("message", (data) => {
|
|
2512
|
-
const msg = JSON.parse(data.toString());
|
|
2513
|
-
if (msg.id === 1) {
|
|
2514
|
-
clearTimeout(timeout);
|
|
2515
|
-
resolved = true;
|
|
2516
|
-
ws.close();
|
|
2517
|
-
const value = msg.result?.result?.value;
|
|
2518
|
-
if (value?.success) {
|
|
2519
|
-
resolve2({
|
|
2520
|
-
success: true,
|
|
2521
|
-
message: "\u6269\u5C55\u5DF2\u91CD\u8F7D",
|
|
2522
|
-
extensionId: value.extensionId
|
|
2523
|
-
});
|
|
2524
|
-
} else if (value?.error) {
|
|
2525
|
-
reject(new Error(value.error));
|
|
2526
|
-
} else {
|
|
2527
|
-
reject(new Error(`\u91CD\u8F7D\u5931\u8D25: ${JSON.stringify(value)}`));
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
});
|
|
2531
|
-
ws.on("error", (err) => {
|
|
2532
|
-
clearTimeout(timeout);
|
|
2533
|
-
if (!resolved) {
|
|
2534
|
-
resolved = true;
|
|
2535
|
-
reject(new Error(`CDP \u8FDE\u63A5\u5931\u8D25: ${err.message}`));
|
|
2536
|
-
}
|
|
2537
|
-
});
|
|
2538
|
-
});
|
|
2539
|
-
if (options.json) {
|
|
2540
|
-
console.log(JSON.stringify(result));
|
|
2541
|
-
} else {
|
|
2542
|
-
console.log(`${result.message} (${result.extensionId})`);
|
|
2543
|
-
}
|
|
2544
|
-
} catch (error) {
|
|
2545
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2546
|
-
if (options.json) {
|
|
2547
|
-
console.log(JSON.stringify({ success: false, error: message }));
|
|
2548
|
-
} else {
|
|
2549
|
-
console.error(`\u9519\u8BEF: ${message}`);
|
|
2550
|
-
}
|
|
2551
|
-
process.exit(1);
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2555
2761
|
// packages/cli/src/commands/nav.ts
|
|
2556
2762
|
async function backCommand(options = {}) {
|
|
2557
2763
|
await ensureDaemonRunning();
|
|
@@ -3252,7 +3458,7 @@ async function ensureTabForOrigin(origin, hostname) {
|
|
|
3252
3458
|
if (!newResp.success) {
|
|
3253
3459
|
throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
|
|
3254
3460
|
}
|
|
3255
|
-
await new Promise((
|
|
3461
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
3256
3462
|
return newResp.data?.tabId;
|
|
3257
3463
|
}
|
|
3258
3464
|
function buildFetchScript(url, options) {
|
|
@@ -3398,8 +3604,18 @@ async function historyCommand(subCommand, options = {}) {
|
|
|
3398
3604
|
}
|
|
3399
3605
|
}
|
|
3400
3606
|
|
|
3607
|
+
// packages/cli/src/commands/daemon.ts
|
|
3608
|
+
async function statusCommand(options = {}) {
|
|
3609
|
+
const running = await isDaemonRunning();
|
|
3610
|
+
if (options.json) {
|
|
3611
|
+
console.log(JSON.stringify({ running }));
|
|
3612
|
+
} else {
|
|
3613
|
+
console.log(running ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u4E2D" : "\u6D4F\u89C8\u5668\u672A\u8FD0\u884C");
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3401
3617
|
// packages/cli/src/index.ts
|
|
3402
|
-
var VERSION = "0.
|
|
3618
|
+
var VERSION = "0.9.0";
|
|
3403
3619
|
var HELP_TEXT = `
|
|
3404
3620
|
bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
3405
3621
|
|
|
@@ -3554,9 +3770,9 @@ async function main() {
|
|
|
3554
3770
|
return;
|
|
3555
3771
|
}
|
|
3556
3772
|
if (process.argv.includes("--mcp")) {
|
|
3557
|
-
const mcpPath =
|
|
3558
|
-
const { spawn:
|
|
3559
|
-
const child =
|
|
3773
|
+
const mcpPath = fileURLToPath4(new URL("./mcp.js", import.meta.url));
|
|
3774
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
3775
|
+
const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
|
|
3560
3776
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
3561
3777
|
return;
|
|
3562
3778
|
}
|
|
@@ -3717,24 +3933,6 @@ async function main() {
|
|
|
3717
3933
|
break;
|
|
3718
3934
|
}
|
|
3719
3935
|
case "daemon":
|
|
3720
|
-
case "start": {
|
|
3721
|
-
const hostIdx = process.argv.findIndex((a) => a === "--host");
|
|
3722
|
-
const host = hostIdx >= 0 ? process.argv[hostIdx + 1] : void 0;
|
|
3723
|
-
await daemonCommand({ json: parsed.flags.json, host });
|
|
3724
|
-
break;
|
|
3725
|
-
}
|
|
3726
|
-
case "stop": {
|
|
3727
|
-
await stopCommand({ json: parsed.flags.json });
|
|
3728
|
-
break;
|
|
3729
|
-
}
|
|
3730
|
-
case "status": {
|
|
3731
|
-
await statusCommand({ json: parsed.flags.json });
|
|
3732
|
-
break;
|
|
3733
|
-
}
|
|
3734
|
-
case "reload": {
|
|
3735
|
-
await reloadCommand({ json: parsed.flags.json });
|
|
3736
|
-
break;
|
|
3737
|
-
}
|
|
3738
3936
|
case "close": {
|
|
3739
3937
|
await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
3740
3938
|
break;
|
|
@@ -3797,6 +3995,10 @@ async function main() {
|
|
|
3797
3995
|
await tabCommand(parsed.args, { json: parsed.flags.json });
|
|
3798
3996
|
break;
|
|
3799
3997
|
}
|
|
3998
|
+
case "status": {
|
|
3999
|
+
await statusCommand({ json: parsed.flags.json });
|
|
4000
|
+
break;
|
|
4001
|
+
}
|
|
3800
4002
|
case "frame": {
|
|
3801
4003
|
const selectorOrMain = parsed.args[0];
|
|
3802
4004
|
if (!selectorOrMain) {
|