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.


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 Chrome/Brave 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 socket.


How it works

terminal / python script
        │
        │  Unix socket  (/tmp/browser-cli.sock)
        ▼
  Native Messaging Host  (Python process, launched by the browser)
        │
        │  Native Messaging Protocol  (stdin/stdout, 4-byte length prefix + JSON)
        ▼
  Chrome Extension  (background service worker)
        │
        │  chrome.* APIs
        ▼
  Your running browser
  1. The extension calls chrome.runtime.connectNative('com.browsercli.host') on startup.
  2. The browser launches the native host Python process (registered in the OS).
  3. The native host opens a Unix socket at /tmp/browser-cli.sock.
  4. CLI commands connect to that socket, send a JSON command, and wait for the result.
  5. The native host relays the command to the extension via stdout, receives the result via stdin, and sends it back to the CLI.

No server needs to be running beforehand. The browser manages the native host's lifecycle.

Message format

Every command is a JSON object:

{ "id": "uuid", "command": "tabs.list", "args": {} }

Every response:

{ "id": "uuid", "success": true, "data": [...] }

Installation

Requirements: Python 3.10+, uv, Chrome or Brave

git clone <repo>
cd browser-cli
uv sync
uv run browser-cli install brave   # or: chrome, chromium

The install command will:

  1. Ask you to load the extension/ folder as an unpacked extension in your browser (brave://extensions → Developer mode → Load unpacked)
  2. Ask you to paste the extension ID shown on the extension card
  3. Write the native messaging manifest to your OS so the browser can find the host
  4. Create an executable wrapper script for the native host

After install, fully restart your browser (Quit and reopen — not just close the window). The extension will connect to the native host automatically on startup.


Project structure

browser-cli/
├── browser_cli/
│   ├── __init__.py        # Python API — BrowserCLI class
│   ├── native_host.py     # Native messaging host (launched by browser)
│   ├── client.py          # Unix socket client (used by CLI and Python API)
│   ├── cli.py             # Click CLI entry point
│   └── commands/
│       ├── navigate.py    # open, reload, back, forward, focus
│       ├── tabs.py        # tab management
│       ├── groups.py      # tab group management
│       ├── windows.py     # window management
│       ├── dom.py         # DOM querying and interaction
│       ├── extract.py     # content extraction
│       └── session.py     # session save/load
├── extension/
│   ├── manifest.json      # MV3 extension manifest
│   ├── background.js      # Service worker — receives commands, calls chrome.* APIs
│   └── content.js         # Placeholder for future persistent content script
├── examples/
│   ├── demo.py            # Python API walkthrough
│   └── demo.sh            # Bash CLI walkthrough
└── pyproject.toml

CLI reference

All commands are run with uv run browser-cli <command>.

Navigation (nav)

# Open a URL
browser-cli nav open https://example.com
browser-cli nav open https://example.com --bg               # background, no focus
browser-cli nav open https://example.com --window work      # into a named window
browser-cli nav open https://example.com --group research   # into a tab group (name or ID)

# Reload
browser-cli nav reload                  # reload active tab
browser-cli nav reload 1234             # reload tab by ID
browser-cli nav hard-reload             # bypass cache

# Navigate history
browser-cli nav back
browser-cli nav forward 1234            # forward in specific tab

# Jump to a tab by URL pattern
browser-cli nav focus github            # focuses first tab whose URL contains "github"

Tabs

browser-cli tabs list                        # list all open tabs (all windows)
browser-cli tabs count                       # count all tabs
browser-cli tabs count youtube               # count tabs matching URL pattern
browser-cli tabs filter youtube              # list tabs matching URL pattern
browser-cli tabs query "pull request"        # search tabs by URL or title

browser-cli tabs active 1234                 # switch browser focus to tab
browser-cli tabs html                        # print full HTML of active tab
browser-cli tabs html 1234                   # print HTML of specific tab

browser-cli tabs close 1234                  # close specific tab
browser-cli tabs close --inactive            # close all inactive tabs
browser-cli tabs close --duplicates          # close duplicate URLs (keep first)
browser-cli tabs dedupe                      # same as close --duplicates

browser-cli tabs move 1234 --window 2        # move tab to another window
browser-cli tabs move 1234 --group 42        # move tab into a group

browser-cli tabs sort --by domain            # sort tabs within each window
browser-cli tabs sort --by title
browser-cli tabs sort --by time

browser-cli tabs merge-windows               # pull all tabs into the current window

Tab groups

browser-cli group list                       # list all tab groups
browser-cli group count                      # count groups
browser-cli group query "work"               # search groups by name
browser-cli group tabs 42                    # list tabs inside group ID 42

browser-cli group create "research"          # create a new group
browser-cli group add-tab research           # open a blank tab in the group
browser-cli group add-tab research https://example.com   # open URL in the group
browser-cli group add-tab 42 https://example.com         # by group ID

browser-cli group close 42                   # ungroup the group

Windows

browser-cli windows list                     # list all windows
browser-cli windows open                     # open a new window
browser-cli windows rename 1 "work"          # give a window a local alias
browser-cli windows close 1                  # close a window

DOM

These commands run on the active tab. The tab must be on a regular http:// or https:// page — not a browser internal page like brave://newtab.

browser-cli dom query "h1"                   # return elements matching CSS selector
browser-cli dom text "h1"                    # get text content of matching elements
browser-cli dom attr "a" href                # get attribute value from elements
browser-cli dom exists ".cookie-banner"      # exits 0 if found, 1 if not
browser-cli dom click ".accept-button"       # click an element
browser-cli dom type "#search" "hello"       # type text into an input

Extract

browser-cli extract links                    # all <a href> links on the page
browser-cli extract images                   # all <img> tags (src + alt)
browser-cli extract text                     # all visible text (innerText)
browser-cli extract json "#data"             # parse JSON inside a CSS selector

Sessions

A session is a snapshot of all open tab URLs, stored inside the extension via chrome.storage.local. Sessions survive browser restarts but are lost if the extension is uninstalled or extension data is cleared.

browser-cli session save before-meeting      # save current tabs as a named session
browser-cli session load before-meeting      # reopen all saved tabs
browser-cli session list                     # list all saved sessions (name, tab count, date)
browser-cli session remove before-meeting    # delete a saved session
browser-cli session diff session-a session-b # show which URLs were added / removed
browser-cli session auto-save on             # auto-save after every tab change
browser-cli session auto-save off

Misc

browser-cli clients                          # show connected browser info
browser-cli install brave                    # (re)register the native host

Python API

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.

# Navigation
b.open("https://example.com")
b.open("https://example.com", background=True)
b.open("https://example.com", window="work")
b.reload()
b.hard_reload()
b.back()
b.forward(tab_id=1234)
b.focus_url("github")

# Tabs
tabs = b.tabs_list()          # list of dicts: id, windowId, title, url, active, ...
b.tabs_active(1234)
b.tabs_close(1234)
b.tabs_close_inactive()
b.tabs_close_duplicates()
b.tabs_filter("youtube")      # list of matching tabs
b.tabs_query("pull request")
b.tabs_count("github")        # int
html = b.tabs_html()          # full HTML string of active tab
b.tabs_sort(by="domain")
b.tabs_merge_windows()
b.tabs_dedupe()

# Tab groups
groups = b.group_list()       # list of dicts: id, title, color, collapsed, tabCount
b.group_open("research")      # creates group, returns { id, name }
b.group_close(42)
b.group_tabs(42)              # tabs inside a group

# Windows
windows = b.windows_list()
b.windows_rename(1, "work")
b.windows_open()
b.windows_close(1)

# DOM  (active tab must be http/https)
elements = b.dom_query("h2")           # list of { tag, text, attrs }
texts = b.dom_text(".article p")       # list of strings
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")

# Extract
links = b.extract_links()     # list of { text, href }
images = b.extract_images()   # list of { alt, src }
text = b.extract_text()       # string
data = b.extract_json("#app-data")  # parsed Python object

# Sessions
b.session_save("before-meeting")
b.session_load("before-meeting")
sessions = b.session_list()   # [{ name, tabs, savedAt }, ...]
b.session_remove("before-meeting")
diff = b.session_diff("session-a", "session-b")
# diff = { "added": [...urls], "removed": [...urls] }
b.session_auto_save(True)

# Misc
clients = b.clients()

Error handling

from browser_cli import BrowserCLI, BrowserNotConnected

b = BrowserCLI()
try:
    tabs = b.tabs_list()
except BrowserNotConnected:
    print("Browser is not running or extension is not loaded")
except RuntimeError as e:
    print(f"Browser returned an error: {e}")

Example scripts

See examples/demo.py (Python) and examples/demo.sh (Bash) for full walkthroughs covering tabs, groups, DOM extraction, and session management.

uv run python examples/demo.py
bash examples/demo.sh

Limitations

  • Chrome internal pages (chrome://, brave://, about:) cannot be scripted. DOM and extract commands only work on regular http:// and https:// pages.
  • Profile switching via windows open --profile opens a plain window; launching a different profile requires the browser to be started externally with --profile-directory.
  • One browser at a time — the native host socket supports one connected extension. Running multiple browser profiles simultaneously is not supported.
  • Linux and macOS only — Windows native messaging paths are not yet handled.
S
Description
No description provided
Readme 3.4 MiB
v0.15.6 Latest
2026-06-18 00:52:04 +02:00
Languages
Python 80.3%
TypeScript 18.4%
Shell 0.7%
Just 0.5%