Skip to main content

chrome-agent

PyPI version PyPI downloads Python versions License

A CLI tool that gives AI coding agents the ability to observe and interact with Chrome browsers via the Chrome DevTools Protocol.

Multiple agents and humans can share the same browser simultaneously, each with isolated event subscriptions. One agent drives while another observes network traffic. A human browses while an agent watches for errors. Four agents run a coordinated test suite against a single browser. Each participant sees only the events they subscribed to -- no interference.

Why this exists

AI coding agents need to see and interact with browsers -- to test their code, debug automation, inspect page state. The standard approach (browser MCP tools) uses a persistent server with protocol negotiation and verbose response formatting. chrome-agent takes a different approach: direct access to Chrome's DevTools Protocol with no abstraction layer.

This means full CDP protocol access -- every command, every event, every domain Chrome exposes. Not a curated subset of capabilities, but the complete protocol. Agents compose interactions from CDP primitives the same way DevTools does.

Tracks the running browser, not its own version

Because there is no abstraction layer, chrome-agent tracks your browser rather than its own release. The CLI sends the method name and parameters you give it straight to Chrome and streams back the events you subscribe to -- nothing is validated against a bundled schema. So any command, event, or domain your installed Chrome supports just works, including protocol surface added after the version of chrome-agent you installed. There is no curated subset to fall behind.

For example, the CrashReportContext and WebMCP domains (added to CDP in later Chrome releases) are both absent from an older chrome-agent's typed bindings, yet a method on one returns a normal result through the CLI, with no change to chrome-agent:

chrome-agent myproject-01 CrashReportContext.getEntries
# {"entries": []}

The one point-in-time artifact is the typed Python classes (see Python API) -- an optional convenience layer that snapshots the schema at generation time. They never gate access: CDPClient.send(method=..., params=...) reaches any method regardless. And help reads the protocol schema live from the running browser, so its documentation is always as current as your Chrome.

Installation

uv tool install chrome-agent

Or add to a project:

uv add chrome-agent

Requires Google Chrome or Chromium installed on the system. Single runtime dependency (websockets). No Playwright, no browser downloads.

Quick Start

# Launch a browser -- auto-allocates a port and names the instance
chrome-agent launch
# {"name": "myproject-01", "port": 9222, "pid": 58469, "browser_version": "Chrome/147"}

# Check what's running
chrome-agent status
# myproject-01  port 9222
#   [1] 956FD3C2  https://example.com  "Example Domain"

# Read the page title
chrome-agent myproject-01 Runtime.evaluate '{"expression": "document.title", "returnByValue": true}'

# Navigate
chrome-agent myproject-01 Page.navigate '{"url": "https://example.com"}'

# Take a screenshot (returns base64 PNG in JSON)
chrome-agent myproject-01 Page.captureScreenshot '{"format": "png"}'

# Discover available commands
chrome-agent help myproject-01 Page
chrome-agent help myproject-01 Page.navigate

# Stop the browser when done
chrome-agent stop myproject-01

# Or stop a whole related set at once (quote the pattern -- see Instance Patterns)
chrome-agent stop 'myproject-*'

Instance Patterns

Anywhere an instance name is accepted, a glob works instead -- *, ?, [abc], matched case-sensitively against the registered names.

chrome-agent stop 'myproject-*'      # stops every match, printing the names first
chrome-agent status 'myproject-*'    # lists every match
chrome-agent 'myproject-0[2]' Page.navigate '{"url": "https://example.com"}'

stop and status act on every match. The single-browser commands -- attach, help and one-shot CDP calls -- resolve a pattern that matches exactly one instance and otherwise error with the candidates listed, rather than picking one. A pattern matching nothing is an error, not a silent no-op.

Quote the pattern. Your shell expands an unquoted glob before chrome-agent sees it, and zsh aborts the command outright when nothing in the working directory matches (zsh: no matches found: myproject-*).

A literal name is never treated as a pattern, so chrome-agent stop myproject-01 cannot sweep up myproject-02. Because stop --target closes one tab, combining it with a pattern that matches several instances is refused.

Tab Completion

zsh only, for now. Two ways to install it; pick one.

Sourced from .zshrc -- one line, works anywhere, costs one subprocess (~60 ms) at shell startup. It must come after compinit:

source <(chrome-agent completions zsh)

Installed as a file -- no startup cost, but the directory has to be on $fpath before compinit runs, which is the step that is easy to miss:

mkdir -p ~/.config/zsh/completions
chrome-agent completions zsh > ~/.config/zsh/completions/_chrome-agent
fpath=(~/.config/zsh/completions $fpath)
autoload -Uz compinit && compinit

That second block goes in .zshrc, in that order. If the directory is not on $fpath, the file is simply never read and Tab does nothing -- there is no error to tell you so.

Either way, open a new shell and check it took:

chrome-agent <TAB>

You should get the subcommands and your running instances, each with a description. If nothing happens, echo $_comps[chrome-agent] should print _chrome-agent; an empty result means the completion was never registered.

The generated file is a snapshot of the completion logic, so regenerate it after upgrading chrome-agent (the candidates it offers are always read live; the function itself is not). Sourcing from .zshrc avoids that entirely.

Completes the subcommands, their flags, the live instance names, and CDP method and event names:

chrome-agent stop reader<TAB>              -> ensorcell-reader-01, described by port and tab count
chrome-agent taleb-01 Page.nav<TAB>        -> Page.navigate
chrome-agent attach taleb-01 +Page.load<TAB> -> +Page.loadEventFired

Instance names are read from the registry as you type, so they are what is actually registered rather than a list baked in at install time. Methods and events come from the running browser's own /json/protocol -- the protocol this Chrome implements, not a snapshot shipped with chrome-agent -- cached on disk under the browser version, so a Chrome upgrade invalidates it. Without a running browser you simply get no candidates.

The protocol cache lives under $XDG_CACHE_HOME/chrome-agent (default ~/.cache/chrome-agent), one file per browser version; deleting it is safe and costs one 7 ms refetch.

Two Channels

chrome-agent uses a two-channel pattern for browser interaction:

One-shot mode (commands)

Send a single CDP command. Connects, sends, prints JSON response, disconnects.

chrome-agent <instance> Domain.method '{"param": "value"}'

Good for spot checks, screenshots, quick queries. ~50-80ms per call. If only one instance is running, the instance name can be omitted.

Attach mode (events)

Persistent connection with isolated event subscriptions. Streams events to stdout as JSON lines.

chrome-agent attach <instance> +Page.loadEventFired +Network.requestWillBeSent

Run it in the background while sending one-shot commands:

# Background: observe events
chrome-agent attach myproject-01 +Page.loadEventFired +Network.requestWillBeSent > /tmp/events.jsonl &

# Foreground: send commands -- events appear in the attach stream
chrome-agent myproject-01 Page.navigate '{"url": "https://example.com"}'

Subscribe to exactly the events you need. Each attach session is isolated -- subscribing to Network events in one session does not affect other sessions.

An attach session exits on its own once it has outlived its purpose -- when its instance is retired from the registry, or its browser is gone or no longer reachable -- so a backgrounded observer never lingers indefinitely after the thing it was watching is gone. It also shuts down cleanly (detaching its CDP session) on SIGTERM/SIGINT, even while idle with no events arriving. A transient registry read or CDP-port blip is ridden out rather than acted on.

Operational Commands

chrome-agent launch [--headless] [--fingerprint PATH] [--port PORT] [--no-window-border]
chrome-agent status [<instance|glob>]
chrome-agent attach <instance|glob> [+Event ...] [--target SPEC | --target-id ID | --target-index N | --url SUBSTRING]
chrome-agent stop <instance|glob> [--target SPEC | --target-id ID | --target-index N | --url SUBSTRING]
chrome-agent help [<instance|glob>] [Domain | Domain.method]
chrome-agent cleanup
chrome-agent completions <zsh | instances | methods | events> [<instance>]
chrome-agent --version
Command Description
launch Find Chrome, launch with CDP enabled. Auto-allocates a port and names the instance from the current directory.
status List running instances with their page targets (IDs, URLs, titles). Accepts a glob to list a matching subset.
attach Persistent event observation with isolated subscriptions. Use --target (fewer than 8 digits is a tab index, anything else a target-id prefix), --url substring, or the explicit --target-id / --target-index for multi-tab browsers.
stop Gracefully shut down a browser instance (Browser.close) or close a specific tab (Target.closeTarget). Accepts a glob, stopping every matching instance. Use --target or --url to close a single tab without affecting the browser; because this closes a tab, prefer the explicit --target-id / --target-index.
help Query the browser's protocol schema. Lists domains, commands, events, parameters.
cleanup Remove stale instances (dead browsers) and their session directories.
completions zsh prints a shell completion; instances, methods and events print name:description lines for the registered instances and for the running browser's CDP protocol (what the completion reads as you type).
--version Print the installed chrome-agent version (-V alias) and exit.

Instances are tracked in a registry at /tmp/chrome-agent/registry.json. A headed browser's instance is automatically removed from the registry when its window is closed (its session directory is cleaned up too), so status reflects what is actually running. Liveness is determined by process identity plus port attribution, not a bare PID-existence check: the recorded PID counts only if it is a live process of the launching user whose start time matches what was recorded at launch (so a recycled or namespace-local PID never masquerades as the browser), and a listening CDP port counts only if a process claiming that port with this instance's profile directory can be found -- so browsers started via wrapper/snap launchers (which fork the real browser into another process) are still reported correctly, while a port since claimed by a different browser is not mistaken for this one. A transient connection drop does not retire a live instance: a host suspend/resume severs the supervisor's CDP connection while Chrome keeps running, so the supervisor reconnects and keeps supervising; retirement happens only once the CDP port stops listening. cleanup removes any entries that remain (headless instances, or browsers that were killed abruptly).

Two consequences worth knowing. Launching from inside a PID-namespaced sandbox (a container, bubblewrap, some agent-CLI sandboxes) records the sandbox's local PID in the shared registry; the identity check recognizes such an entry as stale once its browser is gone, instead of treating the aliased host PID as a live browser forever. stop verifies its target before acting: it never sends Browser.close to a port that is serving a different browser (it terminates the instance's own verified process instead, or just cleans up the stale entry), and its SIGTERM fallback only ever fires at a PID verified to be the instance's own browser process.

Interacting with Elements

Agents interact with page elements using a three-step pattern: locate, act, verify.

# Locate -- find element coordinates via JavaScript
chrome-agent myproject-01 Runtime.evaluate '{"expression": "(() => { const r = document.querySelector(\"#submit\").getBoundingClientRect(); return {x: r.x+r.width/2, y: r.y+r.height/2}; })()", "returnByValue": true}'

# Act -- dispatch real input events at those coordinates
chrome-agent myproject-01 Input.dispatchMouseEvent '{"type": "mousePressed", "x": 400, "y": 300, "button": "left", "clickCount": 1}'
chrome-agent myproject-01 Input.dispatchMouseEvent '{"type": "mouseReleased", "x": 400, "y": 300, "button": "left", "clickCount": 1}'

# Verify -- confirm the action worked
chrome-agent myproject-01 Runtime.evaluate '{"expression": "document.title", "returnByValue": true}'

Chrome processes dispatched input events identically to physical input. A human watching the browser sees the cursor move, buttons depress, text highlight, and pages load in real time.

Python API

from chrome_agent.cdp_client import CDPClient, get_ws_url
from chrome_agent.domains.page import Page
from chrome_agent.domains.runtime import Runtime

async with CDPClient(ws_url=get_ws_url(port=9222)) as cdp:
    page = Page(client=cdp)
    runtime = Runtime(client=cdp)

    await page.navigate(url="https://example.com")
    result = await runtime.evaluate(expression="document.title", return_by_value=True)
    print(result["result"]["value"])

54 typed domain classes with snake_case methods, generated from Chrome's protocol schema. They are an optional convenience layer -- a point-in-time snapshot, not a gate. For any method newer than the snapshot, call CDPClient.send(method=..., params=...) directly (see Tracks the running browser, not its own version).

Window Border

So you can tell an agent-driven window apart from your own Chrome windows, every launched browser is marked by default: a colored border + corner badge around each tab (a stable, per-instance color derived from the instance name), and a title prefix so the window reads as 🤖 <instance> — <the page's own title> in the taskbar / Alt-Tab. The marker is drawn in a closed shadow DOM and adds no automation-detection signal (verified against bot.sannysoft.com and CreepJS).

chrome-agent launch                     # marked (default)
chrome-agent launch --no-window-border  # no marker

The marker is suppressed automatically when running --headless (no visible window) or with --fingerprint (the in-page marker is page-observable, and stealth is the point on the sites where fingerprinting is used).

Browser Fingerprinting

For sites that detect automated browsers, launch with a fingerprint profile:

chrome-agent launch --fingerprint profile.json
{
    "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ...",
    "platform": "Linux x86_64",
    "vendor": "Google Inc.",
    "language": "en-US",
    "timezone": "America/Chicago",
    "viewport": {"width": 1920, "height": 1080}
}

Spoofs the user agent (HTTP header and JavaScript), viewport, language, and timezone via Chrome launch flags -- persistent across navigations, with no JavaScript injection.

It deliberately does not patch navigator.webdriver, navigator.platform, navigator.vendor, or window.chrome. An empirical detection audit found those JS overrides are each independently detectable and make the browser more detectable, not less: they flip bot.sannysoft.com's WebDriver test from pass to fail (the override makes navigator.webdriver an own property) and raise CreepJS's headless score. A plain CDP-attached Chrome already reports the native navigator.webdriver === false and keeps the genuine window.chrome shape, so the cleanest profile is one that leaves the JS environment untouched. A profile's platform/vendor should match the host OS (they are retained in the schema but not spoofed). Note that WebRTC can still leak the real IP regardless of profile.

For AI Agents

See AGENTS.md for concise agent instructions (the standard for AI agent tool documentation). It covers the mental model (address an instance, send any CDP command), the sense ⇄ act loop, the two channels, the command reference, and gotchas.

The guide ships with the package, so an agent can reach it from any install without a checkout:

chrome-agent guide          # print the guide
chrome-agent guide --path   # print its path, to read with your own file tools

--path is usually what you want: reading the file directly beats paging 20+ KB through stdout. The command is listed in chrome-agent --help, which is how an agent meeting this tool for the first time finds it.

The bundled copy is captured when the release is built, so it always matches the version installed. To make it load automatically -- most agent harnesses read an AGENTS.md from the project root, not from site-packages -- link it into your project:

ln -s "$(chrome-agent guide --path)" AGENTS-chrome-agent.md

AGENTS.md is a tailorable example, not gospel. Its mechanics are exact, but the operating judgment in it is general -- adapt it to your own sites, tasks, and constraints. A good pattern is to keep a private, project-specific layer on top -- site-specific field notes, extraction playbooks, hard-won gotchas -- that references this public manual and extends it, rather than forking a separate set of instructions. Grow yours the same way.

Collaboration

Multiple participants -- humans, AI agents, or both -- can share a browser simultaneously. Each participant creates an independent CDP session with isolated event subscriptions. One agent enabling Network observation does not flood another agent's event stream.

See docs/collaboration-guide.md for:

  • Human-agent collaboration patterns (you browse, agent watches)
  • Agent-driven workflows (agent drives, you supervise)
  • Multi-agent setups with isolated event subscriptions
  • The observation gap (what CDP sees vs what it misses)
  • Full interaction observation via the binding bridge

For real-time observation using Claude Code's Monitor tool, see AGENTS.md for the practical usage path (subscribing, discovering events via help, the gotchas), and docs/monitor-integration.md for the architecture and usage patterns in depth.

Monitor is specific to Claude Code. Agents on other harnesses can still be event-driven rather than falling back to fixed sleeps -- background attach to a file once, then block on scripts/cdp-wait.py, which returns the instant a matching event lands and also catches events that fired before the wait began. See docs/event-driven-without-monitor.md.

Requirements

  • Python >= 3.11
  • Google Chrome or Chromium (system-installed)
  • Linux with xdotool (optional, for virtual desktop pinning)

Releasing (maintainer)

Releases are cut from the project root with release X.Y.Z (or release for an interactive version prompt). The tool bumps pyproject.toml, commits, tags, pushes -- which triggers the PyPI publish workflow via GitHub Actions Trusted Publishing. Release notes auto-generate from commit messages between tags, so commits should read well as changelog entries.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chrome_agent-0.6.0.tar.gz (141.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chrome_agent-0.6.0-py3-none-any.whl (178.7 kB view details)

Uploaded Python 3

File details

Details for the file chrome_agent-0.6.0.tar.gz.

File metadata

  • Download URL: chrome_agent-0.6.0.tar.gz
  • Upload date:
  • Size: 141.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for chrome_agent-0.6.0.tar.gz
Algorithm Hash digest
SHA256 0da3520be521159523b864dbc93d9f67ac8c39c1afb5c08bc0d1851ccc7272ab
MD5 6266313dfc42c5052699115a678fbf8c
BLAKE2b-256 cbebb8433bb8ebfc3cc7559a5a295e0871b4f7b67e86171e292e6687c497c9ab

See more details on using hashes here.

File details

Details for the file chrome_agent-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: chrome_agent-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 178.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for chrome_agent-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a5e09dc775caf355483e0be0e219aaa448a7165661ca3331892f4daa3f24d745
MD5 828ef3162ba3a1fe1093d8362b4d5543
BLAKE2b-256 1367843b63892d5e901e9fea9922983b2e5ef027f7f6a7137ee8aa7584a3814c

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 files

0.5.8

2 files

0.5.7

2 files

0.5.6

2 files

0.5.5

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page