Files
daniel156161 479a0f1964
Testing / remote-protocol-compat (0.9.3) (push) Successful in 43s
Testing / test (push) Successful in 1m1s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 39s
Build & Publish Package / publish (push) Successful in 58s
Package Extension / package-extension (push) Successful in 1m15s
feat: improve remote browser tree routing
- Allow remote host aliases passed via --browser to fan out for read-only
  multi-browser SDK paths while preserving strict routing for mutating commands.
- Add remote host grouping and scoped profile labels to tabs tree output so
  global views avoid repeated host prefixes.
- Carry browser family metadata through remote targets, tabs, and groups and
  style tree browser labels by family.
- Split CLI rendering helpers into a typed rendering package with dedicated
  common, label, tabs-tree, and windows-tree modules.
- Bump browser-cli and extension versions to 0.15.5.
- Cover the new routing and rendering behavior with unit and CLI tests.
2026-06-18 00:12:17 +02:00

165 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.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, remote_targets_for_alias, send_command, send_command_async
from browser_cli.errors import BrowserNotConnected
from browser_cli.models import BrowserCounts, Group, Tab
from browser_cli.sdk import (
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
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", {})