bb-browser 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +350 -49
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,10 @@ import "./chunk-D4HDZEJT.js";
|
|
|
12
12
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13
13
|
|
|
14
14
|
// packages/cli/src/cdp-client.ts
|
|
15
|
-
import { readFileSync } from "fs";
|
|
15
|
+
import { readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
16
16
|
import { request as httpRequest } from "http";
|
|
17
17
|
import { request as httpsRequest } from "https";
|
|
18
|
+
import os2 from "os";
|
|
18
19
|
import path2 from "path";
|
|
19
20
|
import { fileURLToPath } from "url";
|
|
20
21
|
import WebSocket from "ws";
|
|
@@ -159,25 +160,31 @@ async function discoverCdpPort() {
|
|
|
159
160
|
if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
|
|
160
161
|
return { host: "127.0.0.1", port: explicitPort };
|
|
161
162
|
}
|
|
163
|
+
try {
|
|
164
|
+
const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
|
|
165
|
+
const managedPort = Number.parseInt(rawPort.trim(), 10);
|
|
166
|
+
if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
|
|
167
|
+
return { host: "127.0.0.1", port: managedPort };
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
162
171
|
if (process.argv.includes("--openclaw")) {
|
|
163
172
|
const viaOpenClaw = await tryOpenClaw();
|
|
164
173
|
if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
|
|
165
174
|
return viaOpenClaw;
|
|
166
175
|
}
|
|
167
176
|
}
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
return
|
|
177
|
+
const launched = await launchManagedBrowser();
|
|
178
|
+
if (launched) {
|
|
179
|
+
return launched;
|
|
171
180
|
}
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return { host: "127.0.0.1", port: managedPort };
|
|
181
|
+
if (!process.argv.includes("--openclaw")) {
|
|
182
|
+
const detectedOpenClaw = await tryOpenClaw();
|
|
183
|
+
if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
|
|
184
|
+
return detectedOpenClaw;
|
|
177
185
|
}
|
|
178
|
-
} catch {
|
|
179
186
|
}
|
|
180
|
-
return
|
|
187
|
+
return null;
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
// packages/cli/src/cdp-client.ts
|
|
@@ -232,7 +239,13 @@ async function getJsonVersion(host, port) {
|
|
|
232
239
|
function connectWebSocket(url) {
|
|
233
240
|
return new Promise((resolve2, reject) => {
|
|
234
241
|
const ws = new WebSocket(url);
|
|
235
|
-
ws.once("open", () =>
|
|
242
|
+
ws.once("open", () => {
|
|
243
|
+
const socket = ws._socket;
|
|
244
|
+
if (socket && typeof socket.unref === "function") {
|
|
245
|
+
socket.unref();
|
|
246
|
+
}
|
|
247
|
+
resolve2(ws);
|
|
248
|
+
});
|
|
236
249
|
ws.once("error", reject);
|
|
237
250
|
});
|
|
238
251
|
}
|
|
@@ -500,27 +513,118 @@ async function ensurePageTarget(targetId) {
|
|
|
500
513
|
target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
|
|
501
514
|
} else if (typeof targetId === "string") {
|
|
502
515
|
target = targets.find((item) => item.id === targetId);
|
|
516
|
+
if (!target) {
|
|
517
|
+
const numericTargetId = Number(targetId);
|
|
518
|
+
if (!Number.isNaN(numericTargetId)) {
|
|
519
|
+
target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
503
522
|
}
|
|
504
523
|
target ??= targets[0];
|
|
505
524
|
connectionState.currentTargetId = target.id;
|
|
506
525
|
await attachTarget(target.id);
|
|
507
526
|
return target;
|
|
508
527
|
}
|
|
509
|
-
function
|
|
510
|
-
|
|
528
|
+
async function resolveBackendNodeIdByXPath(targetId, xpath) {
|
|
529
|
+
await sessionCommand(targetId, "DOM.getDocument", { depth: 0 });
|
|
530
|
+
const search = await sessionCommand(targetId, "DOM.performSearch", {
|
|
531
|
+
query: xpath,
|
|
532
|
+
includeUserAgentShadowDOM: true
|
|
533
|
+
});
|
|
534
|
+
try {
|
|
535
|
+
if (!search.resultCount) {
|
|
536
|
+
throw new Error(`Unknown ref xpath: ${xpath}`);
|
|
537
|
+
}
|
|
538
|
+
const { nodeIds } = await sessionCommand(targetId, "DOM.getSearchResults", {
|
|
539
|
+
searchId: search.searchId,
|
|
540
|
+
fromIndex: 0,
|
|
541
|
+
toIndex: search.resultCount
|
|
542
|
+
});
|
|
543
|
+
for (const nodeId of nodeIds) {
|
|
544
|
+
const described = await sessionCommand(targetId, "DOM.describeNode", {
|
|
545
|
+
nodeId
|
|
546
|
+
});
|
|
547
|
+
if (described.node.backendNodeId) {
|
|
548
|
+
return described.node.backendNodeId;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
throw new Error(`XPath resolved but no backend node id found: ${xpath}`);
|
|
552
|
+
} finally {
|
|
553
|
+
await sessionCommand(targetId, "DOM.discardSearchResults", { searchId: search.searchId }).catch(() => {
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
async function parseRef(ref) {
|
|
558
|
+
const targetId = connectionState?.currentTargetId ?? "";
|
|
559
|
+
let refs = connectionState?.refsByTarget.get(targetId) ?? {};
|
|
560
|
+
if (!refs[ref] && targetId) {
|
|
561
|
+
const persistedRefs = loadPersistedRefs(targetId);
|
|
562
|
+
if (persistedRefs) {
|
|
563
|
+
connectionState?.refsByTarget.set(targetId, persistedRefs);
|
|
564
|
+
refs = persistedRefs;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
511
567
|
const found = refs[ref];
|
|
512
|
-
if (!found
|
|
568
|
+
if (!found) {
|
|
513
569
|
throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
|
|
514
570
|
}
|
|
515
|
-
|
|
571
|
+
if (found.backendDOMNodeId) {
|
|
572
|
+
return found.backendDOMNodeId;
|
|
573
|
+
}
|
|
574
|
+
if (targetId && found.xpath) {
|
|
575
|
+
const backendDOMNodeId = await resolveBackendNodeIdByXPath(targetId, found.xpath);
|
|
576
|
+
found.backendDOMNodeId = backendDOMNodeId;
|
|
577
|
+
connectionState?.refsByTarget.set(targetId, refs);
|
|
578
|
+
const pageUrl = await evaluate(targetId, "location.href", true).catch(() => void 0);
|
|
579
|
+
if (pageUrl) {
|
|
580
|
+
persistRefs(targetId, pageUrl, refs);
|
|
581
|
+
}
|
|
582
|
+
return backendDOMNodeId;
|
|
583
|
+
}
|
|
584
|
+
throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
|
|
585
|
+
}
|
|
586
|
+
function getRefsFilePath(targetId) {
|
|
587
|
+
return path2.join(os2.tmpdir(), `bb-browser-refs-${targetId}.json`);
|
|
588
|
+
}
|
|
589
|
+
function loadPersistedRefs(targetId, expectedUrl) {
|
|
590
|
+
try {
|
|
591
|
+
const data = JSON.parse(readFileSync(getRefsFilePath(targetId), "utf-8"));
|
|
592
|
+
if (data.targetId !== targetId) return null;
|
|
593
|
+
if (expectedUrl !== void 0 && data.url !== expectedUrl) return null;
|
|
594
|
+
if (!data.refs || typeof data.refs !== "object") return null;
|
|
595
|
+
return data.refs;
|
|
596
|
+
} catch {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function persistRefs(targetId, url, refs) {
|
|
601
|
+
try {
|
|
602
|
+
writeFileSync(getRefsFilePath(targetId), JSON.stringify({ targetId, url, timestamp: Date.now(), refs }));
|
|
603
|
+
} catch {
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function clearPersistedRefs(targetId) {
|
|
607
|
+
try {
|
|
608
|
+
unlinkSync(getRefsFilePath(targetId));
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
516
611
|
}
|
|
517
612
|
function loadBuildDomTreeScript() {
|
|
518
613
|
const currentDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
519
614
|
const candidates = [
|
|
615
|
+
path2.resolve(currentDir, "./extension/buildDomTree.js"),
|
|
616
|
+
// npm installed: dist/cli.js → ../extension/buildDomTree.js
|
|
617
|
+
path2.resolve(currentDir, "../extension/buildDomTree.js"),
|
|
618
|
+
path2.resolve(currentDir, "../extension/dist/buildDomTree.js"),
|
|
619
|
+
path2.resolve(currentDir, "../packages/extension/public/buildDomTree.js"),
|
|
620
|
+
path2.resolve(currentDir, "../packages/extension/dist/buildDomTree.js"),
|
|
621
|
+
// dev mode: packages/cli/dist/ → ../../../extension/
|
|
520
622
|
path2.resolve(currentDir, "../../../extension/buildDomTree.js"),
|
|
623
|
+
path2.resolve(currentDir, "../../../extension/dist/buildDomTree.js"),
|
|
624
|
+
// dev mode: packages/cli/src/ → ../../extension/
|
|
521
625
|
path2.resolve(currentDir, "../../extension/buildDomTree.js"),
|
|
522
|
-
path2.resolve(currentDir, "../../../packages/extension/buildDomTree.js"),
|
|
523
|
-
path2.resolve(currentDir, "../../../extension/
|
|
626
|
+
path2.resolve(currentDir, "../../../packages/extension/dist/buildDomTree.js"),
|
|
627
|
+
path2.resolve(currentDir, "../../../packages/extension/public/buildDomTree.js")
|
|
524
628
|
];
|
|
525
629
|
for (const candidate of candidates) {
|
|
526
630
|
try {
|
|
@@ -548,8 +652,34 @@ async function resolveNode(targetId, backendNodeId) {
|
|
|
548
652
|
return result.nodeId;
|
|
549
653
|
}
|
|
550
654
|
async function focusNode(targetId, backendNodeId) {
|
|
551
|
-
|
|
552
|
-
|
|
655
|
+
await sessionCommand(targetId, "DOM.focus", { backendNodeId });
|
|
656
|
+
}
|
|
657
|
+
async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
|
|
658
|
+
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
659
|
+
await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
660
|
+
objectId: resolved.object.objectId,
|
|
661
|
+
functionDeclaration: `function(value, clearFirst) {
|
|
662
|
+
if (typeof this.focus === 'function') this.focus();
|
|
663
|
+
if (clearFirst && ('value' in this)) {
|
|
664
|
+
this.value = '';
|
|
665
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
666
|
+
}
|
|
667
|
+
if ('value' in this) {
|
|
668
|
+
this.value = clearFirst ? value : String(this.value ?? '') + value;
|
|
669
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
670
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
return false;
|
|
674
|
+
}`,
|
|
675
|
+
arguments: [
|
|
676
|
+
{ value: text },
|
|
677
|
+
{ value: clearFirst }
|
|
678
|
+
],
|
|
679
|
+
returnByValue: true
|
|
680
|
+
});
|
|
681
|
+
await focusNode(targetId, backendNodeId);
|
|
682
|
+
await sessionCommand(targetId, "Input.insertText", { text });
|
|
553
683
|
}
|
|
554
684
|
async function getNodeBox(targetId, backendNodeId) {
|
|
555
685
|
const result = await sessionCommand(targetId, "DOM.getBoxModel", {
|
|
@@ -583,15 +713,181 @@ async function getAttributeValue(targetId, backendNodeId, attribute) {
|
|
|
583
713
|
}
|
|
584
714
|
async function buildSnapshot(targetId, request) {
|
|
585
715
|
const script = loadBuildDomTreeScript();
|
|
586
|
-
const
|
|
716
|
+
const buildArgs = {
|
|
717
|
+
showHighlightElements: true,
|
|
718
|
+
focusHighlightIndex: -1,
|
|
719
|
+
viewportExpansion: -1,
|
|
720
|
+
debugMode: false,
|
|
721
|
+
startId: 0,
|
|
722
|
+
startHighlightIndex: 0
|
|
723
|
+
};
|
|
724
|
+
const expression = `(() => { ${script}; const fn = globalThis.buildDomTree ?? (typeof window !== 'undefined' ? window.buildDomTree : undefined); if (typeof fn !== 'function') { throw new Error('buildDomTree is not available after script injection'); } return fn(${JSON.stringify({
|
|
725
|
+
...buildArgs
|
|
726
|
+
})}); })()`;
|
|
727
|
+
const value = await evaluate(targetId, expression, true);
|
|
728
|
+
if (!value || !value.map || !value.rootId) {
|
|
729
|
+
const title = await evaluate(targetId, "document.title", true);
|
|
730
|
+
const pageUrl2 = await evaluate(targetId, "location.href", true);
|
|
731
|
+
const fallbackSnapshot = {
|
|
732
|
+
title,
|
|
733
|
+
url: pageUrl2,
|
|
734
|
+
lines: [title || pageUrl2],
|
|
735
|
+
refs: {}
|
|
736
|
+
};
|
|
737
|
+
connectionState?.refsByTarget.set(targetId, {});
|
|
738
|
+
persistRefs(targetId, pageUrl2, {});
|
|
739
|
+
return fallbackSnapshot;
|
|
740
|
+
}
|
|
741
|
+
const snapshot = convertBuildDomTreeResult(value, {
|
|
587
742
|
interactiveOnly: !!request.interactive,
|
|
588
743
|
compact: !!request.compact,
|
|
589
744
|
maxDepth: request.maxDepth,
|
|
590
745
|
selector: request.selector
|
|
591
|
-
})
|
|
592
|
-
const
|
|
593
|
-
connectionState?.refsByTarget.set(targetId,
|
|
594
|
-
|
|
746
|
+
});
|
|
747
|
+
const pageUrl = await evaluate(targetId, "location.href", true);
|
|
748
|
+
connectionState?.refsByTarget.set(targetId, snapshot.refs || {});
|
|
749
|
+
persistRefs(targetId, pageUrl, snapshot.refs || {});
|
|
750
|
+
return snapshot;
|
|
751
|
+
}
|
|
752
|
+
function convertBuildDomTreeResult(result, options) {
|
|
753
|
+
const { interactiveOnly, compact, maxDepth, selector } = options;
|
|
754
|
+
const { rootId, map } = result;
|
|
755
|
+
const refs = {};
|
|
756
|
+
const lines = [];
|
|
757
|
+
const getRole = (node) => {
|
|
758
|
+
const tagName = node.tagName.toLowerCase();
|
|
759
|
+
const role = node.attributes?.role;
|
|
760
|
+
if (role) return role;
|
|
761
|
+
const type = node.attributes?.type?.toLowerCase() || "text";
|
|
762
|
+
const inputRoleMap = {
|
|
763
|
+
text: "textbox",
|
|
764
|
+
password: "textbox",
|
|
765
|
+
email: "textbox",
|
|
766
|
+
url: "textbox",
|
|
767
|
+
tel: "textbox",
|
|
768
|
+
search: "searchbox",
|
|
769
|
+
number: "spinbutton",
|
|
770
|
+
range: "slider",
|
|
771
|
+
checkbox: "checkbox",
|
|
772
|
+
radio: "radio",
|
|
773
|
+
button: "button",
|
|
774
|
+
submit: "button",
|
|
775
|
+
reset: "button",
|
|
776
|
+
file: "button"
|
|
777
|
+
};
|
|
778
|
+
const roleMap = {
|
|
779
|
+
a: "link",
|
|
780
|
+
button: "button",
|
|
781
|
+
input: inputRoleMap[type] || "textbox",
|
|
782
|
+
select: "combobox",
|
|
783
|
+
textarea: "textbox",
|
|
784
|
+
img: "image",
|
|
785
|
+
nav: "navigation",
|
|
786
|
+
main: "main",
|
|
787
|
+
header: "banner",
|
|
788
|
+
footer: "contentinfo",
|
|
789
|
+
aside: "complementary",
|
|
790
|
+
form: "form",
|
|
791
|
+
table: "table",
|
|
792
|
+
ul: "list",
|
|
793
|
+
ol: "list",
|
|
794
|
+
li: "listitem",
|
|
795
|
+
h1: "heading",
|
|
796
|
+
h2: "heading",
|
|
797
|
+
h3: "heading",
|
|
798
|
+
h4: "heading",
|
|
799
|
+
h5: "heading",
|
|
800
|
+
h6: "heading",
|
|
801
|
+
dialog: "dialog",
|
|
802
|
+
article: "article",
|
|
803
|
+
section: "region",
|
|
804
|
+
label: "label",
|
|
805
|
+
details: "group",
|
|
806
|
+
summary: "button"
|
|
807
|
+
};
|
|
808
|
+
return roleMap[tagName] || tagName;
|
|
809
|
+
};
|
|
810
|
+
const collectTextContent = (node, nodeMap, depthLimit = 5) => {
|
|
811
|
+
const texts = [];
|
|
812
|
+
const visit = (nodeId, depth) => {
|
|
813
|
+
if (depth > depthLimit) return;
|
|
814
|
+
const currentNode = nodeMap[nodeId];
|
|
815
|
+
if (!currentNode) return;
|
|
816
|
+
if ("type" in currentNode && currentNode.type === "TEXT_NODE") {
|
|
817
|
+
const text = currentNode.text.trim();
|
|
818
|
+
if (text) texts.push(text);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
for (const childId of currentNode.children || []) visit(childId, depth + 1);
|
|
822
|
+
};
|
|
823
|
+
for (const childId of node.children || []) visit(childId, 0);
|
|
824
|
+
return texts.join(" ").trim();
|
|
825
|
+
};
|
|
826
|
+
const getName = (node) => {
|
|
827
|
+
const attrs = node.attributes || {};
|
|
828
|
+
return attrs["aria-label"] || attrs.title || attrs.placeholder || attrs.alt || attrs.value || collectTextContent(node, map) || attrs.name || void 0;
|
|
829
|
+
};
|
|
830
|
+
const truncateText = (text, length = 50) => text.length <= length ? text : `${text.slice(0, length - 3)}...`;
|
|
831
|
+
const selectorText = selector?.trim().toLowerCase();
|
|
832
|
+
const matchesSelector = (node, role, name) => {
|
|
833
|
+
if (!selectorText) return true;
|
|
834
|
+
const haystack = [node.tagName, role, name, node.xpath || "", ...Object.values(node.attributes || {})].join(" ").toLowerCase();
|
|
835
|
+
return haystack.includes(selectorText);
|
|
836
|
+
};
|
|
837
|
+
if (interactiveOnly) {
|
|
838
|
+
const interactiveNodes = Object.entries(map).filter(([, node]) => !("type" in node) && node.highlightIndex !== void 0 && node.highlightIndex !== null).map(([id, node]) => ({ id, node })).sort((a, b) => (a.node.highlightIndex ?? 0) - (b.node.highlightIndex ?? 0));
|
|
839
|
+
for (const { node } of interactiveNodes) {
|
|
840
|
+
const refId = String(node.highlightIndex);
|
|
841
|
+
const role = getRole(node);
|
|
842
|
+
const name = getName(node);
|
|
843
|
+
if (!matchesSelector(node, role, name)) continue;
|
|
844
|
+
let line = `${role} [ref=${refId}]`;
|
|
845
|
+
if (name) line += ` ${JSON.stringify(truncateText(name))}`;
|
|
846
|
+
lines.push(line);
|
|
847
|
+
refs[refId] = {
|
|
848
|
+
xpath: node.xpath || "",
|
|
849
|
+
role,
|
|
850
|
+
name,
|
|
851
|
+
tagName: node.tagName.toLowerCase()
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return { snapshot: lines.join("\n"), refs };
|
|
855
|
+
}
|
|
856
|
+
const walk = (nodeId, depth) => {
|
|
857
|
+
if (maxDepth !== void 0 && depth > maxDepth) return;
|
|
858
|
+
const node = map[nodeId];
|
|
859
|
+
if (!node) return;
|
|
860
|
+
if ("type" in node && node.type === "TEXT_NODE") {
|
|
861
|
+
const text = node.text.trim();
|
|
862
|
+
if (!text) return;
|
|
863
|
+
lines.push(`${" ".repeat(depth)}- text ${JSON.stringify(truncateText(text, compact ? 80 : 120))}`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const role = getRole(node);
|
|
867
|
+
const name = getName(node);
|
|
868
|
+
if (!matchesSelector(node, role, name)) {
|
|
869
|
+
for (const childId of node.children || []) walk(childId, depth + 1);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const indent = " ".repeat(depth);
|
|
873
|
+
const refId = node.highlightIndex !== void 0 && node.highlightIndex !== null ? String(node.highlightIndex) : null;
|
|
874
|
+
let line = `${indent}- ${role}`;
|
|
875
|
+
if (refId) line += ` [ref=${refId}]`;
|
|
876
|
+
if (name) line += ` ${JSON.stringify(truncateText(name, compact ? 50 : 80))}`;
|
|
877
|
+
if (!compact) line += ` <${node.tagName.toLowerCase()}>`;
|
|
878
|
+
lines.push(line);
|
|
879
|
+
if (refId) {
|
|
880
|
+
refs[refId] = {
|
|
881
|
+
xpath: node.xpath || "",
|
|
882
|
+
role,
|
|
883
|
+
name,
|
|
884
|
+
tagName: node.tagName.toLowerCase()
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
for (const childId of node.children || []) walk(childId, depth + 1);
|
|
888
|
+
};
|
|
889
|
+
walk(rootId, 0);
|
|
890
|
+
return { snapshot: lines.join("\n"), refs };
|
|
595
891
|
}
|
|
596
892
|
function ok(id, data) {
|
|
597
893
|
return { id, success: true, data };
|
|
@@ -635,10 +931,12 @@ async function dispatchRequest(request) {
|
|
|
635
931
|
if (request.tabId === void 0) {
|
|
636
932
|
const created = await browserCommand("Target.createTarget", { url: request.url });
|
|
637
933
|
const newTarget = await ensurePageTarget(created.targetId);
|
|
638
|
-
return ok(request.id, { url: request.url, tabId:
|
|
934
|
+
return ok(request.id, { url: request.url, tabId: newTarget.id });
|
|
639
935
|
}
|
|
640
936
|
await pageCommand(target.id, "Page.navigate", { url: request.url });
|
|
641
|
-
|
|
937
|
+
connectionState?.refsByTarget.delete(target.id);
|
|
938
|
+
clearPersistedRefs(target.id);
|
|
939
|
+
return ok(request.id, { url: request.url, title: target.title, tabId: target.id });
|
|
642
940
|
}
|
|
643
941
|
case "snapshot": {
|
|
644
942
|
const snapshotData = await buildSnapshot(target.id, request);
|
|
@@ -647,7 +945,7 @@ async function dispatchRequest(request) {
|
|
|
647
945
|
case "click":
|
|
648
946
|
case "hover": {
|
|
649
947
|
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
650
|
-
const backendNodeId = parseRef(request.ref);
|
|
948
|
+
const backendNodeId = await parseRef(request.ref);
|
|
651
949
|
const point = await getNodeBox(target.id, backendNodeId);
|
|
652
950
|
await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
|
|
653
951
|
if (request.action === "click") await mouseClick(target.id, point.x, point.y);
|
|
@@ -657,18 +955,14 @@ async function dispatchRequest(request) {
|
|
|
657
955
|
case "type": {
|
|
658
956
|
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
659
957
|
if (request.text == null) return fail(request.id, "Missing text parameter");
|
|
660
|
-
const backendNodeId = parseRef(request.ref);
|
|
661
|
-
await
|
|
662
|
-
if (request.action === "fill") {
|
|
663
|
-
await evaluate(target.id, `document.activeElement && ((document.activeElement.value = ''), document.activeElement.dispatchEvent(new Event('input', { bubbles: true })))`);
|
|
664
|
-
}
|
|
665
|
-
await sessionCommand(target.id, "Input.insertText", { text: request.text });
|
|
958
|
+
const backendNodeId = await parseRef(request.ref);
|
|
959
|
+
await insertTextIntoNode(target.id, backendNodeId, request.text, request.action === "fill");
|
|
666
960
|
return ok(request.id, { value: request.text });
|
|
667
961
|
}
|
|
668
962
|
case "check":
|
|
669
963
|
case "uncheck": {
|
|
670
964
|
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
671
|
-
const backendNodeId = parseRef(request.ref);
|
|
965
|
+
const backendNodeId = await parseRef(request.ref);
|
|
672
966
|
const desired = request.action === "check";
|
|
673
967
|
const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
|
|
674
968
|
await sessionCommand(target.id, "Runtime.callFunctionOn", {
|
|
@@ -679,7 +973,7 @@ async function dispatchRequest(request) {
|
|
|
679
973
|
}
|
|
680
974
|
case "select": {
|
|
681
975
|
if (!request.ref || request.value == null) return fail(request.id, "Missing ref or value parameter");
|
|
682
|
-
const backendNodeId = parseRef(request.ref);
|
|
976
|
+
const backendNodeId = await parseRef(request.ref);
|
|
683
977
|
const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
|
|
684
978
|
await sessionCommand(target.id, "Runtime.callFunctionOn", {
|
|
685
979
|
objectId: resolved.object.objectId,
|
|
@@ -689,7 +983,7 @@ async function dispatchRequest(request) {
|
|
|
689
983
|
}
|
|
690
984
|
case "get": {
|
|
691
985
|
if (!request.ref || !request.attribute) return fail(request.id, "Missing ref or attribute parameter");
|
|
692
|
-
const value = await getAttributeValue(target.id, parseRef(request.ref), request.attribute);
|
|
986
|
+
const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
|
|
693
987
|
return ok(request.id, { value });
|
|
694
988
|
}
|
|
695
989
|
case "screenshot": {
|
|
@@ -698,6 +992,8 @@ async function dispatchRequest(request) {
|
|
|
698
992
|
}
|
|
699
993
|
case "close": {
|
|
700
994
|
await browserCommand("Target.closeTarget", { targetId: target.id });
|
|
995
|
+
connectionState?.refsByTarget.delete(target.id);
|
|
996
|
+
clearPersistedRefs(target.id);
|
|
701
997
|
return ok(request.id, {});
|
|
702
998
|
}
|
|
703
999
|
case "wait": {
|
|
@@ -736,12 +1032,12 @@ async function dispatchRequest(request) {
|
|
|
736
1032
|
return ok(request.id, { result });
|
|
737
1033
|
}
|
|
738
1034
|
case "tab_list": {
|
|
739
|
-
const tabs = (await getTargets()).filter((item) => item.type === "page").map((item, index) => ({ index, url: item.url, title: item.title, active: item.id === connectionState?.currentTargetId || !connectionState?.currentTargetId && index === 0, tabId:
|
|
1035
|
+
const tabs = (await getTargets()).filter((item) => item.type === "page").map((item, index) => ({ index, url: item.url, title: item.title, active: item.id === connectionState?.currentTargetId || !connectionState?.currentTargetId && index === 0, tabId: item.id }));
|
|
740
1036
|
return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
|
|
741
1037
|
}
|
|
742
1038
|
case "tab_new": {
|
|
743
1039
|
const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank" });
|
|
744
|
-
return ok(request.id, { tabId:
|
|
1040
|
+
return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
|
|
745
1041
|
}
|
|
746
1042
|
case "tab_select": {
|
|
747
1043
|
const tabs = (await getTargets()).filter((item) => item.type === "page");
|
|
@@ -749,14 +1045,16 @@ async function dispatchRequest(request) {
|
|
|
749
1045
|
if (!selected) return fail(request.id, "Tab not found");
|
|
750
1046
|
connectionState.currentTargetId = selected.id;
|
|
751
1047
|
await attachTarget(selected.id);
|
|
752
|
-
return ok(request.id, { tabId:
|
|
1048
|
+
return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
|
|
753
1049
|
}
|
|
754
1050
|
case "tab_close": {
|
|
755
1051
|
const tabs = (await getTargets()).filter((item) => item.type === "page");
|
|
756
1052
|
const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
|
|
757
1053
|
if (!selected) return fail(request.id, "Tab not found");
|
|
758
1054
|
await browserCommand("Target.closeTarget", { targetId: selected.id });
|
|
759
|
-
|
|
1055
|
+
connectionState?.refsByTarget.delete(selected.id);
|
|
1056
|
+
clearPersistedRefs(selected.id);
|
|
1057
|
+
return ok(request.id, { tabId: selected.id });
|
|
760
1058
|
}
|
|
761
1059
|
case "frame": {
|
|
762
1060
|
if (!request.selector) return fail(request.id, "Missing selector parameter");
|
|
@@ -908,7 +1206,7 @@ async function ensureDaemonRunning() {
|
|
|
908
1206
|
}
|
|
909
1207
|
|
|
910
1208
|
// packages/cli/src/history-sqlite.ts
|
|
911
|
-
import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
|
|
1209
|
+
import { copyFileSync, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
912
1210
|
import { execSync as execSync2 } from "child_process";
|
|
913
1211
|
import { homedir, tmpdir } from "os";
|
|
914
1212
|
import { join } from "path";
|
|
@@ -966,7 +1264,7 @@ function runHistoryQuery(sql, mapRow) {
|
|
|
966
1264
|
return [];
|
|
967
1265
|
} finally {
|
|
968
1266
|
try {
|
|
969
|
-
|
|
1267
|
+
unlinkSync2(tmpPath);
|
|
970
1268
|
} catch {
|
|
971
1269
|
}
|
|
972
1270
|
}
|
|
@@ -1957,11 +2255,11 @@ async function getCommand(attribute, ref, options = {}) {
|
|
|
1957
2255
|
// packages/cli/src/commands/screenshot.ts
|
|
1958
2256
|
import fs from "fs";
|
|
1959
2257
|
import path3 from "path";
|
|
1960
|
-
import
|
|
2258
|
+
import os3 from "os";
|
|
1961
2259
|
function getDefaultPath() {
|
|
1962
2260
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1963
2261
|
const filename = `bb-screenshot-${timestamp}.png`;
|
|
1964
|
-
return path3.join(
|
|
2262
|
+
return path3.join(os3.tmpdir(), filename);
|
|
1965
2263
|
}
|
|
1966
2264
|
function saveBase64Image(dataUrl, filePath) {
|
|
1967
2265
|
const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
@@ -2507,6 +2805,9 @@ function parseTabSubcommand(args, rawArgv) {
|
|
|
2507
2805
|
return { action: "tab_list" };
|
|
2508
2806
|
}
|
|
2509
2807
|
const first = args[0];
|
|
2808
|
+
if (first === "list") {
|
|
2809
|
+
return { action: "tab_list" };
|
|
2810
|
+
}
|
|
2510
2811
|
if (first === "new") {
|
|
2511
2812
|
return { action: "tab_new", url: args[1] };
|
|
2512
2813
|
}
|
|
@@ -3035,9 +3336,9 @@ async function fetchCommand(url, options = {}) {
|
|
|
3035
3336
|
throw new Error(`Fetch error: ${result.error}`);
|
|
3036
3337
|
}
|
|
3037
3338
|
if (options.output) {
|
|
3038
|
-
const { writeFileSync } = await import("fs");
|
|
3339
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
3039
3340
|
const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
|
|
3040
|
-
|
|
3341
|
+
writeFileSync2(options.output, content, "utf-8");
|
|
3041
3342
|
console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
|
|
3042
3343
|
return;
|
|
3043
3344
|
}
|
|
@@ -3098,7 +3399,7 @@ async function historyCommand(subCommand, options = {}) {
|
|
|
3098
3399
|
}
|
|
3099
3400
|
|
|
3100
3401
|
// packages/cli/src/index.ts
|
|
3101
|
-
var VERSION = "0.8.
|
|
3402
|
+
var VERSION = "0.8.2";
|
|
3102
3403
|
var HELP_TEXT = `
|
|
3103
3404
|
bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
3104
3405
|
|
|
@@ -3714,5 +4015,5 @@ Full guide: https://github.com/epiral/bb-sites/blob/main/SKILL.md`);
|
|
|
3714
4015
|
process.exit(1);
|
|
3715
4016
|
}
|
|
3716
4017
|
}
|
|
3717
|
-
main();
|
|
4018
|
+
main().then(() => process.exit(0));
|
|
3718
4019
|
//# sourceMappingURL=cli.js.map
|