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:
Chris Tate
2026-03-13 02:58:30 -05:00
committed by GitHub
parent 1129a3e7fc
commit a673a77c4e
12 changed files with 369 additions and 32 deletions
+62
View File
@@ -43,6 +43,9 @@ pub struct Config {
pub confirm_interactive: Option<bool>,
pub native: Option<bool>,
pub engine: Option<String>,
pub screenshot_dir: Option<String>,
pub screenshot_quality: Option<u32>,
pub screenshot_format: Option<String>,
}
impl Config {
@@ -86,6 +89,9 @@ impl Config {
confirm_interactive: other.confirm_interactive.or(self.confirm_interactive),
native: other.native.or(self.native),
engine: other.engine.or(self.engine),
screenshot_dir: other.screenshot_dir.or(self.screenshot_dir),
screenshot_quality: other.screenshot_quality.or(self.screenshot_quality),
screenshot_format: other.screenshot_format.or(self.screenshot_format),
}
}
}
@@ -161,6 +167,9 @@ fn extract_config_path(args: &[String]) -> Option<Option<String>> {
"--action-policy",
"--confirm-actions",
"--engine",
"--screenshot-dir",
"--screenshot-quality",
"--screenshot-format",
];
let mut i = 0;
while i < args.len() {
@@ -240,6 +249,9 @@ pub struct Flags {
pub confirm_interactive: bool,
pub native: bool,
pub engine: Option<String>,
pub screenshot_dir: Option<String>,
pub screenshot_quality: Option<u32>,
pub screenshot_format: Option<String>,
// Track which launch-time options were explicitly passed via CLI
// (as opposed to being set only via environment variables)
@@ -347,6 +359,17 @@ pub fn parse_flags(args: &[String]) -> Flags {
|| config.confirm_interactive.unwrap_or(false),
native: env_var_is_truthy("AGENT_BROWSER_NATIVE") || config.native.unwrap_or(false),
engine: env::var("AGENT_BROWSER_ENGINE").ok().or(config.engine),
screenshot_dir: env::var("AGENT_BROWSER_SCREENSHOT_DIR")
.ok()
.or(config.screenshot_dir),
screenshot_quality: env::var("AGENT_BROWSER_SCREENSHOT_QUALITY")
.ok()
.and_then(|s| s.parse().ok())
.or(config.screenshot_quality),
screenshot_format: env::var("AGENT_BROWSER_SCREENSHOT_FORMAT")
.ok()
.or(config.screenshot_format)
.filter(|s| s == "png" || s == "jpeg"),
cli_executable_path: false,
cli_extensions: false,
cli_profile: false,
@@ -586,6 +609,42 @@ pub fn parse_flags(args: &[String]) -> Flags {
i += 1;
}
}
"--screenshot-dir" => {
if let Some(s) = args.get(i + 1) {
flags.screenshot_dir = Some(s.clone());
i += 1;
}
}
"--screenshot-quality" => {
if let Some(s) = args.get(i + 1) {
if let Ok(n) = s.parse::<u32>() {
if n <= 100 {
flags.screenshot_quality = Some(n);
} else {
eprintln!(
"{} --screenshot-quality must be 0-100, got {}",
color::warning_indicator(),
n
);
}
}
i += 1;
}
}
"--screenshot-format" => {
if let Some(s) = args.get(i + 1) {
if s == "png" || s == "jpeg" {
flags.screenshot_format = Some(s.clone());
} else {
eprintln!(
"{} --screenshot-format must be png or jpeg, got '{}'",
color::warning_indicator(),
s
);
}
i += 1;
}
}
"--config" => {
// Already handled by load_config(); skip the value
i += 1;
@@ -640,6 +699,9 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
"--confirm-actions",
"--config",
"--engine",
"--screenshot-dir",
"--screenshot-quality",
"--screenshot-format",
];
let mut i = 0;