fix(cli): 修复 type/keyboard 的 --delay 参数解析
将 --delay <ms> 从输入文本中剥离并写入 delay 字段,避免搜索词混入参数。 同时支持使用 -- 终止参数解析以输入字面量 --delay 文本,并补充回归测试与帮助文档。
This commit is contained in:
@@ -143,10 +143,10 @@ agent-browser open <url> # Navigate to URL (aliases: goto, navigate
|
|||||||
agent-browser click <sel> # Click element (--new-tab to open in new tab)
|
agent-browser click <sel> # Click element (--new-tab to open in new tab)
|
||||||
agent-browser dblclick <sel> # Double-click element
|
agent-browser dblclick <sel> # Double-click element
|
||||||
agent-browser focus <sel> # Focus element
|
agent-browser focus <sel> # Focus element
|
||||||
agent-browser type <sel> <text> # Type into element
|
agent-browser type <sel> <text> [--delay <ms>] # Type into element
|
||||||
agent-browser fill <sel> <text> # Clear and fill
|
agent-browser fill <sel> <text> # Clear and fill
|
||||||
agent-browser press <key> # Press key (Enter, Tab, Control+a) (alias: key)
|
agent-browser press <key> # Press key (Enter, Tab, Control+a) (alias: key)
|
||||||
agent-browser keyboard type <text> # Type with real keystrokes (no selector, current focus)
|
agent-browser keyboard type <text> [--delay <ms>] # Type with real keystrokes (no selector, current focus)
|
||||||
agent-browser keyboard inserttext <text> # Insert text without key events (no selector)
|
agent-browser keyboard inserttext <text> # Insert text without key events (no selector)
|
||||||
agent-browser keydown <key> # Hold key down
|
agent-browser keydown <key> # Hold key down
|
||||||
agent-browser keyup <key> # Release key
|
agent-browser keyup <key> # Release key
|
||||||
|
|||||||
Generated
+1
-1
@@ -4,7 +4,7 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agent-browser-stealth"
|
name = "agent-browser-stealth"
|
||||||
version = "0.14.0-fork.2"
|
version = "0.14.0-fork.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "agent-browser-stealth"
|
name = "agent-browser-stealth"
|
||||||
version = "0.14.0-fork.2"
|
version = "0.14.0-fork.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Stealth browser automation CLI for AI agents with anti-bot evasions"
|
description = "Stealth browser automation CLI for AI agents with anti-bot evasions"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
+112
-9
@@ -71,6 +71,62 @@ pub fn gen_id() -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse free-form text arguments with optional `--delay <ms>`.
|
||||||
|
///
|
||||||
|
/// `--` can be used to stop flag parsing if text must include `--delay` literally.
|
||||||
|
fn parse_text_with_optional_delay(
|
||||||
|
args: &[&str],
|
||||||
|
context: &str,
|
||||||
|
usage: &'static str,
|
||||||
|
) -> Result<(String, Option<u64>), ParseError> {
|
||||||
|
let mut text_parts: Vec<&str> = Vec::new();
|
||||||
|
let mut delay_ms: Option<u64> = None;
|
||||||
|
let mut parse_flags = true;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while i < args.len() {
|
||||||
|
let arg = args[i];
|
||||||
|
|
||||||
|
if parse_flags && arg == "--" {
|
||||||
|
parse_flags = false;
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if parse_flags && arg == "--delay" {
|
||||||
|
let raw = args
|
||||||
|
.get(i + 1)
|
||||||
|
.ok_or_else(|| ParseError::MissingArguments {
|
||||||
|
context: format!("{} --delay", context),
|
||||||
|
usage,
|
||||||
|
})?;
|
||||||
|
let parsed = raw.parse::<u64>().map_err(|_| ParseError::InvalidValue {
|
||||||
|
message: format!(
|
||||||
|
"Invalid --delay value: {} (must be a non-negative integer in milliseconds)",
|
||||||
|
raw
|
||||||
|
),
|
||||||
|
usage,
|
||||||
|
})?;
|
||||||
|
delay_ms = Some(parsed);
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
text_parts.push(arg);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = text_parts.join(" ");
|
||||||
|
if text.is_empty() {
|
||||||
|
return Err(ParseError::MissingArguments {
|
||||||
|
context: context.to_string(),
|
||||||
|
usage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((text, delay_ms))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError> {
|
pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(ParseError::MissingArguments {
|
return Err(ParseError::MissingArguments {
|
||||||
@@ -165,9 +221,18 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
"type" => {
|
"type" => {
|
||||||
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
|
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
|
||||||
context: "type".to_string(),
|
context: "type".to_string(),
|
||||||
usage: "type <selector> <text>",
|
usage: "type <selector> <text> [--delay <ms>]",
|
||||||
})?;
|
})?;
|
||||||
Ok(json!({ "id": id, "action": "type", "selector": sel, "text": rest[1..].join(" ") }))
|
let (text, delay) = parse_text_with_optional_delay(
|
||||||
|
&rest[1..],
|
||||||
|
"type",
|
||||||
|
"type <selector> <text> [--delay <ms>]",
|
||||||
|
)?;
|
||||||
|
let mut cmd = json!({ "id": id, "action": "type", "selector": sel, "text": text });
|
||||||
|
if let Some(ms) = delay {
|
||||||
|
cmd["delay"] = json!(ms);
|
||||||
|
}
|
||||||
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
"hover" => {
|
"hover" => {
|
||||||
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
|
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
|
||||||
@@ -272,14 +337,16 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
})?;
|
})?;
|
||||||
match *sub {
|
match *sub {
|
||||||
"type" => {
|
"type" => {
|
||||||
let text: String = rest[1..].join(" ");
|
let (text, delay) = parse_text_with_optional_delay(
|
||||||
if text.is_empty() {
|
&rest[1..],
|
||||||
return Err(ParseError::MissingArguments {
|
"keyboard type",
|
||||||
context: "keyboard type".to_string(),
|
"keyboard type <text> [--delay <ms>]",
|
||||||
usage: "keyboard type <text>",
|
)?;
|
||||||
});
|
let mut cmd = json!({ "id": id, "action": "keyboard", "subaction": "type", "text": text });
|
||||||
|
if let Some(ms) = delay {
|
||||||
|
cmd["delay"] = json!(ms);
|
||||||
}
|
}
|
||||||
Ok(json!({ "id": id, "action": "keyboard", "subaction": "type", "text": text }))
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
"inserttext" | "insertText" => {
|
"inserttext" | "insertText" => {
|
||||||
let text: String = rest[1..].join(" ");
|
let text: String = rest[1..].join(" ");
|
||||||
@@ -2300,6 +2367,29 @@ mod tests {
|
|||||||
assert_eq!(cmd["text"], "some text");
|
assert_eq!(cmd["text"], "some text");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_command_with_delay() {
|
||||||
|
let cmd =
|
||||||
|
parse_command(&args("type #input some text --delay 120"), &default_flags()).unwrap();
|
||||||
|
assert_eq!(cmd["action"], "type");
|
||||||
|
assert_eq!(cmd["selector"], "#input");
|
||||||
|
assert_eq!(cmd["text"], "some text");
|
||||||
|
assert_eq!(cmd["delay"], 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_command_with_literal_delay_text() {
|
||||||
|
let cmd = parse_command(
|
||||||
|
&args("type #input -- --delay 120 should be typed"),
|
||||||
|
&default_flags(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cmd["action"], "type");
|
||||||
|
assert_eq!(cmd["selector"], "#input");
|
||||||
|
assert_eq!(cmd["text"], "--delay 120 should be typed");
|
||||||
|
assert!(cmd.get("delay").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_select() {
|
fn test_select() {
|
||||||
let cmd = parse_command(&args("select #menu option1"), &default_flags()).unwrap();
|
let cmd = parse_command(&args("select #menu option1"), &default_flags()).unwrap();
|
||||||
@@ -2479,6 +2569,19 @@ mod tests {
|
|||||||
assert_eq!(cmd["selector"], "#element");
|
assert_eq!(cmd["selector"], "#element");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyboard_type_with_delay() {
|
||||||
|
let cmd = parse_command(
|
||||||
|
&args("keyboard type natural typing --delay 90"),
|
||||||
|
&default_flags(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cmd["action"], "keyboard");
|
||||||
|
assert_eq!(cmd["subaction"], "type");
|
||||||
|
assert_eq!(cmd["text"], "natural typing");
|
||||||
|
assert_eq!(cmd["delay"], 90);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wait_timeout() {
|
fn test_wait_timeout() {
|
||||||
let cmd = parse_command(&args("wait 5000"), &default_flags()).unwrap();
|
let cmd = parse_command(&args("wait 5000"), &default_flags()).unwrap();
|
||||||
|
|||||||
+8
-4
@@ -719,10 +719,11 @@ Examples:
|
|||||||
r##"
|
r##"
|
||||||
agent-browser type - Type text into an element
|
agent-browser type - Type text into an element
|
||||||
|
|
||||||
Usage: agent-browser type <selector> <text>
|
Usage: agent-browser type <selector> <text> [--delay <ms>]
|
||||||
|
|
||||||
Types text into the specified element character by character.
|
Types text into the specified element character by character.
|
||||||
Unlike fill, this does not clear existing content first.
|
Unlike fill, this does not clear existing content first.
|
||||||
|
Use --delay to add per-character delay (milliseconds).
|
||||||
|
|
||||||
Global Options:
|
Global Options:
|
||||||
--json Output as JSON
|
--json Output as JSON
|
||||||
@@ -730,7 +731,9 @@ Global Options:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
agent-browser type "#search" "hello"
|
agent-browser type "#search" "hello"
|
||||||
|
agent-browser type "#search" "iphone" --delay 120
|
||||||
agent-browser type @e2 "additional text"
|
agent-browser type @e2 "additional text"
|
||||||
|
agent-browser type @e2 -- "--delay 120 (literal text)"
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
For typing into contenteditable editors (Lexical, ProseMirror, etc.)
|
For typing into contenteditable editors (Lexical, ProseMirror, etc.)
|
||||||
@@ -961,7 +964,7 @@ the current focus — essential for contenteditable editors like
|
|||||||
Lexical, ProseMirror, CodeMirror, and Monaco.
|
Lexical, ProseMirror, CodeMirror, and Monaco.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
type <text> Type text character-by-character with real
|
type <text> [--delay <ms>] Type text character-by-character with real
|
||||||
key events (keydown, keypress, keyup per char)
|
key events (keydown, keypress, keyup per char)
|
||||||
inserttext <text> Insert text without key events (like paste)
|
inserttext <text> Insert text without key events (like paste)
|
||||||
|
|
||||||
@@ -974,6 +977,7 @@ Global Options:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
agent-browser keyboard type "Hello, World!"
|
agent-browser keyboard type "Hello, World!"
|
||||||
|
agent-browser keyboard type "human pacing" --delay 90
|
||||||
agent-browser keyboard type "# My Heading"
|
agent-browser keyboard type "# My Heading"
|
||||||
agent-browser keyboard inserttext "pasted content"
|
agent-browser keyboard inserttext "pasted content"
|
||||||
|
|
||||||
@@ -2016,10 +2020,10 @@ Core Commands:
|
|||||||
open <url> Navigate to URL
|
open <url> Navigate to URL
|
||||||
click <sel> Click element (or @ref)
|
click <sel> Click element (or @ref)
|
||||||
dblclick <sel> Double-click element
|
dblclick <sel> Double-click element
|
||||||
type <sel> <text> Type into element
|
type <sel> <text> [--delay <ms>] Type into element
|
||||||
fill <sel> <text> Clear and fill
|
fill <sel> <text> Clear and fill
|
||||||
press <key> Press key (Enter, Tab, Control+a)
|
press <key> Press key (Enter, Tab, Control+a)
|
||||||
keyboard type <text> Type text with real keystrokes (no selector)
|
keyboard type <text> [--delay <ms>] Type text with real keystrokes (no selector)
|
||||||
keyboard inserttext <text> Insert text without key events
|
keyboard inserttext <text> Insert text without key events
|
||||||
hover <sel> Hover element
|
hover <sel> Hover element
|
||||||
focus <sel> Focus element
|
focus <sel> Focus element
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ agent-browser open <url> # Navigate (aliases: goto, navigate)
|
|||||||
agent-browser click <sel> # Click element (--new-tab to open in new tab)
|
agent-browser click <sel> # Click element (--new-tab to open in new tab)
|
||||||
agent-browser dblclick <sel> # Double-click
|
agent-browser dblclick <sel> # Double-click
|
||||||
agent-browser fill <sel> <text> # Clear and fill
|
agent-browser fill <sel> <text> # Clear and fill
|
||||||
agent-browser type <sel> <text> # Type into element
|
agent-browser type <sel> <text> [--delay <ms>] # Type into element
|
||||||
agent-browser press <key> # Press key (Enter, Tab, Control+a) (alias: key)
|
agent-browser press <key> # Press key (Enter, Tab, Control+a) (alias: key)
|
||||||
agent-browser keyboard type <text> # Type at current focus (no selector needed)
|
agent-browser keyboard type <text> [--delay <ms>] # Type at current focus (no selector needed)
|
||||||
agent-browser keyboard inserttext <text> # Insert text without key events
|
agent-browser keyboard inserttext <text> # Insert text without key events
|
||||||
agent-browser keydown <key> # Hold key down
|
agent-browser keydown <key> # Hold key down
|
||||||
agent-browser keyup <key> # Release key
|
agent-browser keyup <key> # Release key
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ agent-browser snapshot -s "#selector" # Scope to CSS selector
|
|||||||
agent-browser click @e1 # Click element
|
agent-browser click @e1 # Click element
|
||||||
agent-browser click @e1 --new-tab # Click and open in new tab
|
agent-browser click @e1 --new-tab # Click and open in new tab
|
||||||
agent-browser fill @e2 "text" # Clear and type text
|
agent-browser fill @e2 "text" # Clear and type text
|
||||||
agent-browser type @e2 "text" # Type without clearing
|
agent-browser type @e2 "text" --delay 120 # Type without clearing (human-like pacing)
|
||||||
agent-browser select @e1 "option" # Select dropdown option
|
agent-browser select @e1 "option" # Select dropdown option
|
||||||
agent-browser check @e1 # Check checkbox
|
agent-browser check @e1 # Check checkbox
|
||||||
agent-browser press Enter # Press key
|
agent-browser press Enter # Press key
|
||||||
agent-browser keyboard type "text" # Type at current focus (no selector)
|
agent-browser keyboard type "text" --delay 90 # Type at current focus (no selector)
|
||||||
agent-browser keyboard inserttext "text" # Insert without key events
|
agent-browser keyboard inserttext "text" # Insert without key events
|
||||||
agent-browser scroll down 500 # Scroll page
|
agent-browser scroll down 500 # Scroll page
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user