""" browser_cli — Python SDK for controlling your running browser. Usage: from browser_cli import BrowserCLI b = BrowserCLI() tabs = b.tabs.list() # list[Tab] tabs[0].close() tabs[0].move(forward=True) groups = b.groups.list() # list[Group] groups[0].tabs() groups[0].add_tab("https://example.com") b.nav.open("https://example.com") b.dom.click("#submit") b.session.save("work") # When multiple browser instances are active, pass the alias: b = BrowserCLI(browser="brave") Commands are grouped into namespaces on the client: b.nav navigation (open, reload, back, forward, focus, search) b.tabs tabs (list, open, close, move, status, mute, sort, ...) b.groups tab groups (list, create, add_tab, move, close) b.windows browser windows (list, open, close, rename) b.dom page elements (query, click, type, wait_for, eval, ...) b.extract content extraction (links, images, text, json, markdown) b.page page info b.storage localStorage / sessionStorage b.cookies cookies (list, get, set) b.session sessions (save, load, list, diff, ...) b.perf performance profile + background jobs b.extension control the extension itself b.decorators workflow decorators for scripts """ from collections.abc import Callable from browser_cli.client import active_browser_targets, remote_browser_targets, send_command, send_command_async from browser_cli.errors import BrowserNotConnected from browser_cli.models import BrowserCounts, Group, Tab from browser_cli.sdk import ( CookiesNS, DecoratorsNS, DomNS, ExtensionNS, ExtractNS, GroupsNS, NAMESPACE_SPECS, NavigationNS, PageNS, PerfNS, SessionNS, StorageNS, TabsNS, WindowsNS, ) from browser_cli.sdk.factories import FactoryMixin from browser_cli.sdk.routing import RoutingMixin from browser_cli.async_sdk import AsyncBrowserCLI __all__ = ["BrowserCLI", "AsyncBrowserCLI", "BrowserCounts", "BrowserNotConnected", "Tab", "Group"] class BrowserCLI(FactoryMixin, RoutingMixin): """Client for a running browser, with commands grouped into namespaces. The client itself holds the connection target (browser/remote/key) and the shared machinery; the actual commands live on namespace accessors such as :attr:`tabs`, :attr:`dom`, and :attr:`session`. Object construction (``Tab``/``Group``) comes from :class:`~browser_cli.sdk.factories.FactoryMixin` and multi-browser fan-out from :class:`~browser_cli.sdk.routing.RoutingMixin`. """ _browser: str | None _remote: str | None _key: str | None _command_sender: Callable nav: NavigationNS tabs: TabsNS groups: GroupsNS windows: WindowsNS dom: DomNS extract: ExtractNS page: PageNS storage: StorageNS cookies: CookiesNS session: SessionNS perf: PerfNS extension: ExtensionNS decorators: DecoratorsNS def __init__( self, browser: str | None = None, remote: str | None = None, key: str | None = None, *, _command_sender=None, ): """ Args: browser: Profile alias to target. Required when multiple browser instances are active. Equivalent to ``--browser`` on the CLI. remote: Connect to a remote browser exposed via ``browser-cli serve``. Format: ``"host:port"`` (e.g. ``"browser-host.example:8765"``). Can be combined with ``browser`` to route to a specific remote profile. key: Path to Ed25519 private key PEM for pubkey auth, or ``"agent"`` to use a key from the SSH agent (YubiKey, gpg-agent, etc.). Defaults to ``~/.config/browser-cli/client.key.pem`` if that file exists. """ self._browser = browser self._remote = remote self._key = key if key else None self._command_sender = _command_sender or send_command for name, namespace_type in NAMESPACE_SPECS: setattr(self, name, namespace_type(self)) self.decorators = DecoratorsNS(self) @property def browser(self) -> str | None: """Target browser/profile alias, equivalent to ``--browser``.""" return self._browser @property def remote(self) -> str | None: """Remote endpoint used by this client, if any.""" return self._remote @property def key(self) -> str | None: """Ed25519 key spec used for remote auth, if explicitly configured.""" return self._key def dispatch(self, command: str, args: dict | None = None): """Dispatch one browser command using this client's target settings.""" return self._command_sender(command, args, profile=self._browser, remote=self._remote, key=self._key) def require_tab(self, data, error: str): """Convert a tab-like command response into a bound Tab.""" return self.require_tab_response(data, error) _FIELD_MISSING = object() def field(self, result, key, default=None, *, fallback=_FIELD_MISSING): """Read a named field from command output.""" if fallback is self._FIELD_MISSING: return self._field(result, key, default) return self._field(result, key, default, fallback=fallback) def _cmd(self, command: str, args: dict | None = None): return self.dispatch(command, args) def command(self, command: str, args: dict | None = None): """Send a raw browser-cli command and return its response. This is the SDK escape hatch for commands that do not have a dedicated namespace method yet. """ return self._cmd(command, args or {}) def clients(self) -> list[dict]: """Return the active browser clients known to this connection.""" return self._cmd("clients.list", {})