use crate::color; use crate::connection::Response; pub fn print_response(resp: &Response, json_mode: bool) { if json_mode { println!("{}", serde_json::to_string(resp).unwrap_or_default()); return; } if !resp.success { eprintln!( "{} {}", color::error_indicator(), resp.error.as_deref().unwrap_or("Unknown error") ); return; } if let Some(data) = &resp.data { // Navigation response if let Some(url) = data.get("url").and_then(|v| v.as_str()) { if let Some(title) = data.get("title").and_then(|v| v.as_str()) { println!("{} {}", color::success_indicator(), color::bold(title)); println!(" {}", color::dim(url)); return; } println!("{}", url); return; } // Snapshot if let Some(snapshot) = data.get("snapshot").and_then(|v| v.as_str()) { println!("{}", snapshot); return; } // Title if let Some(title) = data.get("title").and_then(|v| v.as_str()) { println!("{}", title); return; } // Text if let Some(text) = data.get("text").and_then(|v| v.as_str()) { println!("{}", text); return; } // HTML if let Some(html) = data.get("html").and_then(|v| v.as_str()) { println!("{}", html); return; } // Value if let Some(value) = data.get("value").and_then(|v| v.as_str()) { println!("{}", value); return; } // Count if let Some(count) = data.get("count").and_then(|v| v.as_i64()) { println!("{}", count); return; } // Boolean results if let Some(visible) = data.get("visible").and_then(|v| v.as_bool()) { println!("{}", visible); return; } if let Some(enabled) = data.get("enabled").and_then(|v| v.as_bool()) { println!("{}", enabled); return; } if let Some(checked) = data.get("checked").and_then(|v| v.as_bool()) { println!("{}", checked); return; } // Eval result if let Some(result) = data.get("result") { println!( "{}", serde_json::to_string_pretty(result).unwrap_or_default() ); return; } // Tabs if let Some(tabs) = data.get("tabs").and_then(|v| v.as_array()) { for (i, tab) in tabs.iter().enumerate() { let title = tab .get("title") .and_then(|v| v.as_str()) .unwrap_or("Untitled"); let url = tab.get("url").and_then(|v| v.as_str()).unwrap_or(""); let active = tab.get("active").and_then(|v| v.as_bool()).unwrap_or(false); let marker = if active { color::cyan("→") } else { " ".to_string() }; println!("{} [{}] {} - {}", marker, i, title, url); } return; } // Console logs if let Some(logs) = data.get("messages").and_then(|v| v.as_array()) { for log in logs { let level = log.get("type").and_then(|v| v.as_str()).unwrap_or("log"); let text = log.get("text").and_then(|v| v.as_str()).unwrap_or(""); println!("{} {}", color::console_level_prefix(level), text); } return; } // Errors if let Some(errors) = data.get("errors").and_then(|v| v.as_array()) { for err in errors { let msg = err.get("message").and_then(|v| v.as_str()).unwrap_or(""); println!("{} {}", color::error_indicator(), msg); } return; } // Cookies if let Some(cookies) = data.get("cookies").and_then(|v| v.as_array()) { for cookie in cookies { let name = cookie.get("name").and_then(|v| v.as_str()).unwrap_or(""); let value = cookie.get("value").and_then(|v| v.as_str()).unwrap_or(""); println!("{}={}", name, value); } return; } // Network requests if let Some(requests) = data.get("requests").and_then(|v| v.as_array()) { if requests.is_empty() { println!("No requests captured"); } else { for req in requests { let method = req.get("method").and_then(|v| v.as_str()).unwrap_or("GET"); let url = req.get("url").and_then(|v| v.as_str()).unwrap_or(""); let resource_type = req.get("resourceType").and_then(|v| v.as_str()).unwrap_or(""); println!("{} {} ({})", method, url, resource_type); } } return; } // Cleared requests if let Some(cleared) = data.get("cleared").and_then(|v| v.as_bool()) { if cleared { println!("\x1b[32m✓\x1b[0m Request log cleared"); return; } } // Bounding box if let Some(box_data) = data.get("box") { println!( "{}", serde_json::to_string_pretty(box_data).unwrap_or_default() ); return; } // Element styles if let Some(elements) = data.get("elements").and_then(|v| v.as_array()) { for (i, el) in elements.iter().enumerate() { let tag = el.get("tag").and_then(|v| v.as_str()).unwrap_or("?"); let text = el.get("text").and_then(|v| v.as_str()).unwrap_or(""); println!("[{}] {} \"{}\"", i, tag, text); if let Some(box_data) = el.get("box") { let w = box_data.get("width").and_then(|v| v.as_i64()).unwrap_or(0); let h = box_data.get("height").and_then(|v| v.as_i64()).unwrap_or(0); let x = box_data.get("x").and_then(|v| v.as_i64()).unwrap_or(0); let y = box_data.get("y").and_then(|v| v.as_i64()).unwrap_or(0); println!(" box: {}x{} at ({}, {})", w, h, x, y); } if let Some(styles) = el.get("styles") { let font_size = styles.get("fontSize").and_then(|v| v.as_str()).unwrap_or(""); let font_weight = styles.get("fontWeight").and_then(|v| v.as_str()).unwrap_or(""); let font_family = styles.get("fontFamily").and_then(|v| v.as_str()).unwrap_or(""); let color = styles.get("color").and_then(|v| v.as_str()).unwrap_or(""); let bg = styles.get("backgroundColor").and_then(|v| v.as_str()).unwrap_or(""); let radius = styles.get("borderRadius").and_then(|v| v.as_str()).unwrap_or(""); println!(" font: {} {} {}", font_size, font_weight, font_family); println!(" color: {}", color); println!(" background: {}", bg); if radius != "0px" { println!(" border-radius: {}", radius); } } println!(); } return; } // Closed if data.get("closed").is_some() { println!("{} Browser closed", color::success_indicator()); return; } // Recording start (has "started" field) if let Some(started) = data.get("started").and_then(|v| v.as_bool()) { if started { if let Some(path) = data.get("path").and_then(|v| v.as_str()) { println!("{} Recording started: {}", color::success_indicator(), path); } else { println!("{} Recording started", color::success_indicator()); } return; } } // Recording restart (has "stopped" field - from recording_restart action) if data.get("stopped").is_some() { let path = data.get("path").and_then(|v| v.as_str()).unwrap_or("unknown"); if let Some(prev_path) = data.get("previousPath").and_then(|v| v.as_str()) { println!("{} Recording restarted: {} (previous saved to {})", color::success_indicator(), path, prev_path); } else { println!("{} Recording started: {}", color::success_indicator(), path); } return; } // Recording stop (has "frames" field - from recording_stop action) if data.get("frames").is_some() { if let Some(path) = data.get("path").and_then(|v| v.as_str()) { if let Some(error) = data.get("error").and_then(|v| v.as_str()) { println!("{} Recording saved to {} - {}", color::warning_indicator(), path, error); } else { println!("{} Recording saved to {}", color::success_indicator(), path); } } else { println!("{} Recording stopped", color::success_indicator()); } return; } // Screenshot path (no "started" or "frames" field) if let Some(path) = data.get("path").and_then(|v| v.as_str()) { println!("{} Screenshot saved to {}", color::success_indicator(), color::green(path)); return; } // Screenshot base64 if let Some(base64) = data.get("base64").and_then(|v| v.as_str()) { println!("{}", base64); return; } // Default success println!("{} Done", color::success_indicator()); } } /// Print command-specific help. Returns true if help was printed, false if command unknown. pub fn print_command_help(command: &str) -> bool { let help = match command { // === Navigation === "open" | "goto" | "navigate" => r##" agent-browser open - Navigate to a URL Usage: agent-browser open Navigates the browser to the specified URL. If no protocol is provided, https:// is automatically prepended. Aliases: goto, navigate Global Options: --json Output as JSON --session Use specific session --headers Set HTTP headers (scoped to this origin) --headed Show browser window Examples: agent-browser open example.com agent-browser open https://github.com agent-browser open localhost:3000 agent-browser open api.example.com --headers '{"Authorization": "Bearer token"}' # ^ Headers only sent to api.example.com, not other domains "##, "back" => r##" agent-browser back - Navigate back in history Usage: agent-browser back Goes back one page in the browser history, equivalent to clicking the browser's back button. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser back "##, "forward" => r##" agent-browser forward - Navigate forward in history Usage: agent-browser forward Goes forward one page in the browser history, equivalent to clicking the browser's forward button. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser forward "##, "reload" => r##" agent-browser reload - Reload the current page Usage: agent-browser reload Reloads the current page, equivalent to pressing F5 or clicking the browser's reload button. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser reload "##, // === Core Actions === "click" => r##" agent-browser click - Click an element Usage: agent-browser click Clicks on the specified element. The selector can be a CSS selector, XPath, or an element reference from snapshot (e.g., @e1). Global Options: --json Output as JSON --session Use specific session Examples: agent-browser click "#submit-button" agent-browser click @e1 agent-browser click "button.primary" agent-browser click "//button[@type='submit']" "##, "dblclick" => r##" agent-browser dblclick - Double-click an element Usage: agent-browser dblclick Double-clicks on the specified element. Useful for text selection or triggering double-click handlers. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser dblclick "#editable-text" agent-browser dblclick @e5 "##, "fill" => r##" agent-browser fill - Clear and fill an input field Usage: agent-browser fill Clears the input field and fills it with the specified text. This replaces any existing content in the field. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser fill "#email" "user@example.com" agent-browser fill @e3 "Hello World" agent-browser fill "input[name='search']" "query" "##, "type" => r##" agent-browser type - Type text into an element Usage: agent-browser type Types text into the specified element character by character. Unlike fill, this does not clear existing content first. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser type "#search" "hello" agent-browser type @e2 "additional text" "##, "hover" => r##" agent-browser hover - Hover over an element Usage: agent-browser hover Moves the mouse to hover over the specified element. Useful for triggering hover states or dropdown menus. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser hover "#dropdown-trigger" agent-browser hover @e4 "##, "focus" => r##" agent-browser focus - Focus an element Usage: agent-browser focus Sets keyboard focus to the specified element. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser focus "#input-field" agent-browser focus @e2 "##, "check" => r##" agent-browser check - Check a checkbox Usage: agent-browser check Checks a checkbox element. If already checked, no action is taken. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser check "#terms-checkbox" agent-browser check @e7 "##, "uncheck" => r##" agent-browser uncheck - Uncheck a checkbox Usage: agent-browser uncheck Unchecks a checkbox element. If already unchecked, no action is taken. Global Options: --json Output as JSON --session Use specific session Examples: agent-browser uncheck "#newsletter-opt-in" agent-browser uncheck @e8 "##, "select" => r##" agent-browser select - Select a dropdown option Usage: agent-browser select Selects one or more options in a