Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
be9776e
feat: add output formats, event collection, URL blocking, and persist…
aeroxy Jun 14, 2026
c87278b
fix: address review findings on event collection and URL blocking
aeroxy Jun 14, 2026
30c8b12
fix: streamline application of network rules in persistent session setup
aeroxy Jun 14, 2026
4e8d1b7
fix: refine event collection logic in console and network commands
aeroxy Jun 14, 2026
20546cc
fix: improve error handling in session management and console event p…
aeroxy Jun 14, 2026
fa9268f
refactor: run page commands on the persistent session and dedupe bloc…
aeroxy Jun 14, 2026
bbb25c9
chore: cargo fmt
aeroxy Jun 14, 2026
dc3c94b
fix: reapply URL blocks on degraded session; order network output chr…
aeroxy Jun 14, 2026
108ffcb
fix: surface network-rule/socket errors and harden kill-daemon PID ha…
aeroxy Jun 14, 2026
edff530
feat: track viewport/geolocation as emulation state and isolate emula…
aeroxy Jun 14, 2026
b66207f
fix: refuse kill-daemon on PID 0 and cap event buffers in O(1)
aeroxy Jun 14, 2026
461446e
fix: correct per-tab blocklist isolation and surface silent failures
aeroxy Jun 14, 2026
4f84cfd
fix: case-insensitive --type filtering for console and network commands
aeroxy Jun 14, 2026
f3a7a2b
fix: apply global --block-url/--unblock-url flags during initial tab …
aeroxy Jun 14, 2026
bfad04b
fix: improve error handling for PID file reading in daemon management
aeroxy Jun 14, 2026
582a6af
fix: improve error handling for PID file reading in daemon management
aeroxy Jun 14, 2026
69d9c2e
fix: preserve network redirect chains and short-circuit sw_logs on em…
aeroxy Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,174 changes: 1,161 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ chrono = "0.4.44"
clap = { version = "4", features = ["derive", "env"] }
dirs = "5"
futures-util = "0.3"
libc = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
toon-format = "0.5"
tokio-tungstenite = "0.24"

[dev-dependencies]
Expand Down
66 changes: 56 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,41 @@ These commands interact with tools injected into the page via `window.__dtmcp.to

| Command | Description |
|---------|-------------|
| `emulate` | Get/set page-based emulation overrides (viewport, geolocation) |
| `emulate --viewport 1280x720` | Set viewport size (page-based, persists) |
| `emulate --geolocation 37.77,-122.41` | Set geolocation (page-based, persists) |
| `emulate --clear-all` | Clear all emulation overrides |
| `emulate` | Get/set emulation overrides (viewport, geolocation, URL blocking) |
| `emulate --viewport 1280x720` | Set viewport size (per-tab, persists across navigation) |
| `emulate --geolocation 37.77,-122.41` | Set geolocation (per-tab, persists across navigation) |
| `emulate --block-url <pattern>` | Block URL pattern on subsequent requests (glob; per-tab) |
| `emulate --unblock-url <pattern>` | Un-block a previously blocked pattern |
| `emulate --clear-blocks` | Clear all blocked URL patterns |
| `emulate --clear-all` | Clear all overrides (viewport, geolocation, blocks) |
| `wait-for <text> [--timeout ms]` | Wait for text to appear (default 30s) |

`emulate` with no flags shows all active overrides. Viewport and geolocation overrides are **page-based** — they persist until cleared or the page is closed.
`emulate` with no flags shows the active tab's overrides. Viewport, geolocation, and URL blocks are all **per-tab** — each page keeps its own. They persist across navigation within that tab and do **not** leak to other tabs, so you can hold (say) a mobile viewport with images blocked on one tab and a desktop baseline on another at the same time. They persist until you clear them (`--clear-viewport`, `--clear-geolocation`, `--clear-blocks`, or `--clear-all`), the tab closes, or the daemon exits.

### Network and console inspection

The daemon keeps a persistent CDP session on the active page that continuously collects Network and Runtime events. `console` and `network` **drain** whatever has accumulated since the last call (or since the session attached, if never drained).

| Command | Description |
|---------|-------------|
| `console` | Drain accumulated console messages |
| `console --type error --type warning` | Filter by level (`log`, `warn`, `info`, `error`, `debug`, `exception`) |
| `console --duration 5000` | Live collection for 5 s (consumes events — they won't reappear on a later drain) |
| `network` | Drain accumulated network requests |
| `network --type Fetch --type XHR` | Filter by resource type (`Document`, `Script`, `Stylesheet`, `Image`, `Font`, `XHR`, `Fetch`, `Manifest`, `Media`, `Other`) |
| `network --duration 5000` | Live collection for 5 s |
| `sw-logs [--duration 2000]` | Collect console logs from extension service workers (2 s default) |
| `sw-logs --extension-id <id>` | Filter service-worker logs to one extension |

A drain without a `--duration` returns instantly. Adding `--duration N` switches the command to *live mode* and blocks for `N` ms.

### Daemon

| Command | Description |
|---------|-------------|
| `kill-daemon` | Stop the background daemon cleanly |

`kill-daemon` signals the running daemon with `SIGTERM`, removes the socket and PID file, and exits. It's a no-op if no daemon is running. Prefer this over `pkill -f __daemon__` — the process name is shared by legitimate Chrome children processes.

## Global options

Expand All @@ -172,31 +200,42 @@ These commands interact with tools injected into the page via `window.__dtmcp.to
| `--target <name>` | Target page by friendly name or raw ID |
| `--page <index>` | Target page by index |
| `--json` | JSON output |
| `--toon` | TOON output (compact tabular encoding for LLM agents; mutually exclusive with `--json`) |
| `--block-url <pattern>` | Add a URL pattern to the active tab's block list (repeatable; persists until un-blocked or cleared) |
| `--unblock-url <pattern>` | Remove a URL pattern from the active tab's block list (repeatable) |
| `--ws-endpoint <url>` | Explicit WebSocket URL |
| `--user-data-dir <path>` | Custom Chrome profile directory |
| `--channel <ch>` | Chrome channel (stable/beta/canary/dev) |

Global `--block-url` and `--unblock-url` update the **active tab's** block list and apply via `Network.setBlockedURLs`; the daemon re-applies each tab's list when that tab is in use, so blocking is isolated per tab. **Note:** Chrome only blocks *subresources* (images, scripts, fetch/XHR, stylesheets, CDN, trackers, fonts). The top-level navigation document itself is never blocked — e.g. `--block-url "*example.com*"` then `navigate https://example.com` still loads the page, but any `*.png`, `*.woff2`, etc. subresources on it are blocked.

## Daemon details

- **Socket**: `/tmp/chrome-devtools-daemon.sock`
- **PID file**: `/tmp/chrome-devtools-daemon.pid`
- **Idle timeout**: 5 minutes (auto-exits, cleans up socket)
- **Protocol**: Length-prefixed JSON over Unix socket
- **Spawned by**: First CLI invocation (transparent to user)
- **Kill manually**: `pkill -f __daemon__` or delete the socket
- **Kill**: `chrome-devtools kill-daemon` (or delete the socket + PID file)

The daemon keeps a persistent CDP session on the current page to:
- Continuously collect `Network.*` and `Runtime.consoleAPICalled`/`exceptionThrown` events for `console` and `network` drains.
- Re-apply `Network.setBlockedURLs` and emulation state across page-level commands.
- Re-attach to a new target when `--target` changes (the previous target's event buffers are discarded on the switch).

## Source layout

```
src/
├── main.rs # Entry point + daemon dispatch
├── lib.rs # CLI (clap) + command routing
├── cdp.rs # Raw CDP over WebSocket (JSON-RPC)
├── cdp.rs # Raw CDP over WebSocket (JSON-RPC) + persistent session
├── browser.rs # Auto-connect (DevToolsActivePort)
├── daemon.rs # Background daemon (persistent connection)
├── client.rs # Talks to daemon via Unix socket
├── protocol.rs # IPC message types
├── protocol.rs # IPC message types (DaemonRequest / DaemonResponse)
├── friendly.rs # Target ID → word-pair names
├── format.rs # OutputFormat (text/json/toon) + format_structured helper
├── result.rs # Command result types
├── error.rs # CLI error types and codes
├── constants.rs # Shared constants
Expand All @@ -207,10 +246,13 @@ src/
├── pages.rs # list/new/close/select/wait-for
├── screenshot.rs
├── evaluate.rs
├── executor.rs # Command dispatch
├── executor.rs # Command dispatch + persistent-session reuse
├── input.rs # click/fill/type/press/hover
├── snapshot.rs
├── emulation.rs # emulate (viewport/geolocation get/set/clear)
├── emulation.rs # emulate (viewport/geolocation/blocklist get/set/clear)
├── console.rs # console drain / live collection
├── network.rs # network drain / live collection
├── sw_logs.rs # extension service-worker log collection
└── third_party.rs # list-3p-tools/execute-3p-tool
```

Expand All @@ -231,6 +273,10 @@ chrome-devtools --target red-snake click "#submit"

# 4. Extract data
chrome-devtools --target red-snake evaluate "document.title"

# 5. Inspect what the page did under the hood
chrome-devtools --target red-snake network # drain accumulated requests
chrome-devtools --target red-snake console # drain console + exceptions
```

Always pass `--target` from step 2 onward to stay on the same page.
Expand Down
Loading