diff --git a/cli/Cargo.lock b/cli/Cargo.lock index b4230b93..00b37a68 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "agent-browser" -version = "0.6.0" +version = "0.7.1" dependencies = [ "dirs", "libc", diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 535aae19..aaa09e75 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -485,14 +485,106 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result { let name = rest.get(1).ok_or_else(|| ParseError::MissingArguments { context: "cookies set".to_string(), - usage: "cookies set ", + usage: "cookies set [--url ] [--domain ] [--path ] [--httpOnly] [--secure] [--sameSite ] [--expires ]", })?; let value = rest.get(2).ok_or_else(|| ParseError::MissingArguments { context: "cookies set".to_string(), - usage: "cookies set ", + usage: "cookies set [--url ] [--domain ] [--path ] [--httpOnly] [--secure] [--sameSite ] [--expires ]", })?; + + let mut cookie = json!({ "name": name, "value": value }); + + // Parse optional flags + let mut i = 3; + while i < rest.len() { + match rest[i] { + "--url" => { + if let Some(url) = rest.get(i + 1) { + cookie["url"] = json!(url); + i += 2; + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --url".to_string(), + usage: "--url ", + }); + } + } + "--domain" => { + if let Some(domain) = rest.get(i + 1) { + cookie["domain"] = json!(domain); + i += 2; + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --domain".to_string(), + usage: "--domain ", + }); + } + } + "--path" => { + if let Some(path) = rest.get(i + 1) { + cookie["path"] = json!(path); + i += 2; + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --path".to_string(), + usage: "--path ", + }); + } + } + "--httpOnly" => { + cookie["httpOnly"] = json!(true); + i += 1; + } + "--secure" => { + cookie["secure"] = json!(true); + i += 1; + } + "--sameSite" => { + if let Some(same_site) = rest.get(i + 1) { + // Validate sameSite value + if *same_site == "Strict" || *same_site == "Lax" || *same_site == "None" { + cookie["sameSite"] = json!(same_site); + i += 2; + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --sameSite".to_string(), + usage: "--sameSite ", + }); + } + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --sameSite".to_string(), + usage: "--sameSite ", + }); + } + } + "--expires" => { + if let Some(expires_str) = rest.get(i + 1) { + if let Ok(expires) = expires_str.parse::() { + cookie["expires"] = json!(expires); + i += 2; + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --expires".to_string(), + usage: "--expires ", + }); + } + } else { + return Err(ParseError::MissingArguments { + context: "cookies set --expires".to_string(), + usage: "--expires ", + }); + } + } + _ => { + // Unknown flag, skip it (or could error) + i += 1; + } + } + } + Ok( - json!({ "id": id, "action": "cookies_set", "cookies": [{ "name": name, "value": value }] }), + json!({ "id": id, "action": "cookies_set", "cookies": [cookie] }), ) } "clear" => Ok(json!({ "id": id, "action": "cookies_clear" })), @@ -1254,6 +1346,102 @@ mod tests { assert_eq!(cmd["action"], "cookies_clear"); } + #[test] + fn test_cookies_set_with_url() { + let cmd = parse_command(&args("cookies set mycookie myvalue --url https://example.com"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["url"], "https://example.com"); + } + + #[test] + fn test_cookies_set_with_domain() { + let cmd = parse_command(&args("cookies set mycookie myvalue --domain example.com"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["domain"], "example.com"); + } + + #[test] + fn test_cookies_set_with_path() { + let cmd = parse_command(&args("cookies set mycookie myvalue --path /api"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["path"], "/api"); + } + + #[test] + fn test_cookies_set_with_httponly() { + let cmd = parse_command(&args("cookies set mycookie myvalue --httpOnly"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["httpOnly"], true); + } + + #[test] + fn test_cookies_set_with_secure() { + let cmd = parse_command(&args("cookies set mycookie myvalue --secure"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["secure"], true); + } + + #[test] + fn test_cookies_set_with_samesite() { + let cmd = parse_command(&args("cookies set mycookie myvalue --sameSite Strict"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["sameSite"], "Strict"); + } + + #[test] + fn test_cookies_set_with_expires() { + let cmd = parse_command(&args("cookies set mycookie myvalue --expires 1234567890"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["expires"], 1234567890); + } + + #[test] + fn test_cookies_set_with_multiple_flags() { + let cmd = parse_command(&args("cookies set mycookie myvalue --url https://example.com --httpOnly --secure --sameSite Lax"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["url"], "https://example.com"); + assert_eq!(cmd["cookies"][0]["httpOnly"], true); + assert_eq!(cmd["cookies"][0]["secure"], true); + assert_eq!(cmd["cookies"][0]["sameSite"], "Lax"); + } + + #[test] + fn test_cookies_set_with_all_flags() { + let cmd = parse_command(&args("cookies set mycookie myvalue --url https://example.com --domain example.com --path /api --httpOnly --secure --sameSite None --expires 9999999999"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "cookies_set"); + assert_eq!(cmd["cookies"][0]["name"], "mycookie"); + assert_eq!(cmd["cookies"][0]["value"], "myvalue"); + assert_eq!(cmd["cookies"][0]["url"], "https://example.com"); + assert_eq!(cmd["cookies"][0]["domain"], "example.com"); + assert_eq!(cmd["cookies"][0]["path"], "/api"); + assert_eq!(cmd["cookies"][0]["httpOnly"], true); + assert_eq!(cmd["cookies"][0]["secure"], true); + assert_eq!(cmd["cookies"][0]["sameSite"], "None"); + assert_eq!(cmd["cookies"][0]["expires"], 9999999999i64); + } + + #[test] + fn test_cookies_set_invalid_samesite() { + let result = parse_command(&args("cookies set mycookie myvalue --sameSite Invalid"), &default_flags()); + assert!(result.is_err()); + } + // === Storage Tests === #[test] diff --git a/cli/src/output.rs b/cli/src/output.rs index 2e348629..f8fab1e7 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -1082,18 +1082,46 @@ Usage: agent-browser cookies [operation] [args] Manage browser cookies for the current context. Operations: - get Get all cookies (default) - set Set a cookie - clear Clear all cookies + get Get all cookies (default) + set [options] Set a cookie with optional properties + clear Clear all cookies + +Cookie Set Options: + --url URL for the cookie (allows setting before page load) + --domain Cookie domain (e.g., ".example.com") + --path Cookie path (e.g., "/api") + --httpOnly Set HttpOnly flag (prevents JavaScript access) + --secure Set Secure flag (HTTPS only) + --sameSite SameSite policy + --expires Expiration time (Unix timestamp in seconds) + +Note: If --url, --domain, and --path are all omitted, the cookie will be set +for the current page URL. Global Options: --json Output as JSON --session Use specific session Examples: - agent-browser cookies - agent-browser cookies get + # Simple cookie for current page agent-browser cookies set session_id "abc123" + + # Set cookie for a URL before loading it (useful for authentication) + agent-browser cookies set session_id "abc123" --url https://app.example.com + + # Set secure, httpOnly cookie with domain and path + agent-browser cookies set auth_token "xyz789" --domain example.com --path /api --httpOnly --secure + + # Set cookie with SameSite policy + agent-browser cookies set tracking_consent "yes" --sameSite Strict + + # Set cookie with expiration (Unix timestamp) + agent-browser cookies set temp_token "temp123" --expires 1735689600 + + # Get all cookies + agent-browser cookies + + # Clear all cookies agent-browser cookies clear "## } @@ -1494,7 +1522,7 @@ Network: agent-browser network requests [--clear] [--filter ] Storage: - cookies [get|set|clear] Manage cookies + cookies [get|set|clear] Manage cookies (set supports --url, --domain, --path, --httpOnly, --secure, --sameSite, --expires) storage Manage web storage Tabs: