fd5447cbb9
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Package Extension / package-extension (push) Successful in 43s
Build & Publish Package / publish (push) Successful in 43s
Testing / test (push) Successful in 45s
Restructure the Python API and internals around composable namespaces and a standalone transport/endpoint layer. Bump to 0.12.0. Python API: - Replace flat methods (b.tabs_list(), b.group_list()) with namespaces: b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage, b.cookies, b.session, b.perf, b.extension. - Shrink browser_cli/__init__.py to a thin composition root; move all behaviour into browser_cli/sdk/ (one module per namespace + factories, base, routing). Internals: - Add browser_cli/transport.py and remote_transport.py to isolate IPC from command logic; client.py now delegates instead of owning transport. - Add browser_cli/endpoints.py for endpoint resolution and browser_cli/errors.py for shared error types. - Extract markdown rendering into browser_cli/markdown.py (out of extract). - Add USER_AGENT to version_manager. Tooling & tests: - Add justfile with common dev tasks. - Update CLI commands and demo to the namespaced API. - Rework tests for the new layout; add test_transport.py and test_refactor_boundaries.py to lock in module boundaries. BREAKING CHANGE: flat API methods are removed in favour of namespaces (e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
128 lines
4.7 KiB
Python
128 lines
4.7 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
|
|
"""
|
|
from browser_cli.client import BrowserNotConnected, active_browser_targets, remote_browser_targets, send_command
|
|
from browser_cli.models import BrowserCounts, Group, Tab
|
|
from browser_cli.sdk import (
|
|
CookiesNS,
|
|
DomNS,
|
|
ExtensionNS,
|
|
ExtractNS,
|
|
GroupsNS,
|
|
NavigationNS,
|
|
PageNS,
|
|
PerfNS,
|
|
SessionNS,
|
|
StorageNS,
|
|
TabsNS,
|
|
WindowsNS,
|
|
)
|
|
from browser_cli.sdk.factories import FactoryMixin
|
|
from browser_cli.sdk.routing import RoutingMixin
|
|
|
|
__all__ = ["BrowserCLI", "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`.
|
|
"""
|
|
|
|
def __init__(self, browser: str | None = None, remote: str | None = None, key: str | None = 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. ``"192.168.1.10: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
|
|
|
|
# Command namespaces.
|
|
self.nav = NavigationNS(self)
|
|
self.tabs = TabsNS(self)
|
|
self.groups = GroupsNS(self)
|
|
self.windows = WindowsNS(self)
|
|
self.dom = DomNS(self)
|
|
self.extract = ExtractNS(self)
|
|
self.page = PageNS(self)
|
|
self.storage = StorageNS(self)
|
|
self.cookies = CookiesNS(self)
|
|
self.session = SessionNS(self)
|
|
self.perf = PerfNS(self)
|
|
self.extension = ExtensionNS(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 _cmd(self, command: str, args: dict | None = None):
|
|
return send_command(command, args, profile=self._browser, remote=self._remote, key=self._key)
|
|
|
|
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", {})
|