bb-browser 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -130
- package/README.zh-CN.md +169 -0
- package/dist/chunk-H7M4J4CW.js +53 -0
- package/dist/chunk-H7M4J4CW.js.map +1 -0
- package/dist/cli.js +585 -20
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/mcp.js +21243 -0
- package/dist/mcp.js.map +1 -0
- package/extension/dist/background.js +700 -543
- package/extension/dist/background.js.map +1 -1
- package/extension/dist/manifest.json +1 -1
- package/package.json +12 -7
- package/dist/chunk-TUO443YI.js +0 -21
- package/dist/chunk-TUO443YI.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
COMMAND_TIMEOUT,
|
|
4
4
|
DAEMON_BASE_URL,
|
|
5
5
|
generateId
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-H7M4J4CW.js";
|
|
7
7
|
|
|
8
8
|
// packages/cli/src/client.ts
|
|
9
9
|
async function sendCommand(request) {
|
|
@@ -600,6 +600,7 @@ async function scrollCommand(direction, pixels, options = {}) {
|
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
// packages/cli/src/commands/daemon.ts
|
|
603
|
+
import { spawn as spawn2 } from "child_process";
|
|
603
604
|
async function daemonCommand(options = {}) {
|
|
604
605
|
if (await isDaemonRunning()) {
|
|
605
606
|
if (options.json) {
|
|
@@ -609,23 +610,29 @@ async function daemonCommand(options = {}) {
|
|
|
609
610
|
}
|
|
610
611
|
return;
|
|
611
612
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
} else {
|
|
617
|
-
console.log("Daemon \u542F\u52A8\u4E2D...");
|
|
618
|
-
}
|
|
619
|
-
await startDaemon();
|
|
620
|
-
} catch (error) {
|
|
621
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
622
|
-
if (options.json) {
|
|
623
|
-
console.log(JSON.stringify({ success: false, error: message }));
|
|
624
|
-
} else {
|
|
625
|
-
console.error(`\u542F\u52A8\u5931\u8D25: ${message}`);
|
|
626
|
-
}
|
|
627
|
-
process.exit(1);
|
|
613
|
+
const daemonPath = getDaemonPath();
|
|
614
|
+
const args = [daemonPath];
|
|
615
|
+
if (options.host) {
|
|
616
|
+
args.push("--host", options.host);
|
|
628
617
|
}
|
|
618
|
+
if (options.json) {
|
|
619
|
+
console.log(JSON.stringify({ success: true, message: "Daemon \u542F\u52A8\u4E2D..." }));
|
|
620
|
+
} else {
|
|
621
|
+
console.log("Daemon \u542F\u52A8\u4E2D...");
|
|
622
|
+
}
|
|
623
|
+
await new Promise((resolve2, reject) => {
|
|
624
|
+
const child = spawn2(process.execPath, args, {
|
|
625
|
+
stdio: "inherit"
|
|
626
|
+
});
|
|
627
|
+
child.on("exit", (code) => {
|
|
628
|
+
if (code && code !== 0) {
|
|
629
|
+
reject(new Error(`Daemon exited with code ${code}`));
|
|
630
|
+
} else {
|
|
631
|
+
resolve2();
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
child.on("error", reject);
|
|
635
|
+
});
|
|
629
636
|
}
|
|
630
637
|
async function stopCommand(options = {}) {
|
|
631
638
|
if (!await isDaemonRunning()) {
|
|
@@ -1200,6 +1207,7 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
|
|
|
1200
1207
|
abort: options.abort,
|
|
1201
1208
|
body: options.body
|
|
1202
1209
|
} : void 0,
|
|
1210
|
+
withBody: subCommand === "requests" ? options.withBody : void 0,
|
|
1203
1211
|
tabId: options.tabId
|
|
1204
1212
|
});
|
|
1205
1213
|
if (options.json) {
|
|
@@ -1223,6 +1231,22 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
|
|
|
1223
1231
|
const status = req.failed ? `FAILED (${req.failureReason})` : req.status ? `${req.status} ${req.statusText || ""}` : "pending";
|
|
1224
1232
|
console.log(`${req.method} ${req.url}`);
|
|
1225
1233
|
console.log(` \u7C7B\u578B: ${req.type}, \u72B6\u6001: ${status}`);
|
|
1234
|
+
if (options.withBody) {
|
|
1235
|
+
const requestHeaderCount = req.requestHeaders ? Object.keys(req.requestHeaders).length : 0;
|
|
1236
|
+
const responseHeaderCount = req.responseHeaders ? Object.keys(req.responseHeaders).length : 0;
|
|
1237
|
+
console.log(` \u8BF7\u6C42\u5934: ${requestHeaderCount}, \u54CD\u5E94\u5934: ${responseHeaderCount}`);
|
|
1238
|
+
if (req.requestBody !== void 0) {
|
|
1239
|
+
const preview = req.requestBody.length > 200 ? `${req.requestBody.slice(0, 200)}...` : req.requestBody;
|
|
1240
|
+
console.log(` \u8BF7\u6C42\u4F53: ${preview}`);
|
|
1241
|
+
}
|
|
1242
|
+
if (req.responseBody !== void 0) {
|
|
1243
|
+
const preview = req.responseBody.length > 200 ? `${req.responseBody.slice(0, 200)}...` : req.responseBody;
|
|
1244
|
+
console.log(` \u54CD\u5E94\u4F53: ${preview}`);
|
|
1245
|
+
}
|
|
1246
|
+
if (req.bodyError) {
|
|
1247
|
+
console.log(` Body\u9519\u8BEF: ${req.bodyError}`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1226
1250
|
console.log("");
|
|
1227
1251
|
}
|
|
1228
1252
|
}
|
|
@@ -1424,6 +1448,500 @@ async function traceCommand(subCommand, options = {}) {
|
|
|
1424
1448
|
}
|
|
1425
1449
|
}
|
|
1426
1450
|
|
|
1451
|
+
// packages/cli/src/commands/fetch.ts
|
|
1452
|
+
function matchTabOrigin(tabUrl, targetHostname) {
|
|
1453
|
+
try {
|
|
1454
|
+
const tabHostname = new URL(tabUrl).hostname;
|
|
1455
|
+
return tabHostname === targetHostname || tabHostname.endsWith("." + targetHostname);
|
|
1456
|
+
} catch {
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
async function ensureTabForOrigin(origin, hostname) {
|
|
1461
|
+
const listReq = { id: generateId(), action: "tab_list" };
|
|
1462
|
+
const listResp = await sendCommand(listReq);
|
|
1463
|
+
if (listResp.success && listResp.data?.tabs) {
|
|
1464
|
+
const matchingTab = listResp.data.tabs.find(
|
|
1465
|
+
(tab) => matchTabOrigin(tab.url, hostname)
|
|
1466
|
+
);
|
|
1467
|
+
if (matchingTab) {
|
|
1468
|
+
return matchingTab.tabId;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
|
|
1472
|
+
if (!newResp.success) {
|
|
1473
|
+
throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
|
|
1474
|
+
}
|
|
1475
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1476
|
+
return newResp.data?.tabId;
|
|
1477
|
+
}
|
|
1478
|
+
function buildFetchScript(url, options) {
|
|
1479
|
+
const method = (options.method || "GET").toUpperCase();
|
|
1480
|
+
const hasBody = options.body && method !== "GET" && method !== "HEAD";
|
|
1481
|
+
let headersExpr = "{}";
|
|
1482
|
+
if (options.headers) {
|
|
1483
|
+
try {
|
|
1484
|
+
JSON.parse(options.headers);
|
|
1485
|
+
headersExpr = options.headers;
|
|
1486
|
+
} catch {
|
|
1487
|
+
throw new Error(`--headers must be valid JSON. Got: ${options.headers}`);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return `(async () => {
|
|
1491
|
+
try {
|
|
1492
|
+
const resp = await fetch(${JSON.stringify(url)}, {
|
|
1493
|
+
method: ${JSON.stringify(method)},
|
|
1494
|
+
credentials: 'include',
|
|
1495
|
+
headers: ${headersExpr}${hasBody ? `,
|
|
1496
|
+
body: ${JSON.stringify(options.body)}` : ""}
|
|
1497
|
+
});
|
|
1498
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
1499
|
+
let body;
|
|
1500
|
+
if (contentType.includes('application/json') && resp.status !== 204) {
|
|
1501
|
+
try { body = await resp.json(); } catch { body = await resp.text(); }
|
|
1502
|
+
} else {
|
|
1503
|
+
body = await resp.text();
|
|
1504
|
+
}
|
|
1505
|
+
return JSON.stringify({
|
|
1506
|
+
status: resp.status,
|
|
1507
|
+
contentType,
|
|
1508
|
+
body
|
|
1509
|
+
});
|
|
1510
|
+
} catch (e) {
|
|
1511
|
+
return JSON.stringify({ error: e.message });
|
|
1512
|
+
}
|
|
1513
|
+
})()`;
|
|
1514
|
+
}
|
|
1515
|
+
async function fetchCommand(url, options = {}) {
|
|
1516
|
+
if (!url) {
|
|
1517
|
+
throw new Error(
|
|
1518
|
+
"\u7F3A\u5C11 URL \u53C2\u6570\n \u7528\u6CD5: bb-browser fetch <url> [--json] [--method POST] [--body '{...}']\n \u793A\u4F8B: bb-browser fetch https://www.reddit.com/api/me.json --json"
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
await ensureDaemonRunning();
|
|
1522
|
+
const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
|
|
1523
|
+
let targetTabId = options.tabId;
|
|
1524
|
+
if (isAbsolute) {
|
|
1525
|
+
let origin;
|
|
1526
|
+
let hostname;
|
|
1527
|
+
try {
|
|
1528
|
+
const parsed = new URL(url);
|
|
1529
|
+
origin = parsed.origin;
|
|
1530
|
+
hostname = parsed.hostname;
|
|
1531
|
+
} catch {
|
|
1532
|
+
throw new Error(`\u65E0\u6548\u7684 URL: ${url}`);
|
|
1533
|
+
}
|
|
1534
|
+
if (!targetTabId) {
|
|
1535
|
+
targetTabId = await ensureTabForOrigin(origin, hostname);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
const script = buildFetchScript(url, options);
|
|
1539
|
+
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
1540
|
+
const evalResp = await sendCommand(evalReq);
|
|
1541
|
+
if (!evalResp.success) {
|
|
1542
|
+
throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
|
|
1543
|
+
}
|
|
1544
|
+
const rawResult = evalResp.data?.result;
|
|
1545
|
+
if (rawResult === void 0 || rawResult === null) {
|
|
1546
|
+
throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
|
|
1547
|
+
}
|
|
1548
|
+
let result;
|
|
1549
|
+
try {
|
|
1550
|
+
result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
|
|
1551
|
+
} catch {
|
|
1552
|
+
console.log(rawResult);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
if (result.error) {
|
|
1556
|
+
throw new Error(`Fetch error: ${result.error}`);
|
|
1557
|
+
}
|
|
1558
|
+
if (options.output) {
|
|
1559
|
+
const { writeFileSync } = await import("fs");
|
|
1560
|
+
const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
|
|
1561
|
+
writeFileSync(options.output, content, "utf-8");
|
|
1562
|
+
console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (typeof result.body === "object") {
|
|
1566
|
+
console.log(JSON.stringify(result.body, null, 2));
|
|
1567
|
+
} else {
|
|
1568
|
+
console.log(result.body);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// packages/cli/src/commands/site.ts
|
|
1573
|
+
import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
1574
|
+
import { join, relative } from "path";
|
|
1575
|
+
import { homedir } from "os";
|
|
1576
|
+
import { execSync } from "child_process";
|
|
1577
|
+
var BB_DIR = join(homedir(), ".bb-browser");
|
|
1578
|
+
var LOCAL_SITES_DIR = join(BB_DIR, "sites");
|
|
1579
|
+
var COMMUNITY_SITES_DIR = join(BB_DIR, "bb-sites");
|
|
1580
|
+
var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
|
|
1581
|
+
function parseSiteMeta(filePath, source) {
|
|
1582
|
+
let content;
|
|
1583
|
+
try {
|
|
1584
|
+
content = readFileSync(filePath, "utf-8");
|
|
1585
|
+
} catch {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
const sitesDir = source === "local" ? LOCAL_SITES_DIR : COMMUNITY_SITES_DIR;
|
|
1589
|
+
const relPath = relative(sitesDir, filePath);
|
|
1590
|
+
const defaultName = relPath.replace(/\.js$/, "").replace(/\\/g, "/");
|
|
1591
|
+
const metaMatch = content.match(/\/\*\s*@meta\s*\n([\s\S]*?)\*\//);
|
|
1592
|
+
if (metaMatch) {
|
|
1593
|
+
try {
|
|
1594
|
+
const metaJson = JSON.parse(metaMatch[1]);
|
|
1595
|
+
return {
|
|
1596
|
+
name: metaJson.name || defaultName,
|
|
1597
|
+
description: metaJson.description || "",
|
|
1598
|
+
domain: metaJson.domain || "",
|
|
1599
|
+
args: metaJson.args || {},
|
|
1600
|
+
capabilities: metaJson.capabilities,
|
|
1601
|
+
readOnly: metaJson.readOnly,
|
|
1602
|
+
example: metaJson.example,
|
|
1603
|
+
filePath,
|
|
1604
|
+
source
|
|
1605
|
+
};
|
|
1606
|
+
} catch {
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
const meta = {
|
|
1610
|
+
name: defaultName,
|
|
1611
|
+
description: "",
|
|
1612
|
+
domain: "",
|
|
1613
|
+
args: {},
|
|
1614
|
+
filePath,
|
|
1615
|
+
source
|
|
1616
|
+
};
|
|
1617
|
+
const tagPattern = /\/\/\s*@(\w+)[ \t]+(.*)/g;
|
|
1618
|
+
let match;
|
|
1619
|
+
while ((match = tagPattern.exec(content)) !== null) {
|
|
1620
|
+
const [, key, value] = match;
|
|
1621
|
+
switch (key) {
|
|
1622
|
+
case "name":
|
|
1623
|
+
meta.name = value.trim();
|
|
1624
|
+
break;
|
|
1625
|
+
case "description":
|
|
1626
|
+
meta.description = value.trim();
|
|
1627
|
+
break;
|
|
1628
|
+
case "domain":
|
|
1629
|
+
meta.domain = value.trim();
|
|
1630
|
+
break;
|
|
1631
|
+
case "args":
|
|
1632
|
+
for (const arg of value.trim().split(/[,\s]+/).filter(Boolean)) {
|
|
1633
|
+
meta.args[arg] = { required: true };
|
|
1634
|
+
}
|
|
1635
|
+
break;
|
|
1636
|
+
case "example":
|
|
1637
|
+
meta.example = value.trim();
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return meta;
|
|
1642
|
+
}
|
|
1643
|
+
function scanSites(dir, source) {
|
|
1644
|
+
if (!existsSync2(dir)) return [];
|
|
1645
|
+
const sites = [];
|
|
1646
|
+
function walk(currentDir) {
|
|
1647
|
+
let entries;
|
|
1648
|
+
try {
|
|
1649
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
1650
|
+
} catch {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
for (const entry of entries) {
|
|
1654
|
+
const fullPath = join(currentDir, entry.name);
|
|
1655
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
1656
|
+
walk(fullPath);
|
|
1657
|
+
} else if (entry.isFile() && entry.name.endsWith(".js")) {
|
|
1658
|
+
const meta = parseSiteMeta(fullPath, source);
|
|
1659
|
+
if (meta) sites.push(meta);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
walk(dir);
|
|
1664
|
+
return sites;
|
|
1665
|
+
}
|
|
1666
|
+
function getAllSites() {
|
|
1667
|
+
const community = scanSites(COMMUNITY_SITES_DIR, "community");
|
|
1668
|
+
const local = scanSites(LOCAL_SITES_DIR, "local");
|
|
1669
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1670
|
+
for (const s of community) byName.set(s.name, s);
|
|
1671
|
+
for (const s of local) byName.set(s.name, s);
|
|
1672
|
+
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
1673
|
+
}
|
|
1674
|
+
function matchTabOrigin2(tabUrl, domain) {
|
|
1675
|
+
try {
|
|
1676
|
+
const tabOrigin = new URL(tabUrl).hostname;
|
|
1677
|
+
return tabOrigin === domain || tabOrigin.endsWith("." + domain);
|
|
1678
|
+
} catch {
|
|
1679
|
+
return false;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
function siteList(options) {
|
|
1683
|
+
const sites = getAllSites();
|
|
1684
|
+
if (sites.length === 0) {
|
|
1685
|
+
console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
|
|
1686
|
+
console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
|
|
1687
|
+
console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (options.json) {
|
|
1691
|
+
console.log(JSON.stringify(sites.map((s) => ({
|
|
1692
|
+
name: s.name,
|
|
1693
|
+
description: s.description,
|
|
1694
|
+
domain: s.domain,
|
|
1695
|
+
args: s.args,
|
|
1696
|
+
source: s.source
|
|
1697
|
+
})), null, 2));
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const s of sites) {
|
|
1702
|
+
const platform = s.name.split("/")[0];
|
|
1703
|
+
if (!groups.has(platform)) groups.set(platform, []);
|
|
1704
|
+
groups.get(platform).push(s);
|
|
1705
|
+
}
|
|
1706
|
+
for (const [platform, items] of groups) {
|
|
1707
|
+
console.log(`
|
|
1708
|
+
${platform}/`);
|
|
1709
|
+
for (const s of items) {
|
|
1710
|
+
const cmd = s.name.split("/").slice(1).join("/");
|
|
1711
|
+
const src = s.source === "local" ? " (local)" : "";
|
|
1712
|
+
const desc = s.description ? ` - ${s.description}` : "";
|
|
1713
|
+
console.log(` ${cmd.padEnd(20)}${desc}${src}`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
console.log();
|
|
1717
|
+
}
|
|
1718
|
+
function siteSearch(query, options) {
|
|
1719
|
+
const sites = getAllSites();
|
|
1720
|
+
const q = query.toLowerCase();
|
|
1721
|
+
const matches = sites.filter(
|
|
1722
|
+
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
|
|
1723
|
+
);
|
|
1724
|
+
if (matches.length === 0) {
|
|
1725
|
+
console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
|
|
1726
|
+
console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (options.json) {
|
|
1730
|
+
console.log(JSON.stringify(matches.map((s) => ({
|
|
1731
|
+
name: s.name,
|
|
1732
|
+
description: s.description,
|
|
1733
|
+
domain: s.domain,
|
|
1734
|
+
source: s.source
|
|
1735
|
+
})), null, 2));
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
for (const s of matches) {
|
|
1739
|
+
const src = s.source === "local" ? " (local)" : "";
|
|
1740
|
+
console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
function siteUpdate() {
|
|
1744
|
+
mkdirSync(BB_DIR, { recursive: true });
|
|
1745
|
+
if (existsSync2(join(COMMUNITY_SITES_DIR, ".git"))) {
|
|
1746
|
+
console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
|
|
1747
|
+
try {
|
|
1748
|
+
execSync("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
|
|
1749
|
+
console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
|
|
1750
|
+
} catch (e) {
|
|
1751
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
1752
|
+
console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
|
|
1753
|
+
process.exit(1);
|
|
1754
|
+
}
|
|
1755
|
+
} else {
|
|
1756
|
+
console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
|
|
1757
|
+
try {
|
|
1758
|
+
execSync(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
|
|
1759
|
+
console.log("\u514B\u9686\u5B8C\u6210\u3002");
|
|
1760
|
+
} catch (e) {
|
|
1761
|
+
console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
1762
|
+
console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
|
|
1763
|
+
process.exit(1);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
const sites = scanSites(COMMUNITY_SITES_DIR, "community");
|
|
1767
|
+
console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
|
|
1768
|
+
}
|
|
1769
|
+
async function siteRun(name, args, options) {
|
|
1770
|
+
const sites = getAllSites();
|
|
1771
|
+
const site = sites.find((s) => s.name === name);
|
|
1772
|
+
if (!site) {
|
|
1773
|
+
const fuzzy = sites.filter((s) => s.name.includes(name));
|
|
1774
|
+
console.error(`[error] site: "${name}" not found.`);
|
|
1775
|
+
if (fuzzy.length > 0) {
|
|
1776
|
+
console.error(" Did you mean:");
|
|
1777
|
+
for (const s of fuzzy.slice(0, 5)) {
|
|
1778
|
+
console.error(` bb-browser site ${s.name}`);
|
|
1779
|
+
}
|
|
1780
|
+
} else {
|
|
1781
|
+
console.error(" Try: bb-browser site list");
|
|
1782
|
+
console.error(" Or: bb-browser site update");
|
|
1783
|
+
}
|
|
1784
|
+
process.exit(1);
|
|
1785
|
+
}
|
|
1786
|
+
const argNames = Object.keys(site.args);
|
|
1787
|
+
const argMap = {};
|
|
1788
|
+
const positionalArgs = [];
|
|
1789
|
+
for (let i = 0; i < args.length; i++) {
|
|
1790
|
+
if (args[i].startsWith("--")) {
|
|
1791
|
+
const flagName = args[i].slice(2);
|
|
1792
|
+
if (flagName in site.args && args[i + 1]) {
|
|
1793
|
+
argMap[flagName] = args[i + 1];
|
|
1794
|
+
i++;
|
|
1795
|
+
}
|
|
1796
|
+
} else {
|
|
1797
|
+
positionalArgs.push(args[i]);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
let posIdx = 0;
|
|
1801
|
+
for (const argName of argNames) {
|
|
1802
|
+
if (!argMap[argName] && posIdx < positionalArgs.length) {
|
|
1803
|
+
argMap[argName] = positionalArgs[posIdx++];
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
for (const [argName, argDef] of Object.entries(site.args)) {
|
|
1807
|
+
if (argDef.required && !argMap[argName]) {
|
|
1808
|
+
console.error(`[error] site ${name}: missing required argument "${argName}".`);
|
|
1809
|
+
const usage = argNames.map((a) => {
|
|
1810
|
+
const def = site.args[a];
|
|
1811
|
+
return def.required ? `<${a}>` : `[${a}]`;
|
|
1812
|
+
}).join(" ");
|
|
1813
|
+
console.error(` Usage: bb-browser site ${name} ${usage}`);
|
|
1814
|
+
if (site.example) console.error(` Example: ${site.example}`);
|
|
1815
|
+
process.exit(1);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
const jsContent = readFileSync(site.filePath, "utf-8");
|
|
1819
|
+
const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
|
|
1820
|
+
const argsJson = JSON.stringify(argMap);
|
|
1821
|
+
const script = `(${jsBody})(${argsJson})`;
|
|
1822
|
+
await ensureDaemonRunning();
|
|
1823
|
+
let targetTabId = options.tabId;
|
|
1824
|
+
if (!targetTabId && site.domain) {
|
|
1825
|
+
const listReq = { id: generateId(), action: "tab_list" };
|
|
1826
|
+
const listResp = await sendCommand(listReq);
|
|
1827
|
+
if (listResp.success && listResp.data?.tabs) {
|
|
1828
|
+
const matchingTab = listResp.data.tabs.find(
|
|
1829
|
+
(tab) => matchTabOrigin2(tab.url, site.domain)
|
|
1830
|
+
);
|
|
1831
|
+
if (matchingTab) {
|
|
1832
|
+
targetTabId = matchingTab.tabId;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (!targetTabId) {
|
|
1836
|
+
const newResp = await sendCommand({
|
|
1837
|
+
id: generateId(),
|
|
1838
|
+
action: "tab_new",
|
|
1839
|
+
url: `https://${site.domain}`
|
|
1840
|
+
});
|
|
1841
|
+
targetTabId = newResp.data?.tabId;
|
|
1842
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
1846
|
+
const evalResp = await sendCommand(evalReq);
|
|
1847
|
+
if (!evalResp.success) {
|
|
1848
|
+
console.error(`[error] site ${name}: eval failed.`);
|
|
1849
|
+
console.error(` ${evalResp.error}`);
|
|
1850
|
+
console.error(` Check: is ${site.domain} open and logged in?`);
|
|
1851
|
+
process.exit(1);
|
|
1852
|
+
}
|
|
1853
|
+
const result = evalResp.data?.result;
|
|
1854
|
+
if (result === void 0 || result === null) {
|
|
1855
|
+
if (options.json) {
|
|
1856
|
+
console.log(JSON.stringify({ id: evalReq.id, success: true, data: null }));
|
|
1857
|
+
} else {
|
|
1858
|
+
console.log("(no output)");
|
|
1859
|
+
}
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
let parsed;
|
|
1863
|
+
try {
|
|
1864
|
+
parsed = typeof result === "string" ? JSON.parse(result) : result;
|
|
1865
|
+
} catch {
|
|
1866
|
+
parsed = result;
|
|
1867
|
+
}
|
|
1868
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
1869
|
+
const errObj = parsed;
|
|
1870
|
+
if (options.json) {
|
|
1871
|
+
console.log(JSON.stringify({ id: evalReq.id, success: false, error: errObj.error, hint: errObj.hint }));
|
|
1872
|
+
} else {
|
|
1873
|
+
console.error(`[error] site ${name}: ${errObj.error}`);
|
|
1874
|
+
if (errObj.hint) console.error(` Hint: ${errObj.hint}`);
|
|
1875
|
+
}
|
|
1876
|
+
process.exit(1);
|
|
1877
|
+
}
|
|
1878
|
+
if (options.json) {
|
|
1879
|
+
console.log(JSON.stringify({ id: evalReq.id, success: true, data: parsed }));
|
|
1880
|
+
} else {
|
|
1881
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
async function siteCommand(args, options = {}) {
|
|
1885
|
+
const subCommand = args[0];
|
|
1886
|
+
if (!subCommand || subCommand === "--help" || subCommand === "-h") {
|
|
1887
|
+
console.log(`bb-browser site - \u7F51\u7AD9 CLI \u5316\uFF08\u7BA1\u7406\u548C\u8FD0\u884C site adapter\uFF09
|
|
1888
|
+
|
|
1889
|
+
\u7528\u6CD5:
|
|
1890
|
+
bb-browser site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter
|
|
1891
|
+
bb-browser site search <query> \u641C\u7D22 adapter
|
|
1892
|
+
bb-browser site <name> [args...] \u8FD0\u884C adapter\uFF08\u7B80\u5199\uFF09
|
|
1893
|
+
bb-browser site run <name> [args...] \u8FD0\u884C adapter
|
|
1894
|
+
bb-browser site update \u66F4\u65B0\u793E\u533A adapter \u5E93 (git clone/pull)
|
|
1895
|
+
|
|
1896
|
+
\u76EE\u5F55:
|
|
1897
|
+
${LOCAL_SITES_DIR} \u79C1\u6709 adapter\uFF08\u4F18\u5148\uFF09
|
|
1898
|
+
${COMMUNITY_SITES_DIR} \u793E\u533A adapter
|
|
1899
|
+
|
|
1900
|
+
\u793A\u4F8B:
|
|
1901
|
+
bb-browser site update
|
|
1902
|
+
bb-browser site list
|
|
1903
|
+
bb-browser site reddit/thread https://www.reddit.com/r/LocalLLaMA/comments/...
|
|
1904
|
+
bb-browser site twitter/user yan5xu
|
|
1905
|
+
bb-browser site search reddit`);
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
switch (subCommand) {
|
|
1909
|
+
case "list":
|
|
1910
|
+
siteList(options);
|
|
1911
|
+
break;
|
|
1912
|
+
case "search":
|
|
1913
|
+
if (!args[1]) {
|
|
1914
|
+
console.error("[error] site search: <query> is required.");
|
|
1915
|
+
console.error(" Usage: bb-browser site search <query>");
|
|
1916
|
+
process.exit(1);
|
|
1917
|
+
}
|
|
1918
|
+
siteSearch(args[1], options);
|
|
1919
|
+
break;
|
|
1920
|
+
case "update":
|
|
1921
|
+
siteUpdate();
|
|
1922
|
+
break;
|
|
1923
|
+
case "run":
|
|
1924
|
+
if (!args[1]) {
|
|
1925
|
+
console.error("[error] site run: <name> is required.");
|
|
1926
|
+
console.error(" Usage: bb-browser site run <name> [args...]");
|
|
1927
|
+
console.error(" Try: bb-browser site list");
|
|
1928
|
+
process.exit(1);
|
|
1929
|
+
}
|
|
1930
|
+
await siteRun(args[1], args.slice(2), options);
|
|
1931
|
+
break;
|
|
1932
|
+
default:
|
|
1933
|
+
if (subCommand.includes("/")) {
|
|
1934
|
+
await siteRun(subCommand, args.slice(1), options);
|
|
1935
|
+
} else {
|
|
1936
|
+
console.error(`[error] site: unknown subcommand "${subCommand}".`);
|
|
1937
|
+
console.error(" Available: list, search, run, update");
|
|
1938
|
+
console.error(" Try: bb-browser site --help");
|
|
1939
|
+
process.exit(1);
|
|
1940
|
+
}
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1427
1945
|
// packages/cli/src/index.ts
|
|
1428
1946
|
var VERSION = "0.3.0";
|
|
1429
1947
|
var HELP_TEXT = `
|
|
@@ -1480,6 +1998,12 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
|
1480
1998
|
trace start \u5F00\u59CB\u5F55\u5236\u7528\u6237\u64CD\u4F5C
|
|
1481
1999
|
trace stop \u505C\u6B62\u5F55\u5236\uFF0C\u8F93\u51FA\u4E8B\u4EF6\u5217\u8868
|
|
1482
2000
|
trace status \u67E5\u770B\u5F55\u5236\u72B6\u6001
|
|
2001
|
+
fetch <url> \u5728\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u4E2D fetch\uFF08\u81EA\u52A8\u540C\u6E90\u8DEF\u7531\uFF0C\u5E26\u767B\u5F55\u6001\uFF09
|
|
2002
|
+
site \u7F51\u7AD9 CLI \u5316 \u2014 \u7BA1\u7406\u548C\u8FD0\u884C site adapter
|
|
2003
|
+
site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter
|
|
2004
|
+
site search <q> \u641C\u7D22 adapter
|
|
2005
|
+
site <name> \u8FD0\u884C adapter\uFF08\u5982 site reddit/thread <url>\uFF09
|
|
2006
|
+
site update \u66F4\u65B0\u793E\u533A adapter \u5E93
|
|
1483
2007
|
|
|
1484
2008
|
\u9009\u9879\uFF1A
|
|
1485
2009
|
--json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
|
|
@@ -1488,6 +2012,7 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
|
1488
2012
|
-d, --depth <n> \u9650\u5236\u6811\u6DF1\u5EA6\uFF08snapshot \u547D\u4EE4\uFF09
|
|
1489
2013
|
-s, --selector <sel> \u9650\u5B9A CSS \u9009\u62E9\u5668\u8303\u56F4\uFF08snapshot \u547D\u4EE4\uFF09
|
|
1490
2014
|
--tab <tabId> \u6307\u5B9A\u64CD\u4F5C\u7684\u6807\u7B7E\u9875 ID
|
|
2015
|
+
--mcp \u542F\u52A8 MCP server\uFF08\u7528\u4E8E Claude Code / Cursor \u7B49 AI \u5DE5\u5177\uFF09
|
|
1491
2016
|
--help, -h \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
|
|
1492
2017
|
--version, -v \u663E\u793A\u7248\u672C\u53F7
|
|
1493
2018
|
|
|
@@ -1547,7 +2072,7 @@ function parseArgs(argv) {
|
|
|
1547
2072
|
}
|
|
1548
2073
|
} else if (arg === "--id") {
|
|
1549
2074
|
skipNext = true;
|
|
1550
|
-
} else if (arg === "--tab"
|
|
2075
|
+
} else if (arg === "--tab") {
|
|
1551
2076
|
skipNext = true;
|
|
1552
2077
|
} else if (arg.startsWith("-")) {
|
|
1553
2078
|
} else if (result.command === null) {
|
|
@@ -1566,6 +2091,13 @@ async function main() {
|
|
|
1566
2091
|
console.log(VERSION);
|
|
1567
2092
|
return;
|
|
1568
2093
|
}
|
|
2094
|
+
if (process.argv.includes("--mcp")) {
|
|
2095
|
+
const mcpPath = new URL("./mcp.js", import.meta.url).pathname;
|
|
2096
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
2097
|
+
const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
|
|
2098
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
1569
2101
|
if (parsed.flags.help || !parsed.command) {
|
|
1570
2102
|
console.log(HELP_TEXT);
|
|
1571
2103
|
return;
|
|
@@ -1724,7 +2256,9 @@ async function main() {
|
|
|
1724
2256
|
}
|
|
1725
2257
|
case "daemon":
|
|
1726
2258
|
case "start": {
|
|
1727
|
-
|
|
2259
|
+
const hostIdx = process.argv.findIndex((a) => a === "--host");
|
|
2260
|
+
const host = hostIdx >= 0 ? process.argv[hostIdx + 1] : void 0;
|
|
2261
|
+
await daemonCommand({ json: parsed.flags.json, host });
|
|
1728
2262
|
break;
|
|
1729
2263
|
}
|
|
1730
2264
|
case "stop": {
|
|
@@ -1835,9 +2369,10 @@ async function main() {
|
|
|
1835
2369
|
const subCommand = parsed.args[0] || "requests";
|
|
1836
2370
|
const urlOrFilter = parsed.args[1];
|
|
1837
2371
|
const abort = process.argv.includes("--abort");
|
|
2372
|
+
const withBody = process.argv.includes("--with-body");
|
|
1838
2373
|
const bodyIndex = process.argv.findIndex((a) => a === "--body");
|
|
1839
2374
|
const body = bodyIndex >= 0 ? process.argv[bodyIndex + 1] : void 0;
|
|
1840
|
-
await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, tabId: globalTabId });
|
|
2375
|
+
await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId });
|
|
1841
2376
|
break;
|
|
1842
2377
|
}
|
|
1843
2378
|
case "console": {
|
|
@@ -1863,6 +2398,36 @@ async function main() {
|
|
|
1863
2398
|
await traceCommand(subCmd, { json: parsed.flags.json, tabId: globalTabId });
|
|
1864
2399
|
break;
|
|
1865
2400
|
}
|
|
2401
|
+
case "fetch": {
|
|
2402
|
+
const fetchUrl = parsed.args[0];
|
|
2403
|
+
if (!fetchUrl) {
|
|
2404
|
+
console.error("[error] fetch: <url> is required.");
|
|
2405
|
+
console.error(" Usage: bb-browser fetch <url> [--json] [--method POST] [--body '{...}']");
|
|
2406
|
+
console.error(" Example: bb-browser fetch https://www.reddit.com/api/me.json --json");
|
|
2407
|
+
process.exit(1);
|
|
2408
|
+
}
|
|
2409
|
+
const methodIdx = process.argv.findIndex((a) => a === "--method");
|
|
2410
|
+
const fetchMethod = methodIdx >= 0 ? process.argv[methodIdx + 1] : void 0;
|
|
2411
|
+
const fetchBodyIdx = process.argv.findIndex((a) => a === "--body");
|
|
2412
|
+
const fetchBody = fetchBodyIdx >= 0 ? process.argv[fetchBodyIdx + 1] : void 0;
|
|
2413
|
+
const headersIdx = process.argv.findIndex((a) => a === "--headers");
|
|
2414
|
+
const fetchHeaders = headersIdx >= 0 ? process.argv[headersIdx + 1] : void 0;
|
|
2415
|
+
const outputIdx = process.argv.findIndex((a) => a === "--output");
|
|
2416
|
+
const fetchOutput = outputIdx >= 0 ? process.argv[outputIdx + 1] : void 0;
|
|
2417
|
+
await fetchCommand(fetchUrl, {
|
|
2418
|
+
json: parsed.flags.json,
|
|
2419
|
+
method: fetchMethod,
|
|
2420
|
+
body: fetchBody,
|
|
2421
|
+
headers: fetchHeaders,
|
|
2422
|
+
output: fetchOutput,
|
|
2423
|
+
tabId: globalTabId
|
|
2424
|
+
});
|
|
2425
|
+
break;
|
|
2426
|
+
}
|
|
2427
|
+
case "site": {
|
|
2428
|
+
await siteCommand(parsed.args, { json: parsed.flags.json, tabId: globalTabId });
|
|
2429
|
+
break;
|
|
2430
|
+
}
|
|
1866
2431
|
default: {
|
|
1867
2432
|
console.error(`\u9519\u8BEF\uFF1A\u672A\u77E5\u547D\u4EE4 "${parsed.command}"`);
|
|
1868
2433
|
console.error("\u8FD0\u884C bb-browser --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4");
|