add multi browser mode to arragate data from all browsers by tabs list, tabs count, group list, group count and windows list
remove (unnamed) into the group names just leave it a empty string, remove Focused on windows how should the browser know what windows are focused
This commit is contained in:
+95
-12
@@ -17,11 +17,19 @@ Usage:
|
||||
b = BrowserCLI(browser="brave")
|
||||
"""
|
||||
from collections.abc import Callable, Iterable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from browser_cli.client import BrowserNotConnected, send_command
|
||||
from browser_cli.client import BrowserNotConnected, active_browser_targets, send_command
|
||||
from browser_cli.models import Group, Tab
|
||||
|
||||
__all__ = ["BrowserCLI", "BrowserNotConnected", "Tab", "Group"]
|
||||
__all__ = ["BrowserCLI", "BrowserCounts", "BrowserNotConnected", "Tab", "Group"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BrowserCounts:
|
||||
"""Aggregated per-browser counts returned in implicit multi-browser mode."""
|
||||
total: int
|
||||
by_browser: dict[str, int]
|
||||
|
||||
|
||||
class BrowserCLI:
|
||||
@@ -36,9 +44,35 @@ class BrowserCLI:
|
||||
def _cmd(self, command: str, args: dict | None = None):
|
||||
return send_command(command, args, profile=self._browser)
|
||||
|
||||
def _multi_browser_targets(self):
|
||||
if self._browser is not None:
|
||||
return []
|
||||
targets = active_browser_targets()
|
||||
if len(targets) <= 1:
|
||||
return []
|
||||
return targets
|
||||
|
||||
def _collect_multi_browser(self, command: str, args: dict | None = None):
|
||||
results = []
|
||||
for target in self._multi_browser_targets():
|
||||
try:
|
||||
data = send_command(command, args, profile=target.profile)
|
||||
except (BrowserNotConnected, RuntimeError):
|
||||
continue
|
||||
results.append((target, data))
|
||||
if results:
|
||||
return results
|
||||
if self._multi_browser_targets():
|
||||
raise BrowserNotConnected(
|
||||
"Cannot resolve a browser socket automatically.\n"
|
||||
"Make sure the browser is running with the browser-cli extension enabled,\n"
|
||||
"or pass --browser <alias> / set BROWSER_CLI_PROFILE to a known alias."
|
||||
)
|
||||
return []
|
||||
|
||||
# ── Internal factories ────────────────────────────────────────────────
|
||||
|
||||
def _make_tab(self, data: dict) -> Tab:
|
||||
def _make_tab(self, data: dict, *, browser_profile: str | None = None, browser_name: str | None = None) -> Tab:
|
||||
tab = Tab(
|
||||
id=data["id"],
|
||||
window_id=data.get("windowId", 0),
|
||||
@@ -46,19 +80,21 @@ class BrowserCLI:
|
||||
title=data.get("title") or "",
|
||||
url=data.get("url") or "",
|
||||
group_id=data.get("groupId") or None,
|
||||
browser=browser_name,
|
||||
)
|
||||
tab._browser = self
|
||||
tab._browser = self if browser_profile is None else BrowserCLI(browser=browser_profile)
|
||||
return tab
|
||||
|
||||
def _make_group(self, data: dict) -> Group:
|
||||
def _make_group(self, data: dict, *, browser_profile: str | None = None, browser_name: str | None = None) -> Group:
|
||||
group = Group(
|
||||
id=data["id"],
|
||||
title=data.get("title") or "",
|
||||
color=data.get("color") or "",
|
||||
collapsed=data.get("collapsed", False),
|
||||
tab_count=data.get("tabCount", 0),
|
||||
browser=browser_name,
|
||||
)
|
||||
group._browser = self
|
||||
group._browser = self if browser_profile is None else BrowserCLI(browser=browser_profile)
|
||||
return group
|
||||
|
||||
# ── Navigation ────────────────────────────────────────────────────────
|
||||
@@ -99,7 +135,18 @@ class BrowserCLI:
|
||||
# ── Tabs ──────────────────────────────────────────────────────────────
|
||||
|
||||
def tabs_list(self) -> list[Tab]:
|
||||
"""Return all open tabs across all windows."""
|
||||
"""Return all open tabs across all windows.
|
||||
|
||||
When multiple browsers are active and no browser was specified, each Tab
|
||||
includes ``tab.browser`` naming its source browser.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("tabs.list", {})
|
||||
if multi_results:
|
||||
return [
|
||||
self._make_tab(tab, browser_profile=target.profile, browser_name=target.display_name)
|
||||
for target, tabs in multi_results
|
||||
for tab in (tabs or [])
|
||||
]
|
||||
return [self._make_tab(t) for t in (self._cmd("tabs.list", {}) or [])]
|
||||
|
||||
def tabs_close(self, tab_id: int | None = None, *, inactive: bool = False, duplicates: bool = False) -> int:
|
||||
@@ -127,8 +174,15 @@ class BrowserCLI:
|
||||
return [self._make_tab(t) for t in (self._cmd("tabs.filter", {"pattern": pattern_or_filter}) or [])]
|
||||
return self._apply_tab_filter(pattern_or_filter)
|
||||
|
||||
def tabs_count(self, pattern: str | None = None) -> int:
|
||||
"""Count open tabs, optionally filtered by URL pattern."""
|
||||
def tabs_count(self, pattern: str | None = None) -> int | BrowserCounts:
|
||||
"""Count open tabs, optionally filtered by URL pattern.
|
||||
|
||||
Returns ``BrowserCounts`` in implicit multi-browser mode.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("tabs.count", {"pattern": pattern})
|
||||
if multi_results:
|
||||
by_browser = {target.display_name: int(count or 0) for target, count in multi_results}
|
||||
return BrowserCounts(total=sum(by_browser.values()), by_browser=by_browser)
|
||||
return self._cmd("tabs.count", {"pattern": pattern})
|
||||
|
||||
def tabs_query(self, search: str) -> list[Tab]:
|
||||
@@ -166,15 +220,33 @@ class BrowserCLI:
|
||||
# ── Tab Groups ────────────────────────────────────────────────────────
|
||||
|
||||
def group_list(self) -> list[Group]:
|
||||
"""Return all tab groups."""
|
||||
"""Return all tab groups.
|
||||
|
||||
When multiple browsers are active and no browser was specified, each Group
|
||||
includes ``group.browser`` naming its source browser.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("group.list", {})
|
||||
if multi_results:
|
||||
return [
|
||||
self._make_group(group, browser_profile=target.profile, browser_name=target.display_name)
|
||||
for target, groups in multi_results
|
||||
for group in (groups or [])
|
||||
]
|
||||
return [self._make_group(g) for g in (self._cmd("group.list", {}) or [])]
|
||||
|
||||
def group_tabs(self, group_id: int) -> list[Tab]:
|
||||
"""Return all tabs inside a group."""
|
||||
return [self._make_tab(t) for t in (self._cmd("group.tabs", {"groupId": group_id}) or [])]
|
||||
|
||||
def group_count(self) -> int:
|
||||
"""Return the number of tab groups."""
|
||||
def group_count(self) -> int | BrowserCounts:
|
||||
"""Return the number of tab groups.
|
||||
|
||||
Returns ``BrowserCounts`` in implicit multi-browser mode.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("group.count", {})
|
||||
if multi_results:
|
||||
by_browser = {target.display_name: int(count or 0) for target, count in multi_results}
|
||||
return BrowserCounts(total=sum(by_browser.values()), by_browser=by_browser)
|
||||
return self._cmd("group.count", {})
|
||||
|
||||
def group_query(self, search: str) -> list[Group]:
|
||||
@@ -202,6 +274,17 @@ class BrowserCLI:
|
||||
# ── Windows ───────────────────────────────────────────────────────────
|
||||
|
||||
def windows_list(self) -> list[dict]:
|
||||
"""Return browser windows.
|
||||
|
||||
In implicit multi-browser mode each window dict includes a ``browser`` key.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("windows.list", {})
|
||||
if multi_results:
|
||||
return [
|
||||
{**window, "browser": target.display_name}
|
||||
for target, windows in multi_results
|
||||
for window in (windows or [])
|
||||
]
|
||||
return self._cmd("windows.list", {})
|
||||
|
||||
def windows_rename(self, window_id: int, name: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user