From e53ce8f4a412669ec72a1347bc6d94cb6eeed6f6 Mon Sep 17 00:00:00 2001 From: wing Date: Thu, 4 Jun 2026 10:51:59 +0800 Subject: [PATCH] feat: add Microsoft Edge browser support --- README.md | 41 ++++++++++++++-------------- skill/chrome-devtools/SKILL.md | 15 +++++++---- src/browser.rs | 49 +++++++++++++++++++++------------- src/lib.rs | 2 +- 4 files changed, 62 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index cc709e0..2b65b04 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) ``` @@ -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 | |----------------------|--------------------| @@ -174,16 +174,17 @@ These commands interact with tools injected into the page via `window.__dtmcp.to | `--json` | JSON output | | `--ws-endpoint ` | Explicit WebSocket URL | | `--user-data-dir ` | Custom Chrome profile directory | -| `--channel ` | Chrome channel (stable/beta/canary/dev) | +| `--channel ` | 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 diff --git a/skill/chrome-devtools/SKILL.md b/skill/chrome-devtools/SKILL.md index 73e0ac8..b18b17a 100644 --- a/skill/chrome-devtools/SKILL.md +++ b/skill/chrome-devtools/SKILL.md @@ -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 diff --git a/src/browser.rs b/src/browser.rs index 3482dfb..0c02089 100644 --- a/src/browser.rs +++ b/src/browser.rs @@ -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 @@ -31,9 +31,9 @@ fn read_devtools_active_port(user_data_dir: &Path) -> Result { 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() ) @@ -57,25 +57,28 @@ fn read_devtools_active_port(user_data_dir: &Path) -> Result { .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 { #[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) } @@ -88,7 +91,11 @@ fn default_chrome_user_data_dir(channel: &str) -> Result { "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) } @@ -97,13 +104,17 @@ fn default_chrome_user_data_dir(channel: &str) -> Result { { 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) } diff --git a/src/lib.rs b/src/lib.rs index e883e18..279dcb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub struct Cli { #[arg(long, global = true, env = "CHROME_USER_DATA_DIR")] pub user_data_dir: Option, - /// 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,