feat: Support for screenshot annotate on rust (#706)

Co-authored-by: hidezhao <hidezhao@tencent.com>
This commit is contained in:
Hideeee
2026-03-11 22:25:28 -05:00
committed by GitHub
co-authored by hidezhao
parent 89f9c97ac2
commit 18c3112dbd
11 changed files with 739 additions and 85 deletions
+66 -31
View File
@@ -30,6 +30,31 @@ use output::{
use std::path::PathBuf;
use std::process::Command as ProcessCommand;
fn serialize_json_value(value: &serde_json::Value) -> String {
serde_json::to_string(value).unwrap_or_else(|_| {
r#"{"success":false,"error":"Failed to serialize JSON response"}"#.to_string()
})
}
fn print_json_value(value: serde_json::Value) {
println!("{}", serialize_json_value(&value));
}
fn print_json_error(message: impl AsRef<str>) {
print_json_value(json!({
"success": false,
"error": message.as_ref(),
}));
}
fn print_json_error_with_type(message: impl AsRef<str>, error_type: &str) {
print_json_value(json!({
"success": false,
"error": message.as_ref(),
"type": error_type,
}));
}
/// Run a local auth command (auth_save/list/show/delete) via node auth-cli.js.
/// These commands don't need a browser, so we handle them directly to avoid
/// sending passwords through the daemon's Unix socket channel.
@@ -63,7 +88,7 @@ fn run_auth_cli(cmd: &serde_json::Value, json_mode: bool) -> ! {
Some(p) => p.clone(),
None => {
if json_mode {
println!(r#"{{"success":false,"error":"auth-cli.js not found"}}"#);
print_json_error("auth-cli.js not found");
} else {
eprintln!(
"{} auth-cli.js not found. Set AGENT_BROWSER_HOME or run from project directory.",
@@ -92,7 +117,7 @@ fn run_auth_cli(cmd: &serde_json::Value, json_mode: bool) -> ! {
if stdout.is_empty() {
if json_mode {
println!(r#"{{"success":false,"error":"No response from auth-cli"}}"#);
print_json_error("No response from auth-cli");
} else {
eprintln!("{} No response from auth-cli", color::error_indicator());
}
@@ -125,10 +150,7 @@ fn run_auth_cli(cmd: &serde_json::Value, json_mode: bool) -> ! {
}
Err(e) => {
if json_mode {
println!(
r#"{{"success":false,"error":"Failed to run auth-cli: {}"}}"#,
e
);
print_json_error(format!("Failed to run auth-cli: {}", e));
} else {
eprintln!("{} Failed to run auth-cli: {}", color::error_indicator(), e);
}
@@ -235,7 +257,12 @@ fn run_session(args: &[String], session: &str, json_mode: bool) {
None | Some(_) => {
// Just show current session
if json_mode {
println!(r#"{{"success":true,"data":{{"session":"{}"}}}}"#, session);
print_json_value(json!({
"success": true,
"data": {
"session": session,
},
}));
} else {
println!("{}", session);
}
@@ -326,11 +353,7 @@ fn main() {
ParseError::InvalidValue { .. } => "invalid_value",
ParseError::InvalidSessionName { .. } => "invalid_session_name",
};
println!(
r#"{{"success":false,"error":"{}","type":"{}"}}"#,
e.format().replace('\n', " "),
error_type
);
print_json_error_with_type(e.format(), error_type);
} else {
eprintln!("{}", color::red(&e.format()));
}
@@ -385,10 +408,7 @@ fn main() {
if !validation::is_valid_session_name(name) {
let msg = validation::session_name_error(name);
if flags.json {
println!(
r#"{{"success":false,"error":"{}","type":"invalid_session_name"}}"#,
msg.replace('"', "\\\"")
);
print_json_error_with_type(msg, "invalid_session_name");
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -423,7 +443,7 @@ fn main() {
Ok(result) => result,
Err(e) => {
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, e);
print_json_error(e);
} else {
eprintln!("{} {}", color::error_indicator(), e);
}
@@ -494,7 +514,7 @@ fn main() {
if flags.cdp.is_some() && flags.provider.is_some() {
let msg = "Cannot use --cdp and -p/--provider together";
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -504,7 +524,7 @@ fn main() {
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);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -514,7 +534,7 @@ fn main() {
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);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -524,7 +544,7 @@ fn main() {
if flags.provider.is_some() && !flags.extensions.is_empty() {
let msg = "Cannot use --extension with -p/--provider (extensions require local browser)";
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -534,7 +554,7 @@ fn main() {
if flags.cdp.is_some() && !flags.extensions.is_empty() {
let msg = "Cannot use --extension with --cdp (extensions require local browser)";
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -572,7 +592,7 @@ fn main() {
if let Some(msg) = err {
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -600,7 +620,7 @@ fn main() {
Ok(0) => {
let msg = "Invalid CDP port: port must be greater than 0".to_string();
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(&msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -612,7 +632,7 @@ fn main() {
p
);
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(&msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -625,7 +645,7 @@ fn main() {
cdp_value
);
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(&msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -662,7 +682,7 @@ fn main() {
if let Some(msg) = err {
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -693,7 +713,7 @@ fn main() {
if let Some(msg) = err {
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
print_json_error(msg);
} else {
eprintln!("{} {}", color::error_indicator(), msg);
}
@@ -797,7 +817,7 @@ fn main() {
.error
.unwrap_or_else(|| "Browser launch failed".to_string());
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, error_msg);
print_json_error(error_msg);
} else {
eprintln!("{} {}", color::error_indicator(), error_msg);
}
@@ -805,7 +825,7 @@ fn main() {
}
Err(e) => {
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, e);
print_json_error(e);
} else {
eprintln!(
"{} Could not configure browser: {}",
@@ -892,7 +912,7 @@ fn main() {
}
Err(e) => {
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, e);
print_json_error(e);
} else {
eprintln!("{} {}", color::error_indicator(), e);
}
@@ -958,4 +978,19 @@ mod tests {
assert_eq!(result["username"], "user");
assert_eq!(result["password"], "p@ss:w0rd");
}
#[test]
fn test_serialize_json_value_escapes_control_characters() {
let payload = serialize_json_value(&json!({
"success": false,
"error": "Daemon process exited during startup:\nline \"quoted\"\u{001b}[2mansi\u{001b}[22m",
}));
let parsed: serde_json::Value = serde_json::from_str(&payload).unwrap();
assert_eq!(parsed["success"], false);
assert_eq!(
parsed["error"],
"Daemon process exited during startup:\nline \"quoted\"\u{001b}[2mansi\u{001b}[22m"
);
}
}