Rust CLI that connects to an existing Chrome browser via the DevTools Protocol. Auto-connects by default — no manual WebSocket URL needed.
brew tap opzero1/chrome-devtools-cli
brew install chrome-devtoolscargo build --release
# Binary: ./target/release/chrome-devtoolsInspired by chrome-devtools-mcp — the official MCP server for Chrome DevTools. It works well, but MCP-based browser tools consume a lot of token context: every interaction sends and receives large protocol payloads through the MCP layer.
99% of the time the browser being controlled is the user's own Chrome with their own credentials, so there is no need for a full headless browser stack like Puppeteer or Playwright, and no need for the MCP overhead.
This is a lightweight Rust binary that talks directly to Chrome's DevTools Protocol. One command in, one result out. No separate browser process, no credential handoff, no heavyweight runtime. The agent skill for this tool is a single SKILL.md file — the entire context overhead is this documentation.
chrome-devtools navigate https://example.com
│
├─ Try daemon (Unix socket /tmp/chrome-devtools-daemon.sock)
│ └─ If running → send command → get result
│
├─ If no daemon → spawn one (background process)
│ └─ Daemon connects to Chrome WebSocket (approval may appear here)
│ └─ Listens on Unix socket, configurable idle timeout (default 5m)
│
└─ Fallback → direct WebSocket connection (no daemon)
The daemon keeps a persistent WebSocket connection while it is alive, so repeated commands usually reuse the same connection. If the daemon restarts (idle timeout/crash) or the CLI falls back to direct mode, Chrome may prompt for DevTools access again.
Chrome must have remote debugging enabled:
- Open Chrome
- Go to
chrome://inspect/#remote-debugging - Enable the remote debugging server
By default, the CLI reads DevToolsActivePort from Chrome's user data directory:
| OS | Default path |
|---|---|
| macOS | ~/Library/Application Support/Google/Chrome/ |
| Linux | ~/.config/google-chrome/ |
| Windows | %LOCALAPPDATA%\Google\Chrome\User Data\ |
Override with --user-data-dir, --channel (beta/canary/dev), or --ws-endpoint.
Every page-level command outputs a friendly target name like [target:red-snake]. This is a deterministic word-pair derived from Chrome's internal target ID — same page always gets the same name.
For multi-step automation, treat --target as required after your first page-discovery command (navigate or list-pages).
# Navigate — note the target name
chrome-devtools navigate https://example.com
# Navigated to https://example.com
# [target:red-snake]
# Pin subsequent commands to the same page
chrome-devtools --target red-snake screenshot --output /tmp/page.png
chrome-devtools --target red-snake evaluate "document.title"Without --target, commands default to page index 0, which may vary as Chrome reorders tabs. Use unpinned commands only for one-off checks; for workflows, always capture and reuse the target name.
list-pages shows all pages with their friendly names:
[0] (green-dog) My App — https://localhost:3000
[1] (red-snake) Example Domain — https://example.com
[2] (bold-stag) GitHub — https://github.com
You can also use --page <index> for quick one-offs, or pass the raw hex target ID.
| Command | Description |
|---|---|
navigate <url> |
Go to URL (waits for load) |
navigate --back |
Go back in history |
navigate --forward |
Go forward |
navigate --reload |
Reload page |
new-page <url> |
Open new tab |
close-page <index> |
Close tab by index |
select-page <index> |
Bring tab to front |
list-pages |
List all open tabs |
| Command | Description |
|---|---|
screenshot --output <path> |
Save screenshot to file |
screenshot --full-page |
Capture full scrollable page |
record-video --output <path> |
Record page to MP4 video (requires ffmpeg) |
evaluate <expr> |
Run JavaScript, return result |
snapshot |
Accessibility tree dump |
| Command | Description |
|---|---|
click <selector> |
Click element by CSS selector |
fill <selector> <value> |
Fill input field |
type-text <text> |
Type into focused element |
press-key <key> |
Press key (e.g. Enter, Control+A) |
hover <selector> |
Hover over element |
| Command | Description |
|---|---|
resize <w> <h> |
Set viewport size |
wait-for <text> [--timeout ms] |
Wait for text to appear (default 30s) |
Record the visible page to an MP4 file. Requires ffmpeg in PATH.
# Record 5 seconds at default settings (12 fps, quality 80)
chrome-devtools --target red-snake record-video --output recording.mp4
# Record 10 seconds at higher quality and frame rate
chrome-devtools --target red-snake record-video --output demo.mp4 --duration 10 --fps 24 --quality 90
# Cap resolution to 1280x720
chrome-devtools --target red-snake record-video --output small.mp4 --width 1280 --height 720| Flag | Default | Description |
|---|---|---|
--output / -o |
(required) | Output MP4 file path |
--duration |
5 |
Recording duration in seconds |
--fps |
12 |
Target frames per second |
--quality |
80 |
JPEG quality for screencast frames (1-100) |
--width |
(none) | Max capture width |
--height |
(none) | Max capture height |
Install ffmpeg: brew install ffmpeg (macOS), sudo apt install ffmpeg (Ubuntu), or see ffmpeg.org.
| Flag | Description |
|---|---|
--target <name> |
Target page by friendly name or raw ID |
--page <index> |
Target page by index |
--json |
JSON output |
--ws-endpoint <url> |
Explicit WebSocket URL |
--user-data-dir <path> |
Custom Chrome profile directory |
--channel <ch> |
Chrome channel (stable/beta/canary/dev) |
--daemon-idle-timeout <value> |
Daemon idle timeout (30m, 1h, never, or Ns/Nm/Nh) |
You can also set CHROME_DEVTOOLS_DAEMON_IDLE_TIMEOUT as an environment fallback.
Examples:
# Keep daemon alive for 30 minutes of inactivity
chrome-devtools --daemon-idle-timeout 30m list-pages
# Keep daemon alive for 1 hour (env fallback)
CHROME_DEVTOOLS_DAEMON_IDLE_TIMEOUT=1h chrome-devtools navigate https://example.com
# Disable idle shutdown
chrome-devtools --daemon-idle-timeout never snapshot- Socket:
/tmp/chrome-devtools-daemon.sock - PID file:
/tmp/chrome-devtools-daemon.pid - Idle timeout: 5 minutes by default (configurable via
--daemon-idle-timeoutorCHROME_DEVTOOLS_DAEMON_IDLE_TIMEOUT) - Protocol: Length-prefixed JSON over Unix socket
- Spawned by: First CLI invocation (transparent to user)
- Kill manually:
pkill -f __daemon__or delete the socket
If a daemon is already running, passing a new --daemon-idle-timeout updates it for future idle periods (no manual restart required).
src/
├── main.rs # CLI (clap) + daemon-first dispatch
├── cdp.rs # Raw CDP over WebSocket (JSON-RPC)
├── browser.rs # Auto-connect (DevToolsActivePort)
├── daemon.rs # Background daemon (persistent connection)
├── client.rs # Talks to daemon via Unix socket
├── protocol.rs # IPC message types
├── friendly.rs # Target ID → word-pair names
└── commands/
├── navigate.rs
├── pages.rs # list/new/close/select/resize/wait-for
├── screenshot.rs
├── record_video.rs # screencast → ffmpeg MP4
├── evaluate.rs
├── input.rs # click/fill/type/press/hover
└── snapshot.rs
# 1. Discover/select the page and capture [target:name]
chrome-devtools navigate https://example.com
# [target:red-snake] (save this)
# 2. From here on, always pin --target
chrome-devtools --target red-snake snapshot
chrome-devtools --target red-snake screenshot --output /tmp/page.png
# 3. Interact
chrome-devtools --target red-snake fill "#email" "user@example.com"
chrome-devtools --target red-snake click "#submit"
# 4. Extract data
chrome-devtools --target red-snake evaluate "document.title"Always pass --target from step 2 onward to stay on the same page.
Patch releases are tag-driven.
- Bump
Cargo.tomlandFormula/chrome-devtools.rbto the new patch version. - Push a tag like
v0.1.2. - Let
.github/workflows/release.ymluploadchrome-devtools-macos-arm64.zipto the GitHub release. - Compute the published zip
sha256and replace the formula placeholder before testing the tap install. - Run the brew install/test smoke flow against the published asset.
skill/chrome-devtools/SKILL.md is a Claude Code skill that teaches the agent how to use this binary. Drop it into any Claude Code plugin's skills/ directory and set chrome-devtools to the binary path. The skill covers the full workflow, all commands, and the --target pinning pattern — everything needed to reliably automate Chrome without large context overhead.
MIT