---
name: 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.
---

# Chrome DevTools CLI

## Purpose

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.

## Workflow

### 1. Get a Chrome binary
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.

### 2. Launch with a debugging port
```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:
- Always pass a fresh `--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.
- If a sandbox blocks CDP or localhost networking, this whole skill is dead on arrival. Verify before building on it:

```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
```

### 3. Zero-setup one-shots (no CDP session needed)
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
```

### 4. Drive the live session with cdp.js
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:
- Blank page in production: `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.
- Slow page: `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.
- Flaky layout: `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 ...`.

### 5. Raw CDP when the wrapper is not enough
`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.

## Output Contract

- `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.
- All commands exit 0 on success, non-zero with `error: <message>` on stderr when Chrome is unreachable, the tab is gone, or the page throws.

## Operating Rules

1. Launch once, reuse the session. Do not spawn a fresh Chrome per command; that is what the one-shot flags are for.
2. Never `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.
3. Cookies and auth headers are secrets. `cookies` prints names and domains, not values, by design. Do not echo full cookie values or request bodies into chat or logs.
4. Screenshots and DOM dumps of other people's pages can contain PII. Save them to a working directory, never paste them into a conversation.
5. `eval` runs arbitrary JS in the page: only run expressions you wrote or verified. Do not eval snippets copied from untrusted sources.
6. CDP gives full control of the browser. Keep it pointed at your own pages and test targets; driving someone else's logged-in session is not what this skill is for.
7. When CDP is unavailable (blocked port, no binary), fall back to the one-shot flags in step 3, which need no debugging port at all.
