feat: support remote CDP WebSocket URLs in --cdp flag (#99)
Previously, the --cdp flag only accepted a port number and connected via http://localhost:{port}. This made it impossible to connect to remote browser services like Kernel, Browserless, etc. that provide WebSocket URLs. The --cdp flag now accepts either: - A port number (e.g., 9222) for local connections - A full WebSocket URL (e.g., wss://...) for remote browser services Changes: - Added cdpUrl field to LaunchCommand type - Updated protocol validation to accept URL format with scheme validation - Modified connectViaCDP to detect and handle both formats - Handle numeric strings for JSON serialization edge cases - Updated CLI to send cdpUrl or cdpPort based on input format - Updated README with examples for remote connections Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
c4139fa389
commit
e892bceadf
+68
-37
@@ -80,7 +80,8 @@ fn run_session(args: &[String], session: &str, json_mode: bool) {
|
||||
let running = unsafe { libc::kill(pid as i32, 0) == 0 };
|
||||
#[cfg(windows)]
|
||||
let running = unsafe {
|
||||
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
|
||||
let handle =
|
||||
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
|
||||
if handle != 0 {
|
||||
CloseHandle(handle);
|
||||
true
|
||||
@@ -192,7 +193,12 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
let daemon_result = match ensure_daemon(&flags.session, flags.headed, flags.executable_path.as_deref(), &flags.extensions) {
|
||||
let daemon_result = match ensure_daemon(
|
||||
&flags.session,
|
||||
flags.headed,
|
||||
flags.executable_path.as_deref(),
|
||||
&flags.extensions,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
if flags.json {
|
||||
@@ -205,7 +211,9 @@ fn main() {
|
||||
};
|
||||
|
||||
// Warn if executable_path was specified but daemon was already running
|
||||
if daemon_result.already_running && (flags.executable_path.is_some() || !flags.extensions.is_empty()) {
|
||||
if daemon_result.already_running
|
||||
&& (flags.executable_path.is_some() || !flags.extensions.is_empty())
|
||||
{
|
||||
if !flags.json {
|
||||
if flags.executable_path.is_some() {
|
||||
eprintln!("{} --executable-path ignored: daemon already running. Use 'agent-browser close' first to restart with new path.", color::warning_indicator());
|
||||
@@ -238,47 +246,70 @@ fn main() {
|
||||
}
|
||||
|
||||
// Connect via CDP if --cdp flag is set
|
||||
if let Some(ref port) = flags.cdp {
|
||||
let cdp_port: u16 = match port.parse::<u32>() {
|
||||
Ok(p) if p == 0 => {
|
||||
let msg = "Invalid CDP port: port must be greater than 0".to_string();
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||
} else {
|
||||
eprintln!("{} {}", color::error_indicator(), msg);
|
||||
// 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 {
|
||||
let launch_cmd = if cdp_value.starts_with("ws://")
|
||||
|| cdp_value.starts_with("wss://")
|
||||
|| cdp_value.starts_with("http://")
|
||||
|| cdp_value.starts_with("https://")
|
||||
{
|
||||
// It's a URL - use cdpUrl field
|
||||
json!({
|
||||
"id": gen_id(),
|
||||
"action": "launch",
|
||||
"cdpUrl": cdp_value
|
||||
})
|
||||
} else {
|
||||
// It's a port number - validate and use cdpPort field
|
||||
let cdp_port: u16 = match cdp_value.parse::<u32>() {
|
||||
Ok(p) if p == 0 => {
|
||||
let msg = "Invalid CDP port: port must be greater than 0".to_string();
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||
} else {
|
||||
eprintln!("{} {}", color::error_indicator(), msg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
Ok(p) if p > 65535 => {
|
||||
let msg = format!("Invalid CDP port: {} is out of range (valid range: 1-65535)", p);
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||
} else {
|
||||
eprintln!("{} {}", color::error_indicator(), msg);
|
||||
Ok(p) if p > 65535 => {
|
||||
let msg = format!(
|
||||
"Invalid CDP port: {} is out of range (valid range: 1-65535)",
|
||||
p
|
||||
);
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||
} else {
|
||||
eprintln!("{} {}", color::error_indicator(), msg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
Ok(p) => p as u16,
|
||||
Err(_) => {
|
||||
let msg = format!("Invalid CDP port: '{}' is not a valid number. Port must be a number between 1 and 65535", port);
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||
} else {
|
||||
eprintln!("{} {}", color::error_indicator(), msg);
|
||||
Ok(p) => p as u16,
|
||||
Err(_) => {
|
||||
let msg = format!(
|
||||
"Invalid CDP value: '{}' is not a valid port number or URL",
|
||||
cdp_value
|
||||
);
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||
} else {
|
||||
eprintln!("{} {}", color::error_indicator(), msg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
json!({
|
||||
"id": gen_id(),
|
||||
"action": "launch",
|
||||
"cdpPort": cdp_port
|
||||
})
|
||||
};
|
||||
|
||||
let launch_cmd = json!({
|
||||
"id": gen_id(),
|
||||
"action": "launch",
|
||||
"cdpPort": cdp_port
|
||||
});
|
||||
|
||||
let err = match send_command(launch_cmd, &flags.session) {
|
||||
Ok(resp) if resp.success => None,
|
||||
Ok(resp) => Some(resp.error.unwrap_or_else(|| "CDP connection failed".to_string())),
|
||||
Ok(resp) => Some(
|
||||
resp.error
|
||||
.unwrap_or_else(|| "CDP connection failed".to_string()),
|
||||
),
|
||||
Err(e) => Some(e.to_string()),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user