""" Typed dataclasses returned by the BrowserCLI Python API. Each object is bound to a BrowserCLI instance so you can call actions directly on it: tabs = b.tabs.list() tabs[0].close() tabs[0].move(forward=True) groups = b.groups.list() groups[0].tabs() groups[0].add_tab("https://example.com") """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Protocol class BoundBrowser(Protocol): tabs: Any groups: Any nav: Any def dispatch(self, command: str, args: dict | None = None): ... # ── BrowserCounts ─────────────────────────────────────────────────────────── @dataclass(frozen=True) class BrowserCounts: """Aggregated per-browser counts returned in implicit multi-browser mode.""" total: int by_browser: dict[str, int] browser_groups: dict[str, str] = field(default_factory=dict) # ── Tab ─────────────────────────────────────────────────────────────────────── @dataclass class Tab: """A browser tab.""" id: int window_id: int active: bool muted: bool = False title: str = "" url: str = "" group_id: int | None = None index: int = 0 browser: str | None = None browser_name: str | None = None browser_group: str | None = None _browser: BoundBrowser | None = field(default=None, repr=False, compare=False, init=False) def _b(self) -> BoundBrowser: if self._browser is None: raise RuntimeError("Tab is not bound to a BrowserCLI instance") return self._browser def _command(self, name: str, args: dict | None = None): browser = self._b() return browser.dispatch(name, args) def close(self) -> None: """Close this tab.""" self._command("tabs.close", {"tabId": self.id}) def activate(self) -> None: """Switch browser focus to this tab.""" self._command("tabs.active", {"tabId": self.id}) def mute(self) -> None: """Mute this tab.""" self._command("tabs.mute", {"tabId": self.id}) def unmute(self) -> None: """Unmute this tab.""" self._command("tabs.unmute", {"tabId": self.id}) def reload(self) -> None: """Reload this tab.""" self._command("navigate.reload", {"tabId": self.id}) def hard_reload(self) -> None: """Hard-reload this tab (bypass cache).""" self._command("navigate.hard_reload", {"tabId": self.id}) def move( self, *, forward: bool = False, backward: bool = False, group_id: int | None = None, window_id: int | None = None, index: int | None = None, ) -> None: """Move this tab. Args: forward: Move one position to the right within the window. backward: Move one position to the left within the window. group_id: Move into the tab group with this ID. window_id: Move to the window with this ID. index: Absolute position index in the target window. """ self._command("tabs.move", { "tabId": self.id, "forward": forward, "backward": backward, "groupId": group_id, "windowId": window_id, "index": index, }) def html(self) -> str: """Return the full HTML source of this tab.""" return self._command("tabs.html", {"tabId": self.id}) def screenshot(self, *, format: str = "png", quality: int | None = None) -> str: """Capture this tab's visible area. Returns a base64 data URL.""" return self._b().tabs.screenshot(self.id, format=format, quality=quality) def pin(self) -> None: """Pin this tab.""" self._command("tabs.pin", {"tabId": self.id}) def unpin(self) -> None: """Unpin this tab.""" self._command("tabs.unpin", {"tabId": self.id}) def refresh(self) -> Tab: """Return a fresh snapshot of this tab.""" return self._b().tabs.status(self.id) def wait_for_load(self, *, timeout: float = 30.0, ready_state: str = "complete") -> Tab: """Wait until this tab reaches the requested readyState.""" return self._b().tabs.wait_for_load(self.id, timeout=timeout, ready_state=ready_state) def watch_url(self, pattern: str, *, timeout: float = 30.0) -> Tab: """Wait until this tab's URL matches regex *pattern*.""" return self._b().tabs.watch_url(pattern, tab_id=self.id, timeout=timeout) def open(self, url: str, *, background: bool = False) -> None: """Navigate this tab to *url* in place.""" self._b().nav.to(self.id, url) # ── Group ───────────────────────────────────────────────────────────────────── @dataclass class Group: """A browser tab group.""" id: int title: str color: str collapsed: bool tab_count: int window_id: int | None = None browser: str | None = None browser_name: str | None = None browser_group: str | None = None _browser: BoundBrowser | None = field(default=None, repr=False, compare=False, init=False) def _b(self) -> BoundBrowser: if self._browser is None: raise RuntimeError("Group is not bound to a BrowserCLI instance") return self._browser def _command(self, name: str, args: dict | None = None): browser = self._b() return browser.dispatch(name, args) def close(self) -> None: """Ungroup (and close) this tab group.""" self._command("group.close", {"groupId": self.id}) def tabs(self) -> list[Tab]: """Return all tabs inside this group.""" return self._b().groups.tabs(self.id) def move(self, *, forward: bool = False, backward: bool = False) -> None: """Move this group forward or backward among groups.""" self._command("group.move", { "group": str(self.id), "forward": forward, "backward": backward, }) def add_tab(self, url: str | None = None) -> int | None: """Open a new tab inside this group. Returns the new tab ID.""" return self._b().groups.add_tab(self.id, url)