Files
browser-cli/browser_cli/__init__.py
T
daniel156161 076914e5b7 refactor: reorganize client transport and extension internals
- Split client, native, remote, serve, markdown, and SDK internals into focused packages with direct imports.
- Move local and remote transport framing/protocol helpers behind clearer module boundaries.
- Break up the extension injected DOM logic into a separate content dispatch bundle and dedicated content modules.
- Add explicit client handling for passive remote discovery without noisy PQ warnings.
- Keep behavior covered with updated unit, integration, and extension tests.
2026-06-13 23:31:24 +02:00

168 lines
5.4 KiB
Python

"""
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", {})