chrome-cdp-cli

module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT

README

chrome-cdp

Drive your real, already-running Chrome — your actual tabs, logins, cookies, and extensions — from the command line. Built for humans and AI agents alike: every command speaks one JSON envelope and one stable exit-code contract.

Go Reference CI CodeQL License Go Version

chrome-cdp open https://example.com     # open a tab, get its id
chrome-cdp snap --role button           # see what's clickable — by accessible name
chrome-cdp find "login button"          # or describe it and get ranked matches
chrome-cdp click --by name "Sign in"    # act by meaning, not a brittle CSS id
chrome-cdp click --at 512,340           # or at a pixel, for canvas/WebGL surfaces

Because it attaches to the browser you're already using, an app you're signed into loads authenticated — no headless browser, no second login, no credential ever typed.

Quickstart

  1. Install — macOS via Homebrew, or Go:

    brew install --cask sanketsudake/tap/chrome-cdp
    # or:
    go install github.com/sanketsudake/chrome-cdp-cli/cmd/chrome-cdp@latest
    

    Recent Homebrew may print a tap-trust notice for third-party taps on first install. The install still proceeds; to acknowledge it explicitly, run brew trust --cask sanketsudake/tap/chrome-cdp first.

  2. Let Chrome accept a debugger. Launch it with the flag — this never prompts:

    open -a "Google Chrome" --args --remote-debugging-port=9222   # macOS
    google-chrome --remote-debugging-port=9222                    # Linux
    

    Or, to attach to a Chrome that is already running on the default profile, toggle chrome://inspect/#remote-debugging on. That path raises a consent prompt on every fresh attach, and the prompt is modal to the whole browser — until it is answered Chrome accepts no other input, and it can sit behind the window, so an unanswered one looks like a crash. chrome-cdp waits for it (see --consent-timeout) and never suppresses it.

  3. Check the connectionchrome-cdp doctor actually connects and reports ready, consent_pending, or no_endpoint, with the exact fix.

  4. Drive it:

    chrome-cdp use url:github        # pick a tab; later commands need no --target
    chrome-cdp snap                  # read the page as an accessibility tree
    chrome-cdp click --by name "New" --role button
    

That's the whole loop: list → use → snap → act → verify. The logged-in web app guide walks it end to end.

Why chrome-cdp

  • Your real session. It drives the Chrome you're logged into — real cookies, SSO, extensions — so there's nothing to authenticate and no secret to store.
  • Three ways to point at a control, in order of durability. By meaning — ARIA accessible name, visible label, or grid cell — which survives the cosmetic churn that breaks CSS ids. By description, when you don't know the name: find "login button" ranks candidates and returns the exact name, a ref, and a centre point. By pixel, when there is nothing to name: click --at 512,340 is the only way into a canvas, map, or PDF viewer, which expose a single node to the accessibility tree. (A drop zone with no file input behind it takes upload --drop, for the same reason.)
  • Made for automation. One JSON envelope, one exit-code contract; a background daemon holds the connection so the consent prompt appears once per session, not per command.
  • Drives the hard widgets. Portal menus, multi-level cascade prompts, and native <select>s that a synthetic click can't open — the select verb opens them.
  • Works on modern Chrome. It reads Chrome's DevToolsActivePort and connects directly, so it keeps working where the classic --remote-debugging-port flag stopped (default profile, Chrome M136+).

The loop, in commands

The Quickstart covered picking a tab. What follows is the part you repeat.

Read what's there — as structure, not pixels.

chrome-cdp snap --role button --grep "[AP]M"   # the a11y tree, filtered server-side
chrome-cdp find "search bar" --role textbox    # or describe it: ref, exact name, centre
chrome-cdp grid                                # a table as {headers, rows}

Act on it — by meaning where there is one, by pixel where there isn't.

chrome-cdp fill --by cell "Mon, 7/13" "8"           # a grid input by its column header
chrome-cdp fill --by label "Notes" "hi"             # a form control by its visible label
chrome-cdp select "Time Type" "Projects > Acme > Time Entry" --role textbox
chrome-cdp upload --drop ".dropzone" ./report.pdf   # a drop zone with no file input

Confirm it worked — before the next step depends on it.

chrome-cdp click --by name "Approve" --wait-text "Success"   # act and confirm in one call
chrome-cdp wait --idle                                       # settle an SPA, not a fixed sleep
chrome-cdp net --failed --body                               # what the page actually requested

When no verb covers what you need, raw reaches any CDP method directly — chrome-cdp raw Network.setCacheDisabled '{"cacheDisabled":true}'. Full command, flag, and exit-code tables live in the CLI reference.

For AI agents

chrome-cdp is meant to be a tool an agent calls: snap returns the page's actionable structure as text (roles, names, states, refs) instead of pixels, every result is one parseable envelope, and failures classify by exit code so the agent branches on a number, not on prose. See Using chrome-cdp from an AI agent.

An Agent Skill that teaches the whole loop ships in skills/drive-chrome-cdp — point your harness at it.

Use it from an MCP client

chrome-cdp mcp runs the same verbs as a Model Context Protocol server on stdio — one entry in your client's config, no wrapper to write:

{ "mcpServers": { "chrome-cdp": { "command": "chrome-cdp", "args": ["mcp"] } } }

It exposes a deliberately small set of tools (grouped, chrome_cdp_-prefixed, with a batch that collapses a five-step interaction into one round trip), and every result is the same envelope the CLI prints — a failure comes back with its code and exit, not as prose.

It requires a policy allow-list and refuses to start without one. Run chrome-cdp policy init on the tab you want it to drive first: at a shell you decided to run each command, but an assistant holding this connection can act as you on every site you are signed into, and that deserves an explicit boundary. eval and raw are denied in this mode unless you pass --allow-eval — they can navigate the tab out of the allow-list themselves, which would make the boundary decorative. chrome-cdp mcp --read-only is a good way to try it — it exposes only verbs that cannot change a page or close a tab. See the MCP section of the CLI reference.

Configure

Persist flags you'd otherwise retype in ~/.config/chrome-cdp/config.toml — see config.example.toml.

json = true            # default to machine-readable output
timeout = "10s"
target = "url:github"  # default tab when neither --target nor `use` is set

Precedence, highest first: command-line flag > CHROME_CDP_* env var > config file > built-in default. Shell completion is built in: chrome-cdp completion bash|zsh|fish|powershell.

Security

A live debug endpoint is full control of whatever your Chrome is signed into — treat enabling remote debugging (by flag or by toggle) like opening a local root shell into your browser's sessions, and only do it when you intend to automate.

Bound it with a policy. chrome-cdp policy init allow-lists the tab you are on; the CLI then refuses everything else, and chrome-cdp mcp will not start without one. It is off until you configure it, and it guards against a misdirected caller rather than acting as a sandbox — see Policy for origins, verbs, upload roots, and the audit log.

  • Loopback only. It connects to 127.0.0.1 and never binds the debug port to a non-loopback interface. It never suppresses Chrome's "Allow debugging?" consent or the automation banner.
  • Don't pass secrets as arguments. type <selector> <text> takes text as a positional argument, visible in ps and shell history — don't type passwords through it on a shared machine.
  • Password fields read back masked. value, snap, and find report a password input's contents as bullets, and upload --drop never attaches its temporary input to the page, so a page-global listener cannot see your files. This is accident-prevention, not a boundary: eval still reads whatever the page holds, deliberately — one explicit path beats several incidental ones.
  • Managed-launch fallback uses your system Chrome with a dedicated profile and does not disable the sandbox.

Documentation

Develop

go build -o chrome-cdp ./cmd/chrome-cdp
go test ./...          # spawns a headless Chrome for the integration tests
go test -short ./...   # skip the live-Chrome tests

Architecture: internal/result (envelope + exit codes), internal/target (target grammar), internal/browser (connection logic), internal/chrome (chromedp-backed driver), internal/daemon (the held-connection RPC), internal/cli (the cobra command tree).

License

Released under the MIT License.

Directories

Path Synopsis
cmd
chrome-cdp command
Command chrome-cdp drives the user's local Chrome over CDP.
Command chrome-cdp drives the user's local Chrome over CDP.
internal
browser
Package browser holds the connection-layer logic for reaching Chrome over CDP: the DevToolsActivePort reader (Path B), the endpoint resolution, the vocabulary a probe answers in (WSState), and the connection-ladder decision.
Package browser holds the connection-layer logic for reaching Chrome over CDP: the DevToolsActivePort reader (Path B), the endpoint resolution, the vocabulary a probe answers in (WSState), and the connection-ladder decision.
chrome
Package chrome connects to Chrome over CDP (via chromedp) and exposes the Browser port the CLI commands drive.
Package chrome connects to Chrome over CDP (via chromedp) and exposes the Browser port the CLI commands drive.
chrometest
Package chrometest provides a shared chrome.Browser test double.
Package chrometest provides a shared chrome.Browser test double.
cli
Package cli builds chrome-cdp's cobra command tree and translates every command into the uniform result envelope + exit-code contract.
Package cli builds chrome-cdp's cobra command tree and translates every command into the uniform result envelope + exit-code contract.
config
Package config loads optional persistent defaults from a TOML file and merges them with CHROME_CDP_* environment variables into the effective global-flag defaults.
Package config loads optional persistent defaults from a TOML file and merges them with CHROME_CDP_* environment variables into the effective global-flag defaults.
daemon
Package daemon runs a background process that holds one CDP connection and serves Browser calls over a Unix socket.
Package daemon runs a background process that holds one CDP connection and serves Browser calls over a Unix socket.
encode
Package encode turns captured frames into a shareable artifact: an animated GIF, a numbered PNG directory, or — when ffmpeg is on PATH — an MP4/WebM.
Package encode turns captured frames into a shareable artifact: an animated GIF, a numbered PNG directory, or — when ffmpeg is on PATH — an MP4/WebM.
eventbuf
Package eventbuf provides the bounded, concurrency-safe ring buffers that retain the CDP events a held connection receives, per target.
Package eventbuf provides the bounded, concurrency-safe ring buffers that retain the CDP events a held connection receives, per target.
mcp
Package mcp serves chrome-cdp's verbs to Model Context Protocol clients over stdio (RFC-0004).
Package mcp serves chrome-cdp's verbs to Model Context Protocol clients over stdio (RFC-0004).
policy
Package policy bounds what chrome-cdp may drive: which origins it will act on, which verbs are permitted there, and which local paths may be uploaded.
Package policy bounds what chrome-cdp may drive: which origins it will act on, which verbs are permitted there, and which local paths may be uploaded.
probetest
Package probetest builds the stub debug endpoints that RFC-0013's tests run against: a port that accepts and stalls, one that completes the WebSocket upgrade (now or later), and one with nothing listening at all.
Package probetest builds the stub debug endpoints that RFC-0013's tests run against: a port that accepts and stalls, one that completes the WebSocket upgrade (now or later), and one with nothing listening at all.
recipe
Package recipe loads, validates, and resolves recipes: saved `session` scripts with a small header.
Package recipe loads, validates, and resolves recipes: saved `session` scripts with a small header.
result
Package result defines chrome-cdp's uniform output envelope and the stable exit-code contract that both humans and the Claude skill depend on.
Package result defines chrome-cdp's uniform output envelope and the stable exit-code contract that both humans and the Claude skill depend on.
state
Package state persists the sticky "current target" across CLI invocations, keyed per browser endpoint, under $XDG_STATE_HOME.
Package state persists the sticky "current target" across CLI invocations, keyed per browser endpoint, under $XDG_STATE_HOME.
target
Package target resolves a <target> spec against the live set of Chrome tabs.
Package target resolves a <target> spec against the live set of Chrome tabs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL