refactor(api): namespaced SDK + dedicated transport layer
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()).
This commit is contained in:
2026-06-11 13:58:41 +02:00
parent 0813ae2de9
commit fd5447cbb9
52 changed files with 3344 additions and 2348 deletions
+56
View File
@@ -0,0 +1,56 @@
"""Tab groups namespace: ``b.groups.*``."""
from __future__ import annotations
from typing import TYPE_CHECKING
from browser_cli.models import Group, Tab
from browser_cli.sdk.base import Namespace
if TYPE_CHECKING:
from browser_cli import BrowserCounts
class GroupsNS(Namespace):
"""List, create, query, and modify tab groups."""
def list(self) -> list[Group]:
"""Return all tab groups.
When multiple browsers are active and no browser was specified, each Group
includes ``group.browser`` naming its source browser.
"""
return self._c._multi_list("group.list", {}, self._c._make_group_for)
def count(self) -> "int | BrowserCounts":
"""Return the number of tab groups.
Returns ``BrowserCounts`` in implicit multi-browser mode.
"""
return self._c._multi_count("group.count", {})
def query(self, search: str) -> list[Group]:
"""Search groups by name."""
return [self._c._make_group(g) for g in (self._c._cmd("group.query", {"search": search}) or [])]
def create(self, name: str) -> Group:
"""Create a new tab group with *name*. Returns the created Group."""
data = self._c._cmd("group.open", {"name": name})
if isinstance(data, dict):
return self._c._make_group(data)
return Group(id=data, title=name, color="", collapsed=False, tab_count=0)
def tabs(self, group_id: int) -> list[Tab]:
"""Return all tabs inside a group."""
return [self._c._make_tab(t) for t in (self._c._cmd("group.tabs", {"groupId": group_id}) or [])]
def add_tab(self, group: str | int, url: str | None = None) -> int | None:
"""Open a new tab (optionally at URL) inside a group. Returns the new tab ID."""
result = self._c._cmd("group.add_tab", {"group": str(group), "url": url})
return self._c._field(result, "tabId", fallback=result)
def move(self, group: str | int, *, forward: bool = False, backward: bool = False) -> dict | None:
"""Move a tab group forward or backward. Returns the raw move result."""
return self._c._cmd("group.move", {"group": str(group), "forward": forward, "backward": backward})
def close(self, group_id: int, *, gentle_mode: str = "auto") -> None:
"""Ungroup (and close) a tab group by ID."""
self._c._cmd("group.close", {"groupId": group_id, "gentleMode": gentle_mode})