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/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  COMMAND_TIMEOUT,
4
4
  DAEMON_BASE_URL,
5
5
  generateId
6
- } from "./chunk-TUO443YI.js";
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
- try {
613
- const { startDaemon } = await import("@bb-browser/daemon");
614
- if (options.json) {
615
- console.log(JSON.stringify({ success: true, message: "Daemon \u542F\u52A8\u4E2D..." }));
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" && !result.command) {
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
- await daemonCommand({ json: parsed.flags.json });
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");