Minimal Chrome controller for AI agents — CDP over Node.js, wrapped as a pip-installable Python library.
Controls a real visible Chrome window. Thread-safe: one shared Chrome instance runs per process, and each thread/asyncio task automatically gets its own Chrome tab (CDP target) and state, so parallel agents never share a tab or interfere with each other's clicks.
Requires: Python 3.11+, Node.js 22+, Google Chrome (macOS, Linux, or Windows — set CHROME_PATH if it isn't found automatically)
# From local path
pip install -e C:/path/to/agentBrowser
# From git
pip install git+https://github.com/yourname/agentBrowser.gitimport agent_browser as ab
# Navigate — returns snapshot with @ref IDs for every interactive element
result = ab.navigate("https://example.com")
# result = {
# "success": True,
# "url": "https://example.com",
# "snapshot": "- link \"About\" [ref=e1, url=...]\n- button \"Sign in\" [ref=e2]\n...",
# "refs": {"e1": {"role": "link", "name": "About", ...}, ...}
# }
# Refresh snapshot after a click or page change
snap = ab.snapshot()
# Interact using @ref IDs from the snapshot
ab.click("e2") # or ab.click("@e2")
ab.type_text("hello") # types into currently focused element
ab.press_key("Enter") # key names: Enter, Tab, Escape, ArrowDown, ...
ab.scroll("down", 600) # scroll down 600px
ab.back() # browser history back
# Close this thread's Chrome instance when done
ab.close_browser()Skip the navigate → find search box → click → type → submit flow:
ab.google_search("machine learning papers 2024")
ab.bing_search("site:github.com pytorch examples")
ab.baidu_search("人工智能最新论文")
ab.reddit_search("best async Python libraries")Each returns the same dict as navigate().
agent_browser.tools provides OpenAI-compatible tool definitions and a dispatch() function so you can plug directly into any LLM tool-calling loop:
from agent_browser.tools import TOOL_DEFINITIONS, dispatch
from openai import OpenAI
import json
client = OpenAI()
messages = [{"role": "user", "content": "Search Google for recent LLM papers and tell me the top results"}]
while True:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=TOOL_DEFINITIONS,
)
msg = response.choices[0].message
messages.append(msg)
if not msg.tool_calls:
print(msg.content)
break
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = dispatch(tc.function.name, args) # returns JSON string
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})Available tools (12): browser_navigate, browser_snapshot, browser_click, browser_type, browser_press_key, browser_scroll, browser_back, browser_close, google_search, bing_search, baidu_search, reddit_search
By default each Chrome instance starts fresh. To give agents your login sessions:
Option A — Import cookies from your existing Chrome profile (run with Chrome closed):
python -m agent_browser setup-profile
# or: agent-browser setup-profile
# To use a specific Chrome profile instead of auto-detecting:
python -m agent_browser setup-profile "Profile 2"Option B — Log in manually inside an agent-managed Chrome window:
python -m agent_browser login
# or: agent-browser loginThis opens Chrome with the agent profile. Log in to whatever sites you need, then close Chrome. Cookies are saved automatically.
How the profile is used at runtime:
~/.agentbrowser/profiles/
shared/ ← your cookies/sessions (written by setup-profile or login)
run_<PID>/ ← per-run scratch copy of shared (created at agent startup)
<PID>_t1/ ← per-thread copy for worker thread 1
<PID>_t2/ ← per-thread copy for worker thread 2
...
Each run and each thread gets its own copy, so a crashed agent never corrupts the shared profile, and parallel threads never share a Chrome session.
To check what's configured:
python -m agent_browser infoOne Chrome instance runs per process. Each thread (or asyncio task) automatically gets its own
Chrome tab — a CDP "target" — on first use, isolated via a ContextVar. No configuration needed:
from concurrent.futures import ThreadPoolExecutor
import agent_browser as ab
def scrape(url: str) -> str:
result = ab.navigate(url)
# ... ab.click(), ab.snapshot(), etc.
# close_session() closes only this thread's tab — NOT the shared Chrome process.
# Do not call close_browser() here: it shuts down Chrome for every thread.
ab.close_session()
return result["snapshot"]
urls = ["https://example.com", "https://github.com", "https://arxiv.org"]
with ThreadPoolExecutor(max_workers=len(urls)) as pool:
snapshots = list(pool.map(scrape, urls))By default all tabs share one Chrome profile (same cookies). Pass isolated=True to
create_session() if a thread needs its own cookie jar.
| Variable | Default | Description |
|---|---|---|
AGENT_BROWSER_PORT |
9222 |
CDP port for the shared Chrome instance |
AGENT_BROWSER_PROFILES_DIR |
~/.agentbrowser/profiles |
Root directory for all managed profiles |
AGENT_BROWSER_PROFILE |
set automatically | Profile subdirectory name; set by ensure_browser_profile() |
AGENT_BROWSER_STATE_DIR |
set per-target | Per-tab dir for refs.json; set automatically |
CHROME_PATH |
auto-detect | Override the Chrome executable path (required if Chrome is installed somewhere non-standard) |
AGENT_BROWSER_NODE |
auto-detect | Override which Node.js 22+ binary to run the CLI with. agentBrowser needs the native WebSocket global (Node 22+); if the node on PATH is older, agentBrowser also checks common Homebrew/nvm install locations before giving up |
The underlying CLI can be used standalone without Python:
node src/cli.js start # launch Chrome on port 9222
node src/cli.js open https://example.com # navigate
node src/cli.js snapshot --json # dump interactive elements as JSON
node src/cli.js click @e1 # click by @ref
node src/cli.js keyboard type "search query" # type text
node src/cli.js keyboard press Enter # press a key
node src/cli.js scroll down 800 # scroll
node src/cli.js back # navigate back
node src/cli.js close # close Chrome
node src/cli.js screenshot page.png # take screenshotSame env vars apply: AGENT_BROWSER_PORT, AGENT_BROWSER_STATE_DIR, AGENT_BROWSER_PROFILES_DIR, AGENT_BROWSER_PROFILE, CHROME_PATH.
agentBrowser/
├── pyproject.toml
├── package.json
├── src/
│ ├── cli.js ← Node.js CDP controller (canonical source)
│ └── cdp.js ← CDP WebSocket client
├── agent_browser/ ← Python package (pip install target)
│ ├── __init__.py ← public API
│ ├── _core.py ← thread-local slots, CLI subprocess wrapper
│ ├── _profile.py ← shared/run/thread profile management
│ ├── tools.py ← TOOL_DEFINITIONS + dispatch() for LLM tool calling
│ ├── __main__.py ← python -m agent_browser setup-profile / login / info
│ └── data/
│ ├── cli.js ← bundled copy of src/cli.js (for non-editable installs)
│ └── cdp.js ← bundled copy of src/cdp.js
└── scripts/
└── google-smoke-test.js
agent_browser/data/*.js are copies of src/*.js — keep them in sync when modifying the CLI.