bb-browser 0.8.3 → 0.10.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-AHGAQEFO.js +0 -0
- package/dist/chunk-D4HDZEJT.js +0 -0
- package/dist/chunk-DBJBHYC7.js +0 -0
- package/dist/chunk-FSL4RNI6.js +53 -0
- package/dist/chunk-FSL4RNI6.js.map +1 -0
- package/dist/cli.js +423 -87
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/daemon.js.map +1 -1
- package/dist/jq-HHMLHEPA.js +0 -0
- 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 +21 -16
- 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 };
|
|
@@ -105,6 +108,18 @@ function findBrowserExecutable() {
|
|
|
105
108
|
}
|
|
106
109
|
return null;
|
|
107
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
|
+
}
|
|
108
123
|
async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
109
124
|
const executable = findBrowserExecutable();
|
|
110
125
|
if (!executable) {
|
|
@@ -155,7 +170,7 @@ async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
|
155
170
|
if (await canConnect("127.0.0.1", port)) {
|
|
156
171
|
return { host: "127.0.0.1", port };
|
|
157
172
|
}
|
|
158
|
-
await new Promise((
|
|
173
|
+
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
159
174
|
}
|
|
160
175
|
return null;
|
|
161
176
|
}
|
|
@@ -202,11 +217,35 @@ var jsErrors = [];
|
|
|
202
217
|
var errorsEnabled = false;
|
|
203
218
|
var traceRecording = false;
|
|
204
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
|
+
}
|
|
205
244
|
function buildRequestError(error) {
|
|
206
245
|
return error instanceof Error ? error : new Error(String(error));
|
|
207
246
|
}
|
|
208
247
|
function fetchJson(url) {
|
|
209
|
-
return new Promise((
|
|
248
|
+
return new Promise((resolve3, reject) => {
|
|
210
249
|
const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
|
|
211
250
|
const req = requester(url, { method: "GET" }, (res) => {
|
|
212
251
|
const chunks = [];
|
|
@@ -218,7 +257,7 @@ function fetchJson(url) {
|
|
|
218
257
|
return;
|
|
219
258
|
}
|
|
220
259
|
try {
|
|
221
|
-
|
|
260
|
+
resolve3(JSON.parse(raw));
|
|
222
261
|
} catch (error) {
|
|
223
262
|
reject(error);
|
|
224
263
|
}
|
|
@@ -241,14 +280,14 @@ async function getJsonVersion(host, port) {
|
|
|
241
280
|
return { webSocketDebuggerUrl: url };
|
|
242
281
|
}
|
|
243
282
|
function connectWebSocket(url) {
|
|
244
|
-
return new Promise((
|
|
283
|
+
return new Promise((resolve3, reject) => {
|
|
245
284
|
const ws = new WebSocket(url);
|
|
246
285
|
ws.once("open", () => {
|
|
247
286
|
const socket = ws._socket;
|
|
248
287
|
if (socket && typeof socket.unref === "function") {
|
|
249
288
|
socket.unref();
|
|
250
289
|
}
|
|
251
|
-
|
|
290
|
+
resolve3(ws);
|
|
252
291
|
});
|
|
253
292
|
ws.once("error", reject);
|
|
254
293
|
});
|
|
@@ -264,6 +303,7 @@ function createState(host, port, browserWsUrl, browserSocket) {
|
|
|
264
303
|
sessions: /* @__PURE__ */ new Map(),
|
|
265
304
|
attachedTargets: /* @__PURE__ */ new Map(),
|
|
266
305
|
refsByTarget: /* @__PURE__ */ new Map(),
|
|
306
|
+
currentTargetId: loadPersistedCurrentTargetId(host, port),
|
|
267
307
|
activeFrameIdByTarget: /* @__PURE__ */ new Map(),
|
|
268
308
|
dialogHandlers: /* @__PURE__ */ new Map()
|
|
269
309
|
};
|
|
@@ -300,6 +340,10 @@ function createState(host, port, browserWsUrl, browserSocket) {
|
|
|
300
340
|
state.attachedTargets.delete(sessionId);
|
|
301
341
|
state.activeFrameIdByTarget.delete(targetId);
|
|
302
342
|
state.dialogHandlers.delete(targetId);
|
|
343
|
+
if (state.currentTargetId === targetId) {
|
|
344
|
+
state.currentTargetId = void 0;
|
|
345
|
+
persistCurrentTargetId(state.host, state.port, void 0);
|
|
346
|
+
}
|
|
303
347
|
}
|
|
304
348
|
}
|
|
305
349
|
return;
|
|
@@ -343,8 +387,8 @@ async function browserCommand(method, params = {}) {
|
|
|
343
387
|
if (!state) throw new Error("CDP connection not initialized");
|
|
344
388
|
const id = state.nextMessageId++;
|
|
345
389
|
const payload = JSON.stringify({ id, method, params });
|
|
346
|
-
const promise = new Promise((
|
|
347
|
-
state.browserPending.set(id, { resolve:
|
|
390
|
+
const promise = new Promise((resolve3, reject) => {
|
|
391
|
+
state.browserPending.set(id, { resolve: resolve3, reject, method });
|
|
348
392
|
});
|
|
349
393
|
state.browserSocket.send(payload);
|
|
350
394
|
return promise;
|
|
@@ -355,13 +399,13 @@ async function sessionCommand(targetId, method, params = {}) {
|
|
|
355
399
|
const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
|
|
356
400
|
const id = state.nextMessageId++;
|
|
357
401
|
const payload = JSON.stringify({ id, method, params, sessionId });
|
|
358
|
-
return new Promise((
|
|
402
|
+
return new Promise((resolve3, reject) => {
|
|
359
403
|
const check = (raw) => {
|
|
360
404
|
const msg = JSON.parse(raw.toString());
|
|
361
405
|
if (msg.id === id && msg.sessionId === sessionId) {
|
|
362
406
|
state.browserSocket.off("message", check);
|
|
363
407
|
if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
|
|
364
|
-
else
|
|
408
|
+
else resolve3(msg.result);
|
|
365
409
|
}
|
|
366
410
|
};
|
|
367
411
|
state.browserSocket.on("message", check);
|
|
@@ -512,6 +556,7 @@ async function getTargets() {
|
|
|
512
556
|
async function ensurePageTarget(targetId) {
|
|
513
557
|
const targets = (await getTargets()).filter((target2) => target2.type === "page");
|
|
514
558
|
if (targets.length === 0) throw new Error("No page target found");
|
|
559
|
+
const persistedTargetId = targetId === void 0 ? connectionState?.currentTargetId : void 0;
|
|
515
560
|
let target;
|
|
516
561
|
if (typeof targetId === "number") {
|
|
517
562
|
target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
|
|
@@ -523,9 +568,11 @@ async function ensurePageTarget(targetId) {
|
|
|
523
568
|
target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
|
|
524
569
|
}
|
|
525
570
|
}
|
|
571
|
+
} else if (persistedTargetId) {
|
|
572
|
+
target = targets.find((item) => item.id === persistedTargetId);
|
|
526
573
|
}
|
|
527
574
|
target ??= targets[0];
|
|
528
|
-
|
|
575
|
+
setCurrentTargetId(target.id);
|
|
529
576
|
await attachTarget(target.id);
|
|
530
577
|
return target;
|
|
531
578
|
}
|
|
@@ -642,19 +689,16 @@ async function evaluate(targetId, expression, returnByValue = true) {
|
|
|
642
689
|
const result = await sessionCommand(targetId, "Runtime.evaluate", {
|
|
643
690
|
expression,
|
|
644
691
|
awaitPromise: true,
|
|
645
|
-
returnByValue
|
|
692
|
+
returnByValue,
|
|
693
|
+
replMode: true
|
|
646
694
|
});
|
|
647
695
|
if (result.exceptionDetails) {
|
|
648
|
-
throw new Error(
|
|
696
|
+
throw new Error(
|
|
697
|
+
result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Runtime.evaluate failed"
|
|
698
|
+
);
|
|
649
699
|
}
|
|
650
700
|
return result.result.value ?? result.result;
|
|
651
701
|
}
|
|
652
|
-
async function resolveNode(targetId, backendNodeId) {
|
|
653
|
-
const result = await sessionCommand(targetId, "DOM.pushNodesByBackendIdsToFrontend", {
|
|
654
|
-
backendNodeIds: [backendNodeId]
|
|
655
|
-
});
|
|
656
|
-
return result.nodeId;
|
|
657
|
-
}
|
|
658
702
|
async function focusNode(targetId, backendNodeId) {
|
|
659
703
|
await sessionCommand(targetId, "DOM.focus", { backendNodeId });
|
|
660
704
|
}
|
|
@@ -662,40 +706,77 @@ async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
|
|
|
662
706
|
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
663
707
|
await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
664
708
|
objectId: resolved.object.objectId,
|
|
665
|
-
functionDeclaration: `function(
|
|
709
|
+
functionDeclaration: `function(clearFirst) {
|
|
710
|
+
if (typeof this.scrollIntoView === 'function') {
|
|
711
|
+
this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
|
|
712
|
+
}
|
|
666
713
|
if (typeof this.focus === 'function') this.focus();
|
|
667
|
-
if (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
+
}
|
|
675
738
|
return true;
|
|
676
739
|
}
|
|
677
740
|
return false;
|
|
678
741
|
}`,
|
|
679
742
|
arguments: [
|
|
680
|
-
{ value: text },
|
|
681
743
|
{ value: clearFirst }
|
|
682
744
|
],
|
|
683
745
|
returnByValue: true
|
|
684
746
|
});
|
|
685
|
-
|
|
686
|
-
|
|
747
|
+
if (text) {
|
|
748
|
+
await focusNode(targetId, backendNodeId);
|
|
749
|
+
await sessionCommand(targetId, "Input.insertText", { text });
|
|
750
|
+
}
|
|
687
751
|
}
|
|
688
|
-
async function
|
|
689
|
-
const
|
|
690
|
-
|
|
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
|
|
691
771
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
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;
|
|
699
780
|
}
|
|
700
781
|
async function mouseClick(targetId, x, y) {
|
|
701
782
|
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
|
|
@@ -703,9 +784,14 @@ async function mouseClick(targetId, x, y) {
|
|
|
703
784
|
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
704
785
|
}
|
|
705
786
|
async function getAttributeValue(targetId, backendNodeId, attribute) {
|
|
706
|
-
const nodeId = await resolveNode(targetId, backendNodeId);
|
|
707
787
|
if (attribute === "text") {
|
|
708
|
-
|
|
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 ?? "");
|
|
709
795
|
}
|
|
710
796
|
const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
711
797
|
const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
@@ -933,7 +1019,7 @@ async function dispatchRequest(request) {
|
|
|
933
1019
|
case "open": {
|
|
934
1020
|
if (!request.url) return fail(request.id, "Missing url parameter");
|
|
935
1021
|
if (request.tabId === void 0) {
|
|
936
|
-
const created = await browserCommand("Target.createTarget", { url: request.url });
|
|
1022
|
+
const created = await browserCommand("Target.createTarget", { url: request.url, background: true });
|
|
937
1023
|
const newTarget = await ensurePageTarget(created.targetId);
|
|
938
1024
|
return ok(request.id, { url: request.url, tabId: newTarget.id });
|
|
939
1025
|
}
|
|
@@ -950,7 +1036,7 @@ async function dispatchRequest(request) {
|
|
|
950
1036
|
case "hover": {
|
|
951
1037
|
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
952
1038
|
const backendNodeId = await parseRef(request.ref);
|
|
953
|
-
const point = await
|
|
1039
|
+
const point = await getInteractablePoint(target.id, backendNodeId);
|
|
954
1040
|
await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
|
|
955
1041
|
if (request.action === "click") await mouseClick(target.id, point.x, point.y);
|
|
956
1042
|
return ok(request.id, {});
|
|
@@ -986,7 +1072,14 @@ async function dispatchRequest(request) {
|
|
|
986
1072
|
return ok(request.id, { value: request.value });
|
|
987
1073
|
}
|
|
988
1074
|
case "get": {
|
|
989
|
-
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");
|
|
990
1083
|
const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
|
|
991
1084
|
return ok(request.id, { value });
|
|
992
1085
|
}
|
|
@@ -1001,7 +1094,7 @@ async function dispatchRequest(request) {
|
|
|
1001
1094
|
return ok(request.id, {});
|
|
1002
1095
|
}
|
|
1003
1096
|
case "wait": {
|
|
1004
|
-
await new Promise((
|
|
1097
|
+
await new Promise((resolve3) => setTimeout(resolve3, request.ms ?? 1e3));
|
|
1005
1098
|
return ok(request.id, {});
|
|
1006
1099
|
}
|
|
1007
1100
|
case "press": {
|
|
@@ -1040,14 +1133,14 @@ async function dispatchRequest(request) {
|
|
|
1040
1133
|
return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
|
|
1041
1134
|
}
|
|
1042
1135
|
case "tab_new": {
|
|
1043
|
-
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 });
|
|
1044
1137
|
return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
|
|
1045
1138
|
}
|
|
1046
1139
|
case "tab_select": {
|
|
1047
1140
|
const tabs = (await getTargets()).filter((item) => item.type === "page");
|
|
1048
1141
|
const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
|
|
1049
1142
|
if (!selected) return fail(request.id, "Tab not found");
|
|
1050
|
-
|
|
1143
|
+
setCurrentTargetId(selected.id);
|
|
1051
1144
|
await attachTarget(selected.id);
|
|
1052
1145
|
return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
|
|
1053
1146
|
}
|
|
@@ -1057,6 +1150,9 @@ async function dispatchRequest(request) {
|
|
|
1057
1150
|
if (!selected) return fail(request.id, "Tab not found");
|
|
1058
1151
|
await browserCommand("Target.closeTarget", { targetId: selected.id });
|
|
1059
1152
|
connectionState?.refsByTarget.delete(selected.id);
|
|
1153
|
+
if (connectionState?.currentTargetId === selected.id) {
|
|
1154
|
+
setCurrentTargetId(void 0);
|
|
1155
|
+
}
|
|
1060
1156
|
clearPersistedRefs(selected.id);
|
|
1061
1157
|
return ok(request.id, { tabId: selected.id });
|
|
1062
1158
|
}
|
|
@@ -1164,7 +1260,167 @@ async function dispatchRequest(request) {
|
|
|
1164
1260
|
}
|
|
1165
1261
|
}
|
|
1166
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
|
+
|
|
1167
1422
|
// packages/cli/src/client.ts
|
|
1423
|
+
var MONITOR_ACTIONS = /* @__PURE__ */ new Set(["network", "console", "errors", "trace"]);
|
|
1168
1424
|
var jqExpression;
|
|
1169
1425
|
function setJqExpression(expression) {
|
|
1170
1426
|
jqExpression = expression;
|
|
@@ -1183,13 +1439,23 @@ function handleJqResponse(response) {
|
|
|
1183
1439
|
}
|
|
1184
1440
|
}
|
|
1185
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
|
+
}
|
|
1186
1449
|
return sendCommand(request);
|
|
1187
1450
|
}
|
|
1188
1451
|
|
|
1189
1452
|
// packages/cli/src/daemon-manager.ts
|
|
1190
|
-
import { fileURLToPath as
|
|
1191
|
-
import { dirname, resolve } from "path";
|
|
1192
|
-
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";
|
|
1456
|
+
async function isDaemonRunning() {
|
|
1457
|
+
return await isManagedBrowserRunning();
|
|
1458
|
+
}
|
|
1193
1459
|
async function ensureDaemonRunning() {
|
|
1194
1460
|
try {
|
|
1195
1461
|
await ensureCdpConnection();
|
|
@@ -1207,7 +1473,7 @@ async function ensureDaemonRunning() {
|
|
|
1207
1473
|
}
|
|
1208
1474
|
|
|
1209
1475
|
// packages/cli/src/history-sqlite.ts
|
|
1210
|
-
import { copyFileSync, existsSync as
|
|
1476
|
+
import { copyFileSync, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
1211
1477
|
import { execSync as execSync2 } from "child_process";
|
|
1212
1478
|
import { homedir, tmpdir } from "os";
|
|
1213
1479
|
import { join } from "path";
|
|
@@ -1231,7 +1497,7 @@ function getHistoryPathCandidates() {
|
|
|
1231
1497
|
}
|
|
1232
1498
|
function findHistoryPath() {
|
|
1233
1499
|
for (const historyPath of getHistoryPathCandidates()) {
|
|
1234
|
-
if (
|
|
1500
|
+
if (existsSync4(historyPath)) {
|
|
1235
1501
|
return historyPath;
|
|
1236
1502
|
}
|
|
1237
1503
|
}
|
|
@@ -1351,7 +1617,7 @@ function getHistoryDomains(days) {
|
|
|
1351
1617
|
}
|
|
1352
1618
|
|
|
1353
1619
|
// packages/cli/src/commands/site.ts
|
|
1354
|
-
import { readFileSync as readFileSync2, readdirSync, existsSync as
|
|
1620
|
+
import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync5, mkdirSync } from "fs";
|
|
1355
1621
|
import { join as join2, relative } from "path";
|
|
1356
1622
|
import { homedir as homedir2 } from "os";
|
|
1357
1623
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -1370,6 +1636,10 @@ function checkCliUpdate() {
|
|
|
1370
1636
|
} catch {
|
|
1371
1637
|
}
|
|
1372
1638
|
}
|
|
1639
|
+
function exitJsonError(error, extra = {}) {
|
|
1640
|
+
console.log(JSON.stringify({ success: false, error, ...extra }, null, 2));
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1373
1643
|
function parseSiteMeta(filePath, source) {
|
|
1374
1644
|
let content;
|
|
1375
1645
|
try {
|
|
@@ -1433,7 +1703,7 @@ function parseSiteMeta(filePath, source) {
|
|
|
1433
1703
|
return meta;
|
|
1434
1704
|
}
|
|
1435
1705
|
function scanSites(dir, source) {
|
|
1436
|
-
if (!
|
|
1706
|
+
if (!existsSync5(dir)) return [];
|
|
1437
1707
|
const sites = [];
|
|
1438
1708
|
function walk(currentDir) {
|
|
1439
1709
|
let entries;
|
|
@@ -1487,6 +1757,10 @@ function matchTabOrigin(tabUrl, domain) {
|
|
|
1487
1757
|
function siteList(options) {
|
|
1488
1758
|
const sites = getAllSites();
|
|
1489
1759
|
if (sites.length === 0) {
|
|
1760
|
+
if (options.json) {
|
|
1761
|
+
console.log("[]");
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1490
1764
|
console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
|
|
1491
1765
|
console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
|
|
1492
1766
|
console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
|
|
@@ -1527,6 +1801,10 @@ function siteSearch(query, options) {
|
|
|
1527
1801
|
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
|
|
1528
1802
|
);
|
|
1529
1803
|
if (matches.length === 0) {
|
|
1804
|
+
if (options.json) {
|
|
1805
|
+
console.log("[]");
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1530
1808
|
console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
|
|
1531
1809
|
console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
|
|
1532
1810
|
return;
|
|
@@ -1545,34 +1823,63 @@ function siteSearch(query, options) {
|
|
|
1545
1823
|
console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
|
|
1546
1824
|
}
|
|
1547
1825
|
}
|
|
1548
|
-
function siteUpdate() {
|
|
1826
|
+
function siteUpdate(options = {}) {
|
|
1549
1827
|
mkdirSync(BB_DIR, { recursive: true });
|
|
1550
|
-
|
|
1551
|
-
|
|
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
|
+
}
|
|
1552
1833
|
try {
|
|
1553
1834
|
execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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
|
+
}
|
|
1557
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
|
+
}
|
|
1558
1846
|
console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
1559
1847
|
console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
|
|
1560
1848
|
process.exit(1);
|
|
1561
1849
|
}
|
|
1562
1850
|
} else {
|
|
1563
|
-
|
|
1851
|
+
if (!options.json) {
|
|
1852
|
+
console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
|
|
1853
|
+
}
|
|
1564
1854
|
try {
|
|
1565
1855
|
execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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
|
+
}
|
|
1569
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
|
+
}
|
|
1570
1867
|
console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
1571
1868
|
console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
|
|
1572
1869
|
process.exit(1);
|
|
1573
1870
|
}
|
|
1574
1871
|
}
|
|
1575
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
|
+
}
|
|
1576
1883
|
console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
|
|
1577
1884
|
console.log(`\u2B50 Like bb-browser? \u2192 bb-browser star`);
|
|
1578
1885
|
checkCliUpdate();
|
|
@@ -1583,6 +1890,9 @@ function findSiteByName(name) {
|
|
|
1583
1890
|
function siteInfo(name, options) {
|
|
1584
1891
|
const site = findSiteByName(name);
|
|
1585
1892
|
if (!site) {
|
|
1893
|
+
if (options.json) {
|
|
1894
|
+
exitJsonError(`adapter "${name}" not found`, { action: "bb-browser site list" });
|
|
1895
|
+
}
|
|
1586
1896
|
console.error(`[error] site info: adapter "${name}" not found.`);
|
|
1587
1897
|
console.error(" Try: bb-browser site list");
|
|
1588
1898
|
process.exit(1);
|
|
@@ -1695,6 +2005,12 @@ async function siteRun(name, args, options) {
|
|
|
1695
2005
|
const site = sites.find((s) => s.name === name);
|
|
1696
2006
|
if (!site) {
|
|
1697
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
|
+
}
|
|
1698
2014
|
console.error(`[error] site: "${name}" not found.`);
|
|
1699
2015
|
if (fuzzy.length > 0) {
|
|
1700
2016
|
console.error(" Did you mean:");
|
|
@@ -1729,11 +2045,17 @@ async function siteRun(name, args, options) {
|
|
|
1729
2045
|
}
|
|
1730
2046
|
for (const [argName, argDef] of Object.entries(site.args)) {
|
|
1731
2047
|
if (argDef.required && !argMap[argName]) {
|
|
1732
|
-
console.error(`[error] site ${name}: missing required argument "${argName}".`);
|
|
1733
2048
|
const usage = argNames.map((a) => {
|
|
1734
2049
|
const def = site.args[a];
|
|
1735
2050
|
return def.required ? `<${a}>` : `[${a}]`;
|
|
1736
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}".`);
|
|
1737
2059
|
console.error(` Usage: bb-browser site ${name} ${usage}`);
|
|
1738
2060
|
if (site.example) console.error(` Example: ${site.example}`);
|
|
1739
2061
|
process.exit(1);
|
|
@@ -1744,7 +2066,7 @@ async function siteRun(name, args, options) {
|
|
|
1744
2066
|
const argsJson = JSON.stringify(argMap);
|
|
1745
2067
|
const script = `(${jsBody})(${argsJson})`;
|
|
1746
2068
|
if (options.openclaw) {
|
|
1747
|
-
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-
|
|
2069
|
+
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-HBJH6UFO.js");
|
|
1748
2070
|
let targetId;
|
|
1749
2071
|
if (site.domain) {
|
|
1750
2072
|
const tabs = ocGetTabs();
|
|
@@ -1753,7 +2075,7 @@ async function siteRun(name, args, options) {
|
|
|
1753
2075
|
targetId = existing.targetId;
|
|
1754
2076
|
} else {
|
|
1755
2077
|
targetId = ocOpenTab(`https://${site.domain}`);
|
|
1756
|
-
await new Promise((
|
|
2078
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
1757
2079
|
}
|
|
1758
2080
|
} else {
|
|
1759
2081
|
const tabs = ocGetTabs();
|
|
@@ -1815,7 +2137,7 @@ async function siteRun(name, args, options) {
|
|
|
1815
2137
|
url: `https://${site.domain}`
|
|
1816
2138
|
});
|
|
1817
2139
|
targetTabId = newResp.data?.tabId;
|
|
1818
|
-
await new Promise((
|
|
2140
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
1819
2141
|
}
|
|
1820
2142
|
}
|
|
1821
2143
|
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
@@ -1929,7 +2251,7 @@ async function siteCommand(args, options = {}) {
|
|
|
1929
2251
|
await siteRecommend(options);
|
|
1930
2252
|
break;
|
|
1931
2253
|
case "update":
|
|
1932
|
-
siteUpdate();
|
|
2254
|
+
siteUpdate(options);
|
|
1933
2255
|
break;
|
|
1934
2256
|
case "run":
|
|
1935
2257
|
if (!args[1]) {
|
|
@@ -1955,9 +2277,9 @@ async function siteCommand(args, options = {}) {
|
|
|
1955
2277
|
}
|
|
1956
2278
|
function silentUpdate() {
|
|
1957
2279
|
const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
|
|
1958
|
-
if (!
|
|
1959
|
-
import("child_process").then(({ spawn:
|
|
1960
|
-
const child =
|
|
2280
|
+
if (!existsSync5(gitDir)) return;
|
|
2281
|
+
import("child_process").then(({ spawn: spawn3 }) => {
|
|
2282
|
+
const child = spawn3("git", ["pull", "--ff-only"], {
|
|
1961
2283
|
cwd: COMMUNITY_SITES_DIR,
|
|
1962
2284
|
stdio: "ignore",
|
|
1963
2285
|
detached: true
|
|
@@ -2255,17 +2577,17 @@ async function getCommand(attribute, ref, options = {}) {
|
|
|
2255
2577
|
|
|
2256
2578
|
// packages/cli/src/commands/screenshot.ts
|
|
2257
2579
|
import fs from "fs";
|
|
2258
|
-
import
|
|
2259
|
-
import
|
|
2580
|
+
import path4 from "path";
|
|
2581
|
+
import os4 from "os";
|
|
2260
2582
|
function getDefaultPath() {
|
|
2261
2583
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2262
2584
|
const filename = `bb-screenshot-${timestamp}.png`;
|
|
2263
|
-
return
|
|
2585
|
+
return path4.join(os4.tmpdir(), filename);
|
|
2264
2586
|
}
|
|
2265
2587
|
function saveBase64Image(dataUrl, filePath) {
|
|
2266
2588
|
const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
2267
2589
|
const buffer = Buffer.from(base64Data, "base64");
|
|
2268
|
-
const dir =
|
|
2590
|
+
const dir = path4.dirname(filePath);
|
|
2269
2591
|
if (!fs.existsSync(dir)) {
|
|
2270
2592
|
fs.mkdirSync(dir, { recursive: true });
|
|
2271
2593
|
}
|
|
@@ -2273,7 +2595,7 @@ function saveBase64Image(dataUrl, filePath) {
|
|
|
2273
2595
|
}
|
|
2274
2596
|
async function screenshotCommand(outputPath, options = {}) {
|
|
2275
2597
|
await ensureDaemonRunning();
|
|
2276
|
-
const filePath = outputPath ?
|
|
2598
|
+
const filePath = outputPath ? path4.resolve(outputPath) : getDefaultPath();
|
|
2277
2599
|
const request = {
|
|
2278
2600
|
id: generateId(),
|
|
2279
2601
|
action: "screenshot",
|
|
@@ -3136,7 +3458,7 @@ async function ensureTabForOrigin(origin, hostname) {
|
|
|
3136
3458
|
if (!newResp.success) {
|
|
3137
3459
|
throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
|
|
3138
3460
|
}
|
|
3139
|
-
await new Promise((
|
|
3461
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
3140
3462
|
return newResp.data?.tabId;
|
|
3141
3463
|
}
|
|
3142
3464
|
function buildFetchScript(url, options) {
|
|
@@ -3282,8 +3604,18 @@ async function historyCommand(subCommand, options = {}) {
|
|
|
3282
3604
|
}
|
|
3283
3605
|
}
|
|
3284
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
|
+
|
|
3285
3617
|
// packages/cli/src/index.ts
|
|
3286
|
-
var VERSION = "0.
|
|
3618
|
+
var VERSION = "0.10.0";
|
|
3287
3619
|
var HELP_TEXT = `
|
|
3288
3620
|
bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
3289
3621
|
|
|
@@ -3438,9 +3770,9 @@ async function main() {
|
|
|
3438
3770
|
return;
|
|
3439
3771
|
}
|
|
3440
3772
|
if (process.argv.includes("--mcp")) {
|
|
3441
|
-
const mcpPath =
|
|
3442
|
-
const { spawn:
|
|
3443
|
-
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" });
|
|
3444
3776
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
3445
3777
|
return;
|
|
3446
3778
|
}
|
|
@@ -3663,6 +3995,10 @@ async function main() {
|
|
|
3663
3995
|
await tabCommand(parsed.args, { json: parsed.flags.json });
|
|
3664
3996
|
break;
|
|
3665
3997
|
}
|
|
3998
|
+
case "status": {
|
|
3999
|
+
await statusCommand({ json: parsed.flags.json });
|
|
4000
|
+
break;
|
|
4001
|
+
}
|
|
3666
4002
|
case "frame": {
|
|
3667
4003
|
const selectorOrMain = parsed.args[0];
|
|
3668
4004
|
if (!selectorOrMain) {
|