feat: add screenshot output config, clipboard CLI commands, and fix wait --text native path (#749)
* feat: add screenshot output config, clipboard CLI commands, and fix wait --text native path
## Summary
- Add `--screenshot-dir`, `--screenshot-quality`, and `--screenshot-format` CLI flags (with corresponding `AGENT_BROWSER_SCREENSHOT_DIR`, `AGENT_BROWSER_SCREENSHOT_QUALITY`, `AGENT_BROWSER_SCREENSHOT_FORMAT` env vars) so users can configure where and how screenshots are saved without specifying a full path every time
- Add `clipboard read`, `clipboard write <text>`, `clipboard copy`, and `clipboard paste` CLI commands, exposing the existing protocol-level clipboard handlers that were previously only accessible via JSON-RPC
- Fix `wait --text` in native mode: the CLI was emitting `selector: "text=..."` (a Playwright-style locator) which native's `querySelector` can't handle. Now emits a `text` field that correctly hits the native `wait_for_text` polling path
- Add native clipboard `copy` and `paste` support via CDP `Input.dispatchKeyEvent`, and a `write` operation to the Node.js handler
* fix: resolve CI failures in Rust formatting and TypeScript typecheck
Use string-based page.evaluate for clipboard writeText to avoid
referencing `navigator` in Node.js compilation context. Run cargo fmt
to fix formatting in commands.rs and screenshot.rs.
* fix: clipboard write captures full multi-word text
Use rest[1..].join(" ") instead of rest.get(1) so unquoted multi-word
input like `clipboard write hello world` sends the full string rather
than silently dropping everything after the first word.
* improvements
* fixes
* improvements
* improvements
This commit is contained in:
@@ -1415,6 +1415,10 @@ async fn handle_screenshot(cmd: &Value, state: &mut DaemonState) -> Result<Value
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(|q| q as i32),
|
||||
annotate,
|
||||
output_dir: cmd
|
||||
.get("screenshotDir")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from),
|
||||
};
|
||||
|
||||
if annotate {
|
||||
@@ -1641,6 +1645,11 @@ async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
|
||||
let session_id = mgr.active_session_id()?.to_string();
|
||||
let timeout_ms = cmd.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30000);
|
||||
|
||||
if let Some(text) = cmd.get("text").and_then(|v| v.as_str()) {
|
||||
wait_for_text(&mgr.client, &session_id, text, timeout_ms).await?;
|
||||
return Ok(json!({ "waited": "text", "text": text }));
|
||||
}
|
||||
|
||||
if let Some(selector) = cmd.get("selector").and_then(|v| v.as_str()) {
|
||||
let state_str = cmd
|
||||
.get("state")
|
||||
@@ -1655,11 +1664,6 @@ async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
|
||||
return Ok(json!({ "waited": "url", "url": url_pattern }));
|
||||
}
|
||||
|
||||
if let Some(text) = cmd.get("text").and_then(|v| v.as_str()) {
|
||||
wait_for_text(&mgr.client, &session_id, text, timeout_ms).await?;
|
||||
return Ok(json!({ "waited": "text", "text": text }));
|
||||
}
|
||||
|
||||
if let Some(fn_str) = cmd.get("function").and_then(|v| v.as_str()) {
|
||||
wait_for_function(&mgr.client, &session_id, fn_str, timeout_ms).await?;
|
||||
return Ok(json!({ "waited": "function" }));
|
||||
@@ -3107,8 +3111,13 @@ async fn handle_clipboard(cmd: &Value, state: &DaemonState) -> Result<Value, Str
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("read");
|
||||
|
||||
let session_id = mgr.active_session_id()?.to_string();
|
||||
|
||||
// cfg! is compile-time; assumes the browser runs on the same OS as the CLI binary.
|
||||
let modifier: i32 = if cfg!(target_os = "macos") { 4 } else { 2 };
|
||||
|
||||
match action {
|
||||
"write" | "copy" => {
|
||||
"write" => {
|
||||
let text = cmd
|
||||
.get("text")
|
||||
.or_else(|| cmd.get("value"))
|
||||
@@ -3119,7 +3128,17 @@ async fn handle_clipboard(cmd: &Value, state: &DaemonState) -> Result<Value, Str
|
||||
serde_json::to_string(text).unwrap_or_default()
|
||||
);
|
||||
mgr.evaluate(&js, None).await?;
|
||||
Ok(json!({ "copied": text }))
|
||||
Ok(json!({ "written": text }))
|
||||
}
|
||||
"copy" => {
|
||||
interaction::press_key_with_modifiers(&mgr.client, &session_id, "c", Some(modifier))
|
||||
.await?;
|
||||
Ok(json!({ "copied": true }))
|
||||
}
|
||||
"paste" => {
|
||||
interaction::press_key_with_modifiers(&mgr.client, &session_id, "v", Some(modifier))
|
||||
.await?;
|
||||
Ok(json!({ "pasted": true }))
|
||||
}
|
||||
_ => {
|
||||
let result = mgr.evaluate("navigator.clipboard.readText()", None).await?;
|
||||
@@ -4190,6 +4209,7 @@ async fn handle_diff_screenshot(cmd: &Value, state: &DaemonState) -> Result<Valu
|
||||
format: "png".to_string(),
|
||||
quality: None,
|
||||
annotate: false,
|
||||
output_dir: None,
|
||||
};
|
||||
|
||||
let result =
|
||||
|
||||
Reference in New Issue
Block a user