* fix: add ref for cursor-interactive content roles
* fix: format
* feat: always include cursor-interactive elements in snapshot, -C is deprecated
* feat: process StaticText aggregation and deduplication
* update test
* clean up
* fix: escape text of elements in snapshot
* fix: redundant slicing
* fix: cargo fmt
* feat: deduplicate redundant StaticText
---------
Co-authored-by: 羲洋 <lipengyang.lpy@alibaba-inc.com>
* refactor: make --full/-f a command-level flag instead of global
Move --full/-f from global flags (parsed in flags.rs) to command-level
parsing in commands.rs, scoped to the three commands that actually use
it: `screenshot`, `diff screenshot`, and `diff url`.
This frees up `-f` for other commands (e.g. `--follow` on
`console`/`errors`, see #867) and better reflects that full-page
capture is not a global concern.
Changes:
- Remove `full` from Flags struct, Config struct, and global flag parsing
- Remove `--full`/`-f` from clean_args global boolean flags list
- Parse `--full`/`-f` inline in `screenshot` command handler
- Accept `-f` shorthand in `diff screenshot` and `diff url` (previously
only `--full` was accepted at command level)
- Remove fallback from global `flags.full` in diff subcommands
- Update tests to pass --full as a command argument rather than a global flag
Fixes#876
* fix: remove stale AGENT_BROWSER_FULL env var from help and add -f shorthand tests
- Remove AGENT_BROWSER_FULL from help text in output.rs since the env
var is no longer read after moving --full to command-level parsing
- Add test_screenshot_full_page_shorthand to verify screenshot -f works
- Add test_diff_screenshot_command_full_flag_shorthand to verify
diff screenshot -f works
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Expose HAR recording as a CLI subcommand under the existing `network`
command so users can capture and export network traffic without a
separate tool or opening the browser twice.
- Parse `network har start` and `network har stop [path]` in commands.rs
- Enrich HarEntry with request/response headers, timestamps, status text,
resource type, HTTP version, and body sizes from CDP events
- Produce HAR 1.2 output with creator/browser metadata, query strings,
and proper header arrays compatible with Chrome DevTools HAR viewer
- Auto-generate output path under ~/.agent-browser/tmp/har/ when omitted
- Add har_stop to skip_launch list so export works without a live browser
- Update help text, README, docs site, SKILL.md, and security policy docs
- Add unit tests for parsing, HAR entry serialization, and stop behavior
Add `batch` command that reads a JSON array of commands from stdin
and executes them sequentially against the daemon. This avoids
per-command process startup overhead when AI agents run multi-step
browser workflows.
Supports --bail to stop on first error (default: continue all)
and --json for structured output as an array of results.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add --idle-timeout CLI flag for daemon auto-shutdown
Add user-friendly --idle-timeout flag that converts time strings
to milliseconds. Supports formats like '10s', '3m', '1h', or raw ms.
This addresses a common need for ephemeral/CI environments where
daemon processes can be orphaned if not explicitly closed, leading
to resource consumption from zombie chrome-headless-shell processes.
Co-authored-by: Hermes (via claude-sonnet-4-20250520) <agent@hermes.ai>
* fix: address idle-timeout review feedback
* fix: normalize idle-timeout parsing
---------
Co-authored-by: Merlin <merlin@rbeckner.com>
Co-authored-by: Hermes (via claude-sonnet-4-20250520) <agent@hermes.ai>
When user explicitly sets --headed false, the CLI was ignoring this
flag because the launch condition only checked if flags.headed was
true. This meant that --headed false would not trigger a launch
command, and subsequent commands would auto-launch with default
headless=true.
The fix adds a cli_headed flag to track when the user explicitly
sets --headed (regardless of value), and includes this in the
launch condition check.
Fixes#743
This PR fixes CI build failures by addressing code formatting and linting issues that were causing the builds to fail.
**Changes made:**
1. **Rust formatting fixes in `cli/src/commands.rs`:**
- Removed unnecessary multi-line formatting for clipboard operations
- Applied consistent single-line formatting for return statements
- Fixed line length and formatting for the `test_wait_text_with_timeout` test function
2. **TypeScript fixes in `src/actions.ts`:**
- Fixed `waitForFunction` usage in the `handleWait` function by replacing the function parameter approach with a string-based implementation
- Properly escaped the text parameter using `JSON.stringify` to prevent potential injection issues
These changes ensure the code passes linting checks (clippy for Rust, ESLint for TypeScript) and formatting validation (rustfmt, prettier) that are enforced in the CI pipeline.
Fixes#751
* 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
* Fix clippy warnings across CLI codebase
Fixes#653
* Fix remaining items_after_test_module clippy warnings
Move functions defined after `mod tests` blocks to before the test
modules in recording.rs and webdriver/client.rs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: suppress spurious --native warning when set via env var
When AGENT_BROWSER_NATIVE=1 is set via environment variable, every
command after the first would warn:
⚠ --native ignored: daemon already running.
This is a false positive — the daemon was already spawned in native
mode and inherited the env var. The warning should only fire when
--native is explicitly passed on the CLI to an already-running daemon.
Add cli_native flag (consistent with existing cli_* pattern) to
distinguish CLI origin from env var origin.
* fix: add flag to test cfg
* fix: cli_native should track flag presence, not value
--native false on CLI should still warn when daemon is already
running, since the user is explicitly trying to change the mode.
The CLI's URL normalization was auto-prepending https:// to any URL
whose scheme wasn't in the allowlist (http, https, about, data, file).
This caused chrome-extension:// URLs to become
https://chrome-extension//... which fails with ERR_NAME_NOT_RESOLVED,
preventing navigation to extension pages (popup, side panel, options).
Add chrome-extension:// and chrome:// to the open command's scheme
allowlist, and update the record start/restart commands to preserve
any URL that already contains :// instead of only checking for http.
Fixes#409
* add security hardening features
- Add authentication vault (`auth save/login/list/show/delete`) so credentials are stored locally and never exposed to the LLM (fixes Snyk W007)
- Add `--content-boundaries` flag to wrap page-sourced output in structural markers, helping LLMs distinguish tool output from untrusted page content (fixes Snyk W011)
- Add `--allowed-domains` flag to restrict browser navigation to trusted domains
- Add `--action-policy` for static allow/deny gating of action categories, with opt-in `--confirm-actions`/`--confirm-interactive` for orchestrator or human-in-the-loop confirmation
- Add `--max-output` flag to truncate large page outputs, preventing context flooding
- New docs page at /security, updated README, SKILL.md, CLI help text, and templates
* fixes
* fixes
* fixes
* fixes
* fixes
* fixes
* fixes
* docs
* add --selector flag to scroll command
The `scroll` command uses `window.scrollBy()`, which has no effect on apps
that use custom scrollable containers (e.g. a nested div with overflow-y: auto).
The backend `handleScroll` already supports a `selector` parameter, but the CLI
never exposed it. This adds `-s` / `--selector` to the `scroll` command so users
can target a specific scrollable element:
agent-browser scroll down 500 --selector "div.scroll-container"
Also fixes the backend to apply `direction`/`amount` when a selector is present
(previously those fields were only used in the no-selector branch).
Closes#501
* fixes
* feat: add --download-path option
Adds a `--download-path` flag (and `AGENT_BROWSER_DOWNLOAD_PATH` env / `downloadPath` config key) to set a default download directory for browser downloads.
Without this, Playwright stores downloads in a temp directory that is deleted when the browser closes. The new option passes through to Playwright's `downloadsPath` on `launch()` and `launchPersistentContext()`.
Fixes#507
* improvements
* fixes
* fixes
Adds `keyboard type` and `keyboard insertText` subcommands that
operate on the currently focused element without requiring a selector.
Essential for contenteditable editors (Lexical, ProseMirror, CodeMirror,
Monaco) where `type <selector>` doesn't trigger the editor's internal
event pipeline (beforeinput/DOM mutation).
- `keyboard type <text>` — page.keyboard.type() with real keystrokes
- `keyboard insertText <text>` — page.keyboard.insertText()
Note: `keyboard press` intentionally omitted — the existing top-level
`press` command already operates on current focus.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The warning "⚠ --annotate only applies to the screenshot command" fires
on every non-screenshot command when annotate is set in config. This is
noisy for users who set it as a persistent default.
Add cli_annotate tracking (matching the existing cli_* pattern) so the
warning only fires when --annotate is passed as a CLI flag.
Fixes#519. Playwright defaults `colorScheme` to `light` on all new contexts, overriding the browser/OS dark mode setting. This is especially disruptive in CDP mode, where every reconnection resets the scheme. The `set media dark` command also didn't persist its choice to new tabs or pages.
- Add `--color-scheme <dark|light|no-preference>` flag, config key (`colorScheme`), and env var (`AGENT_BROWSER_COLOR_SCHEME`)
- Store the preference in `BrowserManager` and automatically apply it to all new contexts (via Playwright's context option) and all new pages (via `page.emulateMedia` in `setupPageTracking`)
- `set media dark/light` now also persists its choice for subsequent pages and tabs
* feat: Enable capture of profiling data
Adding a new set of commands:
```
agent-browser profiler start
agent-browser profiler stop trace.json
```
With this, agents can start a profiling trace, perform a set of actions, and then extract the profiling data for analysis.
**Note:** I was originally going to call it `agent-browser profile` but I realized that might cause confusion with the `--profile` flag
CDP supports a couple commands for starting/stopping a trace.
When a trace is running, it emits events that need to be picked up.
We store these locally in the daemon until the trace is completed.
When the final event is received, we dump all of them into an output file.
That file can be loaded directly into chrome devtools or another analysis tool to visualize what happened during the agentic run.
Added some basic rust tests for parsing the commands (since they have some optional / required args)
TS daemon adds ~6 tests to make sure the profiling lifecycle (including saving the output file) works as intended
* add docs
* fixes
* fixes
---------
Co-authored-by: Chris Tate <chris@ctate.dev>
## Summary
- Return a `ParseError` when `--headers` receives invalid JSON instead of silently dropping the headers and proceeding
- Reject `frame` commands that provide no `selector`, `name`, or `url` (previously returned `{ switched: true }` without doing anything)
- Add missing mutual exclusion check for `--cdp` + `--extension` (extensions require a local browser, not a CDP connection)
## Summary
- Fix `allowFileAccess` being silently stripped from launch commands by adding it to the Zod schema in `protocol.ts` (the `--allow-file-access` CLI flag was not reaching the browser)
- Fix `trace stop` requiring a path argument despite help text documenting it as optional -- now works with or without a path
- Fix `addscript`/`addstyle` silently succeeding when neither `content` nor `url` is provided -- now returns a validation error
- Replace hardcoded ANSI escape code with `color::error_indicator()` in `main.rs` to respect `NO_COLOR`
- Fix double-parse pattern and add descriptive expect messages in `commands.rs`
- Fix incomplete string escaping in `snapshot.ts` `buildSelector` (use `JSON.stringify` instead of manual quote escaping)
- Simplify redundant ternary in `snapshot.ts` cursor-interactive role assignment
- Sync docs changelog with CHANGELOG.md (v0.8.1 through v0.10.0)
Rebased and fixed implementation of PR #184 features on current main:
Session persistence:
- --session-name flag and AGENT_BROWSER_SESSION_NAME env var auto-save/restore
cookies and localStorage across browser restarts
- State files stored in ~/.agent-browser/sessions/ with owner-only permissions
- AES-256-GCM encryption via AGENT_BROWSER_ENCRYPTION_KEY env var
- Auto-expiration of old state files (AGENT_BROWSER_STATE_EXPIRE_DAYS, default 30)
State management commands:
- state list: list saved state files with metadata
- state show <file>: display state summary (cookies, origins, domains)
- state rename <old> <new>: rename state files
- state clear [name] [--all]: clear saved states
- state clean --older-than <days>: delete expired states
New --new-tab flag for click command:
- Opens link href in a new tab instead of navigating the current tab
Security hardening:
- Session name validation prevents path traversal (CLI + daemon)
- safeHeaderMerge prevents prototype pollution in header merging
- WebSocket stream server binds to 127.0.0.1 only
- State files written with 0o600 permissions
Fixes applied over the original PR:
- Use color.rs module instead of hardcoded ANSI escape codes
- Align CLI output field names with daemon response format
- Add CLI-level --session-name validation (not just daemon-side)
- Avoid adding "DOM" to tsconfig.json lib (use proper typing in evaluate)
- Keep version at 0.9.3 (matches current main)
- Centralize session name validation in daemon.ts helper
- Update all documentation (README, SKILL.md, docs site, --help output)
Co-authored-by: Chris Tate <chris@ctate.dev>
* cargo fmt
* fix: remove redundant `use libc` import (clippy::single_component_path_imports)
* fix: use `.first()` instead of `.get(0)` (clippy::get_first)
* fix: use `.copied()` instead of `.map(|s| *s)` (clippy::map_clone)
* fix: allow too_many_arguments on ensure_daemon (clippy::too_many_arguments)
* fix: use `then_some` instead of `then` with closure (clippy::unnecessary_lazy_evaluations)
* fix: use pattern match instead of redundant guard (clippy::redundant_guards)
* fix: use pattern match instead of redundant guard in commands.rs (clippy::redundant_guards)
* fix: use `contains()` instead of `iter().any()` for simple equality (clippy::manual_contains)
* Add changeset
* feat: add --allow-file-access flag for file:// URL support
Adds the ability to open and interact with local files using file:// URLs.
This enables use cases like viewing local PDFs, testing local HTML files,
and allowing JavaScript to access other local files via XHR.
The flag adds Chromium's --allow-file-access-from-files and --allow-file-access
launch arguments. Only supported in Chromium browsers.
Fixes#345
* fix: add cli_allow_file_access tracking to prevent spurious warning
When --allow-file-access is set via AGENT_BROWSER_ALLOW_FILE_ACCESS env var
(not CLI), don't warn about the flag being ignored when daemon is already running.
* fix: only warn about ignored flags when explicitly passed via CLI
The warning about launch-time options being ignored (when daemon is
already running) was incorrectly shown when options were set via
environment variables like AGENT_BROWSER_EXECUTABLE_PATH, even when
no CLI flag was passed.
Now the warning only appears when flags are explicitly passed on the
command line, not when values come solely from environment variables.
Fixes#372
* feat: add cursor-interactive element detection in snapshots
Add -C/--cursor flag to snapshot command that detects clickable elements
that don't have proper ARIA roles but are interactive based on:
- cursor: pointer CSS style
- onclick attribute/handler
- tabindex attribute
This helps with modern web apps that use custom divs/spans as buttons.
Fixes#366
* fix: add cursor option to getSnapshot type signature
The warning about launch-time options being ignored (when daemon is
already running) was incorrectly shown when options were set via
environment variables like AGENT_BROWSER_EXECUTABLE_PATH, even when
no CLI flag was passed.
Now the warning only appears when flags are explicitly passed on the
command line, not when values come solely from environment variables.
Fixes#372
* Add base64 input for eval command
Adds -b/--base64 flag to decode script from base64, avoiding shell escaping issues for AI agents.
* Document base64 eval in SKILL.md
* Add CLI flags for cookie URL, domain, path, httpOnly, secure, and expires
Extends the `cookies set` command to support setting cookies with additional parameters before loading a page, solving authentication workflows where cookies need to be set for different domains.
**Key changes:**
- Added CLI flags: `--url`, `--domain`, `--path`, `--httpOnly`, `--secure`, `--sameSite`, `--expires`
- Added comprehensive test coverage for all new flags and combinations
- Updated help documentation with usage examples
- No daemon changes needed - it already supported these parameters
**Example usage:**
```bash
agent-browser cookies set session_id "abc123" --url https://app.example.com --httpOnly --secure
```
This allows setting cookies for a URL before opening the page, eliminating the need for workarounds in cross-domain authentication scenarios.
Fixes#261
* Update lock
* Fix compilation error
* feat: add support for ignoring HTTPS certificate errors
* fix: update warning message for already running daemon to include ignore HTTPS errors option
* docs: add documentation for --ignore-https-errors option in README and SKILL.md
* feat: initialize ignore_https_errors flag in command context
* fix: change launch_cmd to mutable for cdp value handling
* fix(screenshot): support refs and improve error messages
* fix(cli): support selector argument in screenshot command
* Fix CSS class selectors being treated as file paths
* fix(test): update screenshot test assertions
---------
Co-authored-by: Chris Tate <chris@ctate.dev>
* feat: add download and waitfordownload CLI commands
Add CLI support for the existing download functionality in the daemon:
- `download <selector> <path>`: Click an element to trigger download
and save to specified path
- `wait --download [path] [--timeout ms]`: Wait for any download to
complete, optionally save to path with configurable timeout
Includes comprehensive unit tests and help documentation.
* fix: download command ref support and output message
- Fix handleDownload to use browser.getLocator() for ref selector support
- Fix CLI output to show "Downloaded to" instead of "Screenshot saved"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Chris Tate <chris@ctate.dev>
* Add --profile flag for persistent browser profiles
Adds support for persistent browser profiles that preserve cookies,
localStorage, and login sessions across browser restarts.
Changes:
- Add --profile <path> CLI flag (flags.rs)
- Add AGENT_BROWSER_PROFILE environment variable support
- Add profile field to LaunchCommand type (types.ts)
- Use launchPersistentContext when profile is specified (browser.ts)
- Update help text and README with documentation
Usage:
agent-browser --profile ~/.myapp-profile open myapp.com
This enables AI agents to maintain authenticated sessions across
browser restarts without re-authenticating each time.
* Expand tilde in profile path to home directory
* fix: add missing profile field to test Flags struct
---------
Co-authored-by: Chris Tate <chris@ctate.dev>
Adds a `connect <port>` command that establishes a CDP connection
to a running browser. The daemon remembers the connection, so
subsequent commands work without needing --cdp on every call.
Example:
agent-browser connect 9222
agent-browser snapshot # works without --cdp
agent-browser tab
agent-browser close