feat: add --auto-connect flag to discover and connect to running Chrome (#432)
This commit is contained in:
@@ -6,6 +6,19 @@ Instructions for AI coding agents working with this codebase.
|
|||||||
|
|
||||||
- Do not use emojis in code, output, or documentation. Unicode symbols (✓, ✗, →, ⚠) are acceptable.
|
- Do not use emojis in code, output, or documentation. Unicode symbols (✓, ✗, →, ⚠) are acceptable.
|
||||||
- CLI colored output uses `cli/src/color.rs`. This module respects the `NO_COLOR` environment variable. Never use hardcoded ANSI color codes.
|
- CLI colored output uses `cli/src/color.rs`. This module respects the `NO_COLOR` environment variable. Never use hardcoded ANSI color codes.
|
||||||
|
- CLI flags must always use kebab-case (e.g., `--auto-connect`, `--allow-file-access`). Never use camelCase for flags (e.g., `--autoConnect` is wrong).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
When adding or changing user-facing features (new flags, commands, behaviors, environment variables, etc.), update **all** of the following:
|
||||||
|
|
||||||
|
1. `cli/src/output.rs` -- `--help` output (flags list, examples, environment variables)
|
||||||
|
2. `README.md` -- Options table, relevant feature sections, examples
|
||||||
|
3. `skills/agent-browser/SKILL.md` -- so AI agents know about the feature
|
||||||
|
4. `docs/src/app/` -- the Next.js docs site (MDX pages)
|
||||||
|
5. Inline doc comments in the relevant source files
|
||||||
|
|
||||||
|
This applies to changes that either human users or AI agents would need to know about. Do not skip any of these locations.
|
||||||
|
|
||||||
<!-- opensrc:start -->
|
<!-- opensrc:start -->
|
||||||
|
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ The `-C` flag is useful for modern web apps that use custom clickable elements (
|
|||||||
| `--exact` | Exact text match |
|
| `--exact` | Exact text match |
|
||||||
| `--headed` | Show browser window (not headless) |
|
| `--headed` | Show browser window (not headless) |
|
||||||
| `--cdp <port>` | Connect via Chrome DevTools Protocol |
|
| `--cdp <port>` | Connect via Chrome DevTools Protocol |
|
||||||
|
| `--auto-connect` | Auto-discover and connect to running Chrome (or `AGENT_BROWSER_AUTO_CONNECT` env) |
|
||||||
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
|
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
|
||||||
| `--allow-file-access` | Allow file:// URLs to access local files (Chromium only) |
|
| `--allow-file-access` | Allow file:// URLs to access local files (Chromium only) |
|
||||||
| `--debug` | Debug output |
|
| `--debug` | Debug output |
|
||||||
@@ -555,6 +556,28 @@ This enables control of:
|
|||||||
- WebView2 applications
|
- WebView2 applications
|
||||||
- Any browser exposing a CDP endpoint
|
- Any browser exposing a CDP endpoint
|
||||||
|
|
||||||
|
### Auto-Connect
|
||||||
|
|
||||||
|
Use `--auto-connect` to automatically discover and connect to a running Chrome instance without specifying a port:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Auto-discover running Chrome with remote debugging
|
||||||
|
agent-browser --auto-connect open example.com
|
||||||
|
agent-browser --auto-connect snapshot
|
||||||
|
|
||||||
|
# Or via environment variable
|
||||||
|
AGENT_BROWSER_AUTO_CONNECT=1 agent-browser snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
Auto-connect discovers Chrome by:
|
||||||
|
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
|
||||||
|
2. Falling back to probing common debugging ports (9222, 9229)
|
||||||
|
|
||||||
|
This is useful when:
|
||||||
|
- Chrome 144+ has remote debugging enabled via `chrome://inspect/#remote-debugging` (which uses a dynamic port)
|
||||||
|
- You want a zero-configuration connection to your existing browser
|
||||||
|
- You don't want to track which port Chrome is using
|
||||||
|
|
||||||
## Streaming (Browser Preview)
|
## Streaming (Browser Preview)
|
||||||
|
|
||||||
Stream the browser viewport via WebSocket for live preview or "pair browsing" where a human can watch and interact alongside an AI agent.
|
Stream the browser viewport via WebSocket for live preview or "pair browsing" where a human can watch and interact alongside an AI agent.
|
||||||
|
|||||||
@@ -1429,6 +1429,7 @@ mod tests {
|
|||||||
ignore_https_errors: false,
|
ignore_https_errors: false,
|
||||||
allow_file_access: false,
|
allow_file_access: false,
|
||||||
device: None,
|
device: None,
|
||||||
|
auto_connect: false,
|
||||||
cli_executable_path: false,
|
cli_executable_path: false,
|
||||||
cli_extensions: false,
|
cli_extensions: false,
|
||||||
cli_profile: false,
|
cli_profile: false,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub struct Flags {
|
|||||||
pub ignore_https_errors: bool,
|
pub ignore_https_errors: bool,
|
||||||
pub allow_file_access: bool,
|
pub allow_file_access: bool,
|
||||||
pub device: Option<String>,
|
pub device: Option<String>,
|
||||||
|
pub auto_connect: bool,
|
||||||
|
|
||||||
// Track which launch-time options were explicitly passed via CLI
|
// Track which launch-time options were explicitly passed via CLI
|
||||||
// (as opposed to being set only via environment variables)
|
// (as opposed to being set only via environment variables)
|
||||||
@@ -65,6 +66,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
ignore_https_errors: false,
|
ignore_https_errors: false,
|
||||||
allow_file_access: env::var("AGENT_BROWSER_ALLOW_FILE_ACCESS").is_ok(),
|
allow_file_access: env::var("AGENT_BROWSER_ALLOW_FILE_ACCESS").is_ok(),
|
||||||
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok(),
|
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok(),
|
||||||
|
auto_connect: env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok(),
|
||||||
// Track CLI-passed flags (default false, set to true when flag is passed)
|
// Track CLI-passed flags (default false, set to true when flag is passed)
|
||||||
cli_executable_path: false,
|
cli_executable_path: false,
|
||||||
cli_extensions: false,
|
cli_extensions: false,
|
||||||
@@ -175,6 +177,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"--auto-connect" => flags.auto_connect = true,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -194,6 +197,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
|||||||
"--debug",
|
"--debug",
|
||||||
"--ignore-https-errors",
|
"--ignore-https-errors",
|
||||||
"--allow-file-access",
|
"--allow-file-access",
|
||||||
|
"--auto-connect",
|
||||||
];
|
];
|
||||||
// Global flags that take a value (need to skip the next arg too)
|
// Global flags that take a value (need to skip the next arg too)
|
||||||
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
||||||
|
|||||||
+53
-2
@@ -289,7 +289,27 @@ fn main() {
|
|||||||
if flags.json {
|
if flags.json {
|
||||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("\x1b[31m✗\x1b[0m {}", msg);
|
eprintln!("{} {}", color::error_indicator(), msg);
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.auto_connect && flags.cdp.is_some() {
|
||||||
|
let msg = "Cannot use --auto-connect and --cdp together";
|
||||||
|
if flags.json {
|
||||||
|
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||||
|
} else {
|
||||||
|
eprintln!("{} {}", color::error_indicator(), msg);
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.auto_connect && flags.provider.is_some() {
|
||||||
|
let msg = "Cannot use --auto-connect and -p/--provider together";
|
||||||
|
if flags.json {
|
||||||
|
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||||
|
} else {
|
||||||
|
eprintln!("{} {}", color::error_indicator(), msg);
|
||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -299,11 +319,42 @@ fn main() {
|
|||||||
if flags.json {
|
if flags.json {
|
||||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("\x1b[31m✗\x1b[0m {}", msg);
|
eprintln!("{} {}", color::error_indicator(), msg);
|
||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-connect to existing browser
|
||||||
|
if flags.auto_connect {
|
||||||
|
let mut launch_cmd = json!({
|
||||||
|
"id": gen_id(),
|
||||||
|
"action": "launch",
|
||||||
|
"autoConnect": true
|
||||||
|
});
|
||||||
|
|
||||||
|
if flags.ignore_https_errors {
|
||||||
|
launch_cmd["ignoreHTTPSErrors"] = json!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = match send_command(launch_cmd, &flags.session) {
|
||||||
|
Ok(resp) if resp.success => None,
|
||||||
|
Ok(resp) => Some(
|
||||||
|
resp.error
|
||||||
|
.unwrap_or_else(|| "Auto-connect failed".to_string()),
|
||||||
|
),
|
||||||
|
Err(e) => Some(e.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(msg) = err {
|
||||||
|
if flags.json {
|
||||||
|
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||||
|
} else {
|
||||||
|
eprintln!("{} {}", color::error_indicator(), msg);
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Connect via CDP if --cdp flag is set
|
// Connect via CDP if --cdp flag is set
|
||||||
// Accepts either a port number (e.g., "9222") or a full URL (e.g., "ws://..." or "wss://...")
|
// Accepts either a port number (e.g., "9222") or a full URL (e.g., "ws://..." or "wss://...")
|
||||||
if let Some(ref cdp_value) = flags.cdp {
|
if let Some(ref cdp_value) = flags.cdp {
|
||||||
|
|||||||
@@ -1778,6 +1778,7 @@ Options:
|
|||||||
--full, -f Full page screenshot
|
--full, -f Full page screenshot
|
||||||
--headed Show browser window (not headless)
|
--headed Show browser window (not headless)
|
||||||
--cdp <port> Connect via CDP (Chrome DevTools Protocol)
|
--cdp <port> Connect via CDP (Chrome DevTools Protocol)
|
||||||
|
--auto-connect Auto-discover and connect to running Chrome
|
||||||
--debug Debug output
|
--debug Debug output
|
||||||
--version, -V Show version
|
--version, -V Show version
|
||||||
|
|
||||||
@@ -1785,6 +1786,7 @@ Environment:
|
|||||||
AGENT_BROWSER_SESSION Session name (default: "default")
|
AGENT_BROWSER_SESSION Session name (default: "default")
|
||||||
AGENT_BROWSER_EXECUTABLE_PATH Custom browser executable path
|
AGENT_BROWSER_EXECUTABLE_PATH Custom browser executable path
|
||||||
AGENT_BROWSER_PROVIDER Browser provider (ios, browserbase, kernel, browseruse)
|
AGENT_BROWSER_PROVIDER Browser provider (ios, browserbase, kernel, browseruse)
|
||||||
|
AGENT_BROWSER_AUTO_CONNECT Auto-discover and connect to running Chrome
|
||||||
AGENT_BROWSER_STREAM_PORT Enable WebSocket streaming on port (e.g., 9223)
|
AGENT_BROWSER_STREAM_PORT Enable WebSocket streaming on port (e.g., 9223)
|
||||||
AGENT_BROWSER_IOS_DEVICE Default iOS device name
|
AGENT_BROWSER_IOS_DEVICE Default iOS device name
|
||||||
AGENT_BROWSER_IOS_UDID Default iOS device UDID
|
AGENT_BROWSER_IOS_UDID Default iOS device UDID
|
||||||
@@ -1798,6 +1800,7 @@ Examples:
|
|||||||
agent-browser get text @e1
|
agent-browser get text @e1
|
||||||
agent-browser screenshot --full
|
agent-browser screenshot --full
|
||||||
agent-browser --cdp 9222 snapshot # Connect via CDP port
|
agent-browser --cdp 9222 snapshot # Connect via CDP port
|
||||||
|
agent-browser --auto-connect snapshot # Auto-discover running Chrome
|
||||||
agent-browser --profile ~/.myapp open example.com # Persistent profile
|
agent-browser --profile ~/.myapp open example.com # Persistent profile
|
||||||
|
|
||||||
iOS Simulator (requires Xcode and Appium):
|
iOS Simulator (requires Xcode and Appium):
|
||||||
|
|||||||
@@ -34,6 +34,30 @@ The `--cdp` flag accepts either:
|
|||||||
- A port number (e.g., `9222`) for local connections via `http://localhost:{port}`
|
- A port number (e.g., `9222`) for local connections via `http://localhost:{port}`
|
||||||
- A full WebSocket URL (e.g., `wss://...` or `ws://...`) for remote browser services
|
- A full WebSocket URL (e.g., `wss://...` or `ws://...`) for remote browser services
|
||||||
|
|
||||||
|
## Auto-Connect
|
||||||
|
|
||||||
|
Use `--auto-connect` to automatically discover and connect to a running Chrome instance without specifying a port:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Auto-discover running Chrome with remote debugging
|
||||||
|
agent-browser --auto-connect open example.com
|
||||||
|
agent-browser --auto-connect snapshot
|
||||||
|
|
||||||
|
# Or via environment variable
|
||||||
|
AGENT_BROWSER_AUTO_CONNECT=1 agent-browser snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
Auto-connect discovers Chrome by:
|
||||||
|
|
||||||
|
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
|
||||||
|
2. Falling back to probing common debugging ports (9222, 9229)
|
||||||
|
|
||||||
|
This is useful when:
|
||||||
|
|
||||||
|
- Chrome 144+ has remote debugging enabled via `chrome://inspect/#remote-debugging` (which uses a dynamic port)
|
||||||
|
- You want a zero-configuration connection to your existing browser
|
||||||
|
- You don't want to track which port Chrome is using
|
||||||
|
|
||||||
## Use cases
|
## Use cases
|
||||||
|
|
||||||
This enables control of:
|
This enables control of:
|
||||||
@@ -63,6 +87,7 @@ This enables control of:
|
|||||||
| `--exact` | Exact text match |
|
| `--exact` | Exact text match |
|
||||||
| `--headed` | Show browser window |
|
| `--headed` | Show browser window |
|
||||||
| `--cdp <port\|url>` | CDP connection (port or WebSocket URL) |
|
| `--cdp <port\|url>` | CDP connection (port or WebSocket URL) |
|
||||||
|
| `--auto-connect` | Auto-discover and connect to running Chrome |
|
||||||
| `--debug` | Debug output |
|
| `--debug` | Debug output |
|
||||||
|
|
||||||
## Cloud providers
|
## Cloud providers
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ agent-browser reload # Reload page
|
|||||||
--profile <path> # Persistent browser profile directory
|
--profile <path> # Persistent browser profile directory
|
||||||
--headed # Show browser window (not headless)
|
--headed # Show browser window (not headless)
|
||||||
--cdp <port> # Connect via Chrome DevTools Protocol
|
--cdp <port> # Connect via Chrome DevTools Protocol
|
||||||
|
--auto-connect # Auto-discover and connect to running Chrome
|
||||||
--executable-path <path> # Custom browser executable
|
--executable-path <path> # Custom browser executable
|
||||||
--args <args> # Browser launch args (comma separated)
|
--args <args> # Browser launch args (comma separated)
|
||||||
--user-agent <ua> # Custom User-Agent string
|
--user-agent <ua> # Custom User-Agent string
|
||||||
|
|||||||
@@ -122,6 +122,17 @@ agent-browser --session site2 snapshot -i
|
|||||||
agent-browser session list
|
agent-browser session list
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Connect to Existing Chrome
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Auto-discover running Chrome with remote debugging enabled
|
||||||
|
agent-browser --auto-connect open https://example.com
|
||||||
|
agent-browser --auto-connect snapshot
|
||||||
|
|
||||||
|
# Or with explicit CDP port
|
||||||
|
agent-browser --cdp 9222 snapshot
|
||||||
|
```
|
||||||
|
|
||||||
### Visual Browser (Debugging)
|
### Visual Browser (Debugging)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+147
-3
@@ -16,7 +16,7 @@ import {
|
|||||||
} from 'playwright-core';
|
} from 'playwright-core';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
import { existsSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
||||||
import type { LaunchCommand } from './types.js';
|
import type { LaunchCommand } from './types.js';
|
||||||
import { type RefMap, type EnhancedSnapshot, getEnhancedSnapshot, parseRef } from './snapshot.js';
|
import { type RefMap, type EnhancedSnapshot, getEnhancedSnapshot, parseRef } from './snapshot.js';
|
||||||
|
|
||||||
@@ -1082,10 +1082,14 @@ export class BrowserManager {
|
|||||||
|
|
||||||
if (this.isLaunched()) {
|
if (this.isLaunched()) {
|
||||||
const needsRelaunch =
|
const needsRelaunch =
|
||||||
(!cdpEndpoint && this.cdpEndpoint !== null) ||
|
(!cdpEndpoint && !options.autoConnect && this.cdpEndpoint !== null) ||
|
||||||
(!!cdpEndpoint && this.needsCdpReconnect(cdpEndpoint));
|
(!!cdpEndpoint && this.needsCdpReconnect(cdpEndpoint)) ||
|
||||||
|
(!!options.autoConnect && !this.isCdpConnectionAlive());
|
||||||
if (needsRelaunch) {
|
if (needsRelaunch) {
|
||||||
await this.close();
|
await this.close();
|
||||||
|
} else if (options.autoConnect && this.isCdpConnectionAlive()) {
|
||||||
|
// Already connected via auto-connect, no need to reconnect
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1096,6 +1100,11 @@ export class BrowserManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.autoConnect) {
|
||||||
|
await this.autoConnectViaCDP();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Cloud browser providers require explicit opt-in via -p flag or AGENT_BROWSER_PROVIDER env var
|
// Cloud browser providers require explicit opt-in via -p flag or AGENT_BROWSER_PROVIDER env var
|
||||||
// -p flag takes precedence over env var
|
// -p flag takes precedence over env var
|
||||||
const provider = options.provider ?? process.env.AGENT_BROWSER_PROVIDER;
|
const provider = options.provider ?? process.env.AGENT_BROWSER_PROVIDER;
|
||||||
@@ -1283,6 +1292,141 @@ export class BrowserManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Chrome's default user data directory paths for the current platform.
|
||||||
|
* Returns an array of candidate paths to check (stable, then beta/canary).
|
||||||
|
*/
|
||||||
|
private getChromeUserDataDirs(): string[] {
|
||||||
|
const home = os.homedir();
|
||||||
|
const platform = os.platform();
|
||||||
|
|
||||||
|
if (platform === 'darwin') {
|
||||||
|
return [
|
||||||
|
path.join(home, 'Library', 'Application Support', 'Google', 'Chrome'),
|
||||||
|
path.join(home, 'Library', 'Application Support', 'Google', 'Chrome Canary'),
|
||||||
|
path.join(home, 'Library', 'Application Support', 'Chromium'),
|
||||||
|
];
|
||||||
|
} else if (platform === 'win32') {
|
||||||
|
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, 'AppData', 'Local');
|
||||||
|
return [
|
||||||
|
path.join(localAppData, 'Google', 'Chrome', 'User Data'),
|
||||||
|
path.join(localAppData, 'Google', 'Chrome SxS', 'User Data'),
|
||||||
|
path.join(localAppData, 'Chromium', 'User Data'),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Linux
|
||||||
|
return [
|
||||||
|
path.join(home, '.config', 'google-chrome'),
|
||||||
|
path.join(home, '.config', 'google-chrome-unstable'),
|
||||||
|
path.join(home, '.config', 'chromium'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to read the DevToolsActivePort file from a Chrome user data directory.
|
||||||
|
* Returns { port, wsPath } if found, or null if not available.
|
||||||
|
*/
|
||||||
|
private readDevToolsActivePort(userDataDir: string): { port: number; wsPath: string } | null {
|
||||||
|
const filePath = path.join(userDataDir, 'DevToolsActivePort');
|
||||||
|
try {
|
||||||
|
if (!existsSync(filePath)) return null;
|
||||||
|
const content = readFileSync(filePath, 'utf-8').trim();
|
||||||
|
const lines = content.split('\n');
|
||||||
|
if (lines.length < 2) return null;
|
||||||
|
|
||||||
|
const port = parseInt(lines[0].trim(), 10);
|
||||||
|
const wsPath = lines[1].trim();
|
||||||
|
|
||||||
|
if (isNaN(port) || port <= 0 || port > 65535) return null;
|
||||||
|
if (!wsPath) return null;
|
||||||
|
|
||||||
|
return { port, wsPath };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to discover a Chrome CDP endpoint by querying an HTTP debug port.
|
||||||
|
* Returns the WebSocket debugger URL if available.
|
||||||
|
*/
|
||||||
|
private async probeDebugPort(port: number): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
||||||
|
signal: AbortSignal.timeout(2000),
|
||||||
|
});
|
||||||
|
if (!response.ok) return null;
|
||||||
|
const data = (await response.json()) as { webSocketDebuggerUrl?: string };
|
||||||
|
return data.webSocketDebuggerUrl ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-discover and connect to a running Chrome/Chromium instance.
|
||||||
|
*
|
||||||
|
* Discovery strategy:
|
||||||
|
* 1. Read DevToolsActivePort from Chrome's default user data directories
|
||||||
|
* 2. If found, connect using the port and WebSocket path from that file
|
||||||
|
* 3. If not found, probe common debugging ports (9222, 9229)
|
||||||
|
* 4. If a port responds, connect via CDP
|
||||||
|
*/
|
||||||
|
private async autoConnectViaCDP(): Promise<void> {
|
||||||
|
// Strategy 1: Check DevToolsActivePort files
|
||||||
|
const userDataDirs = this.getChromeUserDataDirs();
|
||||||
|
for (const dir of userDataDirs) {
|
||||||
|
const activePort = this.readDevToolsActivePort(dir);
|
||||||
|
if (activePort) {
|
||||||
|
// Verify the port is actually responding
|
||||||
|
const wsUrl = await this.probeDebugPort(activePort.port);
|
||||||
|
if (wsUrl) {
|
||||||
|
// Connect using the discovered WebSocket URL
|
||||||
|
await this.connectViaCDP(wsUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Port from file exists but not responding; try HTTP endpoint directly
|
||||||
|
const httpUrl = `http://127.0.0.1:${activePort.port}`;
|
||||||
|
try {
|
||||||
|
await this.connectViaCDP(httpUrl);
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// Port listed but not connectable, try next directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 2: Probe common debugging ports
|
||||||
|
const commonPorts = [9222, 9229];
|
||||||
|
for (const port of commonPorts) {
|
||||||
|
const wsUrl = await this.probeDebugPort(port);
|
||||||
|
if (wsUrl) {
|
||||||
|
await this.connectViaCDP(wsUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found
|
||||||
|
const platform = os.platform();
|
||||||
|
let hint: string;
|
||||||
|
if (platform === 'darwin') {
|
||||||
|
hint =
|
||||||
|
'Start Chrome with: /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222\n' +
|
||||||
|
'Or enable remote debugging in Chrome 144+ at chrome://inspect/#remote-debugging';
|
||||||
|
} else if (platform === 'win32') {
|
||||||
|
hint =
|
||||||
|
'Start Chrome with: chrome.exe --remote-debugging-port=9222\n' +
|
||||||
|
'Or enable remote debugging in Chrome 144+ at chrome://inspect/#remote-debugging';
|
||||||
|
} else {
|
||||||
|
hint =
|
||||||
|
'Start Chrome with: google-chrome --remote-debugging-port=9222\n' +
|
||||||
|
'Or enable remote debugging in Chrome 144+ at chrome://inspect/#remote-debugging';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`No running Chrome instance with remote debugging found.\n${hint}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up console, error, and close tracking for a page
|
* Set up console, error, and close tracking for a page
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const launchSchema = baseCommandSchema.extend({
|
|||||||
{ message: 'CDP URL must start with ws://, wss://, http://, or https://' }
|
{ message: 'CDP URL must start with ws://, wss://, http://, or https://' }
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
|
autoConnect: z.boolean().optional(),
|
||||||
executablePath: z.string().optional(),
|
executablePath: z.string().optional(),
|
||||||
extensions: z.array(z.string()).optional(),
|
extensions: z.array(z.string()).optional(),
|
||||||
headers: z.record(z.string()).optional(),
|
headers: z.record(z.string()).optional(),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface LaunchCommand extends BaseCommand {
|
|||||||
executablePath?: string;
|
executablePath?: string;
|
||||||
cdpPort?: number;
|
cdpPort?: number;
|
||||||
cdpUrl?: string;
|
cdpUrl?: string;
|
||||||
|
autoConnect?: boolean; // Auto-discover and connect to running Chrome via DevToolsActivePort
|
||||||
extensions?: string[];
|
extensions?: string[];
|
||||||
profile?: string; // Path to persistent browser profile directory
|
profile?: string; // Path to persistent browser profile directory
|
||||||
storageState?: string; // Path to storage state JSON file
|
storageState?: string; // Path to storage state JSON file
|
||||||
|
|||||||
Reference in New Issue
Block a user