add multi browser mode to arragate data from all browsers by tabs list, tabs count, group list, group count and windows list
Package Extension / package-extension (push) Successful in 12s
Build & Publish Package / publish (push) Successful in 22s

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:
2026-04-10 12:49:51 +02:00
parent 6979f2ef30
commit 61b774a7a4
14 changed files with 578 additions and 79 deletions
+95 -12
View File
@@ -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: