Debug the live web from the terminal with Chrome DevTools Protocol. Triggers when an agent needs to screenshot a page, run JS in a live tab, capture console errors, profile performance, record a trace, or watch network traffic, all without opening a browser window. Launch Chrome once, then drive it with scripts/cdp.js or raw CDP commands.
./install.sh chrome-devtools-cliname: chrome_devtools_cli description: Debug the live web from the terminal with Chrome DevTools Protocol. Triggers when an agent needs to screenshot a page, run JS in a live tab, capture console errors, profile performance, record a trace, or watch network traffic, all without opening a browser window. Launch Chrome once, then drive it with scripts/cdp.js or raw CDP commands.
Puppeteer and Playwright are great for UI automation, but they are heavy when all you want is to look at a live page: take a screenshot, run a snippet of JS, grab console errors, measure heap, or record a perf trace. This skill teaches the lighter path: launch Chrome once with a remote-debugging port, then talk to it over the Chrome DevTools Protocol (CDP) from the terminal. scripts/cdp.js is a zero-dependency Node client that wraps the everyday commands; references/cdp-domains.md is the full domain cheat sheet for anything exotic.
The dedicated headless build is chrome-headless-shell. Since M132 it ships separately from Chrome; --headless=old no longer does anything. Install it with no sudo:
bash
npx @puppeteer/browsers install chrome-headless-shell@stable --path ~/chrome
Use the path it prints. Any Chromium also works, but the headless shell starts faster and uses less memory.
bash
SHELL=$(npx -y get-chrome-from-puppeteer 2>/dev/null) # or the path from step 1
$SHELL --headless --remote-debugging-port=9222 \
--user-data-dir=$(mktemp -d) \
--no-sandbox --disable-dev-shm-usage about:blank &
Rules that save pain:
--user-data-dir; without it Chrome refuses a second instance with a singleton lock error.--no-sandbox is for your own throwaway VM or container only, never production.bash
curl -s http://127.0.0.1:9222/json/version # must print {"Browser": "HeadlessChrome/...", ...}
curl -s http://127.0.0.1:9222/json/list # open targets, each with a webSocketDebuggerUrl
Chrome's own CLI flags handle the three most common jobs. These render the full page first, including JS, so --dump-dom beats curl for SPAs.
bash
$SHELL --headless --no-sandbox --dump-dom https://example.com > page.html
$SHELL --headless --no-sandbox --screenshot=shot.png --window-size=1440,900 https://example.com
$SHELL --headless --no-sandbox --print-to-pdf=page.pdf https://example.com
# --virtual-time-budget=8000 fast-forwards 8s of virtual time: handy for pages that animate in
Zero dependencies (Node 18+). Prints JSON to stdout, so it composes with jq.
bash
cd scripts
node cdp.js tabs # list open pages
node cdp.js navigate https://example.com
node cdp.js eval 'document.title' # run JS, print the value
node cdp.js eval 'JSON.stringify(performance.getEntriesByType("navigation")[0].toJSON())' | jq .
node cdp.js shot full.png https://example.com # full-page screenshot
node cdp.js pdf page.pdf https://example.com
node cdp.js console 15 # capture console output for 15s
node cdp.js metrics https://example.com | jq '{JSHeapUsedSize, Nodes, LayoutCount}'
node cdp.js trace trace.json 6 # 6s devtools.timeline trace of a reload
node cdp.js net 10 https://example.com | jq '.[] | [.status, .url] | @tsv' -r
node cdp.js cookies https://example.com
Typical debugging loops:
console 10 to catch the JS error, net 10 <url> to find the failed request, eval to check document.readyState and whether the app's root element mounted.metrics before and after an interaction to see heap and node growth; trace trace.json 6 then open trace.json in DevTools Performance panel (import the file) to see what the main thread did.shot at two viewport widths with --tab to compare, or eval 'document.documentElement.outerHTML.length'.To work a second tab, pass --tab <id> from tabs output. To hit a different port, CDP_PORT=9223 node cdp.js ....
references/cdp-domains.md lists every domain: Emulation (viewport, CPU throttling, geolocation), Input (real mouse clicks, typing), Target (scratch tabs), HeapProfiler (leak hunting), Network interception. The pattern is always the same: Page.enable (or the domain's enable), send the command, read the result or subscribe to its events. Tracing.start takes categories as a comma-separated string ("devtools.timeline"), not an array; passing an array errors with -32602.
tabs, eval, console, metrics, net, cookies print JSON to stdout. Pipe into jq.shot and pdf write the file and print {ok, file, bytes}.trace writes {traceEvents: [...]} JSON importable into DevTools > Performance, and prints a summary with the top event names.error: <message> on stderr when Chrome is unreachable, the tab is gone, or the page throws.pkill -f chrome to clean up: if your own shell command line contains "chrome", pkill matches it and kills your shell. Kill by PID from $!, or just use a fresh --user-data-dir per run.cookies prints names and domains, not values, by design. Do not echo full cookie values or request bodies into chat or logs.eval runs arbitrary JS in the page: only run expressions you wrote or verified. Do not eval snippets copied from untrusted sources.publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.