Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Chrome DevTools CLI

High-performance rust CLI that connects to an existing Chrome browser via the DevTools Protocol. Auto-connects by default, no manual WebSocket URL needed.
High-performance rust CLI that connects to an existing Chrome or Edge browser via the DevTools Protocol. Auto-connects by default, no manual WebSocket URL needed.

[![crates.io](https://img.shields.io/crates/v/chrome-devtools-cli.svg)](https://crates.io/crates/chrome-devtools-cli)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
Expand Down Expand Up @@ -42,12 +42,12 @@ This is a lightweight Rust binary that talks directly to Chrome's DevTools Proto
```
chrome-devtools navigate https://example.com
├─ Try daemon (Unix socket /tmp/chrome-devtools-daemon.sock)
├─ Try daemon (Unix socket on macOS/Linux, TCP localhost on Windows)
│ └─ If running → send command → get result
├─ If no daemon → spawn one (background process)
│ └─ Daemon connects to Chrome WebSocket (one-time approval)
│ └─ Listens on Unix socket, 5-min idle timeout
│ └─ Daemon connects to Chrome/Edge WebSocket (one-time approval)
│ └─ Listens for IPC connections, 5-min idle timeout
└─ Fallback → direct WebSocket connection (no daemon)
```
Expand All @@ -56,23 +56,23 @@ The daemon keeps a persistent WebSocket connection to Chrome, so the browser onl

## Prerequisites

Chrome must have remote debugging enabled:
Chrome or Edge must have remote debugging enabled:

1. Open Chrome
2. Go to `chrome://inspect/#remote-debugging`
1. Open Chrome/Edge
2. Go to `chrome://inspect/#remote-debugging` (or `edge://inspect/#remote-debugging`)
3. Enable the remote debugging server

## Auto-connect

By default, the CLI reads `DevToolsActivePort` from Chrome's user data directory:
By default, the CLI reads `DevToolsActivePort` from the browser's user data directory:

| OS | Default path |
|----|-------------|
| macOS | `~/Library/Application Support/Google/Chrome/` |
| Linux | `~/.config/google-chrome/` |
| Windows | `%LOCALAPPDATA%\Google\Chrome\User Data\` |
| OS | Chrome (default) | Edge (`--channel edge`) |
|----|-----------------|------------------------|
| macOS | `~/Library/Application Support/Google/Chrome/` | `~/Library/Application Support/Microsoft Edge/` |
| Linux | `~/.config/google-chrome/` | `~/.config/microsoft-edge/` |
| Windows | `%LOCALAPPDATA%\Google\Chrome\User Data\` | `%LOCALAPPDATA%\Microsoft\Edge\User Data\` |

Override with `--user-data-dir`, `--channel` (beta/canary/dev), or `--ws-endpoint`. All three also read from environment variables:
Override with `--user-data-dir`, `--channel` (stable/beta/canary/dev/edge/edge-beta/edge-canary/edge-dev), or `--ws-endpoint`. All three also read from environment variables:

| Environment Variable | Corresponding Flag |
|----------------------|--------------------|
Expand Down Expand Up @@ -174,16 +174,17 @@ These commands interact with tools injected into the page via `window.__dtmcp.to
| `--json` | JSON output |
| `--ws-endpoint <url>` | Explicit WebSocket URL |
| `--user-data-dir <path>` | Custom Chrome profile directory |
| `--channel <ch>` | Chrome channel (stable/beta/canary/dev) |
| `--channel <ch>` | Browser channel (stable/beta/canary/dev/edge/edge-beta/edge-canary/edge-dev) |

## 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
- **IPC (macOS/Linux)**: Unix socket at `/tmp/chrome-devtools-daemon.sock`
- **IPC (Windows)**: TCP on `127.0.0.1` with address stored in `%TEMP%\chrome-devtools-daemon.addr`
- **PID file**: `/tmp/chrome-devtools-daemon.pid` (Unix) or `%TEMP%\chrome-devtools-daemon.pid` (Windows)
- **Idle timeout**: 5 minutes (auto-exits, cleans up)
- **Protocol**: Length-prefixed JSON
- **Spawned by**: First CLI invocation (transparent to user)
- **Kill manually**: `pkill -f __daemon__` or delete the socket
- **Kill manually**: `pkill -f __daemon__` (Unix) or terminate via PID file (Windows)

## Source layout

Expand Down
15 changes: 10 additions & 5 deletions skill/chrome-devtools/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
---
name: chrome-devtools
description: Use when the user asks to "take a screenshot of a website", "navigate to a URL", "fill a form in the browser", "interact with Chrome", or when a chrome automation task is needed.
description: Use when the user asks to "take a screenshot of a website", "navigate to a URL", "fill a form in the browser", "interact with Chrome/Edge", "connect Chrome/Edge", or when a browser automation task is needed.
user-invocable: true
---

## Prerequisites

Chrome must have remote debugging enabled:
1. Open Chrome
2. Go to `chrome://inspect/#remote-debugging`
Chrome or Edge must have remote debugging enabled:
1. Open Chrome/Edge
2. Go to `chrome://inspect/#remote-debugging` (or `edge://inspect/#remote-debugging`)
3. Enable the remote debugging server

The CLI auto-connects by reading Chrome's `DevToolsActivePort` file — no WebSocket URL needed.
The CLI auto-connects by reading the browser's `DevToolsActivePort` file — no WebSocket URL needed.

For Edge, pass `--channel edge`:
```bash
chrome-devtools --channel edge list-pages
```

## Core Capabilities

Expand Down
49 changes: 30 additions & 19 deletions src/browser.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{anyhow, bail, Result};
use std::path::{Path, PathBuf};

/// Resolve the WebSocket URL for connecting to Chrome.
/// Resolve the WebSocket URL for connecting to Chrome or Edge.
///
/// Priority:
/// 1. Explicit --ws-endpoint
Expand Down Expand Up @@ -31,9 +31,9 @@ fn read_devtools_active_port(user_data_dir: &Path) -> Result<String> {
let content = std::fs::read_to_string(&port_path).map_err(|_| {
anyhow!(
"Could not read DevToolsActivePort at {}\n\n\
Make sure Chrome is running with remote debugging enabled:\n\
1. Open Chrome\n\
2. Go to chrome://inspect/#remote-debugging\n\
Make sure Chrome or Edge is running with remote debugging enabled:\n\
1. Open Chrome/Edge\n\
2. Go to chrome://inspect/#remote-debugging (or edge://inspect/#remote-debugging)\n\
3. Enable the remote debugging server",
port_path.display()
)
Expand All @@ -57,25 +57,28 @@ fn read_devtools_active_port(user_data_dir: &Path) -> Result<String> {
.map_err(|_| anyhow!("Invalid port '{}' in DevToolsActivePort", lines[0]))?;

if port == 0 {
bail!("Port 0 in DevToolsActivePort — Chrome may not be running");
bail!("Port 0 in DevToolsActivePort — browser may not be running");
}

let path = lines[1];
Ok(format!("ws://127.0.0.1:{port}{path}"))
}

/// Get the default Chrome user data directory for the given channel.
/// Get the default browser user data directory for the given channel.
fn default_chrome_user_data_dir(channel: &str) -> Result<PathBuf> {
#[cfg(target_os = "macos")]
{
let home = dirs::home_dir().ok_or_else(|| anyhow!("Cannot determine home directory"))?;
let base = home.join("Library/Application Support/Google");
let dir = match channel {
"stable" | "chrome" => base.join("Chrome"),
"beta" => base.join("Chrome Beta"),
"canary" => base.join("Chrome Canary"),
"dev" => base.join("Chrome Dev"),
_ => bail!("Unknown Chrome channel: {channel}"),
"stable" | "chrome" => home.join("Library/Application Support/Google/Chrome"),
"beta" => home.join("Library/Application Support/Google/Chrome Beta"),
"canary" => home.join("Library/Application Support/Google/Chrome Canary"),
"dev" => home.join("Library/Application Support/Google/Chrome Dev"),
"edge" => home.join("Library/Application Support/Microsoft Edge"),
"edge-beta" => home.join("Library/Application Support/Microsoft Edge Beta"),
"edge-canary" => home.join("Library/Application Support/Microsoft Edge Canary"),
"edge-dev" => home.join("Library/Application Support/Microsoft Edge Dev"),
_ => bail!("Unknown browser channel: {channel}. Valid: stable, beta, canary, dev, edge, edge-beta, edge-canary, edge-dev"),
};
Ok(dir)
}
Expand All @@ -88,7 +91,11 @@ fn default_chrome_user_data_dir(channel: &str) -> Result<PathBuf> {
"beta" => home.join(".config/google-chrome-beta"),
"canary" => home.join(".config/google-chrome-unstable"),
"dev" => home.join(".config/google-chrome-unstable"),
_ => bail!("Unknown Chrome channel: {channel}"),
"edge" => home.join(".config/microsoft-edge"),
"edge-beta" => home.join(".config/microsoft-edge-beta"),
"edge-canary" => home.join(".config/microsoft-edge-canary"),
"edge-dev" => home.join(".config/microsoft-edge-dev"),
_ => bail!("Unknown browser channel: {channel}. Valid: stable, beta, canary, dev, edge, edge-beta, edge-canary, edge-dev"),
};
Ok(dir)
}
Expand All @@ -97,13 +104,17 @@ fn default_chrome_user_data_dir(channel: &str) -> Result<PathBuf> {
{
let local_app_data =
std::env::var("LOCALAPPDATA").map_err(|_| anyhow!("LOCALAPPDATA not set"))?;
let base = PathBuf::from(local_app_data).join("Google");
let base = PathBuf::from(&local_app_data);
let dir = match channel {
"stable" | "chrome" => base.join("Chrome/User Data"),
"beta" => base.join("Chrome Beta/User Data"),
"canary" => base.join("Chrome SxS/User Data"),
"dev" => base.join("Chrome Dev/User Data"),
_ => bail!("Unknown Chrome channel: {channel}"),
"stable" | "chrome" => base.join("Google/Chrome/User Data"),
"beta" => base.join("Google/Chrome Beta/User Data"),
"canary" => base.join("Google/Chrome SxS/User Data"),
"dev" => base.join("Google/Chrome Dev/User Data"),
"edge" => base.join("Microsoft/Edge/User Data"),
"edge-beta" => base.join("Microsoft/Edge Beta/User Data"),
"edge-canary" => base.join("Microsoft/Edge SxS/User Data"),
"edge-dev" => base.join("Microsoft/Edge Dev/User Data"),
_ => bail!("Unknown browser channel: {channel}. Valid: stable, beta, canary, dev, edge, edge-beta, edge-canary, edge-dev"),
};
Ok(dir)
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct Cli {
#[arg(long, global = true, env = "CHROME_USER_DATA_DIR")]
pub user_data_dir: Option<String>,

/// Chrome channel: stable, beta, canary, dev
/// Browser channel: stable, beta, canary, dev, edge, edge-beta, edge-canary, edge-dev
#[arg(long, global = true, default_value = "stable", env = "CHROME_CHANNEL")]
pub channel: String,

Expand Down