feat(cli): 默认开启 stealth 并支持 wait 区间超时
This commit is contained in:
+55
-23
@@ -111,10 +111,12 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
let mut nav_cmd = json!({ "id": id, "action": "navigate", "url": url });
|
let mut nav_cmd = json!({ "id": id, "action": "navigate", "url": url });
|
||||||
// If --headers flag is set, include headers (scoped to this origin)
|
// If --headers flag is set, include headers (scoped to this origin)
|
||||||
if let Some(ref headers_json) = flags.headers {
|
if let Some(ref headers_json) = flags.headers {
|
||||||
let headers = serde_json::from_str::<serde_json::Value>(headers_json)
|
let headers =
|
||||||
.map_err(|_| ParseError::InvalidValue {
|
serde_json::from_str::<serde_json::Value>(headers_json).map_err(|_| {
|
||||||
|
ParseError::InvalidValue {
|
||||||
message: format!("Invalid JSON for --headers: {}", headers_json),
|
message: format!("Invalid JSON for --headers: {}", headers_json),
|
||||||
usage: "open <url> --headers '{\"Key\": \"Value\"}'",
|
usage: "open <url> --headers '{\"Key\": \"Value\"}'",
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
nav_cmd["headers"] = headers;
|
nav_cmd["headers"] = headers;
|
||||||
}
|
}
|
||||||
@@ -287,7 +289,9 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
usage: "keyboard inserttext <text>",
|
usage: "keyboard inserttext <text>",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(json!({ "id": id, "action": "keyboard", "subaction": "insertText", "text": text }))
|
Ok(
|
||||||
|
json!({ "id": id, "action": "keyboard", "subaction": "insertText", "text": text }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => Err(ParseError::UnknownSubcommand {
|
_ => Err(ParseError::UnknownSubcommand {
|
||||||
subcommand: sub.to_string(),
|
subcommand: sub.to_string(),
|
||||||
@@ -386,8 +390,16 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
return Ok(cmd);
|
return Ok(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: selector or timeout
|
// Default: selector, timeout, or range (e.g. 2000-5000)
|
||||||
if let Some(arg) = rest.first() {
|
if let Some(arg) = rest.first() {
|
||||||
|
// Check for range syntax: "2000-5000"
|
||||||
|
if let Some((min_str, max_str)) = arg.split_once('-') {
|
||||||
|
if let (Ok(min), Ok(max)) = (min_str.parse::<u64>(), max_str.parse::<u64>()) {
|
||||||
|
return Ok(
|
||||||
|
json!({ "id": id, "action": "wait", "timeout": min, "timeoutMax": max }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Ok(timeout) = arg.parse::<u64>() {
|
if let Ok(timeout) = arg.parse::<u64>() {
|
||||||
Ok(json!({ "id": id, "action": "wait", "timeout": timeout }))
|
Ok(json!({ "id": id, "action": "wait", "timeout": timeout }))
|
||||||
} else {
|
} else {
|
||||||
@@ -396,7 +408,7 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
} else {
|
} else {
|
||||||
Err(ParseError::MissingArguments {
|
Err(ParseError::MissingArguments {
|
||||||
context: "wait".to_string(),
|
context: "wait".to_string(),
|
||||||
usage: "wait <selector|ms|--url|--load|--fn|--text>",
|
usage: "wait <selector|ms|min-max|--url|--load|--fn|--text>",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -929,9 +941,7 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
})?;
|
})?;
|
||||||
Ok(json!({ "id": id, "action": "state_load", "path": path }))
|
Ok(json!({ "id": id, "action": "state_load", "path": path }))
|
||||||
}
|
}
|
||||||
Some("list") => {
|
Some("list") => Ok(json!({ "id": id, "action": "state_list" })),
|
||||||
Ok(json!({ "id": id, "action": "state_list" }))
|
|
||||||
}
|
|
||||||
Some("clear") => {
|
Some("clear") => {
|
||||||
let mut session_name: Option<&str> = None;
|
let mut session_name: Option<&str> = None;
|
||||||
let mut all = false;
|
let mut all = false;
|
||||||
@@ -952,7 +962,9 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
|
|
||||||
if let Some(name) = session_name {
|
if let Some(name) = session_name {
|
||||||
if !is_valid_session_name(name) {
|
if !is_valid_session_name(name) {
|
||||||
return Err(ParseError::InvalidSessionName { name: name.to_string() });
|
return Err(ParseError::InvalidSessionName {
|
||||||
|
name: name.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,13 +1018,19 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
let new_name = new_name.trim_end_matches(".json");
|
let new_name = new_name.trim_end_matches(".json");
|
||||||
|
|
||||||
if !is_valid_session_name(old_name) {
|
if !is_valid_session_name(old_name) {
|
||||||
return Err(ParseError::InvalidSessionName { name: old_name.to_string() });
|
return Err(ParseError::InvalidSessionName {
|
||||||
|
name: old_name.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if !is_valid_session_name(new_name) {
|
if !is_valid_session_name(new_name) {
|
||||||
return Err(ParseError::InvalidSessionName { name: new_name.to_string() });
|
return Err(ParseError::InvalidSessionName {
|
||||||
|
name: new_name.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(json!({ "id": id, "action": "state_rename", "oldName": old_name, "newName": new_name }))
|
Ok(
|
||||||
|
json!({ "id": id, "action": "state_rename", "oldName": old_name, "newName": new_name }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Some(sub) => Err(ParseError::UnknownSubcommand {
|
Some(sub) => Err(ParseError::UnknownSubcommand {
|
||||||
subcommand: sub.to_string(),
|
subcommand: sub.to_string(),
|
||||||
@@ -1121,7 +1139,10 @@ fn parse_diff(rest: &[&str], id: &str, flags: &Flags) -> Result<Value, ParseErro
|
|||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(ParseError::InvalidValue {
|
return Err(ParseError::InvalidValue {
|
||||||
message: format!("Depth must be a non-negative integer, got: {}", d),
|
message: format!(
|
||||||
|
"Depth must be a non-negative integer, got: {}",
|
||||||
|
d
|
||||||
|
),
|
||||||
usage: "diff snapshot --depth <n>",
|
usage: "diff snapshot --depth <n>",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1187,7 +1208,10 @@ fn parse_diff(rest: &[&str], id: &str, flags: &Flags) -> Result<Value, ParseErro
|
|||||||
}
|
}
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
return Err(ParseError::InvalidValue {
|
return Err(ParseError::InvalidValue {
|
||||||
message: format!("Threshold must be between 0 and 1, got {}", n),
|
message: format!(
|
||||||
|
"Threshold must be between 0 and 1, got {}",
|
||||||
|
n
|
||||||
|
),
|
||||||
usage: "diff screenshot --threshold <0-1>",
|
usage: "diff screenshot --threshold <0-1>",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1304,7 +1328,10 @@ fn parse_diff(rest: &[&str], id: &str, flags: &Flags) -> Result<Value, ParseErro
|
|||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(ParseError::InvalidValue {
|
return Err(ParseError::InvalidValue {
|
||||||
message: format!("Depth must be a non-negative integer, got: {}", d),
|
message: format!(
|
||||||
|
"Depth must be a non-negative integer, got: {}",
|
||||||
|
d
|
||||||
|
),
|
||||||
usage: "diff url <url1> <url2> --depth <n>",
|
usage: "diff url <url1> <url2> --depth <n>",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1896,8 +1923,10 @@ mod tests {
|
|||||||
cli_proxy_bypass: false,
|
cli_proxy_bypass: false,
|
||||||
cli_allow_file_access: false,
|
cli_allow_file_access: false,
|
||||||
cli_annotate: false,
|
cli_annotate: false,
|
||||||
|
cli_stealth: false,
|
||||||
annotate: false,
|
annotate: false,
|
||||||
color_scheme: None,
|
color_scheme: None,
|
||||||
|
stealth: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3029,8 +3058,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diff_snapshot_baseline() {
|
fn test_diff_snapshot_baseline() {
|
||||||
let cmd =
|
let cmd = parse_command(
|
||||||
parse_command(&args("diff snapshot --baseline before.txt"), &default_flags()).unwrap();
|
&args("diff snapshot --baseline before.txt"),
|
||||||
|
&default_flags(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(cmd["action"], "diff_snapshot");
|
assert_eq!(cmd["action"], "diff_snapshot");
|
||||||
assert_eq!(cmd["baseline"], "before.txt");
|
assert_eq!(cmd["baseline"], "before.txt");
|
||||||
}
|
}
|
||||||
@@ -3050,8 +3082,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diff_snapshot_short_flags() {
|
fn test_diff_snapshot_short_flags() {
|
||||||
let cmd =
|
let cmd = parse_command(
|
||||||
parse_command(&args("diff snapshot -b snap.txt -s .content -c -d 2"), &default_flags())
|
&args("diff snapshot -b snap.txt -s .content -c -d 2"),
|
||||||
|
&default_flags(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(cmd["action"], "diff_snapshot");
|
assert_eq!(cmd["action"], "diff_snapshot");
|
||||||
assert_eq!(cmd["baseline"], "snap.txt");
|
assert_eq!(cmd["baseline"], "snap.txt");
|
||||||
@@ -3100,8 +3134,7 @@ mod tests {
|
|||||||
fn test_diff_screenshot_global_full_flag() {
|
fn test_diff_screenshot_global_full_flag() {
|
||||||
let mut flags = default_flags();
|
let mut flags = default_flags();
|
||||||
flags.full = true;
|
flags.full = true;
|
||||||
let cmd =
|
let cmd = parse_command(&args("diff screenshot --baseline b.png"), &flags).unwrap();
|
||||||
parse_command(&args("diff screenshot --baseline b.png"), &flags).unwrap();
|
|
||||||
assert_eq!(cmd["action"], "diff_screenshot");
|
assert_eq!(cmd["action"], "diff_screenshot");
|
||||||
assert_eq!(cmd["fullPage"], true);
|
assert_eq!(cmd["fullPage"], true);
|
||||||
}
|
}
|
||||||
@@ -3145,8 +3178,7 @@ mod tests {
|
|||||||
fn test_diff_url_global_full_flag() {
|
fn test_diff_url_global_full_flag() {
|
||||||
let mut flags = default_flags();
|
let mut flags = default_flags();
|
||||||
flags.full = true;
|
flags.full = true;
|
||||||
let cmd =
|
let cmd = parse_command(&args("diff url https://a.com https://b.com"), &flags).unwrap();
|
||||||
parse_command(&args("diff url https://a.com https://b.com"), &flags).unwrap();
|
|
||||||
assert_eq!(cmd["fullPage"], true);
|
assert_eq!(cmd["fullPage"], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+75
-40
@@ -34,6 +34,7 @@ pub struct Config {
|
|||||||
pub headers: Option<String>,
|
pub headers: Option<String>,
|
||||||
pub annotate: Option<bool>,
|
pub annotate: Option<bool>,
|
||||||
pub color_scheme: Option<String>,
|
pub color_scheme: Option<String>,
|
||||||
|
pub stealth: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -68,6 +69,7 @@ impl Config {
|
|||||||
headers: other.headers.or(self.headers),
|
headers: other.headers.or(self.headers),
|
||||||
annotate: other.annotate.or(self.annotate),
|
annotate: other.annotate.or(self.annotate),
|
||||||
color_scheme: other.color_scheme.or(self.color_scheme),
|
color_scheme: other.color_scheme.or(self.color_scheme),
|
||||||
|
stealth: other.stealth.or(self.stealth),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,8 +158,7 @@ pub fn load_config(args: &[String]) -> Result<Config, String> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some((source, maybe_path)) = explicit {
|
if let Some((source, maybe_path)) = explicit {
|
||||||
let path_str =
|
let path_str = maybe_path.ok_or_else(|| format!("{} requires a file path", source))?;
|
||||||
maybe_path.ok_or_else(|| format!("{} requires a file path", source))?;
|
|
||||||
let path = PathBuf::from(&path_str);
|
let path = PathBuf::from(&path_str);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(format!("config file not found: {}", path_str));
|
return Err(format!("config file not found: {}", path_str));
|
||||||
@@ -203,6 +204,7 @@ pub struct Flags {
|
|||||||
pub session_name: Option<String>,
|
pub session_name: Option<String>,
|
||||||
pub annotate: bool,
|
pub annotate: bool,
|
||||||
pub color_scheme: Option<String>,
|
pub color_scheme: Option<String>,
|
||||||
|
pub stealth: 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)
|
||||||
@@ -216,6 +218,7 @@ pub struct Flags {
|
|||||||
pub cli_proxy_bypass: bool,
|
pub cli_proxy_bypass: bool,
|
||||||
pub cli_allow_file_access: bool,
|
pub cli_allow_file_access: bool,
|
||||||
pub cli_annotate: bool,
|
pub cli_annotate: bool,
|
||||||
|
pub cli_stealth: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_flags(args: &[String]) -> Flags {
|
pub fn parse_flags(args: &[String]) -> Flags {
|
||||||
@@ -241,50 +244,49 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut flags = Flags {
|
let mut flags = Flags {
|
||||||
json: env_var_is_truthy("AGENT_BROWSER_JSON")
|
json: env_var_is_truthy("AGENT_BROWSER_JSON") || config.json.unwrap_or(false),
|
||||||
|| config.json.unwrap_or(false),
|
full: env_var_is_truthy("AGENT_BROWSER_FULL") || config.full.unwrap_or(false),
|
||||||
full: env_var_is_truthy("AGENT_BROWSER_FULL")
|
headed: env_var_is_truthy("AGENT_BROWSER_HEADED") || config.headed.unwrap_or(false),
|
||||||
|| config.full.unwrap_or(false),
|
debug: env_var_is_truthy("AGENT_BROWSER_DEBUG") || config.debug.unwrap_or(false),
|
||||||
headed: env_var_is_truthy("AGENT_BROWSER_HEADED")
|
session: env::var("AGENT_BROWSER_SESSION")
|
||||||
|| config.headed.unwrap_or(false),
|
.ok()
|
||||||
debug: env_var_is_truthy("AGENT_BROWSER_DEBUG")
|
|
||||||
|| config.debug.unwrap_or(false),
|
|
||||||
session: env::var("AGENT_BROWSER_SESSION").ok()
|
|
||||||
.or(config.session)
|
.or(config.session)
|
||||||
.unwrap_or_else(|| "default".to_string()),
|
.unwrap_or_else(|| "default".to_string()),
|
||||||
headers: config.headers,
|
headers: config.headers,
|
||||||
executable_path: env::var("AGENT_BROWSER_EXECUTABLE_PATH").ok()
|
executable_path: env::var("AGENT_BROWSER_EXECUTABLE_PATH")
|
||||||
|
.ok()
|
||||||
.or(config.executable_path),
|
.or(config.executable_path),
|
||||||
cdp: config.cdp,
|
cdp: config.cdp,
|
||||||
extensions,
|
extensions,
|
||||||
profile: env::var("AGENT_BROWSER_PROFILE").ok()
|
profile: env::var("AGENT_BROWSER_PROFILE").ok().or(config.profile),
|
||||||
.or(config.profile),
|
state: env::var("AGENT_BROWSER_STATE").ok().or(config.state),
|
||||||
state: env::var("AGENT_BROWSER_STATE").ok()
|
proxy: env::var("AGENT_BROWSER_PROXY").ok().or(config.proxy),
|
||||||
.or(config.state),
|
proxy_bypass: env::var("AGENT_BROWSER_PROXY_BYPASS")
|
||||||
proxy: env::var("AGENT_BROWSER_PROXY").ok()
|
.ok()
|
||||||
.or(config.proxy),
|
|
||||||
proxy_bypass: env::var("AGENT_BROWSER_PROXY_BYPASS").ok()
|
|
||||||
.or(config.proxy_bypass),
|
.or(config.proxy_bypass),
|
||||||
args: env::var("AGENT_BROWSER_ARGS").ok()
|
args: env::var("AGENT_BROWSER_ARGS").ok().or(config.args),
|
||||||
.or(config.args),
|
user_agent: env::var("AGENT_BROWSER_USER_AGENT")
|
||||||
user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok()
|
.ok()
|
||||||
.or(config.user_agent),
|
.or(config.user_agent),
|
||||||
provider: env::var("AGENT_BROWSER_PROVIDER").ok()
|
provider: env::var("AGENT_BROWSER_PROVIDER").ok().or(config.provider),
|
||||||
.or(config.provider),
|
|
||||||
ignore_https_errors: env_var_is_truthy("AGENT_BROWSER_IGNORE_HTTPS_ERRORS")
|
ignore_https_errors: env_var_is_truthy("AGENT_BROWSER_IGNORE_HTTPS_ERRORS")
|
||||||
|| config.ignore_https_errors.unwrap_or(false),
|
|| config.ignore_https_errors.unwrap_or(false),
|
||||||
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|
||||||
|| config.allow_file_access.unwrap_or(false),
|
|| config.allow_file_access.unwrap_or(false),
|
||||||
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok()
|
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device),
|
||||||
.or(config.device),
|
|
||||||
auto_connect: env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT")
|
auto_connect: env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT")
|
||||||
|| config.auto_connect.unwrap_or(false),
|
|| config.auto_connect.unwrap_or(false),
|
||||||
session_name: env::var("AGENT_BROWSER_SESSION_NAME").ok()
|
session_name: env::var("AGENT_BROWSER_SESSION_NAME")
|
||||||
|
.ok()
|
||||||
.or(config.session_name),
|
.or(config.session_name),
|
||||||
annotate: env_var_is_truthy("AGENT_BROWSER_ANNOTATE")
|
annotate: env_var_is_truthy("AGENT_BROWSER_ANNOTATE") || config.annotate.unwrap_or(false),
|
||||||
|| config.annotate.unwrap_or(false),
|
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME")
|
||||||
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME").ok()
|
.ok()
|
||||||
.or(config.color_scheme),
|
.or(config.color_scheme),
|
||||||
|
stealth: match env::var("AGENT_BROWSER_STEALTH") {
|
||||||
|
Ok(val) => !matches!(val.to_lowercase().as_str(), "0" | "false" | "no" | ""),
|
||||||
|
Err(_) => config.stealth.unwrap_or(true),
|
||||||
|
},
|
||||||
cli_executable_path: false,
|
cli_executable_path: false,
|
||||||
cli_extensions: false,
|
cli_extensions: false,
|
||||||
cli_profile: false,
|
cli_profile: false,
|
||||||
@@ -295,6 +297,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
cli_proxy_bypass: false,
|
cli_proxy_bypass: false,
|
||||||
cli_allow_file_access: false,
|
cli_allow_file_access: false,
|
||||||
cli_annotate: false,
|
cli_annotate: false,
|
||||||
|
cli_stealth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@@ -303,22 +306,30 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
"--json" => {
|
"--json" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.json = val;
|
flags.json = val;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--full" | "-f" => {
|
"--full" | "-f" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.full = val;
|
flags.full = val;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--headed" => {
|
"--headed" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.headed = val;
|
flags.headed = val;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--debug" => {
|
"--debug" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.debug = val;
|
flags.debug = val;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--session" => {
|
"--session" => {
|
||||||
if let Some(s) = args.get(i + 1) {
|
if let Some(s) = args.get(i + 1) {
|
||||||
@@ -403,13 +414,17 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
"--ignore-https-errors" => {
|
"--ignore-https-errors" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.ignore_https_errors = val;
|
flags.ignore_https_errors = val;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--allow-file-access" => {
|
"--allow-file-access" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.allow_file_access = val;
|
flags.allow_file_access = val;
|
||||||
flags.cli_allow_file_access = true;
|
flags.cli_allow_file_access = true;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--device" => {
|
"--device" => {
|
||||||
if let Some(d) = args.get(i + 1) {
|
if let Some(d) = args.get(i + 1) {
|
||||||
@@ -420,7 +435,9 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
"--auto-connect" => {
|
"--auto-connect" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.auto_connect = val;
|
flags.auto_connect = val;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--session-name" => {
|
"--session-name" => {
|
||||||
if let Some(s) = args.get(i + 1) {
|
if let Some(s) = args.get(i + 1) {
|
||||||
@@ -432,7 +449,17 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.annotate = val;
|
flags.annotate = val;
|
||||||
flags.cli_annotate = true;
|
flags.cli_annotate = true;
|
||||||
if consumed { i += 1; }
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"--stealth" => {
|
||||||
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
|
flags.stealth = val;
|
||||||
|
flags.cli_stealth = true;
|
||||||
|
if consumed {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"--color-scheme" => {
|
"--color-scheme" => {
|
||||||
if let Some(s) = args.get(i + 1) {
|
if let Some(s) = args.get(i + 1) {
|
||||||
@@ -465,6 +492,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
|||||||
"--allow-file-access",
|
"--allow-file-access",
|
||||||
"--auto-connect",
|
"--auto-connect",
|
||||||
"--annotate",
|
"--annotate",
|
||||||
|
"--stealth",
|
||||||
];
|
];
|
||||||
// Global flags that always take a value (need to skip the next arg too)
|
// Global flags that always take a value (need to skip the next arg too)
|
||||||
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
||||||
@@ -721,7 +749,10 @@ mod tests {
|
|||||||
assert_eq!(config.session.as_deref(), Some("test-session"));
|
assert_eq!(config.session.as_deref(), Some("test-session"));
|
||||||
assert_eq!(config.session_name.as_deref(), Some("my-app"));
|
assert_eq!(config.session_name.as_deref(), Some("my-app"));
|
||||||
assert_eq!(config.executable_path.as_deref(), Some("/usr/bin/chromium"));
|
assert_eq!(config.executable_path.as_deref(), Some("/usr/bin/chromium"));
|
||||||
assert_eq!(config.extensions, Some(vec!["/ext1".to_string(), "/ext2".to_string()]));
|
assert_eq!(
|
||||||
|
config.extensions,
|
||||||
|
Some(vec!["/ext1".to_string(), "/ext2".to_string()])
|
||||||
|
);
|
||||||
assert_eq!(config.profile.as_deref(), Some("/tmp/profile"));
|
assert_eq!(config.profile.as_deref(), Some("/tmp/profile"));
|
||||||
assert_eq!(config.state.as_deref(), Some("/tmp/state.json"));
|
assert_eq!(config.state.as_deref(), Some("/tmp/state.json"));
|
||||||
assert_eq!(config.proxy.as_deref(), Some("http://proxy:8080"));
|
assert_eq!(config.proxy.as_deref(), Some("http://proxy:8080"));
|
||||||
@@ -1030,7 +1061,11 @@ mod tests {
|
|||||||
let merged = user.merge(project);
|
let merged = user.merge(project);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
merged.extensions,
|
merged.extensions,
|
||||||
Some(vec!["/ext1".to_string(), "/ext2".to_string(), "/ext3".to_string()])
|
Some(vec![
|
||||||
|
"/ext1".to_string(),
|
||||||
|
"/ext2".to_string(),
|
||||||
|
"/ext3".to_string()
|
||||||
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
/// Check if a session name is valid (alphanumeric, hyphens, and underscores only)
|
/// Check if a session name is valid (alphanumeric, hyphens, and underscores only)
|
||||||
pub fn is_valid_session_name(name: &str) -> bool {
|
pub fn is_valid_session_name(name: &str) -> bool {
|
||||||
!name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_')
|
!name.is_empty()
|
||||||
|
&& name
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate error message for invalid session name
|
/// Generate error message for invalid session name
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ Every CLI flag can be set in the config file using its camelCase equivalent:
|
|||||||
<tr><td><code>device</code></td><td><code>--device</code></td><td>string</td></tr>
|
<tr><td><code>device</code></td><td><code>--device</code></td><td>string</td></tr>
|
||||||
<tr><td><code>ignoreHttpsErrors</code></td><td><code>--ignore-https-errors</code></td><td>boolean</td></tr>
|
<tr><td><code>ignoreHttpsErrors</code></td><td><code>--ignore-https-errors</code></td><td>boolean</td></tr>
|
||||||
<tr><td><code>allowFileAccess</code></td><td><code>--allow-file-access</code></td><td>boolean</td></tr>
|
<tr><td><code>allowFileAccess</code></td><td><code>--allow-file-access</code></td><td>boolean</td></tr>
|
||||||
|
<tr><td><code>stealth</code></td><td><code>--stealth</code></td><td>boolean (default: true)</td></tr>
|
||||||
<tr><td><code>cdp</code></td><td><code>--cdp</code></td><td>string</td></tr>
|
<tr><td><code>cdp</code></td><td><code>--cdp</code></td><td>string</td></tr>
|
||||||
<tr><td><code>autoConnect</code></td><td><code>--auto-connect</code></td><td>boolean</td></tr>
|
<tr><td><code>autoConnect</code></td><td><code>--auto-connect</code></td><td>boolean</td></tr>
|
||||||
<tr><td><code>colorScheme</code></td><td><code>--color-scheme</code></td><td>string (<code>dark</code>, <code>light</code>, <code>no-preference</code>)</td></tr>
|
<tr><td><code>colorScheme</code></td><td><code>--color-scheme</code></td><td>string (<code>dark</code>, <code>light</code>, <code>no-preference</code>)</td></tr>
|
||||||
@@ -131,7 +132,7 @@ agent-browser --headed open example.com # same as --headed true
|
|||||||
agent-browser --headed true open example.com # explicit
|
agent-browser --headed true open example.com # explicit
|
||||||
```
|
```
|
||||||
|
|
||||||
This applies to all boolean flags: `--headed`, `--debug`, `--json`, `--ignore-https-errors`, `--allow-file-access`, `--auto-connect`.
|
This applies to all boolean flags: `--headed`, `--debug`, `--json`, `--ignore-https-errors`, `--allow-file-access`, `--stealth`, `--auto-connect`.
|
||||||
|
|
||||||
## Extensions Merging
|
## Extensions Merging
|
||||||
|
|
||||||
|
|||||||
@@ -450,6 +450,7 @@ export async function startDaemon(options?: {
|
|||||||
|
|
||||||
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
||||||
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
||||||
|
const stealth = process.env.AGENT_BROWSER_STEALTH !== '0';
|
||||||
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
||||||
const colorScheme =
|
const colorScheme =
|
||||||
colorSchemeEnv === 'dark' ||
|
colorSchemeEnv === 'dark' ||
|
||||||
@@ -470,6 +471,7 @@ export async function startDaemon(options?: {
|
|||||||
proxy,
|
proxy,
|
||||||
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
||||||
allowFileAccess: allowFileAccess,
|
allowFileAccess: allowFileAccess,
|
||||||
|
stealth,
|
||||||
colorScheme,
|
colorScheme,
|
||||||
autoStateFilePath: getSessionAutoStatePath(),
|
autoStateFilePath: getSessionAutoStatePath(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface LaunchCommand extends BaseCommand {
|
|||||||
ignoreHTTPSErrors?: boolean;
|
ignoreHTTPSErrors?: boolean;
|
||||||
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
||||||
colorScheme?: 'light' | 'dark' | 'no-preference'; // Persistent color scheme override
|
colorScheme?: 'light' | 'dark' | 'no-preference'; // Persistent color scheme override
|
||||||
|
stealth?: boolean; // Enable stealth mode to avoid automation detection
|
||||||
// Auto-load state file for session persistence
|
// Auto-load state file for session persistence
|
||||||
autoStateFilePath?: string;
|
autoStateFilePath?: string;
|
||||||
}
|
}
|
||||||
@@ -828,6 +829,7 @@ export interface WaitCommand extends BaseCommand {
|
|||||||
action: 'wait';
|
action: 'wait';
|
||||||
selector?: string;
|
selector?: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
timeoutMax?: number; // When set with timeout, waits a random duration in [timeout, timeoutMax]
|
||||||
state?: 'attached' | 'detached' | 'visible' | 'hidden';
|
state?: 'attached' | 'detached' | 'visible' | 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user