chromectl — a friendly CLI for the Chrome DevTools Protocol
Drive a Chrome/Chromium instance from the shell over its remote debugging port. Everything is the Chrome DevTools Protocol (CDP) under the hood; this wraps it in ergonomic subcommands with nice output. Not just network — JS execution, console logs/errors, screenshots, PDFs, cookies, SEO audits, page-reading, form flows, Core Web Vitals / Lighthouse, and a raw escape hatch.
Install
pipx install chromectl # from PyPI
pipx upgrade chromectl # pull later releases
npm i -g lighthouse # optional — only for `lighthouse`
Bleeding edge, straight from the repo:
pipx install git+https://github.com/0xenesbayram/chromectl.git
# or, from a local clone: pipx install .
pipx puts chromectl on your PATH in an isolated env and pulls all Python deps
(websocket-client, rich, playwright, readability-lxml, markdownify).
No playwright install needed — the interaction commands attach to your Chrome
over CDP rather than launching their own browser. Playwright and the read/markdown
libs are imported lazily, so the core commands stay fast.
1. Launch Chrome
chromectl start # headless, port 9222, PERSISTENT profile
chromectl start --headful --port 9223 # visible window, custom port
chromectl start --profile ~/.cache/chromectl # custom profile dir
chromectl start --ephemeral # throwaway profile in /tmp (no persistence)
chromectl start --copy-profile # copy your REAL Chrome profile (logins!) then launch
chromectl start --from-profile /path/to/profile # copy from a specific profile dir
--copy-profile auto-detects your default Chrome user-data-dir (per-OS), copies it into
--profile (skipping caches), and launches from the copy — so you debug with your real
logins without touching/using the original profile. Close Chrome using that profile first
so its files aren't mid-write. Security: the copy holds your live cookies/sessions — anyone
who reaches the debug port can act as you, so keep it local and delete it when done.
Multiple instances
Run and manage several browsers at once, each on its own port, addressed by name:
chromectl start --name work # instance on 9222
chromectl start --name scratch --auto-port # a second, on the next free port
chromectl instances # list them + up/down status (--json, --prune)
chromectl -i scratch open https://example.com # target by name (or --port 9223)
chromectl -i work read --json
chromectl stop scratch # stop one by name/port
chromectl stop --all # stop every managed instance
-i NAME (or --port N) selects which browser every command talks to. The registry lives
at ~/.chromectl/instances.json.
Notes:
- Profiles persist by default. Each instance gets a stable dir at
~/.chromectl/profiles/<name>that's reused every time you start that name, so cookies/logins survive restarts. Use--profile PATHfor a custom location,--ephemeralfor a throwaway/tmpprofile, orstop <name> --purgeto delete it. - A non-default profile is mandatory since Chrome 136 — the real default profile
refuses the debug port (anti-cookie-theft).
start's profile is non-default; to reuse your logins, copy your real one first:cp -r ~/.config/google-chrome /tmp/profthenchromectl start --profile /tmp/prof. startsets--remote-allow-origins=*so clients (incl. Playwright) can connect.- Anyone who can reach the port has full, unauthenticated control of that browser (read cookies/sessions, run JS, read traffic). Keep it on localhost; never forward it.
2. Use it
chromectl list # open tabs/targets
chromectl version # browser + protocol info
chromectl open https://example.com # open a tab
chromectl close example # close a tab (by url/title substring or id-prefix)
chromectl goto example https://news.ycombinator.com # navigate
chromectl eval example "document.title" # run JS, get the value back
chromectl eval example "({t: document.title, links: document.links.length})"
chromectl html example --out page.html # dump HTML
chromectl text example # visible text
chromectl cookies example # cookie table (--json for raw)
chromectl screenshot example --full --out shot.png # full-page screenshot
chromectl pdf example --out page.pdf # print to PDF
chromectl console example # tail console.* + JS errors (Ctrl-C)
chromectl watch example # live one-line-per-request network tail
chromectl capture https://github.com --print 3 # Burp-style full req/resp capture
chromectl capture https://api.github.com/ --type xhr --har out.har # filter + HAR export
chromectl capture --attach example --reload # capture an existing tab from reload
chromectl seo https://example.com # on-page SEO audit (open+audit+close)
chromectl seo example # audit an already-loaded tab
# --- run a whole flow in ONE process (script/batch) ---
chromectl run steps.txt # one command per line (# comments ok)
chromectl run --step "open https://site/login" \
--step "wait --selector #user" \
--step "fill-form --set #user=ada --set #pass=pw --submit #go" \
--step "wait --url /dashboard" \
--step "read --json"
# opened tab auto-becomes the target for later steps; one connection; state persists; --keep-going to continue on error
# --- read & extract (turn pages into data) ---
chromectl read example # main content as clean Markdown
chromectl read example --json # {url,title,markdown,chars}
chromectl extract example --field "title=h1" --field "prices=.price[]" --field "img=img@src"
chromectl links example --external --json # list links (filter internal/external)
# --- reliable flows ---
chromectl wait example --selector "#results" --timeout 8000 # wait until it's visible
chromectl wait example --network-idle # or: --text "Done" / --url "/checkout" / --gone
chromectl fill-form login --set "#user=ada" --set "#pass=secret" --submit "#go"
# Most read-only commands accept --json for scripting/agents:
chromectl list --json ; chromectl cookies example --json ; chromectl seo example --json
# --- performance (Tier 3) ---
chromectl perf https://example.com # Core Web Vitals (LCP/CLS/INP/FCP/TTFB)
chromectl perf https://example.com --out trace.json # + raw trace (DevTools ▸ Performance ▸ Load)
chromectl perf --attach example --reload # measure an existing tab
chromectl lighthouse https://example.com --preset desktop # full Lighthouse audit (needs the CLI)
chromectl lighthouse https://example.com --categories performance,seo --out report.json
# --- robust interaction via Playwright-over-CDP (Tier 2) ---
chromectl snapshot example # list interactive elements + save refs
chromectl click example --role button --name "Sign in" # click by ARIA role + name
chromectl click example --ref 3 # click element #3 from last snapshot
chromectl click example --text "Add to cart" # click by visible text
chromectl fill example "ada@x.com" --selector "#email" --enter # fill + submit (auto-waits)
chromectl hover example --selector ".menu" # hover (reveals submenus)
# --- interaction / emulation (Tier 1) ---
chromectl resize example 390 844 --mobile --shot m.png # viewport + full-page screenshot
chromectl emulate example --color dark --shot dark.png # dark mode
chromectl emulate example --geo 48.85,2.35 --throttle slow-3g --hold # geo + network (held)
chromectl type example "hello" --selector "#search" --enter # type into a field
chromectl press example Enter # press key(s): Enter/Tab/ArrowDown/a…
chromectl upload example ./photo.png --selector "#file" # set a file <input>
chromectl dialog example --accept --text "Ada" # auto-answer alert/confirm/prompt
chromectl heap example --out heap.heapsnapshot # V8 heap snapshot (DevTools ▸ Memory)
chromectl raw browser Browser.getVersion # raw CDP command (browser target)
chromectl raw example Runtime.evaluate '{"expression":"1+1","returnByValue":true}'
chromectl proto Network # protocol lookup: a domain…
chromectl proto Network.getResponseBody # …a command's params/returns
chromectl repl example # interactive CDP prompt
Target selection
Anywhere a command takes a target you can pass:
- an id-prefix (
B1B0), a url/title substring (example,github), browserfor the browser-level target (forraw/repl),- or nothing to use the first open page.
Emulation caveat
CDP overrides (emulate/resize viewport, geo, throttle, color-scheme, UA) live on
the CDP connection and revert when it closes. Since each chromectl command connects,
acts, and disconnects, use --shot to capture in the same session, or --hold to keep
the connection open (Ctrl-C to release) so the override persists while you do other work.
Interaction (Tier 2) — how it works
snapshot/click/fill/hover use Playwright attached to your Chrome over CDP
(connect_over_cdp) — no separate browser is launched. You get Playwright's
auto-waiting and actionability checks (waits for the element to exist, be visible, and
be stable before acting) instead of hand-rolled timing. snapshot writes .cdp-snap.json
so --ref N works in a later, separate command (the stateless equivalent of the MCP's
element uids). Locate an element by any of: --ref, --selector, --text, or
--role+--name.
Handy options
--host/--port(or envCDP_HOST/CDP_PORT) — defaultlocalhost:9222.capture:--print N,--out FILE,--har FILE,--type TYPE,--no-bodies,--bodycap N,--max SECONDS,--quiet SECONDS,--attach TARGET,--reload.
How it works (the workflow)
HTTP /json/version → open ONE WebSocket to the browser (or a per-tab socket) →
send CDP commands ({id, method, params}) and receive replies + unsolicited
events → for capture: Network.enable before navigating, correlate events by
requestId, fetch each body on loadingFinished (before Chrome evicts it), merge
the *ExtraInfo events for the real cookies/headers, and reconstruct raw HTTP.
A background thread reads frames into a queue so call() (request/response) and
the capture/console/watch loops (event streams) share one connection cleanly.
Gotcha baked in: websocket-client sends an Origin header by default, which
modern Chrome rejects on the debug port. chromectl sets suppress_origin=True so
connections are accepted without relaunching Chrome.
Layout
chromectl/cli.py— the CLI.chromectl/protocol.json— bundled CDP schema used bychromectl proto/cheat(falls back to live).pyproject.toml— packaging;chromectlconsole entry point.AGENTS.md— the agent-facing interface reference (auto-read by coding agents).
For agents
Read AGENTS.md (or run chromectl cheat --json once) for the full command surface —
no need to call --help per command.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chromectl-0.1.0.tar.gz.
File metadata
- Download URL: chromectl-0.1.0.tar.gz
- Upload date:
- Size: 202.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0dc8efd5ff218089d6e994f1a6479138f200734f73ccf5109c77348368b14607
|
|
| MD5 |
47bba8acf47b410686d3ddb9500e23f6
|
|
| BLAKE2b-256 |
03abd0b7fb96b32ac266b882022488ab1727d484059e8f2ef166d4907e839284
|
File details
Details for the file chromectl-0.1.0-py3-none-any.whl.
File metadata
- Download URL: chromectl-0.1.0-py3-none-any.whl
- Upload date:
- Size: 203.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a5bc6705f4c7b5e9950756ec2c17ed78bacb8b87d37aab915b068498b36433b
|
|
| MD5 |
1c52b6c9d97963b5c8c74ab3f6afedb8
|
|
| BLAKE2b-256 |
c70723bd3649927d6b2843869840a8ade38ffe300853912da3b7628f31c4f12d
|