Skip to main content

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
chromectl start --proxy user:pass@10.0.0.1:8080   # behind a proxy (credentials handled)
chromectl start -- --lang=tr --disable-gpu        # pass any extra Chrome flags

Extra Chrome flags

Anything after a bare -- goes straight to Chrome, or pass them one at a time with --chrome-arg (write --chrome-arg=--flag when the value starts with a dash):

chromectl start --name tr -- --lang=tr --window-size=1280,800 --host-resolver-rules='MAP * 1.2.3.4'
chromectl start --chrome-arg=--disable-dev-shm-usage --chrome-arg=--blink-settings=imagesEnabled=false

Your flags override the ones chromectl sets, so -- --headless=old or -- --user-data-dir=/tmp/p win — and the registry records the values Chrome actually got. instances --json lists them under chrome_args.

Proxies

chromectl start --proxy 10.0.0.1:8080                        # http proxy (default scheme)
chromectl start --proxy socks5://10.0.0.1:1080               # socks5 (also https://, socks4://)
chromectl start --proxy socks5://ada:secret@10.0.0.1:1080    # with credentials
chromectl start --proxy 10.0.0.1:8080 --proxy-auth 'ada:p@ss:word'   # creds outside the URL
chromectl start --proxy 10.0.0.1:8080 --proxy-bypass 'localhost,*.internal'
chromectl start --proxy-pac http://wpad/proxy.pac            # PAC file instead

Chrome has no way to accept proxy credentials on the command line — it opens a login dialog, which is no help headless. So when your proxy needs a username/password, chromectl starts a tiny local relay (chromectl.proxyrelay), points Chrome at it, and the relay adds the credentials on the way upstream — HTTP CONNECT tunnels and SOCKS5 alike. The relay belongs to the instance: stop shuts it down with the browser, and instances shows it.

  • Credentials are handed to the relay through the environment, never argv (argv is visible to every user via ps), and instances masks the password.
  • The relay listens on 127.0.0.1 only, and speaks to one upstream proxy.
  • SOCKS4 has no password auth — use socks5:// or an HTTP proxy if you need credentials.
  • Chrome bypasses proxies for loopback by default. To route 127.0.0.1 traffic through the proxy too (useful when testing), add --proxy-bypass '<-loopback>'.
  • Nothing else changes: chromectl still talks CDP to the browser directly on localhost.

--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 PATH for a custom location, --ephemeral for a throwaway /tmp profile, or stop <name> --purge to 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/prof then chromectl start --profile /tmp/prof.
  • start sets --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),
  • browser for the browser-level target (for raw/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 env CDP_HOST / CDP_PORT) — default localhost: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 by chromectl proto/cheat (falls back to live).
  • pyproject.toml — packaging; chromectl console 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

chromectl-0.2.0.tar.gz (210.3 kB view details)

Uploaded Source

Built Distribution

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

chromectl-0.2.0-py3-none-any.whl (210.1 kB view details)

Uploaded Python 3

File details

Details for the file chromectl-0.2.0.tar.gz.

File metadata

  • Download URL: chromectl-0.2.0.tar.gz
  • Upload date:
  • Size: 210.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.5

File hashes

Hashes for chromectl-0.2.0.tar.gz
Algorithm Hash digest
SHA256 03bec7461e51b367e246ad02f6f77a2ece429077b4d343352024afe4d8be2fb0
MD5 245a0427c6cc02623f665d29093eb5d1
BLAKE2b-256 42a1b0ec542d175d2aeaa1023a88d76a5cb4fc68cfb28c6e3afb3676e6ebd35b

See more details on using hashes here.

File details

Details for the file chromectl-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: chromectl-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 210.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.5

File hashes

Hashes for chromectl-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 28226b4b84836b32dc94d7e5853974730022150220f1ccf621725862d77151c8
MD5 f02450bd312461d62cdac87001c60d14
BLAKE2b-256 8941c650e8c1bd0725bce18c172e6ced141444ca3564ea2d1fde9731e2eed769

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

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