076914e5b7
- 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.
168 lines
5.4 KiB
Python
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", {})
|