Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ use std::io::{self, BufRead};

use crate::flags::Flags;

/// Parse and extract timeout flag from arguments
/// Returns (timeout_value, filtered_args_without_timeout)
fn extract_timeout<'a>(args: &'a [&'a str]) -> (Option<u64>, Vec<&'a str>) {
let mut timeout = None;
let mut filtered = Vec::new();
let mut skip_next = false;

for (i, &arg) in args.iter().enumerate() {
if skip_next {
skip_next = false;
continue;
}

if arg == "--timeout" {
if let Some(&next) = args.get(i + 1) {
timeout = next.parse::<u64>().ok();
skip_next = true;
}
} else {
filtered.push(arg);
}
}

(timeout, filtered)
}

/// Error type for command parsing with contextual information
#[derive(Debug)]
pub enum ParseError {
Expand Down Expand Up @@ -117,11 +143,17 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError

// === Core Actions ===
"click" => {
let sel = rest.get(0).ok_or_else(|| ParseError::MissingArguments {
let (timeout, filtered) = extract_timeout(&rest);
let sel = filtered.get(0).ok_or_else(|| ParseError::MissingArguments {
context: "click".to_string(),
usage: "click <selector>",
usage: "click <selector> [--timeout <ms>]",
})?;
Ok(json!({ "id": id, "action": "click", "selector": sel }))

let mut cmd = json!({ "id": id, "action": "click", "selector": sel });
if let Some(t) = timeout {
cmd["timeout"] = json!(t);
}
Ok(cmd)
}
"dblclick" => {
let sel = rest.get(0).ok_or_else(|| ParseError::MissingArguments {
Expand Down
1 change: 1 addition & 0 deletions skills/agent-browser/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ agent-browser snapshot -s "#selector" # Scope to CSS selector

# Interaction (use @refs from snapshot)
agent-browser click @e1 # Click element
agent-browser click @e1 --timeout 500 # Use if page hangs (optional)
agent-browser fill @e2 "text" # Clear and type text
agent-browser type @e2 "text" # Type without clearing
agent-browser select @e1 "option" # Select dropdown option
Expand Down
1 change: 1 addition & 0 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ async function handleClick(command: ClickCommand, browser: BrowserManager): Prom
button: command.button,
clickCount: command.clickCount,
delay: command.delay,
timeout: command.timeout,
});
} catch (error) {
throw toAIFriendlyError(error, command.selector);
Expand Down
19 changes: 19 additions & 0 deletions src/protocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,29 @@ describe('parseCommand', () => {
}
});

it('should parse click command with timeout', () => {
const result = parseCommand(
cmd({ id: '1', action: 'click', selector: '#btn', timeout: 500 })
);
expect(result.success).toBe(true);
if (result.success) {
expect(result.command.action).toBe('click');
expect(result.command.selector).toBe('#btn');
expect(result.command.timeout).toBe(500);
}
});

it('should reject click without selector', () => {
const result = parseCommand(cmd({ id: '1', action: 'click' }));
expect(result.success).toBe(false);
});

it('should reject click with invalid timeout', () => {
const result = parseCommand(
cmd({ id: '1', action: 'click', selector: '#btn', timeout: -100 })
);
expect(result.success).toBe(false);
});
});

describe('type', () => {
Expand Down
1 change: 1 addition & 0 deletions src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const clickSchema = baseCommandSchema.extend({
button: z.enum(['left', 'right', 'middle']).optional(),
clickCount: z.number().positive().optional(),
delay: z.number().nonnegative().optional(),
timeout: z.number().positive().optional(),
});

const typeSchema = baseCommandSchema.extend({
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface ClickCommand extends BaseCommand {
button?: 'left' | 'right' | 'middle';
clickCount?: number;
delay?: number;
timeout?: number;
}

export interface TypeCommand extends BaseCommand {
Expand Down