feat(sdk): improve Python SDK ergonomics
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 45s
Testing / test (push) Successful in 42s

- Position browser-cli as a CLI plus Python SDK in docs and package metadata.
- Add public target properties and a raw command escape hatch for unsupported commands.
- Add convenience helpers for opening, finding, closing, and accessing tabs.
- Add plural group aliases and a wait_for_selector DOM convenience alias.
- Extend bound Tab objects with screenshot, pin, refresh, load wait, and URL watch helpers.
- Preserve remote auth key configuration when binding remote Tab and Group objects.
- Bump project and extension versions to 0.9.9 and cover SDK additions with tests.
This commit is contained in:
2026-05-19 20:12:16 +02:00
parent eaa1469143
commit e1e4adbb25
7 changed files with 364 additions and 17 deletions
+28 -11
View File
@@ -1,19 +1,16 @@
# browser-cli
Control your real, running browser from the terminal or a Python script — no headless browser, no Playwright, no virtual display. Your actual open tabs, windows, and tab groups respond to your commands.
Control your real, running browser from the terminal or the Python SDK — no headless browser, no Playwright, no virtual display. Your actual open tabs, windows, and tab groups respond to your commands.
---
## What it does
You have 40 tabs open. You want to close all the duplicates, group the GitHub ones, save your session before a meeting, and open a few URLs into a specific group — all from a script. That is what browser-cli is for.
It works by pairing a small browser extension with a Python CLI tool. The extension has full access to your browser's tabs, windows, groups, and page DOM. The CLI talks to it in real time over a local IPC channel.
It works by pairing a small browser extension with a Python package that provides both a CLI and SDK. The extension has full access to your browser's tabs, windows, groups, and page DOM. The CLI and SDK talk to it in real time over a local IPC channel.
---
## How it works
```
terminal / python script
@@ -79,9 +76,9 @@ Only the `browser-cli` command needs to be on your `PATH`. The browser launches
```text
browser-cli/
├── browser_cli/
│ ├── __init__.py # Python API — BrowserCLI class and Python API entry point
│ ├── __init__.py # Python SDK — BrowserCLI class and SDK entry point
│ ├── cli.py # Click CLI entry point
│ ├── client.py # Local IPC client used by CLI and API
│ ├── client.py # Local IPC client used by CLI and SDK
│ ├── models.py # Tab and Group helper models
│ ├── native_host.py # Native messaging host launched by the browser
│ └── commands/
@@ -99,7 +96,7 @@ browser-cli/
│ └── src/ # TypeScript source split by command area
│ └── index.ts # Builds generated extension/background.js
├── examples/
│ ├── demo.py # Python API walkthrough
│ ├── demo.py # Python SDK walkthrough
│ └── demo.sh # Bash CLI walkthrough
├── tests/
│ ├── conftest.py # shared pytest fixtures
@@ -285,7 +282,7 @@ browser-cli completion zsh --script # output raw completion script
---
## Python API
## Python SDK
```python
from browser_cli import BrowserCLI
@@ -293,11 +290,13 @@ from browser_cli import BrowserCLI
b = BrowserCLI()
```
Every CLI command has a corresponding method. The call blocks until the browser responds and returns the data directly as a Python object.
Every CLI command has a corresponding SDK method. The call blocks until the browser responds and returns the data directly as a Python object.
```python
# Navigation
b.open("https://example.com")
tab = b.open_tab("https://example.com") # returns a bound Tab object
tab = b.open_tab("https://example.com", wait=True, timeout=10)
b.open("https://example.com", background=True)
b.open("https://example.com", window="work")
b.reload()
@@ -308,8 +307,14 @@ b.focus_url("github")
# Tabs
tabs = b.tabs_list() # list[Tab]; in multi-browser mode each tab.browser is set
tabs = b.tabs() # short alias for tabs_list()
active = b.active_tab() # active Tab object
tab = b.tab(1234) # tab by ID
tab = b.find_tab("github") # first matching tab or None
tabs = b.find_tabs("github") # alias for tabs_query()
b.tabs_active(1234)
b.tabs_close(1234)
b.close_tab(tab) # accepts Tab or tab ID
b.tabs_close_inactive()
b.tabs_close_duplicates()
b.tabs_filter("youtube") # list of matching tabs
@@ -320,9 +325,19 @@ b.tabs_sort(by="domain")
b.tabs_merge_windows()
b.tabs_dedupe()
# Bound Tab helpers
tab = b.active_tab()
tab.pin()
tab.screenshot()
tab.refresh()
tab.wait_for_load(timeout=10)
tab.watch_url(r"/done$")
# Tab groups
groups = b.group_list() # list[Group]; in multi-browser mode each group.browser is set
b.group_open("research") # creates group, returns { id, name }
groups = b.groups() # short alias for group_list()
b.groups_create("research") # plural alias for group_create()
b.group_create("research") # creates group, returns Group
b.group_close(42)
b.group_tabs(42) # tabs inside a group
b.group_count() # int, or BrowserCounts(...) in multi-browser mode
@@ -341,6 +356,7 @@ attrs = b.dom_attr("a", "href") # list of strings
exists = b.dom_exists(".cookie-banner")# bool
b.dom_click(".accept-button")
b.dom_type("#search", "hello world")
b.wait_for_selector("#results", visible=True, timeout=10)
# Extract
links = b.extract_links() # list of { text, href }
@@ -359,6 +375,7 @@ b.session_auto_save(True)
# Misc
clients = b.clients()
raw = b.command("tabs.count", {"pattern": "github"}) # escape hatch for raw commands
```
**Error handling**