Showing preview only (3,456K chars total). Download the full file or copy to clipboard to get everything.
Repository: vercel-labs/agent-browser
Branch: main
Commit: 9837b9c1aaba
Files: 208
Total size: 3.3 MB
Directory structure:
gitextract_aeoh496c/
├── .changeset/
│ ├── README.md
│ └── config.json
├── .claude-plugin/
│ └── marketplace.json
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .prettierrc
├── AGENTS.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── benchmarks/
│ ├── .gitignore
│ ├── README.md
│ ├── bench.ts
│ ├── package.json
│ ├── scenarios.ts
│ └── tsconfig.json
├── bin/
│ └── agent-browser.js
├── cli/
│ ├── Cargo.toml
│ ├── build.rs
│ ├── cdp-protocol/
│ │ ├── browser_protocol.json
│ │ └── js_protocol.json
│ └── src/
│ ├── color.rs
│ ├── commands.rs
│ ├── connection.rs
│ ├── flags.rs
│ ├── install.rs
│ ├── main.rs
│ ├── native/
│ │ ├── actions.rs
│ │ ├── auth.rs
│ │ ├── browser.rs
│ │ ├── cdp/
│ │ │ ├── chrome.rs
│ │ │ ├── client.rs
│ │ │ ├── discovery.rs
│ │ │ ├── lightpanda.rs
│ │ │ ├── mod.rs
│ │ │ └── types.rs
│ │ ├── cookies.rs
│ │ ├── daemon.rs
│ │ ├── diff.rs
│ │ ├── e2e_tests.rs
│ │ ├── element.rs
│ │ ├── inspect_server.rs
│ │ ├── interaction.rs
│ │ ├── mod.rs
│ │ ├── network.rs
│ │ ├── parity_tests.rs
│ │ ├── policy.rs
│ │ ├── providers.rs
│ │ ├── recording.rs
│ │ ├── screenshot.rs
│ │ ├── snapshot.rs
│ │ ├── state.rs
│ │ ├── storage.rs
│ │ ├── stream.rs
│ │ ├── test_fixtures/
│ │ │ ├── drag_probe.html
│ │ │ ├── html5_drag_probe.html
│ │ │ └── pointer_capture_probe.html
│ │ ├── tracing.rs
│ │ └── webdriver/
│ │ ├── appium.rs
│ │ ├── backend.rs
│ │ ├── client.rs
│ │ ├── ios.rs
│ │ ├── mod.rs
│ │ ├── safari.rs
│ │ └── types.rs
│ ├── output.rs
│ ├── test_utils.rs
│ ├── upgrade.rs
│ └── validation.rs
├── docker/
│ ├── Dockerfile.build
│ └── docker-compose.yml
├── docs/
│ ├── .gitignore
│ ├── components.json
│ ├── eslint.config.mjs
│ ├── mdx-components.tsx
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── src/
│ │ ├── app/
│ │ │ ├── api/
│ │ │ │ ├── docs-chat/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── docs-markdown/
│ │ │ │ │ └── route.ts
│ │ │ │ └── search/
│ │ │ │ └── route.ts
│ │ │ ├── cdp-mode/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── changelog/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── commands/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── configuration/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── diffing/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── engines/
│ │ │ │ ├── chrome/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ └── lightpanda/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── globals.css
│ │ │ ├── installation/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── ios/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── layout.tsx
│ │ │ ├── native-mode/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── next/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── og/
│ │ │ │ ├── [...slug]/
│ │ │ │ │ └── route.tsx
│ │ │ │ ├── og-image.tsx
│ │ │ │ └── route.tsx
│ │ │ ├── page.mdx
│ │ │ ├── profiler/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── providers/
│ │ │ │ ├── browser-use/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ ├── browserbase/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ ├── browserless/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ └── kernel/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── quick-start/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── security/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── selectors/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── sessions/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── skills/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── snapshots/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ └── streaming/
│ │ │ ├── layout.tsx
│ │ │ └── page.mdx
│ │ ├── components/
│ │ │ ├── code-block.tsx
│ │ │ ├── copy-button.tsx
│ │ │ ├── copy-page-button.tsx
│ │ │ ├── diff-demo.tsx
│ │ │ ├── docs-chat.tsx
│ │ │ ├── docs-mobile-nav.tsx
│ │ │ ├── docs-sidebar.tsx
│ │ │ ├── header.tsx
│ │ │ ├── search.tsx
│ │ │ ├── theme-provider.tsx
│ │ │ ├── theme-toggle.tsx
│ │ │ └── ui/
│ │ │ ├── dialog.tsx
│ │ │ └── sheet.tsx
│ │ └── lib/
│ │ ├── docs-navigation.ts
│ │ ├── mdx-to-markdown.ts
│ │ ├── page-metadata.ts
│ │ ├── page-titles.ts
│ │ ├── rate-limit.ts
│ │ ├── search-index.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── examples/
│ └── environments/
│ ├── .gitignore
│ ├── README.md
│ ├── app/
│ │ ├── actions/
│ │ │ └── browse.ts
│ │ ├── api/
│ │ │ └── browse/
│ │ │ └── route.ts
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components/
│ │ └── ui/
│ │ ├── alert.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── resizable.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── toggle-group.tsx
│ │ └── toggle.tsx
│ ├── components.json
│ ├── lib/
│ │ ├── agent-browser-sandbox.ts
│ │ ├── constants.ts
│ │ ├── rate-limit.ts
│ │ └── utils.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── scripts/
│ │ └── create-snapshot.ts
│ └── tsconfig.json
├── package.json
├── scripts/
│ ├── build-all-platforms.sh
│ ├── check-version-sync.js
│ ├── copy-native.js
│ ├── postinstall.js
│ └── sync-version.js
└── skills/
├── agent-browser/
│ ├── SKILL.md
│ ├── references/
│ │ ├── authentication.md
│ │ ├── commands.md
│ │ ├── profiling.md
│ │ ├── proxy-support.md
│ │ ├── session-management.md
│ │ ├── snapshot-refs.md
│ │ └── video-recording.md
│ └── templates/
│ ├── authenticated-session.sh
│ ├── capture-workflow.sh
│ └── form-automation.sh
├── dogfood/
│ ├── SKILL.md
│ ├── references/
│ │ └── issue-taxonomy.md
│ └── templates/
│ └── dogfood-report-template.md
├── electron/
│ └── SKILL.md
├── slack/
│ ├── SKILL.md
│ ├── references/
│ │ └── slack-tasks.md
│ └── templates/
│ └── slack-report-template.md
└── vercel-sandbox/
└── SKILL.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .changeset/README.md
================================================
# Changesets
This project uses [Changesets](https://github.com/changesets/changesets) for versioning and changelog generation.
## Adding a changeset
When you make a change that should be released, run:
```bash
pnpm changeset
```
This will prompt you to:
1. Select the type of change (patch, minor, major)
2. Write a summary of your changes
The changeset file will be committed with your PR.
## Release process
When changesets are merged to `main`, the release workflow will:
1. Create a "Version Packages" PR that updates version numbers and changelogs
2. When that PR is merged, packages are automatically published to npm
================================================
FILE: .changeset/config.json
================================================
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
================================================
FILE: .claude-plugin/marketplace.json
================================================
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "agent-browser",
"description": "Headless browser automation for AI agents",
"owner": {
"name": "Vercel",
"email": "support@vercel.com"
},
"plugins": [
{
"name": "agent-browser",
"description": "Automates browser interactions for web testing, form filling, screenshots, and data extraction",
"source": "./",
"strict": false,
"skills": ["./skills/agent-browser"],
"category": "development"
}
]
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
version-sync:
name: Version Sync Check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check version sync
run: node scripts/check-version-sync.js
rust:
name: Rust
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: cli
- name: Format check
run: cargo fmt --manifest-path cli/Cargo.toml -- --check
- name: Clippy check
run: cargo clippy --manifest-path cli/Cargo.toml -- -D warnings
- name: Run Rust tests
run: cargo test --profile ci --manifest-path cli/Cargo.toml
rust-cross:
name: Rust (${{ matrix.os }} - ${{ matrix.target }})
if: github.event_name != 'pull_request'
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: cli
- name: Run Rust tests
run: cargo test --profile ci --manifest-path cli/Cargo.toml --target ${{ matrix.target }}
native-e2e:
name: Native E2E Tests
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
needs: rust
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: cli
- name: Install Chrome
run: |
cargo run --manifest-path cli/Cargo.toml -- install --with-deps
- name: Run e2e tests
run: cargo test --profile ci --manifest-path cli/Cargo.toml e2e -- --ignored --test-threads=1
windows-integration:
name: Windows Integration Test
if: github.event_name != 'pull_request'
runs-on: windows-latest
needs: rust-cross
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: cli
- name: Build Rust CLI
run: cargo build --release --manifest-path cli/Cargo.toml --target x86_64-pc-windows-msvc
- name: Copy CLI binary to bin directory
run: |
Copy-Item cli/target/x86_64-pc-windows-msvc/release/agent-browser.exe bin/agent-browser-win32-x64.exe
- name: Test agent-browser install command
run: |
$env:PATH = "$pwd\bin;$env:PATH"
for ($i = 1; $i -le 3; $i++) {
bin/agent-browser-win32-x64.exe install
if ($LASTEXITCODE -eq 0) { exit 0 }
Write-Host "Attempt $i failed, retrying in 10 seconds..."
Start-Sleep -Seconds 10
}
exit 1
shell: pwsh
timeout-minutes: 10
- name: Test daemon lifecycle (open, snapshot, close)
run: |
$env:PATH = "$pwd\bin;$env:PATH"
Write-Host "--- Opening page ---"
bin/agent-browser-win32-x64.exe open https://example.com
if ($LASTEXITCODE -ne 0) { Write-Error "open failed"; exit 1 }
Write-Host "--- Taking snapshot ---"
$snapshot = bin/agent-browser-win32-x64.exe snapshot
if ($LASTEXITCODE -ne 0) { Write-Error "snapshot failed"; exit 1 }
Write-Host $snapshot
Write-Host "--- Closing browser ---"
bin/agent-browser-win32-x64.exe close
if ($LASTEXITCODE -ne 0) { Write-Error "close failed"; exit 1 }
Write-Host "--- Windows daemon lifecycle test passed ---"
shell: pwsh
timeout-minutes: 5
global-install:
name: Global Install (${{ matrix.os }})
if: github.event_name != 'pull_request'
runs-on: ${{ matrix.os }}
needs: rust-cross
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: agent-browser-linux-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: agent-browser-darwin-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: agent-browser-win32-x64.exe
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: cli
- name: Build Rust CLI
run: cargo build --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }}
- name: Copy CLI binary to bin directory (Unix)
if: runner.os != 'Windows'
run: cp cli/target/${{ matrix.target }}/release/agent-browser bin/${{ matrix.binary }}
- name: Copy CLI binary to bin directory (Windows)
if: runner.os == 'Windows'
run: Copy-Item cli/target/${{ matrix.target }}/release/agent-browser.exe bin/${{ matrix.binary }}
- name: Test npm global install
run: |
npm pack
npm install -g agent-browser-*.tgz
agent-browser --version
shell: bash
- name: Verify symlink points to native binary (Unix)
if: runner.os != 'Windows'
run: |
SYMLINK=$(npm prefix -g)/bin/agent-browser
TARGET=$(readlink "$SYMLINK")
echo "Symlink: $SYMLINK"
echo "Target: $TARGET"
if [[ "$TARGET" != *"${{ matrix.binary }}"* ]]; then
echo "ERROR: Symlink should point to native binary, not JS wrapper"
exit 1
fi
echo "Symlink correctly points to native binary"
shell: bash
- name: Verify shim points to native binary (Windows)
if: runner.os == 'Windows'
run: |
$shimPath = "$(npm prefix -g)\agent-browser.cmd"
$content = Get-Content $shimPath -Raw
echo "Shim path: $shimPath"
echo "Shim content:"
echo $content
if ($content -notmatch "agent-browser-win32-x64\.exe") {
echo "ERROR: Shim should point to native .exe, not JS wrapper"
exit 1
}
echo "Shim correctly points to native binary"
shell: pwsh
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
branches:
- main
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
jobs:
# Build native binaries for all platforms first
build-binaries:
name: Build ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Linux x64
os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: agent-browser-linux-x64
use_zigbuild: true
- name: Linux ARM64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
binary: agent-browser-linux-arm64
use_zigbuild: true
- name: Linux musl x64
os: ubuntu-latest
target: x86_64-unknown-linux-musl
binary: agent-browser-linux-musl-x64
use_zigbuild: true
- name: Linux musl ARM64
os: ubuntu-latest
target: aarch64-unknown-linux-musl
binary: agent-browser-linux-musl-arm64
use_zigbuild: true
- name: Windows x64
os: ubuntu-latest
target: x86_64-pc-windows-gnu
binary: agent-browser-win32-x64.exe
use_zigbuild: false
- name: macOS x64
os: macos-latest
target: x86_64-apple-darwin
binary: agent-browser-darwin-x64
use_zigbuild: false
- name: macOS ARM64
os: macos-latest
target: aarch64-apple-darwin
binary: agent-browser-darwin-arm64
use_zigbuild: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: pnpm
- name: Install npm dependencies
run: pnpm install --frozen-lockfile
- name: Sync version
run: pnpm run version:sync
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross-compilation tools (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-x86-64-linux-gnu mingw-w64
- name: Install cargo-zigbuild
if: matrix.use_zigbuild
run: |
pip3 install ziglang
cargo install cargo-zigbuild
- name: Configure Rust linkers
if: runner.os == 'Linux'
run: |
mkdir -p ~/.cargo
cat >> ~/.cargo/config.toml << 'EOF'
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
EOF
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: cli
- name: Build with zigbuild
if: matrix.use_zigbuild
run: cargo zigbuild --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }}
- name: Build with cargo
if: '!matrix.use_zigbuild'
run: cargo build --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }}
- name: Copy binary
run: |
mkdir -p artifacts
if [[ "${{ matrix.target }}" == *"windows"* ]]; then
cp cli/target/${{ matrix.target }}/release/agent-browser.exe artifacts/${{ matrix.binary }}
else
cp cli/target/${{ matrix.target }}/release/agent-browser artifacts/${{ matrix.binary }}
chmod +x artifacts/${{ matrix.binary }}
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.binary }}
path: artifacts/${{ matrix.binary }}
retention-days: 7
# Create release PR or publish to npm (with binaries)
release:
name: Release
needs: build-binaries
runs-on: ubuntu-latest
outputs:
published: ${{ steps.changesets.outputs.published }}
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: pnpm
registry-url: 'https://registry.npmjs.org'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Download all binary artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Move binaries to bin directory
run: |
mkdir -p bin
find artifacts -type f -name 'agent-browser-*' -exec mv {} bin/ \;
rm -rf artifacts
chmod +x bin/agent-browser-* 2>/dev/null || true
echo "Binaries in bin/:"
ls -la bin/
- name: Verify all binaries exist
run: |
EXPECTED_BINARIES=(
"agent-browser-linux-x64"
"agent-browser-linux-arm64"
"agent-browser-linux-musl-x64"
"agent-browser-linux-musl-arm64"
"agent-browser-win32-x64.exe"
"agent-browser-darwin-x64"
"agent-browser-darwin-arm64"
)
MIN_SIZE=100000 # Binaries should be at least 100KB
ERRORS=0
for binary in "${EXPECTED_BINARIES[@]}"; do
if [ ! -f "bin/$binary" ]; then
echo "ERROR: Missing bin/$binary"
ERRORS=$((ERRORS + 1))
else
SIZE=$(stat -c%s "bin/$binary" 2>/dev/null || stat -f%z "bin/$binary")
if [ "$SIZE" -lt "$MIN_SIZE" ]; then
echo "ERROR: bin/$binary is too small ($SIZE bytes, expected >= $MIN_SIZE)"
ERRORS=$((ERRORS + 1))
else
echo "OK: bin/$binary ($SIZE bytes)"
fi
fi
done
if [ "$ERRORS" -gt 0 ]; then
echo "Error: $ERRORS binary issues found"
exit 1
fi
echo "All 7 platform binaries present and valid"
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
version: pnpm ci:version
publish: pnpm ci:publish
title: 'chore: version packages'
commit: 'chore: version packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_VERCEL_TOKEN_ELEVATED }}
# Create GitHub release with binaries after npm publish
github-release:
name: Create GitHub Release
needs: release
if: needs.release.outputs.published == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: main
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Move binaries to bin directory
run: |
mkdir -p bin
find artifacts -type f -name 'agent-browser-*' -exec mv {} bin/ \;
rm -rf artifacts
chmod +x bin/agent-browser-* 2>/dev/null || true
ls -la bin/
- name: Verify binaries exist
run: |
BINARY_COUNT=$(ls bin/agent-browser-* 2>/dev/null | wc -l)
if [ "$BINARY_COUNT" -lt 7 ]; then
echo "Error: Expected 7 binaries, found $BINARY_COUNT"
ls -la bin/
exit 1
fi
echo "Found $BINARY_COUNT binaries"
- name: Create GitHub Release
run: |
VERSION=$(node -p "require('./package.json').version")
TAG="v$VERSION"
# Check if release already exists
if gh release view "$TAG" &>/dev/null; then
echo "Release $TAG already exists, uploading binaries..."
gh release upload "$TAG" bin/agent-browser-* --clobber
else
echo "Creating release $TAG..."
gh release create "$TAG" \
--title "$TAG" \
--generate-notes \
bin/agent-browser-*
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
# Dependencies
node_modules/
# Build output
dist/
# Native binaries (keep the launcher scripts)
bin/agent-browser-*
!bin/agent-browser
!bin/agent-browser.cmd
# Rust build artifacts
cli/target/
cli/*.o
# Logs
*.log
npm-debug.log*
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Python
__pycache__/
# Test artifacts
*.png
*.jpeg
*.jpg
*.webm
test/e2e/.dogfood-output/
# Package manager
package-lock.json
yarn.lock
# Environment
.env
.env.local
# opensrc - source code for packages
opensrc/
# Docs site
docs/node_modules/
docs/.next/
docs/out/
docs/package-lock.json
# pnpm
.pnpm-store/
================================================
FILE: .husky/pre-commit
================================================
node scripts/sync-version.js
git add cli/Cargo.toml cli/Cargo.lock
================================================
FILE: .prettierrc
================================================
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
================================================
FILE: AGENTS.md
================================================
# AGENTS.md
Instructions for AI coding agents working with this codebase.
## Package Manager
This project uses **pnpm**. Always use `pnpm` instead of `npm` or `yarn` for installing dependencies, running scripts, etc. (e.g., `pnpm install`, `pnpm run build`).
## Code Style
- Do not use emojis in code, output, or documentation. Unicode symbols (✓, ✗, →, ⚠) are acceptable.
- CLI colored output uses `cli/src/color.rs`. This module respects the `NO_COLOR` environment variable. Never use hardcoded ANSI color codes.
- CLI flags must always use kebab-case (e.g., `--auto-connect`, `--allow-file-access`). Never use camelCase for flags (e.g., `--autoConnect` is wrong).
## Documentation
When adding or changing user-facing features (new flags, commands, behaviors, environment variables, etc.), update **all** of the following:
1. `cli/src/output.rs` -- `--help` output (flags list, examples, environment variables)
2. `README.md` -- Options table, relevant feature sections, examples
3. `skills/agent-browser/SKILL.md` -- so AI agents know about the feature
4. `docs/src/app/` -- the Next.js docs site (MDX pages)
5. Inline doc comments in the relevant source files
This applies to changes that either human users or AI agents would need to know about. Do not skip any of these locations.
In the `docs/src/app/` MDX files, always use HTML `<table>` syntax for tables (not markdown pipe tables). This matches the existing convention across the docs site.
## Architecture
This is a Rust codebase. The browser automation daemon lives in `cli/src/native/` (daemon, actions, browser, CDP client, snapshot, state). The `--engine` flag selects Chrome vs Lightpanda. The `install` command downloads Chrome from Chrome for Testing directly.
## Testing
### Unit Tests
```bash
cd cli && cargo test
```
Runs all unit tests (~320 tests). These are fast and don't require Chrome.
### End-to-End Tests
```bash
cd cli && cargo test e2e -- --ignored --test-threads=1
```
Runs 18 e2e tests that launch real headless Chrome instances and exercise the full native daemon command pipeline. Requirements:
- Chrome must be installed
- Must run serially (`--test-threads=1`) to avoid Chrome instance contention
- Tests are `#[ignore]`'d so they don't run during normal `cargo test`
The e2e tests live in `cli/src/native/e2e_tests.rs` and cover: launch/close, navigation, snapshots, screenshots, form interaction, cookies, storage, tabs, element queries, viewport/emulation, domain filtering, diff, state management, error handling, and Phase 8 commands.
### Linting and Formatting
```bash
cd cli && cargo fmt -- --check # Check formatting
cd cli && cargo clippy # Lint
```
<!-- opensrc:start -->
## Source Code Reference
Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details.
See `opensrc/sources.json` for the list of available packages and their versions.
Use this source code when you need to understand how a package works internally, not just its types/interface.
### Fetching Additional Source Code
To fetch source code for a package or repository you need to understand, run:
```bash
npx opensrc <package> # npm package (e.g., npx opensrc zod)
npx opensrc pypi:<package> # Python package (e.g., npx opensrc pypi:requests)
npx opensrc crates:<package> # Rust crate (e.g., npx opensrc crates:serde)
npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)
```
<!-- opensrc:end -->
================================================
FILE: CHANGELOG.md
================================================
# agent-browser
## 0.21.2
### Patch Changes
- 757626f: ### Bug Fixes
- **Deduplicate text content in snapshots** - Fixed an issue where duplicate text content appeared in page snapshots (#909)
- **Native mouse drag state** - Fixed incorrect raw native mouse drag state not being properly tracked across `down`, `move`, and `up` events (#872)
- **Chrome headless launch failures** - Fixed browser launch failures caused by the `--enable-unsafe-swiftshader` flag in Chrome headless mode (#915)
- **Origin-scoped `--headers` persistence** - Restored correct persistence of origin-scoped headers set via `--headers` across navigation commands (#894)
- **Relative URLs in WebSocket domain filter** - Fixed handling of relative URLs in the WebSocket domain filter script (#624)
## 0.21.1
### Patch Changes
- 1e7619d: ### New Features
- **HAR 1.2 network capture** - Added commands to capture and export network traffic in HAR 1.2 format, including accurate request/response timing, headers, body sizes, and resource types sourced from Chrome DevTools Protocol events (#864)
- **Built-in `upgrade` command** - Added `agent-browser upgrade` to self-update the CLI; automatically detects your installation method (npm, Homebrew, or Cargo) and runs the appropriate update command (#898)
### Documentation
- Added `upgrade` command to the README command reference and installation guide
- Added a dedicated **Updating** section to the README with usage instructions for `agent-browser upgrade`
## 0.21.0
### Minor Changes
- c6de80b: ### New Features
- **`batch` command** -- Execute multiple commands from stdin in a single invocation. Accepts a JSON array of string arrays and returns results sequentially. Supports `--bail` to stop on first error and `--json` for structured output (#865)
- **iframe support** -- CLI interactions and snapshots now traverse into iframe content, enabling automation of cross-frame pages (#869)
- **`network har start/stop` command** -- Capture and export network traffic in HAR 1.2 format (#874)
- **WebSocket fallback for CDP discovery** -- When HTTP-based CDP endpoint discovery fails, the CLI now falls back to a WebSocket connection automatically (#873)
### Improvements
- **`--full`/`-f` refactored to command-level flag** -- Moved from a global flag to a per-command flag for clearer scoping (#877)
- **Enhanced Chrome launch** -- Added `--user-data-dir` support and configurable launch timeout for more reliable browser startup (#852)
### Bug Fixes
- Fixed `/json/list` fallback when `/json/version` endpoint is unavailable, improving compatibility with non-standard CDP implementations (#861)
- Fixed daemon liveness detection for PID namespace isolation (e.g. `unshare`). Uses socket connectivity as the sole liveness check instead of `kill(pid, 0)`, which fails when the caller cannot see the daemon's PID (#879)
- Fixed Ubuntu dependency install accidentally removing system packages (#884)
## 0.20.14
### Patch Changes
- c0d4cf6: ### New Features
- **Idle timeout for daemon auto-shutdown** - Added `--idle-timeout` CLI flag (and `AGENT_BROWSER_IDLE_TIMEOUT_MS` environment variable) to automatically shut down the daemon after a period of inactivity. Accepts human-friendly formats such as `10s`, `3m`, `1h`, or raw milliseconds (#856)
- **Cursor-interactive elements in snapshot tree** - Cursor-interactive elements are now embedded directly into the snapshot tree for richer context (#855)
### Bug Fixes
- Fixed **remote host support** in CDP discovery, enabling connection to browsers running on non-local hosts (#854)
- Fixed **CDP flag propagation** to the daemon process, ensuring reliable CDP reconnection across sessions (#857)
- Fixed **Windows auto-connect profiling** to correctly handle browser connection on Windows (#835, #840)
- Fixed **Windows transient error detection** by recognising Windows-specific socket error codes (`os error 10061` connection refused, `os error 10054` connection reset) during daemon reconnection attempts
## 0.20.13
### Patch Changes
- eda956b: ### Bug Fixes
- **Network idle detection for cached pages** - Fixed an issue where `poll_network_idle` could return immediately when no network events were observed (e.g. pages served from cache). The idle timer is now only satisfied after a consistent **500 ms idle period** has elapsed, preventing false-positive idle detection. The core polling logic has also been extracted into a standalone `poll_network_idle` function to improve testability (#847)
## 0.20.12
### Patch Changes
- 5fa2396: ### Bug Fixes
- Fixed **`snapshot -C`** and **`screenshot --annotate`** hanging when connected over WSS (WebSocket Secure) due to sequential CDP round-trips per interactive element (#842)
### Performance
- **`snapshot -C` (cursor-interactive mode)** now batches CDP calls instead of issuing N×2 sequential round-trips per cursor-interactive element, preventing timeouts on high-latency WSS connections (#842)
- **`screenshot --annotate`** now batches element queries, reducing completion time from potentially 20–40s (e.g. 50+ buttons over WSS) to within expected bounds (#842)
## 0.20.11
### Patch Changes
- 4b5fc78: ### Bug Fixes
- **Material Design checkbox/radio parity** - Restored Playwright-parity behavior for `check`/`uncheck` actions on Material Design controls. These components hide the native `<input>` off-screen and use overlay elements that intercept coordinate-based clicks; the actions now detect this pattern and fall back to a JS `.click()` to correctly toggle state. Also improves `ischecked` to handle nested hidden inputs and ARIA-only checkboxes (#837)
- **Punctuation handling in `type` command** - Fixed incorrect virtual key (VK) codes being used for punctuation characters (e.g. `.`, `@`) in the `type` action, which previously caused those characters to be dropped or mistyped (#836)
## 0.20.10
### Patch Changes
- a3d9662: ### Bug Fixes
- **Restored WebSocket streaming** - Fixed broken WebSocket streaming in the native daemon by keeping the **StreamServer** instance alive so the broadcast channel remains open, and ensuring CDP session IDs and connection status are correctly propagated to stream clients (#826)
- **Filtered internal Chrome targets** - Fixed auto-connect discovery incorrectly attempting to attach to Chrome-internal pages (e.g. `chrome://`, `chrome-extension://`, `devtools://` URLs), which could cause unexpected connection failures (#827)
## 0.20.9
### Patch Changes
- 51d9ab4: ### Bug Fixes
- **Appium v3 iOS capabilities** - Added `appium:` vendor prefix to iOS capabilities (e.g., `appium:automationName`, `appium:deviceName`, `appium:platformVersion`) to comply with the Appium v3 WebDriver protocol requirements (#810)
- **Snapshot `--selector` scoping** - Fixed `snapshot --selector` so that the output is properly scoped to the matched element's subtree rather than returning the full accessibility tree. The selector now resolves the target DOM node's backend IDs and filters the accessibility tree to only include nodes within that subtree (#825)
## 0.20.8
### Patch Changes
- daf7263: ### Bug Fixes
- Fixed **video duration** being reported incorrectly when using real-time ffmpeg encoding for screen recording (#812)
- Removed obsolete **`BrowserManager` TypeScript API** references that no longer reflect the current CLI-based usage model (#821)
### Documentation
- Updated README to replace outdated **`BrowserManager` programmatic API** examples with the current CLI-based approach using `execSync` and `agent-browser` commands (#821)
- Removed the **Programmatic API** section covering `BrowserManager` screencast and input injection methods, which are no longer part of the public API (#821)
## 0.20.7
### Patch Changes
- 25a1526: ### New Features
- **Brave Browser support** - Added auto-discovery of Brave Browser for CDP connections on macOS, Linux, and Windows. The agent will now automatically detect and connect to Brave alongside Chrome, Chromium, and Canary installations (#817)
### Improvements
- **Postinstall message** - The post-install message now detects existing Chrome installations on the system. If a compatible browser is found, it confirms the path and notes it will be used automatically instead of prompting an install. If no browser is detected, the warning is clearer and mentions that installation can be skipped when using `--cdp`, `--provider`, `--engine`, or `--executable-path` (#815)
## 0.20.6
### Patch Changes
- fa91c22: ### Bug Fixes
- **Stale accessibility tree reference fallback** - Fixed an issue where interacting with an element whose **`backend_node_id`** had become stale (e.g. after the DOM was replaced) would fail with a `Could not compute box model` CDP error. Element resolution now re-queries the accessibility tree using role/name lookup to obtain a fresh node ID before retrying the operation (#806)
## 0.20.5
### Patch Changes
- fc091d2: ### Bug Fixes
- **Daemon panic on broken stderr pipe** - Replaced all `eprintln!` calls with `writeln!(std::io::stderr(), ...)` wrapped in `let _ =` to silently discard write errors, preventing the daemon from panicking when the parent process drops the stderr pipe during Chrome launch (#802)
## 0.20.4
### Patch Changes
- e2ebde2: ### Bug Fixes
- **Broadcast channel lag handling** - Fixed an issue where **broadcast channel lag** errors were incorrectly treated as stream closure, causing premature termination of event listeners in reload, response body, download, and navigation wait operations. Lagged messages are now skipped and the loop continues instead of breaking (#797)
### Improvements
- Removed unused **pnpm setup** steps from the `global-install` CI job, simplifying the workflow configuration (#798)
## 0.20.3
### Patch Changes
- e365909: ### Bug Fixes
- **Chrome launch retry** - Chrome will now retry launching up to 3 times with a 500ms delay between attempts, improving resilience against transient startup failures (#791)
- **Remote CDP snapshot hang** - Resolved an issue where snapshots would hang indefinitely over remote CDP (WSS) connections by removing WebSocket message and frame size limits to accommodate large responses (e.g. `Accessibility.getFullAXTree`), accepting binary frames from remote proxies such as Browserless, and immediately clearing pending commands when the connection closes rather than waiting for the 30-second timeout (#792)
## 0.20.2
### Patch Changes
- 944fa01: ### New Features
- **Linux musl (Alpine) builds** - Added pre-built binaries for **linux-musl** targeting both **x64** and **arm64** architectures, enabling native support for Alpine Linux and other musl-based distributions without requiring glibc (#784)
### Improvements
- **Consecutive `--auto-connect` commands** - Added support for issuing multiple consecutive `--auto-connect` commands without requiring a full browser relaunch; external connections are now correctly identified and reused (#786)
- **External browser disconnect behavior** - When using `--auto-connect` or `--cdp`, closing the agent session now disconnects cleanly without shutting down the user's browser process
### Bug Fixes
- **Restored `refs` dict in `--json` snapshot output** - The `refs` map containing role and name metadata for referenced elements is now correctly included in JSON snapshot responses (#787)
- Fixed e2e test assertions for `diff_snapshot` and `domain_filter` to correctly reflect expected behavior (#783)
- Fixed Chrome temp-dir cleanup test failing on Windows (#766)
## 0.20.1
### Patch Changes
- bd05917: ### Bug Fixes
- Fixed **AX tree deserialization** to accept integer `nodeId` and `childIds` values for compatibility with Lightpanda, which sends numeric IDs where Chrome sends strings (#775)
- Fixed **misleading SIGPIPE comment** to accurately describe the default Rust SIGPIPE behavior and why it is reset to `SIG_DFL` (#776)
- Fixed **WebM recording output** to use the VP9 codec (`libvpx-vp9`) instead of H.264, producing valid WebM files; also adds a padding filter to ensure even frame dimensions (#779)
## 0.20.0
### Minor Changes
- 235fa88: ### Full Native Rust
- **100% native Rust** -- Removed the entire Node.js/Playwright daemon. The Rust native daemon is now the only implementation. No Node.js runtime or Playwright dependency required. (#754)
- **99x smaller install** -- Install size reduced from 710 MB to 7 MB by eliminating the Node.js dependency tree.
- **18x less memory** -- Daemon memory usage reduced from 143 MB to 8 MB.
- **1.6x faster cold start** -- Cold start time reduced from 1002ms to 617ms.
- **Benchmarks** -- Added benchmark suite comparing native vs Node.js daemon performance.
- **Chromium installer hardened** -- Fixed zip path traversal vulnerability in Chrome for Testing installer.
### Bug Fixes
- Fixed `--headed false` flag not being respected in CLI (#757)
- Fixed "not found" error pattern in `to_ai_friendly_error` incorrectly catching non-element errors (#759)
- Fixed storage local key lookup parsing and text output (#761)
- Fixed Lightpanda engine launch with release binaries (#760)
- Hardened Lightpanda startup timeouts (#762)
## 0.19.0
### Minor Changes
- 56bb92b: ### New Features
- **Browserless.io provider** -- Added browserless.io as a browser provider, supported in both Node.js and native daemon paths. Connect to remote Browserless instances with `--provider browserless` or `AGENT_BROWSER_PROVIDER=browserless`. Configurable via `BROWSERLESS_API_KEY`, `BROWSERLESS_API_URL`, and `BROWSERLESS_BROWSER_TYPE` environment variables. (#502, #746)
- **`clipboard` command** -- Read from and write to the browser clipboard. Supports `read`, `write <text>`, `copy` (simulates Ctrl+C), and `paste` (simulates Ctrl+V) operations. (#749)
- **Screenshot output configuration** -- New global flags `--screenshot-dir`, `--screenshot-quality`, `--screenshot-format` and corresponding `AGENT_BROWSER_SCREENSHOT_DIR`, `AGENT_BROWSER_SCREENSHOT_QUALITY`, `AGENT_BROWSER_SCREENSHOT_FORMAT` environment variables for persistent screenshot settings. (#749)
### Bug Fixes
- Fixed `wait --text` not working in native daemon path (#749)
- Fixed `BrowserManager.navigate()` and package entry point (#748)
- Fixed extensions not being loaded from `config.json` (#750)
- Fixed scroll on page load (#747)
- Fixed HTML retrieval by using `browser.getLocator()` for selector operations (#745)
## 0.18.0
### Minor Changes
- 942b8cd: ### New Features
- **`inspect` command** - Opens Chrome DevTools for the active page by launching a local proxy server that forwards the DevTools frontend to the browser's CDP WebSocket. Commands continue to work while DevTools is open. Implemented in both Node.js and native paths. (#736)
- **`get cdp-url` subcommand** - Retrieve the Chrome DevTools Protocol WebSocket URL for the active page, useful for external debugging tools. (#736)
- **Native screenshot annotate** - The `--annotate` flag for screenshots now works in the native Rust daemon, bringing parity with the Node.js path. (#706)
### Improvements
- **KERNEL_API_KEY now optional** - External credential injection no longer requires `KERNEL_API_KEY` to be set, making it easier to use Kernel with pre-configured environments. (#687)
- **Browserbase simplified** - Removed the `BROWSERBASE_PROJECT_ID` requirement, reducing setup friction for Browserbase users. (#625)
### Bug Fixes
- Fixed Browserbase API using incorrect endpoint to release sessions (#707)
- Fixed CDP connect paths using hardcoded 10s timeout instead of `getDefaultTimeout()` (#704)
- Fixed lone Unicode surrogates causing errors by sanitizing with `toWellFormed()` (#720)
- Fixed CDP connection failure on IPv6-first systems (#717)
- Fixed recordings not inheriting the current viewport settings (#718)
## 0.17.1
### Patch Changes
- 94cd888: Added support for device scale factor (retina display) in the viewport command via an optional scale parameter. Also added webview target type support for better Electron application compatibility, and the pages list now includes target type information.
## 0.17.0
### Minor Changes
- 94521e7: ### New Features
- **Lightpanda browser engine support** - Added `--engine <name>` flag to select the browser engine (`chrome` by default, or `lightpanda`), implying `--native` mode. Configurable via `AGENT_BROWSER_ENGINE` environment variable (#646)
- **Dialog dismiss command** - Added support for `dismiss` subcommand in dialog command parsing (#605)
### Improvements
- **Daemon startup error reporting** - Daemon startup errors are now surfaced directly instead of showing an opaque timeout message (#614)
- **CDP port discovery** - Replaced broken hand-rolled HTTP client with `reqwest` for more reliable CDP port discovery (#619)
- **Chrome extensions** - Extensions now load correctly by forcing headed mode when extensions are present (#652)
- **Google Translate bar suppression** - Suppressed the Google Translate bar in native headless mode to avoid interference (#649)
- **Auth cookie persistence** - Auth cookies are now persisted on browser close in native mode (#650)
### Bug Fixes
- Fixed native auth login failing due to incompatible encryption format (#648)
### Documentation
- Improved snapshot usage guidance and added reproducibility check (#630)
- Added `--engine` flag to the README options table
### Performance
- Added benchmarks to the CLI codebase (#637)
## 0.16.3
### Patch Changes
- 7d2c895: Fixed an issue where the --native flag was being passed to child processes even when not explicitly specified on the command line. The flag is now only forwarded when the user explicitly provides it, consistent with how other CLI flags like --allow-file-access and --download-path are handled.
## 0.16.2
### Patch Changes
- 01ac557: Added AGENT_BROWSER_HEADED environment variable support for running the browser in headed mode, and improved temporary profile cleanup when launching Chrome directly. Also includes documentation clarification that browser extensions work in both headed and headless modes.
## 0.16.1
### Patch Changes
- c4180c8: Improved Chrome launch reliability by automatically detecting containerized environments (Docker, Podman, Kubernetes) and enabling --no-sandbox when needed. Added support for discovering Playwright-installed Chromium browsers and enhanced error messages with helpful diagnostics when Chrome fails to launch.
## 0.16.0
### Minor Changes
- 05018b3: Added experimental native Rust daemon (`--native` flag, `AGENT_BROWSER_NATIVE=1` env, or `"native": true` in config). The native daemon communicates with Chrome directly via CDP, eliminating Node.js and Playwright dependencies. Supports 150+ commands with full parity to the default Node.js daemon. Includes WebDriver backend for Safari/iOS, CDP protocol codegen, request tracking, frame context management, and comprehensive e2e and parity tests.
## 0.15.3
### Patch Changes
- 62241b5: Fixed Windows compatibility issues including proper handling of extended-length path prefixes from canonicalize(), prevention of MSYS/Git Bash path translation that could mangle arguments, and improved daemon startup reliability. Also added ARM64 Windows support in postinstall shims and expanded CI testing with a full daemon lifecycle test on Windows.
## 0.15.2
### Patch Changes
- 6aea316: Documentation site improvements and internal tooling updates including enhanced code blocks, mobile navigation, and docs chat components. CLI connection and output handling refinements. Skill creator reference documentation and scripts have been reorganized.
## 0.15.1
### Patch Changes
- 7bd8ce9: Added support for chrome:// and chrome-extension:// URLs in navigation and recording commands. These special browser URLs are now preserved as-is instead of having https:// incorrectly prepended.
## 0.15.0
### Minor Changes
- 2e38882: - Added security hardening: authentication vault, content boundary markers, domain allowlist, action policy, action confirmation, and output length limits.
- Added `--download-path` flag (and `AGENT_BROWSER_DOWNLOAD_PATH` env / `downloadPath` config key) to set a default download directory.
- Added `--selector` flag to `scroll` command for scrolling within specific container elements.
## 0.14.0
### Minor Changes
- b7665e5: - Added `keyboard` command for raw keyboard input -- type with real keystrokes, insert text, and press shortcuts at the currently focused element without needing a selector.
- Added `--color-scheme` flag and `AGENT_BROWSER_COLOR_SCHEME` env var for persistent dark/light mode preference across browser sessions.
- Fixed IPC EAGAIN errors (os error 35/11) by adding backpressure-aware socket writes, command serialization, and lowering the default Playwright timeout to 25s (configurable via `AGENT_BROWSER_DEFAULT_TIMEOUT`).
- Fixed remote debugging (CDP) reconnection.
- Fixed state load failing when no browser is running.
- Fixed `--annotate` flag warning appearing when not explicitly passed via CLI.
## 0.13.0
### Minor Changes
- ebd8717: Added new diff commands for comparing snapshots, screenshots, and URLs between page states. You can now run visual pixel diffs against baseline images, compare accessibility tree snapshots with customizable depth and selectors, and diff two URLs side-by-side with optional screenshot comparison.
## 0.12.0
### Minor Changes
- 69ffad0: Add annotated screenshots with the new --annotate flag, which overlays numbered labels on interactive elements and prints a legend mapping each label to its element ref. This enables multimodal AI models to reason about visual layout while using the same @eN refs for subsequent interactions. The flag can also be set via the AGENT_BROWSER_ANNOTATE environment variable.
## 0.11.1
### Patch Changes
- c6fc7df: Added documentation for command chaining with && across README, CLI help output, docs, and skill files, explaining how to efficiently chain multiple agent-browser commands in a single shell invocation since the browser persists via a background daemon.
## 0.11.0
### Minor Changes
- 5dc40b4: Added configuration file support with automatic loading from user and project directories, new profiler commands for Chrome DevTools profiling, computed styles getter, browser extension loading, storage state management, and iOS device emulation. Expanded click command with new-tab option, improved find command with additional actions and filtering options, and enhanced CDP connection to accept WebSocket URLs. Documentation has been significantly expanded with new sections for configuration, profiling, and proxy support.
## 0.10.0
### Minor Changes
- 1112a16: Added session persistence with automatic save/restore of cookies and localStorage across browser restarts using --session-name flag, with optional AES-256-GCM encryption for saved state data. New state management commands allow listing, showing, renaming, clearing, and cleaning up old session files. Also added --new-tab option for click commands to open links in new tabs.
## 0.9.4
### Patch Changes
- 323b6cd: Fix all Clippy lint warnings in the Rust CLI: remove redundant import, use `.first()` instead of `.get(0)`, use `.copied()` instead of `.map(|s| *s)`, use `.contains()` instead of `.iter().any()`, use `then_some` instead of lazy `then`, and simplify redundant match guards.
## 0.9.3
### Patch Changes
- d03e238: Added support for custom executable path in CLI browser launch options. Documentation site received UI improvements including a new chat component with sheet-based interface and updated dependencies.
## 0.9.2
### Patch Changes
- 76d23db: Documentation site migrated to MDX for improved content authoring, added AI-powered docs chat feature, and updated README with Homebrew installation instructions for macOS users.
## 0.9.1
### Patch Changes
- ae34945: Added --allow-file-access flag to enable opening and interacting with local file:// URLs (PDFs, HTML files) by passing Chromium flags that allow JavaScript access to local files. Added -C/--cursor flag for snapshots to include cursor-interactive elements like divs with onclick handlers or cursor:pointer styles, which is useful for modern web apps using custom clickable elements.
## 0.9.0
### Minor Changes
- 9d021bd: Add iOS Simulator and real device support for mobile Safari testing via Appium. New CLI commands include `device list` to show available simulators, `tap` and `swipe` for touch interactions, and the `--device` flag to specify which iOS device to use. Configure with `-p ios` provider flag or `AGENT_BROWSER_PROVIDER=ios` environment variable.
## 0.8.10
### Patch Changes
- 17dba8f: Add --stdin flag for eval command to read JavaScript from stdin, enabling heredoc usage for multiline scripts
- daeede4: Add --stdin flag for the eval command to read JavaScript from stdin, enabling heredoc usage for multiline scripts. Also fix binary permission issues on macOS/Linux when postinstall scripts don't run (e.g., with bun).
## 0.8.9
### Patch Changes
- 0dc36f2: Add --stdin flag for eval command to read JavaScript from stdin, enabling heredoc usage for multiline scripts
## 0.8.8
### Patch Changes
- 2771588: Added base64 encoding support for the eval command with -b/--base64 flag to avoid shell escaping issues when executing JavaScript. Updated documentation with AI agent setup instructions and reorganized the docs structure by consolidating agent mode content into the installation page.
## 0.8.7
### Patch Changes
- d24f753: Fixed browser launch options not being passed correctly when using persistent profiles, ensuring args, userAgent, proxy, and ignoreHTTPSErrors settings now work properly. Added pre-flight checks for socket path length limits and directory write permissions to provide clearer error messages when daemon startup fails. Improved error handling to properly exit with failure status when browser launch fails.
## 0.8.6
### Patch Changes
- d75350a: Improved daemon connection reliability by adding automatic retry logic for transient errors like connection resets, broken pipes, and temporary resource unavailability. The CLI now cleans up stale socket and PID files before starting a new daemon, and includes better detection of daemon responsiveness to handle race conditions during shutdown.
## 0.8.5
### Patch Changes
- cb2f8c3: Fixed version synchronization to automatically update Cargo.lock alongside Cargo.toml during releases, and made the CLI binary executable. This ensures the Rust CLI version stays in sync with the npm package version.
## 0.8.4
### Patch Changes
- 759302e: Fixed "Daemon not found" error when running through AI agents (e.g., Claude Code) by resolving symlinks in the executable path. Previously, npm global bin symlinks weren't being resolved correctly, causing intermittent daemon discovery failures.
## 0.8.3
### Patch Changes
- 4116a8a: Replaced shell-based CLI wrappers with a cross-platform Node.js wrapper to enable npx support on Windows. Added postinstall logic to patch npm's bin entry on global installs, allowing the native binary to be invoked directly with zero overhead. Added CI tests to verify global installation works correctly across all platforms.
## 0.8.2
### Patch Changes
- 7e6336f: Fixed the Windows CMD wrapper to use the native binary directly instead of routing through Node.js, improving startup performance and reliability. Added retry logic to the CI install command to handle transient failures during browser installation.
## 0.8.1
### Patch Changes
- 8eec634: Improved release workflow to validate binary file sizes and ensure binaries are executable after npm install. Updated documentation site with a new mobile navigation system and added v0.8.0 changelog entries. Reformatted CHANGELOG.md for better readability.
## v0.8.0
### New Features
- **Kernel cloud browser provider** - Connect to Kernel (https://kernel.sh) for remote browser infrastructure via `-p kernel` flag or `AGENT_BROWSER_PROVIDER=kernel`. Supports stealth mode, persistent profiles, and automatic profile find-or-create.
- **Ignore HTTPS certificate errors** - New `--ignore-https-errors` flag for working with self-signed certificates and development environments
- **Enhanced cookie management** - Extended `cookies set` command with `--url`, `--domain`, `--path`, `--httpOnly`, `--secure`, `--sameSite`, and `--expires` flags for setting cookies before page load
### Bug Fixes
- Fixed tab list command not recognizing new pages opened via clicks or `target="_blank"` links (#275)
- Fixed `check` command hanging indefinitely (#272)
- Fixed `set device` not applying deviceScaleFactor - HiDPI screenshots now work correctly (#270)
- Fixed state load and profile persistence not working in v0.7.6 (#268)
- Screenshots now save to temp directory when no path is provided (#247)
### Security
- Daemon and stream server now reject cross-origin connections (#274)
## 0.7.6
### Patch Changes
- a4d0c26: Allow null values for the screenshot selector field. Previously, passing a null selector would fail validation, but now it is properly handled as an optional value.
## 0.7.5
### Patch Changes
- 8c2a6ec: Fix GitHub release workflow to handle existing releases. If a release already exists, binaries are uploaded to it instead of failing.
## 0.7.4
### Patch Changes
- 957b5e5: Fix binary permissions on install. npm doesn't preserve execute bits, so postinstall now ensures the native binary is executable.
## 0.7.3
### Patch Changes
- 161d8f5: Fix native binary distribution in npm package. Native binaries for all platforms (Linux x64/arm64, macOS x64/arm64, Windows x64) are now correctly included when publishing.
## 0.7.2
### Patch Changes
- 6afede2: Fix native binary distribution in npm package
Native binaries for all platforms (Linux x64/arm64, macOS x64/arm64, Windows x64) are now included in the npm package. Previously, the release workflow published to npm before building binaries, causing "No binary found" errors on installation.
## 0.7.1
### Patch Changes
- Fix native binary distribution in npm package. Native binaries for all platforms (Linux x64/arm64, macOS x64/arm64, Windows x64) are now included in the npm package. Previously, the release workflow published to npm before building binaries, causing "No binary found" errors on installation.
## 0.7.0
### Minor Changes
- 316e649: ## New Features
- **Cloud browser providers** - Connect to Browserbase or Browser Use for remote browser infrastructure via `-p` flag or `AGENT_BROWSER_PROVIDER` env var
- **Persistent browser profiles** - Store cookies, localStorage, and login sessions across browser restarts with `--profile`
- **Remote CDP WebSocket URLs** - Connect to remote browser services via WebSocket URL (e.g., `--cdp "wss://..."`)
- **Download commands** - New `download` command and `wait --download` for file downloads with ref support
- **Browser launch configuration** - New `--args`, `--user-agent`, and `--proxy-bypass` flags for fine-grained browser control
- **Enhanced skills** - Hierarchical structure with references and templates for Claude Code
## Bug Fixes
- Screenshot command now supports refs and has improved error messages
- WebSocket URLs work in `connect` command
- Fixed socket file location (uses `~/.agent-browser` instead of TMPDIR)
- Windows binary path fix (.exe extension)
- State load and path-based actions now show correct output messages
## Documentation
- Added Claude Code marketplace plugin installation instructions
- Updated skill documentation with references and templates
- Improved error documentation
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Vercel Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# agent-browser
Headless browser automation CLI for AI agents. Fast native Rust CLI.
## Installation
### Global Installation (recommended)
Installs the native Rust binary:
```bash
npm install -g agent-browser
agent-browser install # Download Chrome from Chrome for Testing (first time only)
```
### Project Installation (local dependency)
For projects that want to pin the version in `package.json`:
```bash
npm install agent-browser
agent-browser install
```
Then use via `package.json` scripts or by invoking `agent-browser` directly.
### Homebrew (macOS)
```bash
brew install agent-browser
agent-browser install # Download Chrome from Chrome for Testing (first time only)
```
### Cargo (Rust)
```bash
cargo install agent-browser
agent-browser install # Download Chrome from Chrome for Testing (first time only)
```
### From Source
```bash
git clone https://github.com/vercel-labs/agent-browser
cd agent-browser
pnpm install
pnpm build
pnpm build:native # Requires Rust (https://rustup.rs)
pnpm link --global # Makes agent-browser available globally
agent-browser install
```
### Linux Dependencies
On Linux, install system dependencies:
```bash
agent-browser install --with-deps
```
### Updating
Upgrade to the latest version:
```bash
agent-browser upgrade
```
Detects your installation method (npm, Homebrew, or Cargo) and runs the appropriate update command automatically.
### Requirements
- **Chrome** - Run `agent-browser install` to download Chrome from [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (Google's official automation channel). No Playwright or Node.js required for the daemon.
- **Rust** - Only needed when building from source (see From Source above).
## Quick Start
```bash
agent-browser open example.com
agent-browser snapshot # Get accessibility tree with refs
agent-browser click @e2 # Click by ref from snapshot
agent-browser fill @e3 "test@example.com" # Fill by ref
agent-browser get text @e1 # Get text by ref
agent-browser screenshot page.png
agent-browser close
```
### Traditional Selectors (also supported)
```bash
agent-browser click "#submit"
agent-browser fill "#email" "test@example.com"
agent-browser find role button click --name "Submit"
```
## Commands
### Core Commands
```bash
agent-browser open <url> # Navigate to URL (aliases: goto, navigate)
agent-browser click <sel> # Click element (--new-tab to open in new tab)
agent-browser dblclick <sel> # Double-click element
agent-browser focus <sel> # Focus element
agent-browser type <sel> <text> # Type into element
agent-browser fill <sel> <text> # Clear and fill
agent-browser press <key> # Press key (Enter, Tab, Control+a) (alias: key)
agent-browser keyboard type <text> # Type with real keystrokes (no selector, current focus)
agent-browser keyboard inserttext <text> # Insert text without key events (no selector)
agent-browser keydown <key> # Hold key down
agent-browser keyup <key> # Release key
agent-browser hover <sel> # Hover element
agent-browser select <sel> <val> # Select dropdown option
agent-browser check <sel> # Check checkbox
agent-browser uncheck <sel> # Uncheck checkbox
agent-browser scroll <dir> [px] # Scroll (up/down/left/right, --selector <sel>)
agent-browser scrollintoview <sel> # Scroll element into view (alias: scrollinto)
agent-browser drag <src> <tgt> # Drag and drop
agent-browser upload <sel> <files> # Upload files
agent-browser screenshot [path] # Take screenshot (--full for full page, saves to a temporary directory if no path)
agent-browser screenshot --annotate # Annotated screenshot with numbered element labels
agent-browser screenshot --screenshot-dir ./shots # Save to custom directory
agent-browser screenshot --screenshot-format jpeg --screenshot-quality 80
agent-browser pdf <path> # Save as PDF
agent-browser snapshot # Accessibility tree with refs (best for AI)
agent-browser eval <js> # Run JavaScript (-b for base64, --stdin for piped input)
agent-browser connect <port> # Connect to browser via CDP
agent-browser close # Close browser (aliases: quit, exit)
```
### Get Info
```bash
agent-browser get text <sel> # Get text content
agent-browser get html <sel> # Get innerHTML
agent-browser get value <sel> # Get input value
agent-browser get attr <sel> <attr> # Get attribute
agent-browser get title # Get page title
agent-browser get url # Get current URL
agent-browser get cdp-url # Get CDP WebSocket URL (for DevTools, debugging)
agent-browser get count <sel> # Count matching elements
agent-browser get box <sel> # Get bounding box
agent-browser get styles <sel> # Get computed styles
```
### Check State
```bash
agent-browser is visible <sel> # Check if visible
agent-browser is enabled <sel> # Check if enabled
agent-browser is checked <sel> # Check if checked
```
### Find Elements (Semantic Locators)
```bash
agent-browser find role <role> <action> [value] # By ARIA role
agent-browser find text <text> <action> # By text content
agent-browser find label <label> <action> [value] # By label
agent-browser find placeholder <ph> <action> [value] # By placeholder
agent-browser find alt <text> <action> # By alt text
agent-browser find title <text> <action> # By title attr
agent-browser find testid <id> <action> [value] # By data-testid
agent-browser find first <sel> <action> [value] # First match
agent-browser find last <sel> <action> [value] # Last match
agent-browser find nth <n> <sel> <action> [value] # Nth match
```
**Actions:** `click`, `fill`, `type`, `hover`, `focus`, `check`, `uncheck`, `text`
**Options:** `--name <name>` (filter role by accessible name), `--exact` (require exact text match)
**Examples:**
```bash
agent-browser find role button click --name "Submit"
agent-browser find text "Sign In" click
agent-browser find label "Email" fill "test@test.com"
agent-browser find first ".item" click
agent-browser find nth 2 "a" text
```
### Wait
```bash
agent-browser wait <selector> # Wait for element to be visible
agent-browser wait <ms> # Wait for time (milliseconds)
agent-browser wait --text "Welcome" # Wait for text to appear (substring match)
agent-browser wait --url "**/dash" # Wait for URL pattern
agent-browser wait --load networkidle # Wait for load state
agent-browser wait --fn "window.ready === true" # Wait for JS condition
# Wait for text/element to disappear
agent-browser wait --fn "!document.body.innerText.includes('Loading...')"
agent-browser wait "#spinner" --state hidden
```
**Load states:** `load`, `domcontentloaded`, `networkidle`
### Batch Execution
Execute multiple commands in a single invocation by piping a JSON array of
string arrays to `batch`. This avoids per-command process startup overhead
when running multi-step workflows.
```bash
# Pipe commands as JSON
echo '[
["open", "https://example.com"],
["snapshot", "-i"],
["click", "@e1"],
["screenshot", "result.png"]
]' | agent-browser batch --json
# Stop on first error
agent-browser batch --bail < commands.json
```
### Clipboard
```bash
agent-browser clipboard read # Read text from clipboard
agent-browser clipboard write "Hello, World!" # Write text to clipboard
agent-browser clipboard copy # Copy current selection (Ctrl+C)
agent-browser clipboard paste # Paste from clipboard (Ctrl+V)
```
### Mouse Control
```bash
agent-browser mouse move <x> <y> # Move mouse
agent-browser mouse down [button] # Press button (left/right/middle)
agent-browser mouse up [button] # Release button
agent-browser mouse wheel <dy> [dx] # Scroll wheel
```
### Browser Settings
```bash
agent-browser set viewport <w> <h> [scale] # Set viewport size (scale for retina, e.g. 2)
agent-browser set device <name> # Emulate device ("iPhone 14")
agent-browser set geo <lat> <lng> # Set geolocation
agent-browser set offline [on|off] # Toggle offline mode
agent-browser set headers <json> # Extra HTTP headers
agent-browser set credentials <u> <p> # HTTP basic auth
agent-browser set media [dark|light] # Emulate color scheme
```
### Cookies & Storage
```bash
agent-browser cookies # Get all cookies
agent-browser cookies set <name> <val> # Set cookie
agent-browser cookies clear # Clear cookies
agent-browser storage local # Get all localStorage
agent-browser storage local <key> # Get specific key
agent-browser storage local set <k> <v> # Set value
agent-browser storage local clear # Clear all
agent-browser storage session # Same for sessionStorage
```
### Network
```bash
agent-browser network route <url> # Intercept requests
agent-browser network route <url> --abort # Block requests
agent-browser network route <url> --body <json> # Mock response
agent-browser network unroute [url] # Remove routes
agent-browser network requests # View tracked requests
agent-browser network requests --filter api # Filter requests
agent-browser network har start # Start HAR recording
agent-browser network har stop [output.har] # Stop and save HAR (temp path if omitted)
```
### Tabs & Windows
```bash
agent-browser tab # List tabs
agent-browser tab new [url] # New tab (optionally with URL)
agent-browser tab <n> # Switch to tab n
agent-browser tab close [n] # Close tab
agent-browser window new # New window
```
### Frames
```bash
agent-browser frame <sel> # Switch to iframe
agent-browser frame main # Back to main frame
```
### Dialogs
```bash
agent-browser dialog accept [text] # Accept (with optional prompt text)
agent-browser dialog dismiss # Dismiss
```
### Diff
```bash
agent-browser diff snapshot # Compare current vs last snapshot
agent-browser diff snapshot --baseline before.txt # Compare current vs saved snapshot file
agent-browser diff snapshot --selector "#main" --compact # Scoped snapshot diff
agent-browser diff screenshot --baseline before.png # Visual pixel diff against baseline
agent-browser diff screenshot --baseline b.png -o d.png # Save diff image to custom path
agent-browser diff screenshot --baseline b.png -t 0.2 # Adjust color threshold (0-1)
agent-browser diff url https://v1.com https://v2.com # Compare two URLs (snapshot diff)
agent-browser diff url https://v1.com https://v2.com --screenshot # Also visual diff
agent-browser diff url https://v1.com https://v2.com --wait-until networkidle # Custom wait strategy
agent-browser diff url https://v1.com https://v2.com --selector "#main" # Scope to element
```
### Debug
```bash
agent-browser trace start [path] # Start recording trace
agent-browser trace stop [path] # Stop and save trace
agent-browser profiler start # Start Chrome DevTools profiling
agent-browser profiler stop [path] # Stop and save profile (.json)
agent-browser console # View console messages (log, error, warn, info)
agent-browser console --clear # Clear console
agent-browser errors # View page errors (uncaught JavaScript exceptions)
agent-browser errors --clear # Clear errors
agent-browser highlight <sel> # Highlight element
agent-browser inspect # Open Chrome DevTools for the active page
agent-browser state save <path> # Save auth state
agent-browser state load <path> # Load auth state
agent-browser state list # List saved state files
agent-browser state show <file> # Show state summary
agent-browser state rename <old> <new> # Rename state file
agent-browser state clear [name] # Clear states for session
agent-browser state clear --all # Clear all saved states
agent-browser state clean --older-than <days> # Delete old states
```
### Navigation
```bash
agent-browser back # Go back
agent-browser forward # Go forward
agent-browser reload # Reload page
```
### Setup
```bash
agent-browser install # Download Chrome from Chrome for Testing (Google's official automation channel)
agent-browser install --with-deps # Also install system deps (Linux)
agent-browser upgrade # Upgrade agent-browser to the latest version
```
## Authentication
agent-browser provides multiple ways to persist login sessions so you don't re-authenticate every run.
### Quick summary
| Approach | Best for | Flag / Env |
|----------|----------|------------|
| **Persistent profile** | Full browser state (cookies, IndexedDB, service workers, cache) across restarts | `--profile <path>` / `AGENT_BROWSER_PROFILE` |
| **Session persistence** | Auto-save/restore cookies + localStorage by name | `--session-name <name>` / `AGENT_BROWSER_SESSION_NAME` |
| **Import from your browser** | Grab auth from a Chrome session you already logged into | `--auto-connect` + `state save` |
| **State file** | Load a previously saved state JSON on launch | `--state <path>` / `AGENT_BROWSER_STATE` |
| **Auth vault** | Store credentials locally (encrypted), login by name | `auth save` / `auth login` |
### Import auth from your browser
If you are already logged in to a site in Chrome, you can grab that auth state and reuse it:
```bash
# 1. Launch Chrome with remote debugging enabled
# macOS:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222
# Or use --auto-connect to discover an already-running Chrome
# 2. Connect and save the authenticated state
agent-browser --auto-connect state save ./my-auth.json
# 3. Use the saved auth in future sessions
agent-browser --state ./my-auth.json open https://app.example.com/dashboard
# 4. Or use --session-name for automatic persistence
agent-browser --session-name myapp state load ./my-auth.json
# From now on, --session-name myapp auto-saves/restores this state
```
> **Security notes:**
> - `--remote-debugging-port` exposes full browser control on localhost. Any local process can connect. Only use on trusted machines and close Chrome when done.
> - State files contain session tokens in plaintext. Add them to `.gitignore` and delete when no longer needed. For encryption at rest, set `AGENT_BROWSER_ENCRYPTION_KEY` (see [State Encryption](#state-encryption)).
For full details on login flows, OAuth, 2FA, cookie-based auth, and the auth vault, see the [Authentication](docs/src/app/sessions/page.mdx) docs.
## Sessions
Run multiple isolated browser instances:
```bash
# Different sessions
agent-browser --session agent1 open site-a.com
agent-browser --session agent2 open site-b.com
# Or via environment variable
AGENT_BROWSER_SESSION=agent1 agent-browser click "#btn"
# List active sessions
agent-browser session list
# Output:
# Active sessions:
# -> default
# agent1
# Show current session
agent-browser session
```
Each session has its own:
- Browser instance
- Cookies and storage
- Navigation history
- Authentication state
## Persistent Profiles
By default, browser state (cookies, localStorage, login sessions) is ephemeral and lost when the browser closes. Use `--profile` to persist state across browser restarts:
```bash
# Use a persistent profile directory
agent-browser --profile ~/.myapp-profile open myapp.com
# Login once, then reuse the authenticated session
agent-browser --profile ~/.myapp-profile open myapp.com/dashboard
# Or via environment variable
AGENT_BROWSER_PROFILE=~/.myapp-profile agent-browser open myapp.com
```
The profile directory stores:
- Cookies and localStorage
- IndexedDB data
- Service workers
- Browser cache
- Login sessions
**Tip**: Use different profile paths for different projects to keep their browser state isolated.
## Session Persistence
Alternatively, use `--session-name` to automatically save and restore cookies and localStorage across browser restarts:
```bash
# Auto-save/load state for "twitter" session
agent-browser --session-name twitter open twitter.com
# Login once, then state persists automatically
# State files stored in ~/.agent-browser/sessions/
# Or via environment variable
export AGENT_BROWSER_SESSION_NAME=twitter
agent-browser open twitter.com
```
### State Encryption
Encrypt saved session data at rest with AES-256-GCM:
```bash
# Generate key: openssl rand -hex 32
export AGENT_BROWSER_ENCRYPTION_KEY=<64-char-hex-key>
# State files are now encrypted automatically
agent-browser --session-name secure open example.com
```
| Variable | Description |
| --------------------------------- | -------------------------------------------------- |
| `AGENT_BROWSER_SESSION_NAME` | Auto-save/load state persistence name |
| `AGENT_BROWSER_ENCRYPTION_KEY` | 64-char hex key for AES-256-GCM encryption |
| `AGENT_BROWSER_STATE_EXPIRE_DAYS` | Auto-delete states older than N days (default: 30) |
## Security
agent-browser includes security features for safe AI agent deployments. All features are opt-in -- existing workflows are unaffected until you explicitly enable a feature:
- **Authentication Vault** -- Store credentials locally (always encrypted), reference by name. The LLM never sees passwords. A key is auto-generated at `~/.agent-browser/.encryption-key` if `AGENT_BROWSER_ENCRYPTION_KEY` is not set: `echo "pass" | agent-browser auth save github --url https://github.com/login --username user --password-stdin` then `agent-browser auth login github`
- **Content Boundary Markers** -- Wrap page output in delimiters so LLMs can distinguish tool output from untrusted content: `--content-boundaries`
- **Domain Allowlist** -- Restrict navigation to trusted domains (wildcards like `*.example.com` also match the bare domain): `--allowed-domains "example.com,*.example.com"`. Sub-resource requests (scripts, images, fetch) and WebSocket/EventSource connections to non-allowed domains are also blocked. Include any CDN domains your target pages depend on (e.g., `*.cdn.example.com`).
- **Action Policy** -- Gate destructive actions with a static policy file: `--action-policy ./policy.json`
- **Action Confirmation** -- Require explicit approval for sensitive action categories: `--confirm-actions eval,download`
- **Output Length Limits** -- Prevent context flooding: `--max-output 50000`
| Variable | Description |
| ----------------------------------- | ---------------------------------------- |
| `AGENT_BROWSER_CONTENT_BOUNDARIES` | Wrap page output in boundary markers |
| `AGENT_BROWSER_MAX_OUTPUT` | Max characters for page output |
| `AGENT_BROWSER_ALLOWED_DOMAINS` | Comma-separated allowed domain patterns |
| `AGENT_BROWSER_ACTION_POLICY` | Path to action policy JSON file |
| `AGENT_BROWSER_CONFIRM_ACTIONS` | Action categories requiring confirmation |
| `AGENT_BROWSER_CONFIRM_INTERACTIVE` | Enable interactive confirmation prompts |
See [Security documentation](https://agent-browser.dev/security) for details.
## Snapshot Options
The `snapshot` command supports filtering to reduce output size:
```bash
agent-browser snapshot # Full accessibility tree
agent-browser snapshot -i # Interactive elements only (buttons, inputs, links)
agent-browser snapshot -i -C # Include cursor-interactive elements (divs with onclick, etc.)
agent-browser snapshot -c # Compact (remove empty structural elements)
agent-browser snapshot -d 3 # Limit depth to 3 levels
agent-browser snapshot -s "#main" # Scope to CSS selector
agent-browser snapshot -i -c -d 5 # Combine options
```
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------- |
| `-i, --interactive` | Only show interactive elements (buttons, links, inputs) |
| `-C, --cursor` | Include cursor-interactive elements (cursor:pointer, onclick, tabindex) |
| `-c, --compact` | Remove empty structural elements |
| `-d, --depth <n>` | Limit tree depth |
| `-s, --selector <sel>` | Scope to CSS selector |
The `-C` flag is useful for modern web apps that use custom clickable elements (divs, spans) instead of standard buttons/links.
## Annotated Screenshots
The `--annotate` flag overlays numbered labels on interactive elements in the screenshot. Each label `[N]` corresponds to ref `@eN`, so the same refs work for both visual and text-based workflows.
Annotated screenshots are supported on the CDP-backed browser path (Chrome/Lightpanda). The Safari/WebDriver backend does not yet support `--annotate`.
```bash
agent-browser screenshot --annotate
# -> Screenshot saved to /tmp/screenshot-2026-02-17T12-00-00-abc123.png
# [1] @e1 button "Submit"
# [2] @e2 link "Home"
# [3] @e3 textbox "Email"
```
After an annotated screenshot, refs are cached so you can immediately interact with elements:
```bash
agent-browser screenshot --annotate ./page.png
agent-browser click @e2 # Click the "Home" link labeled [2]
```
This is useful for multimodal AI models that can reason about visual layout, unlabeled icon buttons, canvas elements, or visual state that the text accessibility tree cannot capture.
## Options
| Option | Description |
|--------|-------------|
| `--session <name>` | Use isolated session (or `AGENT_BROWSER_SESSION` env) |
| `--session-name <name>` | Auto-save/restore session state (or `AGENT_BROWSER_SESSION_NAME` env) |
| `--profile <path>` | Persistent browser profile directory (or `AGENT_BROWSER_PROFILE` env) |
| `--state <path>` | Load storage state from JSON file (or `AGENT_BROWSER_STATE` env) |
| `--headers <json>` | Set HTTP headers scoped to the URL's origin |
| `--executable-path <path>` | Custom browser executable (or `AGENT_BROWSER_EXECUTABLE_PATH` env) |
| `--extension <path>` | Load browser extension (repeatable; or `AGENT_BROWSER_EXTENSIONS` env) |
| `--args <args>` | Browser launch args, comma or newline separated (or `AGENT_BROWSER_ARGS` env) |
| `--user-agent <ua>` | Custom User-Agent string (or `AGENT_BROWSER_USER_AGENT` env) |
| `--proxy <url>` | Proxy server URL with optional auth (or `AGENT_BROWSER_PROXY` env) |
| `--proxy-bypass <hosts>` | Hosts to bypass proxy (or `AGENT_BROWSER_PROXY_BYPASS` env) |
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
| `--allow-file-access` | Allow file:// URLs to access local files (Chromium only) |
| `-p, --provider <name>` | Cloud browser provider (or `AGENT_BROWSER_PROVIDER` env) |
| `--device <name>` | iOS device name, e.g. "iPhone 15 Pro" (or `AGENT_BROWSER_IOS_DEVICE` env) |
| `--json` | JSON output (for agents) |
| `--annotate` | Annotated screenshot with numbered element labels (or `AGENT_BROWSER_ANNOTATE` env) |
| `--screenshot-dir <path>` | Default screenshot output directory (or `AGENT_BROWSER_SCREENSHOT_DIR` env) |
| `--screenshot-quality <n>` | JPEG quality 0-100 (or `AGENT_BROWSER_SCREENSHOT_QUALITY` env) |
| `--screenshot-format <fmt>` | Screenshot format: `png`, `jpeg` (or `AGENT_BROWSER_SCREENSHOT_FORMAT` env) |
| `--headed` | Show browser window (not headless) (or `AGENT_BROWSER_HEADED` env) |
| `--cdp <port\|url>` | Connect via Chrome DevTools Protocol (port or WebSocket URL) |
| `--auto-connect` | Auto-discover and connect to running Chrome (or `AGENT_BROWSER_AUTO_CONNECT` env) |
| `--color-scheme <scheme>` | Color scheme: `dark`, `light`, `no-preference` (or `AGENT_BROWSER_COLOR_SCHEME` env) |
| `--download-path <path>` | Default download directory (or `AGENT_BROWSER_DOWNLOAD_PATH` env) |
| `--content-boundaries` | Wrap page output in boundary markers for LLM safety (or `AGENT_BROWSER_CONTENT_BOUNDARIES` env) |
| `--max-output <chars>` | Truncate page output to N characters (or `AGENT_BROWSER_MAX_OUTPUT` env) |
| `--allowed-domains <list>` | Comma-separated allowed domain patterns (or `AGENT_BROWSER_ALLOWED_DOMAINS` env) |
| `--action-policy <path>` | Path to action policy JSON file (or `AGENT_BROWSER_ACTION_POLICY` env) |
| `--confirm-actions <list>` | Action categories requiring confirmation (or `AGENT_BROWSER_CONFIRM_ACTIONS` env) |
| `--confirm-interactive` | Interactive confirmation prompts; auto-denies if stdin is not a TTY (or `AGENT_BROWSER_CONFIRM_INTERACTIVE` env) |
| `--engine <name>` | Browser engine: `chrome` (default), `lightpanda` (or `AGENT_BROWSER_ENGINE` env) |
| `--config <path>` | Use a custom config file (or `AGENT_BROWSER_CONFIG` env) |
| `--debug` | Debug output |
## Configuration
Create an `agent-browser.json` file to set persistent defaults instead of repeating flags on every command.
**Locations (lowest to highest priority):**
1. `~/.agent-browser/config.json` -- user-level defaults
2. `./agent-browser.json` -- project-level overrides (in working directory)
3. `AGENT_BROWSER_*` environment variables override config file values
4. CLI flags override everything
**Example `agent-browser.json`:**
```json
{
"headed": true,
"proxy": "http://localhost:8080",
"profile": "./browser-data",
"userAgent": "my-agent/1.0",
"ignoreHttpsErrors": true
}
```
Use `--config <path>` or `AGENT_BROWSER_CONFIG` to load a specific config file instead of the defaults:
```bash
agent-browser --config ./ci-config.json open example.com
AGENT_BROWSER_CONFIG=./ci-config.json agent-browser open example.com
```
All options from the table above can be set in the config file using camelCase keys (e.g., `--executable-path` becomes `"executablePath"`, `--proxy-bypass` becomes `"proxyBypass"`). Unknown keys are ignored for forward compatibility.
Boolean flags accept an optional `true`/`false` value to override config settings. For example, `--headed false` disables `"headed": true` from config. A bare `--headed` is equivalent to `--headed true`.
Auto-discovered config files that are missing are silently ignored. If `--config <path>` points to a missing or invalid file, agent-browser exits with an error. Extensions from user and project configs are merged (concatenated), not replaced.
> **Tip:** If your project-level `agent-browser.json` contains environment-specific values (paths, proxies), consider adding it to `.gitignore`.
## Default Timeout
The default timeout for standard operations (clicks, waits, fills, etc.) is 25 seconds. This is intentionally below the CLI's 30-second IPC read timeout so that the daemon returns a proper error instead of the CLI timing out with EAGAIN.
Override the default timeout via environment variable:
```bash
# Set a longer timeout for slow pages (in milliseconds)
export AGENT_BROWSER_DEFAULT_TIMEOUT=45000
```
> **Note:** Setting this above 30000 (30s) may cause EAGAIN errors on slow operations because the CLI's read timeout will expire before the daemon responds. The CLI retries transient errors automatically, but response times will increase.
| Variable | Description |
| ------------------------------- | ---------------------------------------- |
| `AGENT_BROWSER_DEFAULT_TIMEOUT` | Default operation timeout in ms (default: 25000) |
## Selectors
### Refs (Recommended for AI)
Refs provide deterministic element selection from snapshots:
```bash
# 1. Get snapshot with refs
agent-browser snapshot
# Output:
# - heading "Example Domain" [ref=e1] [level=1]
# - button "Submit" [ref=e2]
# - textbox "Email" [ref=e3]
# - link "Learn more" [ref=e4]
# 2. Use refs to interact
agent-browser click @e2 # Click the button
agent-browser fill @e3 "test@example.com" # Fill the textbox
agent-browser get text @e1 # Get heading text
agent-browser hover @e4 # Hover the link
```
**Why use refs?**
- **Deterministic**: Ref points to exact element from snapshot
- **Fast**: No DOM re-query needed
- **AI-friendly**: Snapshot + ref workflow is optimal for LLMs
### CSS Selectors
```bash
agent-browser click "#id"
agent-browser click ".class"
agent-browser click "div > button"
```
### Text & XPath
```bash
agent-browser click "text=Submit"
agent-browser click "xpath=//button"
```
### Semantic Locators
```bash
agent-browser find role button click --name "Submit"
agent-browser find label "Email" fill "test@test.com"
```
## Agent Mode
Use `--json` for machine-readable output:
```bash
agent-browser snapshot --json
# Returns: {"success":true,"data":{"snapshot":"...","refs":{"e1":{"role":"heading","name":"Title"},...}}}
agent-browser get text @e1 --json
agent-browser is visible @e2 --json
```
### Optimal AI Workflow
```bash
# 1. Navigate and get snapshot
agent-browser open example.com
agent-browser snapshot -i --json # AI parses tree and refs
# 2. AI identifies target refs from snapshot
# 3. Execute actions using refs
agent-browser click @e2
agent-browser fill @e3 "input text"
# 4. Get new snapshot if page changed
agent-browser snapshot -i --json
```
### Command Chaining
Commands can be chained with `&&` in a single shell invocation. The browser persists via a background daemon, so chaining is safe and more efficient:
```bash
# Open, wait for load, and snapshot in one call
agent-browser open example.com && agent-browser wait --load networkidle && agent-browser snapshot -i
# Chain multiple interactions
agent-browser fill @e1 "user@example.com" && agent-browser fill @e2 "pass" && agent-browser click @e3
# Navigate and screenshot
agent-browser open example.com && agent-browser wait --load networkidle && agent-browser screenshot page.png
```
Use `&&` when you don't need intermediate output. Run commands separately when you need to parse output first (e.g., snapshot to discover refs before interacting).
## Headed Mode
Show the browser window for debugging:
```bash
agent-browser open example.com --headed
```
This opens a visible browser window instead of running headless.
> **Note:** Browser extensions work in both headed and headless mode (Chrome's `--headless=new`).
## Authenticated Sessions
Use `--headers` to set HTTP headers for a specific origin, enabling authentication without login flows:
```bash
# Headers are scoped to api.example.com only
agent-browser open api.example.com --headers '{"Authorization": "Bearer <token>"}'
# Requests to api.example.com include the auth header
agent-browser snapshot -i --json
agent-browser click @e2
# Navigate to another domain - headers are NOT sent (safe!)
agent-browser open other-site.com
```
This is useful for:
- **Skipping login flows** - Authenticate via headers instead of UI
- **Switching users** - Start new sessions with different auth tokens
- **API testing** - Access protected endpoints directly
- **Security** - Headers are scoped to the origin, not leaked to other domains
To set headers for multiple origins, use `--headers` with each `open` command:
```bash
agent-browser open api.example.com --headers '{"Authorization": "Bearer token1"}'
agent-browser open api.acme.com --headers '{"Authorization": "Bearer token2"}'
```
For global headers (all domains), use `set headers`:
```bash
agent-browser set headers '{"X-Custom-Header": "value"}'
```
## Custom Browser Executable
Use a custom browser executable instead of the bundled Chromium. This is useful for:
- **Serverless deployment**: Use lightweight Chromium builds like `@sparticuz/chromium` (~50MB vs ~684MB)
- **System browsers**: Use an existing Chrome/Chromium installation
- **Custom builds**: Use modified browser builds
### CLI Usage
```bash
# Via flag
agent-browser --executable-path /path/to/chromium open example.com
# Via environment variable
AGENT_BROWSER_EXECUTABLE_PATH=/path/to/chromium agent-browser open example.com
```
### Serverless (Vercel)
Run agent-browser + Chrome in an ephemeral Vercel Sandbox microVM. No external server needed:
```typescript
import { Sandbox } from "@vercel/sandbox";
const sandbox = await Sandbox.create({ runtime: "node24" });
await sandbox.runCommand("agent-browser", ["open", "https://example.com"]);
const result = await sandbox.runCommand("agent-browser", ["screenshot", "--json"]);
await sandbox.stop();
```
See the [environments example](examples/environments/) for a working demo with a UI and deploy-to-Vercel button.
### Serverless (AWS Lambda)
```typescript
import chromium from '@sparticuz/chromium';
import { execSync } from 'child_process';
export async function handler() {
const executablePath = await chromium.executablePath();
const result = execSync(
`AGENT_BROWSER_EXECUTABLE_PATH=${executablePath} agent-browser open https://example.com && agent-browser snapshot -i --json`,
{ encoding: 'utf-8' }
);
return JSON.parse(result);
}
```
## Local Files
Open and interact with local files (PDFs, HTML, etc.) using `file://` URLs:
```bash
# Enable file access (required for JavaScript to access local files)
agent-browser --allow-file-access open file:///path/to/document.pdf
agent-browser --allow-file-access open file:///path/to/page.html
# Take screenshot of a local PDF
agent-browser --allow-file-access open file:///Users/me/report.pdf
agent-browser screenshot report.png
```
The `--allow-file-access` flag adds Chromium flags (`--allow-file-access-from-files`, `--allow-file-access`) that allow `file://` URLs to:
- Load and render local files
- Access other local files via JavaScript (XHR, fetch)
- Load local resources (images, scripts, stylesheets)
**Note:** This flag only works with Chromium. For security, it's disabled by default.
## CDP Mode
Connect to an existing browser via Chrome DevTools Protocol:
```bash
# Start Chrome with: google-chrome --remote-debugging-port=9222
# Connect once, then run commands without --cdp
agent-browser connect 9222
agent-browser snapshot
agent-browser tab
agent-browser close
# Or pass --cdp on each command
agent-browser --cdp 9222 snapshot
# Connect to remote browser via WebSocket URL
agent-browser --cdp "wss://your-browser-service.com/cdp?token=..." snapshot
```
The `--cdp` flag accepts either:
- A port number (e.g., `9222`) for local connections via `http://localhost:{port}`
- A full WebSocket URL (e.g., `wss://...` or `ws://...`) for remote browser services
This enables control of:
- Electron apps
- Chrome/Chromium instances with remote debugging
- WebView2 applications
- Any browser exposing a CDP endpoint
### Auto-Connect
Use `--auto-connect` to automatically discover and connect to a running Chrome instance without specifying a port:
```bash
# Auto-discover running Chrome with remote debugging
agent-browser --auto-connect open example.com
agent-browser --auto-connect snapshot
# Or via environment variable
AGENT_BROWSER_AUTO_CONNECT=1 agent-browser snapshot
```
Auto-connect discovers Chrome by:
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
2. Falling back to probing common debugging ports (9222, 9229)
3. If HTTP-based discovery (`/json/version`, `/json/list`) fails, falling back to a direct WebSocket connection
This is useful when:
- Chrome 144+ has remote debugging enabled via `chrome://inspect/#remote-debugging` (which uses a dynamic port)
- You want a zero-configuration connection to your existing browser
- You don't want to track which port Chrome is using
## Streaming (Browser Preview)
Stream the browser viewport via WebSocket for live preview or "pair browsing" where a human can watch and interact alongside an AI agent.
### Enable Streaming
Set the `AGENT_BROWSER_STREAM_PORT` environment variable:
```bash
AGENT_BROWSER_STREAM_PORT=9223 agent-browser open example.com
```
This starts a WebSocket server on the specified port that streams the browser viewport and accepts input events.
### WebSocket Protocol
Connect to `ws://localhost:9223` to receive frames and send input:
**Receive frames:**
```json
{
"type": "frame",
"data": "<base64-encoded-jpeg>",
"metadata": {
"deviceWidth": 1280,
"deviceHeight": 720,
"pageScaleFactor": 1,
"offsetTop": 0,
"scrollOffsetX": 0,
"scrollOffsetY": 0
}
}
```
**Send mouse events:**
```json
{
"type": "input_mouse",
"eventType": "mousePressed",
"x": 100,
"y": 200,
"button": "left",
"clickCount": 1
}
```
**Send keyboard events:**
```json
{
"type": "input_keyboard",
"eventType": "keyDown",
"key": "Enter",
"code": "Enter"
}
```
**Send touch events:**
```json
{
"type": "input_touch",
"eventType": "touchStart",
"touchPoints": [{ "x": 100, "y": 200 }]
}
```
## Architecture
agent-browser uses a client-daemon architecture:
1. **Rust CLI** - Parses commands, communicates with daemon
2. **Rust Daemon** - Pure Rust daemon using direct CDP, no Node.js required
The daemon starts automatically on first command and persists between commands for fast subsequent operations. To auto-shutdown the daemon after a period of inactivity, set `AGENT_BROWSER_IDLE_TIMEOUT_MS` (value in milliseconds). When set, the daemon closes the browser and exits after receiving no commands for the specified duration.
**Browser Engine:** Uses Chrome (from Chrome for Testing) by default. The `--engine` flag selects between `chrome` and `lightpanda`. Supported browsers: Chromium/Chrome (via CDP) and Safari (via WebDriver for iOS).
## Platforms
| Platform | Binary |
| ----------- | ----------- |
| macOS ARM64 | Native Rust |
| macOS x64 | Native Rust |
| Linux ARM64 | Native Rust |
| Linux x64 | Native Rust |
| Windows x64 | Native Rust |
## Usage with AI Agents
### Just ask the agent
The simplest approach -- just tell your agent to use it:
```
Use agent-browser to test the login flow. Run agent-browser --help to see available commands.
```
The `--help` output is comprehensive and most agents can figure it out from there.
### AI Coding Assistants (recommended)
Add the skill to your AI coding assistant for richer context:
```bash
npx skills add vercel-labs/agent-browser
```
This works with Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, Goose, OpenCode, and Windsurf. The skill is fetched from the repository, so it stays up to date automatically -- do not copy `SKILL.md` from `node_modules` as it will become stale.
### Claude Code
Install as a Claude Code skill:
```bash
npx skills add vercel-labs/agent-browser
```
This adds the skill to `.claude/skills/agent-browser/SKILL.md` in your project. The skill teaches Claude Code the full agent-browser workflow, including the snapshot-ref interaction pattern, session management, and timeout handling.
### AGENTS.md / CLAUDE.md
For more consistent results, add to your project or global instructions file:
```markdown
## Browser Automation
Use `agent-browser` for web automation. Run `agent-browser --help` for all commands.
Core workflow:
1. `agent-browser open <url>` - Navigate to page
2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2)
3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs
4. Re-snapshot after page changes
```
## Integrations
### iOS Simulator
Control real Mobile Safari in the iOS Simulator for authentic mobile web testing. Requires macOS with Xcode.
**Setup:**
```bash
# Install Appium and XCUITest driver
npm install -g appium
appium driver install xcuitest
```
**Usage:**
```bash
# List available iOS simulators
agent-browser device list
# Launch Safari on a specific device
agent-browser -p ios --device "iPhone 16 Pro" open https://example.com
# Same commands as desktop
agent-browser -p ios snapshot -i
agent-browser -p ios tap @e1
agent-browser -p ios fill @e2 "text"
agent-browser -p ios screenshot mobile.png
# Mobile-specific commands
agent-browser -p ios swipe up
agent-browser -p ios swipe down 500
# Close session
agent-browser -p ios close
```
Or use environment variables:
```bash
export AGENT_BROWSER_PROVIDER=ios
export AGENT_BROWSER_IOS_DEVICE="iPhone 16 Pro"
agent-browser open https://example.com
```
| Variable | Description |
| -------------------------- | ----------------------------------------------- |
| `AGENT_BROWSER_PROVIDER` | Set to `ios` to enable iOS mode |
| `AGENT_BROWSER_IOS_DEVICE` | Device name (e.g., "iPhone 16 Pro", "iPad Pro") |
| `AGENT_BROWSER_IOS_UDID` | Device UDID (alternative to device name) |
**Supported devices:** All iOS Simulators available in Xcode (iPhones, iPads), plus real iOS devices.
**Note:** The iOS provider boots the simulator, starts Appium, and controls Safari. First launch takes ~30-60 seconds; subsequent commands are fast.
#### Real Device Support
Appium also supports real iOS devices connected via USB. This requires additional one-time setup:
**1. Get your device UDID:**
```bash
xcrun xctrace list devices
# or
system_profiler SPUSBDataType | grep -A 5 "iPhone\|iPad"
```
**2. Sign WebDriverAgent (one-time):**
```bash
# Open the WebDriverAgent Xcode project
cd ~/.appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent
open WebDriverAgent.xcodeproj
```
In Xcode:
- Select the `WebDriverAgentRunner` target
- Go to Signing & Capabilities
- Select your Team (requires Apple Developer account, free tier works)
- Let Xcode manage signing automatically
**3. Use with agent-browser:**
```bash
# Connect device via USB, then:
agent-browser -p ios --device "<DEVICE_UDID>" open https://example.com
# Or use the device name if unique
agent-browser -p ios --device "John's iPhone" open https://example.com
```
**Real device notes:**
- First run installs WebDriverAgent to the device (may require Trust prompt)
- Device must be unlocked and connected via USB
- Slightly slower initial connection than simulator
- Tests against real Safari performance and behavior
### Browserless
[Browserless](https://browserless.io) provides cloud browser infrastructure with a Sessions API. Use it when running agent-browser in environments where a local browser isn't available.
To enable Browserless, use the `-p` flag:
```bash
export BROWSERLESS_API_KEY="your-api-token"
agent-browser -p browserless open https://example.com
```
Or use environment variables for CI/scripts:
```bash
export AGENT_BROWSER_PROVIDER=browserless
export BROWSERLESS_API_KEY="your-api-token"
agent-browser open https://example.com
```
Optional configuration via environment variables:
| Variable | Description | Default |
| -------------------------- | ------------------------------------------------ | --------------------------------------- |
| `BROWSERLESS_API_URL` | Base API URL (for custom regions or self-hosted) | `https://production-sfo.browserless.io` |
| `BROWSERLESS_BROWSER_TYPE` | Type of browser to use (chromium or chrome) | chromium |
| `BROWSERLESS_TTL` | Session TTL in milliseconds | `300000` |
| `BROWSERLESS_STEALTH` | Enable stealth mode (`true`/`false`) | `true` |
When enabled, agent-browser connects to a Browserless cloud session instead of launching a local browser. All commands work identically.
Get your API token from the [Browserless Dashboard](https://browserless.io).
### Browserbase
[Browserbase](https://browserbase.com) provides remote browser infrastructure to make deployment of agentic browsing agents easy. Use it when running the agent-browser CLI in an environment where a local browser isn't feasible.
To enable Browserbase, use the `-p` flag:
```bash
export BROWSERBASE_API_KEY="your-api-key"
agent-browser -p browserbase open https://example.com
```
Or use environment variables for CI/scripts:
```bash
export AGENT_BROWSER_PROVIDER=browserbase
export BROWSERBASE_API_KEY="your-api-key"
agent-browser open https://example.com
```
When enabled, agent-browser connects to a Browserbase session instead of launching a local browser. All commands work identically.
Get your API key from the [Browserbase Dashboard](https://browserbase.com/overview).
### Browser Use
[Browser Use](https://browser-use.com) provides cloud browser infrastructure for AI agents. Use it when running agent-browser in environments where a local browser isn't available (serverless, CI/CD, etc.).
To enable Browser Use, use the `-p` flag:
```bash
export BROWSER_USE_API_KEY="your-api-key"
agent-browser -p browseruse open https://example.com
```
Or use environment variables for CI/scripts:
```bash
export AGENT_BROWSER_PROVIDER=browseruse
export BROWSER_USE_API_KEY="your-api-key"
agent-browser open https://example.com
```
When enabled, agent-browser connects to a Browser Use cloud session instead of launching a local browser. All commands work identically.
Get your API key from the [Browser Use Cloud Dashboard](https://cloud.browser-use.com/settings?tab=api-keys). Free credits are available to get started, with pay-as-you-go pricing after.
### Kernel
[Kernel](https://www.kernel.sh) provides cloud browser infrastructure for AI agents with features like stealth mode and persistent profiles.
To enable Kernel, use the `-p` flag:
```bash
export KERNEL_API_KEY="your-api-key"
agent-browser -p kernel open https://example.com
```
Or use environment variables for CI/scripts:
```bash
export AGENT_BROWSER_PROVIDER=kernel
export KERNEL_API_KEY="your-api-key"
agent-browser open https://example.com
```
Optional configuration via environment variables:
| Variable | Description | Default |
| ------------------------ | -------------------------------------------------------------------------------- | ------- |
| `KERNEL_HEADLESS` | Run browser in headless mode (`true`/`false`) | `false` |
| `KERNEL_STEALTH` | Enable stealth mode to avoid bot detection (`true`/`false`) | `true` |
| `KERNEL_TIMEOUT_SECONDS` | Session timeout in seconds | `300` |
| `KERNEL_PROFILE_NAME` | Browser profile name for persistent cookies/logins (created if it doesn't exist) | (none) |
When enabled, agent-browser connects to a Kernel cloud session instead of launching a local browser. All commands work identically.
**Profile Persistence:** When `KERNEL_PROFILE_NAME` is set, the profile will be created if it doesn't already exist. Cookies, logins, and session data are automatically saved back to the profile when the browser session ends, making them available for future sessions.
Get your API key from the [Kernel Dashboard](https://dashboard.onkernel.com).
## License
Apache-2.0
================================================
FILE: benchmarks/.gitignore
================================================
node_modules/
results.json
================================================
FILE: benchmarks/README.md
================================================
# agent-browser Daemon Benchmarks
Compares command latency and system metrics between the **Node.js daemon** (published npm version) and the **Rust native daemon** (built from source), running inside a [Vercel Sandbox](https://vercel.com/docs/sandbox) microVM.
## What it measures
**Command latency** -- per-scenario timing with warmup, multiple iterations, and stddev:
- `navigate` -- page load round-trip
- `snapshot` -- accessibility tree generation
- `screenshot` -- viewport capture
- `evaluate` -- JavaScript execution
- `click` -- element interaction
- `fill` -- form input
- `agent-loop` -- snapshot/click/snapshot cycle (typical AI agent pattern)
- `full-workflow` -- realistic 7-command sequence
**System metrics** -- collected while the daemon is running:
- Cold start time (daemon spawn + browser launch)
- Binary size and total distribution size (including browser download)
- Daemon RSS and peak RSS (separated from browser process memory)
- Browser RSS (Chrome processes, same for both daemons)
- Daemon CPU time
- Process counts
## Prerequisites
- Node.js 18+
- pnpm
- Vercel Sandbox credentials (token, team ID, project ID)
## Setup
```bash
cd benchmarks
pnpm install
cp .env.example .env
```
Fill in your Vercel Sandbox credentials in `.env`:
```
SANDBOX_VERCEL_TOKEN=your_token
SANDBOX_VERCEL_TEAM_ID=your_team_id
SANDBOX_VERCEL_PROJECT_ID=your_project_id
```
## Usage
```bash
pnpm bench # 10 iterations, 1 warmup, 8 vCPUs
pnpm bench -- --iterations 20 # more iterations for tighter stats
pnpm bench -- --warmup 2 # extra warmup iterations
pnpm bench -- --json # write results.json
pnpm bench -- --branch main # build native from a different branch
pnpm bench -- --vcpus 16 # more vCPUs (faster Rust build)
```
## How it works
1. Creates a Vercel Sandbox (Amazon Linux, configurable vCPUs)
2. Installs Chromium system dependencies
3. **Phase 1 -- Node.js daemon**: installs `agent-browser` from npm (last version with the Node daemon), runs all scenarios, collects metrics
4. **Phase 2 -- Rust native daemon**: installs Rust toolchain, clones the repo, runs `cargo build --release`, replaces the binary, runs the same scenarios, collects metrics
5. Prints comparison tables and optionally writes `results.json`
## Interpreting results
**Command latency** is dominated by Chrome (CDP round-trips), not the daemon. Both daemons are thin relays between the CLI and Chrome, so per-command speedups are typically small. The stddev column helps distinguish real differences from noise.
**Where the native daemon wins** is in cold start (no Node.js runtime to boot), daemon memory (single Rust binary vs V8 heap), and distribution size (no Playwright dependency).
The **daemon RSS** metric isolates the daemon process memory from Chrome. This is the apples-to-apples comparison -- both daemons talk to the same Chrome, but Node.js adds ~140 MB of V8 overhead while the Rust daemon uses ~7 MB.
**Distribution size** includes the daemon plus its browser download. The Node version includes the npm package + Playwright's bundled Chromium. The Rust version is just the binary + Chrome for Testing.
================================================
FILE: benchmarks/bench.ts
================================================
/**
* Node.js Daemon vs Rust Native Daemon benchmark.
*
* Compares the last published npm version (Node.js daemon) against the
* Rust-only build from a given branch, running real agent-browser commands
* inside a Vercel Sandbox.
*
* Captures:
* - Command latency (per-scenario, with warmup + measured iterations + stddev)
* - Cold start time (first launch to daemon ready)
* - Daemon memory (RSS, peak RSS) separated from browser memory
* - Daemon CPU time
* - Process tree (daemon + browser children)
* - Binary and distribution size on disk
*
* Usage:
* pnpm bench # default: 10 iterations, 1 warmup
* pnpm bench -- --iterations 20 # override iterations
* pnpm bench -- --warmup 2 # override warmup count
* pnpm bench -- --json # write results.json
* pnpm bench -- --branch my-branch # override native branch (default: ctate/native-2)
* pnpm bench -- --vcpus 8 # sandbox vCPUs (default: 8, higher = faster Rust build)
*/
import { Sandbox } from "@vercel/sandbox";
import { readFileSync, writeFileSync } from "fs";
import { scenarios, type Scenario } from "./scenarios.js";
// ---------------------------------------------------------------------------
// Env
// ---------------------------------------------------------------------------
function loadEnv() {
try {
const content = readFileSync(".env", "utf-8");
for (const line of content.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const eq = trimmed.indexOf("=");
if (eq === -1) continue;
const key = trimmed.slice(0, eq);
let val = trimmed.slice(eq + 1);
if (
(val.startsWith('"') && val.endsWith('"')) ||
(val.startsWith("'") && val.endsWith("'"))
) {
val = val.slice(1, -1);
}
process.env[key] = val;
}
} catch {}
}
loadEnv();
const credentials = {
token: process.env.SANDBOX_VERCEL_TOKEN!,
teamId: process.env.SANDBOX_VERCEL_TEAM_ID!,
projectId: process.env.SANDBOX_VERCEL_PROJECT_ID!,
};
if (!credentials.token || !credentials.teamId || !credentials.projectId) {
console.error(
"Missing credentials. Set SANDBOX_VERCEL_TOKEN, SANDBOX_VERCEL_TEAM_ID, SANDBOX_VERCEL_PROJECT_ID in .env",
);
process.exit(1);
}
// ---------------------------------------------------------------------------
// CLI args
// ---------------------------------------------------------------------------
function parseArgs() {
const args = process.argv.slice(2);
let iterations = 10;
let warmup = 1;
let json = false;
let branch = "ctate/native-2";
let vcpus = 8;
for (let i = 0; i < args.length; i++) {
if (args[i] === "--iterations" && args[i + 1]) {
iterations = parseInt(args[++i], 10);
} else if (args[i] === "--warmup" && args[i + 1]) {
warmup = parseInt(args[++i], 10);
} else if (args[i] === "--json") {
json = true;
} else if (args[i] === "--branch" && args[i + 1]) {
branch = args[++i];
} else if (args[i] === "--vcpus" && args[i + 1]) {
vcpus = parseInt(args[++i], 10);
}
}
return { iterations, warmup, json, branch, vcpus };
}
const config = parseArgs();
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const TIMEOUT_MS = 30 * 60 * 1000;
const REPO_URL = "https://github.com/vercel-labs/agent-browser.git";
const CHROMIUM_SYSTEM_DEPS = [
"nss",
"nspr",
"libxkbcommon",
"atk",
"at-spi2-atk",
"at-spi2-core",
"libXcomposite",
"libXdamage",
"libXrandr",
"libXfixes",
"libXcursor",
"libXi",
"libXtst",
"libXScrnSaver",
"libXext",
"mesa-libgbm",
"libdrm",
"mesa-libGL",
"mesa-libEGL",
"cups-libs",
"alsa-lib",
"pango",
"cairo",
"gtk3",
"dbus-libs",
];
// ---------------------------------------------------------------------------
// Sandbox helpers
// ---------------------------------------------------------------------------
type SandboxInstance = InstanceType<typeof Sandbox>;
async function run(
sandbox: SandboxInstance,
cmd: string,
args: string[],
): Promise<string> {
const result = await sandbox.runCommand(cmd, args);
const stdout = await result.stdout();
const stderr = await result.stderr();
if (result.exitCode !== 0) {
throw new Error(
`Command failed (exit ${result.exitCode}): ${cmd} ${args.join(" ")}\n${stderr || stdout}`,
);
}
return stdout;
}
async function shell(sandbox: SandboxInstance, script: string): Promise<string> {
return run(sandbox, "sh", ["-c", script]);
}
async function shellSafe(sandbox: SandboxInstance, script: string): Promise<string> {
const result = await sandbox.runCommand("sh", ["-c", script]);
return (await result.stdout()).trim();
}
// ---------------------------------------------------------------------------
// Stats
// ---------------------------------------------------------------------------
interface Stats {
avgMs: number;
stddevMs: number;
minMs: number;
maxMs: number;
p50Ms: number;
samples: number[];
}
function computeStats(samples: number[]): Stats {
const sorted = [...samples].sort((a, b) => a - b);
const sum = sorted.reduce((a, b) => a + b, 0);
const avg = sum / sorted.length;
const variance =
sorted.reduce((acc, v) => acc + (v - avg) ** 2, 0) / sorted.length;
return {
avgMs: Math.round(avg),
stddevMs: Math.round(Math.sqrt(variance)),
minMs: sorted[0],
maxMs: sorted[sorted.length - 1],
p50Ms: sorted[Math.floor(sorted.length / 2)],
samples: sorted,
};
}
// ---------------------------------------------------------------------------
// Metrics collection
// ---------------------------------------------------------------------------
interface ProcessMetrics {
pid: number;
rssKb: number;
vszKb: number;
cpuPercent: number;
memPercent: number;
cpuTimeSec: number;
command: string;
}
interface DaemonMetrics {
coldStartMs: number;
binarySizeBytes: number;
distributionSizeBytes: number;
daemonProcesses: ProcessMetrics[];
browserProcesses: ProcessMetrics[];
daemonRssKb: number;
browserRssKb: number;
daemonPeakRssKb: number;
daemonCpuTimeSec: number;
totalCpuTimeSec: number;
}
async function findDaemonPids(
sandbox: SandboxInstance,
_session: string,
): Promise<number[]> {
// The daemon process name is "agent-browser" but session/daemon flags are
// env vars, not command-line args, so we can't grep them from `ps`.
// Instead, find all agent-browser processes that look like long-running daemons
// (not short-lived CLI invocations -- those exit immediately).
const raw = await shellSafe(
sandbox,
`pgrep -x agent-browser 2>/dev/null || true`,
);
if (!raw) {
// Fallback: broader match on process name
const fallback = await shellSafe(
sandbox,
`pgrep -f 'agent-browser' 2>/dev/null | head -5 || true`,
);
if (!fallback) return [];
return fallback.split("\n").map(Number).filter(Boolean);
}
return raw.split("\n").map(Number).filter(Boolean);
}
async function collectProcessMetrics(
sandbox: SandboxInstance,
pid: number,
): Promise<ProcessMetrics | null> {
const raw = await shellSafe(
sandbox,
`ps -p ${pid} -o pid=,rss=,vsz=,%cpu=,%mem=,cputime=,comm= 2>/dev/null || true`,
);
if (!raw) return null;
const parts = raw.trim().split(/\s+/);
if (parts.length < 7) return null;
// Parse cputime "HH:MM:SS" or "MM:SS" to seconds
const timeParts = parts[5].split(":").map(Number);
let cpuTimeSec = 0;
if (timeParts.length === 3) {
cpuTimeSec = timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2];
} else if (timeParts.length === 2) {
cpuTimeSec = timeParts[0] * 60 + timeParts[1];
}
return {
pid: Number(parts[0]),
rssKb: Number(parts[1]),
vszKb: Number(parts[2]),
cpuPercent: Number(parts[3]),
memPercent: Number(parts[4]),
cpuTimeSec,
command: parts.slice(6).join(" "),
};
}
async function getPeakRssKb(
sandbox: SandboxInstance,
pid: number,
): Promise<number> {
const raw = await shellSafe(
sandbox,
`cat /proc/${pid}/status 2>/dev/null | grep VmHWM | awk '{print $2}' || echo 0`,
);
return Number(raw) || 0;
}
async function getChildPids(
sandbox: SandboxInstance,
pid: number,
): Promise<number[]> {
const raw = await shellSafe(
sandbox,
`pgrep -P ${pid} 2>/dev/null || true`,
);
if (!raw) return [];
return raw.split("\n").map(Number).filter(Boolean);
}
async function getAllDescendantPids(
sandbox: SandboxInstance,
pid: number,
): Promise<number[]> {
const all: number[] = [];
const queue = [pid];
while (queue.length > 0) {
const current = queue.shift()!;
all.push(current);
const children = await getChildPids(sandbox, current);
queue.push(...children);
}
return all;
}
async function collectDaemonMetrics(
sandbox: SandboxInstance,
session: string,
coldStartMs: number,
binarySizeBytes: number,
distributionSizeBytes: number,
): Promise<DaemonMetrics> {
// Find daemon PIDs -- the agent-browser process itself
const daemonPids = await findDaemonPids(sandbox, session);
// Also find the full process tree (daemon + Chrome children)
let allPids: number[] = [];
for (const pid of daemonPids) {
const descendants = await getAllDescendantPids(sandbox, pid);
allPids.push(...descendants);
}
allPids = [...new Set(allPids)];
// If no daemon PIDs found via pgrep, fall back to grabbing all
// agent-browser and chrome processes for metrics
if (allPids.length === 0) {
const fallback = await shellSafe(
sandbox,
`ps -eo pid,comm | grep -E 'agent-browser|chrome' | grep -v grep | awk '{print $1}' || true`,
);
if (fallback) {
allPids = fallback.split("\n").map(Number).filter(Boolean);
}
}
const daemonProcs: ProcessMetrics[] = [];
const browserProcs: ProcessMetrics[] = [];
let daemonPeakRssKb = 0;
for (const pid of allPids) {
const metrics = await collectProcessMetrics(sandbox, pid);
if (!metrics) continue;
const isBrowser = /chrome|chromium/i.test(metrics.command);
if (isBrowser) {
browserProcs.push(metrics);
} else {
daemonProcs.push(metrics);
const peak = await getPeakRssKb(sandbox, pid);
daemonPeakRssKb = Math.max(daemonPeakRssKb, peak);
}
}
const daemonRssKb = daemonProcs.reduce((sum, p) => sum + p.rssKb, 0);
const browserRssKb = browserProcs.reduce((sum, p) => sum + p.rssKb, 0);
const daemonCpuTimeSec = daemonProcs.reduce((sum, p) => sum + p.cpuTimeSec, 0);
const allProcs = [...daemonProcs, ...browserProcs];
const totalCpuTimeSec = allProcs.reduce((sum, p) => sum + p.cpuTimeSec, 0);
return {
coldStartMs,
binarySizeBytes,
distributionSizeBytes,
daemonProcesses: daemonProcs,
browserProcesses: browserProcs,
daemonRssKb,
browserRssKb,
daemonPeakRssKb,
daemonCpuTimeSec,
totalCpuTimeSec,
};
}
async function getBinarySize(
sandbox: SandboxInstance,
): Promise<number> {
// Follow symlinks to get the real binary/script size
const raw = await shellSafe(
sandbox,
`stat -L -c %s "$(readlink -f "$(which agent-browser)")" 2>/dev/null || echo 0`,
);
return Number(raw) || 0;
}
async function getDistributionSize(
sandbox: SandboxInstance,
mode: DaemonMode,
): Promise<number> {
if (mode === "node") {
// Total size of the npm package + Playwright browser
const npmPkg = await shellSafe(
sandbox,
`du -sb "$(npm root -g)/agent-browser" 2>/dev/null | awk '{print $1}' || echo 0`,
);
const pwBrowser = await shellSafe(
sandbox,
`du -sb "$HOME/.cache/ms-playwright" 2>/dev/null | awk '{print $1}' || echo 0`,
);
return (Number(npmPkg) || 0) + (Number(pwBrowser) || 0);
} else {
// Rust binary + Chrome for Testing (checks multiple possible cache paths)
const binary = await shellSafe(
sandbox,
`stat -L -c %s "$(readlink -f "$(which agent-browser)")" 2>/dev/null || echo 0`,
);
const chrome = await shellSafe(
sandbox,
[
`size=0`,
`for d in "$HOME/.cache/agent-browser" "$HOME/.cache/ms-playwright" "$HOME/.agent-browser/chrome"; do`,
` if [ -d "$d" ]; then size=$(du -sb "$d" 2>/dev/null | awk '{print $1}'); break; fi`,
`done`,
`echo $size`,
].join("; "),
);
return (Number(binary) || 0) + (Number(chrome) || 0);
}
}
function formatBytes(bytes: number): string {
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${bytes} B`;
}
function formatKb(kb: number): string {
if (kb >= 1024) return `${(kb / 1024).toFixed(1)} MB`;
return `${kb} KB`;
}
// ---------------------------------------------------------------------------
// Scenario runner
// ---------------------------------------------------------------------------
type DaemonMode = "node" | "native";
function daemonEnv(mode: DaemonMode): Record<string, string> {
return { AGENT_BROWSER_SESSION: `bench-${mode}` };
}
async function agentBrowser(
sandbox: SandboxInstance,
args: string[],
mode: DaemonMode,
): Promise<void> {
const result = await sandbox.runCommand({
cmd: "agent-browser",
args,
env: daemonEnv(mode),
});
if (result.exitCode !== 0) {
const stderr = await result.stderr();
const stdout = await result.stdout();
throw new Error(
`agent-browser ${args.join(" ")} failed (exit ${result.exitCode}): ${stderr || stdout}`,
);
}
}
async function timedAgentBrowser(
sandbox: SandboxInstance,
args: string[],
mode: DaemonMode,
): Promise<number> {
const start = Date.now();
const result = await sandbox.runCommand({
cmd: "agent-browser",
args,
env: daemonEnv(mode),
});
const elapsed = Date.now() - start;
if (result.exitCode !== 0) {
const stderr = await result.stderr();
const stdout = await result.stdout();
throw new Error(
`agent-browser ${args.join(" ")} failed (exit ${result.exitCode}): ${stderr || stdout}`,
);
}
return elapsed;
}
interface ScenarioResult {
name: string;
description: string;
stats: Stats;
error?: string;
}
async function runScenario(
sandbox: SandboxInstance,
scenario: Scenario,
mode: DaemonMode,
iterations: number,
warmup: number,
): Promise<ScenarioResult> {
try {
if (scenario.setup) {
for (const cmd of scenario.setup) {
await agentBrowser(sandbox, cmd, mode);
}
}
for (let w = 0; w < warmup; w++) {
for (const cmd of scenario.commands) {
await agentBrowser(sandbox, cmd, mode);
}
}
const samples: number[] = [];
for (let i = 0; i < iterations; i++) {
let totalMs = 0;
for (const cmd of scenario.commands) {
totalMs += await timedAgentBrowser(sandbox, cmd, mode);
}
samples.push(totalMs);
}
if (scenario.teardown) {
for (const cmd of scenario.teardown) {
await agentBrowser(sandbox, cmd, mode);
}
}
return {
name: scenario.name,
description: scenario.description,
stats: computeStats(samples),
};
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
return {
name: scenario.name,
description: scenario.description,
stats: { avgMs: -1, stddevMs: -1, minMs: -1, maxMs: -1, p50Ms: -1, samples: [] },
error: message,
};
}
}
// ---------------------------------------------------------------------------
// Benchmark phases
// ---------------------------------------------------------------------------
interface DaemonResults {
mode: DaemonMode;
label: string;
scenarios: ScenarioResult[];
metrics: DaemonMetrics;
}
async function benchmarkDaemon(
sandbox: SandboxInstance,
mode: DaemonMode,
label: string,
): Promise<DaemonResults> {
console.log(`\n--- ${label} ---`);
// Measure sizes before launch
const binarySizeBytes = await getBinarySize(sandbox);
const distributionSizeBytes = await getDistributionSize(sandbox, mode);
// Cold start: time the first launch (daemon spawn + browser launch)
const coldStartBegin = Date.now();
await agentBrowser(sandbox, ["open", "about:blank"], mode);
const coldStartMs = Date.now() - coldStartBegin;
console.log(` Cold start: ${coldStartMs}ms`);
console.log(` Binary size: ${formatBytes(binarySizeBytes)}`);
console.log(` Distribution size: ${formatBytes(distributionSizeBytes)}`);
// Run all scenarios
const results: ScenarioResult[] = [];
for (const scenario of scenarios) {
process.stdout.write(` ${scenario.name} `);
const result = await runScenario(
sandbox,
scenario,
mode,
config.iterations,
config.warmup,
);
if (result.error) {
console.log(`FAILED: ${result.error.slice(0, 120)}`);
} else {
const dots = ".".repeat(Math.max(1, 30 - scenario.name.length));
const s = result.stats;
console.log(
`${dots} ${s.avgMs}ms avg +/-${s.stddevMs}ms (p50: ${s.p50Ms}ms, min: ${s.minMs}ms, max: ${s.maxMs}ms)`,
);
}
results.push(result);
}
// Collect system metrics after scenarios (daemon is still running)
const session = `bench-${mode}`;
const metrics = await collectDaemonMetrics(
sandbox,
session,
coldStartMs,
binarySizeBytes,
distributionSizeBytes,
);
// Also grab a full process snapshot for context
const psOutput = await shellSafe(
sandbox,
`ps aux --sort=-rss | head -20`,
);
console.log(`\n Process snapshot (top by RSS):`);
for (const line of psOutput.split("\n").slice(0, 10)) {
console.log(` ${line}`);
}
console.log(`\n Daemon processes (${metrics.daemonProcesses.length}):`);
console.log(` RSS: ${formatKb(metrics.daemonRssKb)} (peak: ${formatKb(metrics.daemonPeakRssKb)})`);
console.log(` CPU time: ${metrics.daemonCpuTimeSec.toFixed(1)}s`);
for (const p of metrics.daemonProcesses) {
console.log(` PID ${p.pid}: ${p.command} (RSS: ${formatKb(p.rssKb)}, CPU: ${p.cpuPercent}%)`);
}
console.log(` Browser processes (${metrics.browserProcesses.length}):`);
console.log(` RSS: ${formatKb(metrics.browserRssKb)}`);
for (const p of metrics.browserProcesses) {
console.log(` PID ${p.pid}: ${p.command} (RSS: ${formatKb(p.rssKb)}, CPU: ${p.cpuPercent}%)`);
}
await agentBrowser(sandbox, ["close"], mode);
console.log(` Browser closed.`);
return { mode, label, scenarios: results, metrics };
}
// ---------------------------------------------------------------------------
// Install helpers
// ---------------------------------------------------------------------------
async function installChromiumDeps(sandbox: SandboxInstance) {
console.log("Installing Chromium system dependencies...");
await shell(
sandbox,
`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,
);
}
async function installNodeDaemon(sandbox: SandboxInstance) {
console.log("Installing agent-browser from npm (Node.js daemon)...");
await run(sandbox, "npm", ["install", "-g", "agent-browser"]);
await run(sandbox, "npx", ["agent-browser", "install"]);
const version = await shell(sandbox, "agent-browser --version 2>&1 || true");
console.log(` version: ${version.trim()}`);
}
async function installNativeDaemon(sandbox: SandboxInstance, branch: string) {
console.log(`\nBuilding native daemon from ${branch}...`);
console.log(" Installing build tools and Rust toolchain...");
const rustStart = Date.now();
await shell(
sandbox,
"sudo dnf install -y gcc gcc-c++ make perl-core openssl-devel 2>&1",
);
await shell(
sandbox,
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 2>&1",
);
console.log(` Rust + build tools installed (${Math.round((Date.now() - rustStart) / 1000)}s)`);
console.log(` Cloning repo (branch: ${branch})...`);
const cloneStart = Date.now();
await shell(
sandbox,
`git clone --depth 1 --branch ${branch} ${REPO_URL} /tmp/agent-browser 2>&1`,
);
console.log(` Cloned (${Math.round((Date.now() - cloneStart) / 1000)}s)`);
console.log(" Building release binary (cargo build --release)...");
const buildStart = Date.now();
await shell(
sandbox,
"source $HOME/.cargo/env && cd /tmp/agent-browser/cli && cargo build --release 2>&1",
);
console.log(` Built (${Math.round((Date.now() - buildStart) / 1000)}s)`);
const npmBinPath = (await shell(sandbox, "which agent-browser")).trim();
console.log(` Replacing ${npmBinPath} with native build...`);
await shell(
sandbox,
`sudo cp /tmp/agent-browser/cli/target/release/agent-browser ${npmBinPath}`,
);
const version = await shell(sandbox, "agent-browser --version 2>&1 || true");
console.log(` version: ${version.trim()}`);
}
// ---------------------------------------------------------------------------
// Output
// ---------------------------------------------------------------------------
function printResults(node: DaemonResults, native: DaemonResults) {
console.log("\n\n========== COMMAND LATENCY ==========\n");
const header =
"Scenario".padEnd(20) + "| Node avg +/-sd | Rust avg +/-sd | Speedup";
const sep = "-".repeat(20) + "|-----------------|-----------------|--------";
console.log(header);
console.log(sep);
for (let i = 0; i < node.scenarios.length; i++) {
const n = node.scenarios[i];
const r = native.scenarios[i];
const name = n.name.padEnd(20);
if (n.error || r.error) {
const nodeVal = n.error ? "FAILED".padEnd(15) : `${n.stats.avgMs}ms`.padEnd(15);
const rustVal = r.error ? "FAILED".padEnd(15) : `${r.stats.avgMs}ms`.padEnd(15);
console.log(`${name}| ${nodeVal} | ${rustVal} | --`);
continue;
}
const nodeVal = `${n.stats.avgMs} +/-${n.stats.stddevMs}ms`.padEnd(15);
const rustVal = `${r.stats.avgMs} +/-${r.stats.stddevMs}ms`.padEnd(15);
const speedup =
r.stats.avgMs > 0
? (n.stats.avgMs / r.stats.avgMs).toFixed(2) + "x"
: "--";
console.log(`${name}| ${nodeVal} | ${rustVal} | ${speedup.padStart(6)}`);
}
console.log("\n\n========== SYSTEM METRICS ==========\n");
const nm = node.metrics;
const rm = native.metrics;
function ratio(a: number, b: number): string {
if (b <= 0) return "--";
return (a / b).toFixed(2) + "x";
}
const metricRows: [string, string, string, string][] = [
[
"Cold start",
`${nm.coldStartMs}ms`,
`${rm.coldStartMs}ms`,
ratio(nm.coldStartMs, rm.coldStartMs),
],
[
"Binary size",
formatBytes(nm.binarySizeBytes),
formatBytes(rm.binarySizeBytes),
ratio(nm.binarySizeBytes, rm.binarySizeBytes),
],
[
"Distribution size",
formatBytes(nm.distributionSizeBytes),
formatBytes(rm.distributionSizeBytes),
ratio(nm.distributionSizeBytes, rm.distributionSizeBytes),
],
[
"Daemon RSS",
formatKb(nm.daemonRssKb),
formatKb(rm.daemonRssKb),
ratio(nm.daemonRssKb, rm.daemonRssKb),
],
[
"Daemon peak RSS",
formatKb(nm.daemonPeakRssKb),
formatKb(rm.daemonPeakRssKb),
ratio(nm.daemonPeakRssKb, rm.daemonPeakRssKb),
],
[
"Browser RSS",
formatKb(nm.browserRssKb),
formatKb(rm.browserRssKb),
ratio(nm.browserRssKb, rm.browserRssKb),
],
[
"Daemon CPU time",
`${nm.daemonCpuTimeSec.toFixed(1)}s`,
`${rm.daemonCpuTimeSec.toFixed(1)}s`,
ratio(nm.daemonCpuTimeSec, rm.daemonCpuTimeSec),
],
[
"Daemon processes",
String(nm.daemonProcesses.length),
String(rm.daemonProcesses.length),
"--",
],
[
"Browser processes",
String(nm.browserProcesses.length),
String(rm.browserProcesses.length),
"--",
],
];
const mHeader =
"Metric".padEnd(20) + "| Node".padEnd(14) + "| Rust".padEnd(14) + "| Ratio";
const mSep = "-".repeat(20) + "|" + "-".repeat(13) + "|" + "-".repeat(13) + "|--------";
console.log(mHeader);
console.log(mSep);
for (const [metric, nodeVal, rustVal, ratio] of metricRows) {
console.log(
`${metric.padEnd(20)}| ${nodeVal.padEnd(12)}| ${rustVal.padEnd(12)}| ${ratio}`,
);
}
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
async function main() {
console.log("agent-browser Daemon Benchmark (Node.js vs Rust Native)");
console.log(`Branch: ${config.branch}`);
console.log(`Iterations: ${config.iterations} (+ ${config.warmup} warmup)`);
console.log(`vCPUs: ${config.vcpus}\n`);
console.log("Creating sandbox...");
const sandbox = await Sandbox.create({
...credentials,
timeout: TIMEOUT_MS,
runtime: "node22",
networkPolicy: "allow-all" as const,
resources: { vcpus: config.vcpus },
});
console.log(`Sandbox: ${sandbox.sandboxId}`);
try {
await installChromiumDeps(sandbox);
// Phase 1: Node.js daemon (from published npm package)
await installNodeDaemon(sandbox);
const nodeResults = await benchmarkDaemon(
sandbox,
"node",
"Node.js Daemon (npm)",
);
// Phase 2: Rust native daemon (built from branch)
await installNativeDaemon(sandbox, config.branch);
const nativeResults = await benchmarkDaemon(
sandbox,
"native",
`Rust Native Daemon (${config.branch})`,
);
printResults(nodeResults, nativeResults);
if (config.json) {
const output = {
timestamp: new Date().toISOString(),
branch: config.branch,
vcpus: config.vcpus,
iterations: config.iterations,
warmup: config.warmup,
node: {
scenarios: nodeResults.scenarios.map((s) => ({
name: s.name,
description: s.description,
...s.stats,
error: s.error,
})),
metrics: nodeResults.metrics,
},
native: {
scenarios: nativeResults.scenarios.map((s) => ({
name: s.name,
description: s.description,
...s.stats,
error: s.error,
})),
metrics: nativeResults.metrics,
},
};
writeFileSync("results.json", JSON.stringify(output, null, 2));
console.log("\nResults written to results.json");
}
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
console.error(`\nFatal error: ${message}`);
process.exit(1);
} finally {
try {
await sandbox.stop();
console.log("\nSandbox stopped.");
} catch {
console.warn("Warning: failed to stop sandbox.");
}
}
}
main();
================================================
FILE: benchmarks/package.json
================================================
{
"name": "agent-browser-benchmarks",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"bench": "tsx bench.ts"
},
"dependencies": {
"@vercel/sandbox": "^1.8.0",
"tsx": "^4.19.0"
}
}
================================================
FILE: benchmarks/scenarios.ts
================================================
/**
* Benchmark scenarios for comparing Node.js daemon vs Rust native daemon.
*
* Each scenario defines CLI commands run via `sandbox.runCommand("agent-browser", args)`.
* Setup/teardown commands run once and are not timed.
* The `commands` array is timed over N iterations.
*/
export interface Scenario {
name: string;
description: string;
setup?: string[][];
commands: string[][];
teardown?: string[][];
}
const FORM_HTML = [
"<html><head><title>Bench</title></head><body>",
"<h1>Benchmark Page</h1>",
"<input id='name' type='text' placeholder='Name'>",
"<input id='email' type='email' placeholder='Email'>",
"<select id='color'><option value='red'>Red</option><option value='blue'>Blue</option></select>",
"<input id='agree' type='checkbox'>",
"<textarea id='bio' placeholder='Bio'></textarea>",
"<button id='submit'>Submit</button>",
"<p id='status'>Ready</p>",
"<a id='link' href='javascript:void(0)' onclick=\"document.getElementById('status').textContent='Clicked'\">Click me</a>",
"<ul>",
...Array.from({ length: 20 }, (_, i) => `<li class='item'>Item ${i + 1}</li>`),
"</ul>",
"</body></html>",
].join("");
const INJECT_FORM_SCRIPT = `document.open(); document.write(${JSON.stringify(FORM_HTML)}); document.close(); 'ok'`;
const SETUP_PAGE: string[][] = [
["open", "about:blank"],
["eval", INJECT_FORM_SCRIPT],
];
export const scenarios: Scenario[] = [
{
name: "navigate",
description: "Page navigation (about:blank round-trip)",
commands: [["open", "about:blank"]],
},
{
name: "snapshot",
description: "DOM snapshot (accessibility tree)",
setup: SETUP_PAGE,
commands: [["snapshot"]],
},
{
name: "screenshot",
description: "Screenshot capture",
setup: SETUP_PAGE,
commands: [["screenshot"]],
},
{
name: "evaluate",
description: "JavaScript evaluation",
setup: SETUP_PAGE,
commands: [
[
"eval",
"document.title + ' ' + document.querySelectorAll('li').length",
],
],
},
{
name: "click",
description: "Element click interaction",
setup: SETUP_PAGE,
commands: [["click", "#link"]],
},
{
name: "fill",
description: "Form field fill",
setup: SETUP_PAGE,
commands: [["fill", "#name", "Benchmark User"]],
},
{
name: "agent-loop",
description: "AI agent loop: snapshot -> click -> snapshot (typical agent cycle)",
setup: SETUP_PAGE,
commands: [["snapshot"], ["click", "#link"], ["snapshot"]],
},
{
name: "full-workflow",
description:
"Realistic workflow: navigate, inject form, snapshot, click, fill, evaluate, screenshot",
commands: [
["open", "about:blank"],
["eval", INJECT_FORM_SCRIPT],
["snapshot"],
["click", "#link"],
["fill", "#name", "Agent User"],
[
"eval",
"document.getElementById('name').value",
],
["screenshot"],
],
},
];
================================================
FILE: benchmarks/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"declaration": true
},
"include": ["*.ts"]
}
================================================
FILE: bin/agent-browser.js
================================================
#!/usr/bin/env node
/**
* Cross-platform CLI wrapper for agent-browser
*
* This wrapper enables npx support on Windows where shell scripts don't work.
* For global installs, postinstall.js patches the shims to invoke the native
* binary directly (zero overhead).
*/
import { spawn, execSync } from 'child_process';
import { existsSync, accessSync, chmodSync, constants } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { platform, arch } from 'os';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Detect if the system uses musl libc (e.g. Alpine Linux)
function isMusl() {
if (platform() !== 'linux') return false;
try {
const result = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' });
return result.toLowerCase().includes('musl');
} catch {
return existsSync('/lib/ld-musl-x86_64.so.1') || existsSync('/lib/ld-musl-aarch64.so.1');
}
}
// Map Node.js platform/arch to binary naming convention
function getBinaryName() {
const os = platform();
const cpuArch = arch();
let osKey;
switch (os) {
case 'darwin':
osKey = 'darwin';
break;
case 'linux':
osKey = isMusl() ? 'linux-musl' : 'linux';
break;
case 'win32':
osKey = 'win32';
break;
default:
return null;
}
let archKey;
switch (cpuArch) {
case 'x64':
case 'x86_64':
archKey = 'x64';
break;
case 'arm64':
case 'aarch64':
archKey = 'arm64';
break;
default:
return null;
}
const ext = os === 'win32' ? '.exe' : '';
return `agent-browser-${osKey}-${archKey}${ext}`;
}
function main() {
const binaryName = getBinaryName();
if (!binaryName) {
console.error(`Error: Unsupported platform: ${platform()}-${arch()}`);
process.exit(1);
}
const binaryPath = join(__dirname, binaryName);
if (!existsSync(binaryPath)) {
console.error(`Error: No binary found for ${platform()}-${arch()}`);
console.error(`Expected: ${binaryPath}`);
console.error('');
console.error('Run "npm run build:native" to build for your platform,');
console.error('or reinstall the package to trigger the postinstall download.');
process.exit(1);
}
// Ensure binary is executable (fixes EACCES on macOS/Linux when postinstall didn't run,
// e.g., when using bun which blocks lifecycle scripts by default)
if (platform() !== 'win32') {
try {
accessSync(binaryPath, constants.X_OK);
} catch {
// Binary exists but isn't executable - fix it
try {
chmodSync(binaryPath, 0o755);
} catch (chmodErr) {
console.error(`Error: Cannot make binary executable: ${chmodErr.message}`);
console.error('Try running: chmod +x ' + binaryPath);
process.exit(1);
}
}
}
// Spawn the native binary with inherited stdio
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: 'inherit',
windowsHide: false,
});
child.on('error', (err) => {
console.error(`Error executing binary: ${err.message}`);
process.exit(1);
});
child.on('close', (code) => {
process.exit(code ?? 0);
});
}
main();
================================================
FILE: cli/Cargo.toml
================================================
[package]
name = "agent-browser"
version = "0.21.2"
edition = "2021"
description = "Fast browser automation CLI for AI agents"
license = "Apache-2.0"
repository = "https://github.com/vercel-labs/agent-browser"
homepage = "https://agent-browser.dev"
readme = "../README.md"
keywords = ["browser", "automation", "ai", "cdp", "chrome"]
categories = ["command-line-utilities", "web-programming"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dirs = "5.0"
base64 = "0.22"
getrandom = "0.2"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util", "time", "sync", "signal", "process"] }
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
futures-util = "0.3"
url = "2"
uuid = { version = "1", features = ["v4"] }
image = "0.25"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-webpki-roots"] }
sha2 = "0.10"
aes-gcm = "0.10"
async-trait = "0.1"
socket2 = "0.6"
similar = "2"
zip = { version = "8.2.0", default-features = false, features = ["deflate"] }
time = { version = "0.3", features = ["formatting"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = ["Win32_System_Threading", "Win32_Foundation"] }
[build-dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
[profile.ci]
inherits = "release"
lto = "thin"
codegen-units = 16
================================================
FILE: cli/build.rs
================================================
use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let protocol_dir = Path::new("cdp-protocol");
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("cdp_generated.rs");
let browser_path = protocol_dir.join("browser_protocol.json");
let js_path = protocol_dir.join("js_protocol.json");
if !browser_path.exists() && !js_path.exists() {
fs::write(
&out_path,
"// No protocol JSON files found in cdp-protocol/\n",
)
.unwrap();
return;
}
let mut all_domains: Vec<Domain> = Vec::new();
for path in [&browser_path, &js_path] {
if !path.exists() {
continue;
}
println!("cargo:rerun-if-changed={}", path.display());
let content = fs::read_to_string(path).unwrap();
let protocol: ProtocolSpec = match serde_json::from_str(&content) {
Ok(p) => p,
Err(e) => {
eprintln!("cargo:warning=Failed to parse {}: {}", path.display(), e);
continue;
}
};
all_domains.extend(protocol.domains);
}
// Collect all known type IDs per domain for cross-domain resolution
let mut domain_types: std::collections::HashMap<String, HashSet<String>> =
std::collections::HashMap::new();
for domain in &all_domains {
let mut types = HashSet::new();
for td in &domain.types {
types.insert(td.id.clone());
}
domain_types.insert(domain.domain.clone(), types);
}
// Known recursive struct fields that need Box wrapping
let recursive_fields: HashSet<(&str, &str, &str)> = [
("DOM", "Node", "contentDocument"),
("DOM", "Node", "templateContent"),
("DOM", "Node", "importedDocument"),
("Accessibility", "AXNode", "sources"),
("Runtime", "StackTrace", "parent"),
]
.into_iter()
.collect();
let mut output = String::new();
output.push_str("use serde::{Deserialize, Serialize};\n\n");
for domain in &all_domains {
generate_domain(domain, &domain_types, &recursive_fields, &mut output);
}
fs::write(&out_path, &output).unwrap();
}
#[allow(dead_code)]
#[derive(serde::Deserialize)]
struct ProtocolSpec {
domains: Vec<Domain>,
}
#[allow(dead_code)]
#[derive(serde::Deserialize, Clone)]
struct Domain {
domain: String,
#[serde(default)]
types: Vec<TypeDef>,
#[serde(default)]
commands: Vec<Command>,
#[serde(default)]
events: Vec<Event>,
}
#[allow(dead_code)]
#[derive(serde::Deserialize, Clone)]
struct TypeDef {
id: String,
#[serde(rename = "type", default)]
type_kind: String,
#[serde(default)]
properties: Vec<Property>,
#[serde(rename = "enum", default)]
enum_values: Vec<String>,
#[serde(default)]
description: Option<String>,
}
#[allow(dead_code)]
#[derive(serde::Deserialize, Clone)]
struct Command {
name: String,
#[serde(default)]
parameters: Vec<Property>,
#[serde(default)]
returns: Vec<Property>,
#[serde(default)]
description: Option<String>,
}
#[allow(dead_code)]
#[derive(serde::Deserialize, Clone)]
struct Event {
name: String,
#[serde(default)]
parameters: Vec<Property>,
#[serde(default)]
description: Option<String>,
}
#[allow(dead_code)]
#[derive(serde::Deserialize, Clone)]
struct Property {
name: String,
#[serde(rename = "type", default)]
type_kind: Option<String>,
#[serde(rename = "$ref", default)]
ref_type: Option<String>,
#[serde(default)]
optional: bool,
#[serde(default)]
description: Option<String>,
#[serde(default)]
items: Option<Box<ItemType>>,
#[serde(rename = "enum", default)]
enum_values: Vec<String>,
}
#[allow(dead_code)]
#[derive(serde::Deserialize, Clone)]
struct ItemType {
#[serde(rename = "type", default)]
type_kind: Option<String>,
#[serde(rename = "$ref", default)]
ref_type: Option<String>,
}
fn to_pascal_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize = true;
for c in s.chars() {
if c == '_' || c == '-' || c == '.' {
capitalize = true;
} else if capitalize {
result.push(c.to_ascii_uppercase());
capitalize = false;
} else {
result.push(c);
}
}
result
}
fn to_snake_case(s: &str) -> String {
let mut result = String::new();
let chars: Vec<char> = s.chars().collect();
for (i, &c) in chars.iter().enumerate() {
if c.is_uppercase() && i > 0 {
// Only insert underscore at transitions from lowercase to uppercase,
// or when an uppercase sequence ends (e.g. "DOM" -> "dom", not "d_o_m")
let prev_upper = chars[i - 1].is_uppercase();
let next_lower = chars.get(i + 1).is_some_and(|n| n.is_lowercase());
if !prev_upper || next_lower {
result.push('_');
}
}
result.push(c.to_ascii_lowercase());
}
result
}
/// Resolve a $ref type reference. Cross-domain refs like "Page.FrameId" become
/// `super::cdp_page::FrameId`. Same-domain refs are used directly.
fn resolve_ref(
r: &str,
current_domain: &str,
domain_types: &std::collections::HashMap<String, HashSet<String>>,
) -> String {
let parts: Vec<&str> = r.split('.').collect();
if parts.len() == 2 {
let ref_domain = parts[0];
let ref_type = parts[1];
if ref_domain == current_domain {
to_pascal_case(ref_type)
} else {
// Check if this type actually exists in the referenced domain
if domain_types
.get(ref_domain)
.is_some_and(|t| t.contains(ref_type))
{
format!(
"super::cdp_{}::{}",
to_snake_case(ref_domain),
to_pascal_case(ref_type)
)
} else {
// Fall back to serde_json::Value for unknown cross-domain refs
"serde_json::Value".to_string()
}
}
} else {
to_pascal_case(r)
}
}
fn map_type_in_domain(
prop: &Property,
current_domain: &str,
domain_types: &std::collections::HashMap<String, HashSet<String>>,
) -> String {
if let Some(ref r) = prop.ref_type {
let type_name = resolve_ref(r, current_domain, domain_types);
if prop.optional {
format!("Option<{}>", type_name)
} else {
type_name
}
} else if let Some(ref t) = prop.type_kind {
let base = match t.as_str() {
"string" => "String".to_string(),
"integer" => "i64".to_string(),
"number" => "f64".to_string(),
"boolean" => "bool".to_string(),
"object" => "serde_json::Value".to_string(),
"any" => "serde_json::Value".to_string(),
"array" => {
if let Some(ref items) = prop.items {
let inner = if let Some(ref r) = items.ref_type {
resolve_ref(r, current_domain, domain_types)
} else {
match items.type_kind.as_deref().unwrap_or("any") {
"string" => "String".to_string(),
"integer" => "i64".to_string(),
"number" => "f64".to_string(),
"boolean" => "bool".to_string(),
_ => "serde_json::Value".to_string(),
}
};
format!("Vec<{}>", inner)
} else {
"Vec<serde_json::Value>".to_string()
}
}
_ => "serde_json::Value".to_string(),
};
if prop.optional {
format!("Option<{}>", base)
} else {
base
}
} else if prop.optional {
"Option<serde_json::Value>".to_string()
} else {
"serde_json::Value".to_string()
}
}
fn is_rust_keyword(s: &str) -> bool {
matches!(
s,
"type"
| "self"
| "Self"
| "super"
| "move"
| "ref"
| "fn"
| "mod"
| "use"
| "pub"
| "let"
| "mut"
| "const"
| "static"
| "if"
| "else"
| "for"
| "while"
| "loop"
| "match"
| "return"
| "break"
| "continue"
| "as"
| "in"
| "impl"
| "trait"
| "struct"
| "enum"
| "where"
| "async"
| "await"
| "dyn"
| "box"
| "yield"
| "override"
| "crate"
| "extern"
)
}
fn generate_domain(
domain: &Domain,
domain_types: &std::collections::HashMap<String, HashSet<String>>,
recursive_fields: &HashSet<(&str, &str, &str)>,
output: &mut String,
) {
let mod_name = to_snake_case(&domain.domain);
output.push_str(&format!(
"#[allow(dead_code, non_snake_case, non_camel_case_types, clippy::enum_variant_names)]\npub mod cdp_{} {{\n",
mod_name
));
output.push_str(" use super::*;\n\n");
for type_def in &domain.types {
if !type_def.enum_values.is_empty() {
// Deduplicate enum variants (some CDP enums have duplicated PascalCase forms)
let mut seen_variants = HashSet::new();
output.push_str(" #[derive(Debug, Clone, Serialize, Deserialize)]\n");
output.push_str(&format!(" pub enum {} {{\n", type_def.id));
for val in &type_def.enum_values {
let mut variant = to_pascal_case(val);
if variant == "Self" {
variant = "SelfValue".to_string();
}
if variant.chars().next().is_some_and(|c| c.is_ascii_digit()) {
variant = format!("V{}", variant);
}
if seen_variants.insert(variant.clone()) {
output.push_str(&format!(
" #[serde(rename = \"{}\")]\n {},\n",
val, variant
));
}
}
output.push_str(" }\n\n");
} else if type_def.type_kind == "object" && !type_def.properties.is_empty() {
output.push_str(
" #[derive(Debug, Clone, Serialize, Deserialize)]\n #[serde(rename_all = \"camelCase\")]\n",
);
output.push_str(&format!(" pub struct {} {{\n", type_def.id));
for prop in &type_def.properties {
let field_name = to_snake_case(&prop.name);
let field_name = if is_rust_keyword(&field_name) {
format!("r#{}", field_name)
} else {
field_name
};
let mut rust_type = map_type_in_domain(prop, &domain.domain, domain_types);
// Wrap recursive fields in Box
if recursive_fields.contains(&(
domain.domain.as_str(),
type_def.id.as_str(),
prop.name.as_str(),
)) {
if rust_type.starts_with("Option<") {
let inner = &rust_type[7..rust_type.len() - 1];
rust_type = format!("Option<Box<{}>>", inner);
} else {
rust_type = format!("Box<{}>", rust_type);
}
}
if prop.optional {
output
.push_str(" #[serde(skip_serializing_if = \"Option::is_none\")]\n");
}
output.push_str(&format!(" pub {}: {},\n", field_name, rust_type));
}
output.push_str(" }\n\n");
} else if type_def.type_kind == "object" && type_def.properties.is_empty() {
output.push_str(&format!(
" pub type {} = serde_json::Value;\n\n",
type_def.id
));
} else if type_def.type_kind == "array" {
output.push_str(&format!(
" pub type {} = Vec<serde_json::Value>;\n\n",
type_def.id
));
} else if type_def.type_kind == "string" && type_def.enum_values.is_empty() {
output.push_str(&format!(" pub type {} = String;\n\n", type_def.id));
} else if type_def.type_kind == "integer" {
output.push_str(&format!(" pub type {} = i64;\n\n", type_def.id));
} else if type_def.type_kind == "number" {
output.push_str(&format!(" pub type {} = f64;\n\n", type_def.id));
}
}
for cmd in &domain.commands {
let pascal_name = to_pascal_case(&cmd.name);
if !cmd.parameters.is_empty() {
output.push_str(
" #[derive(Debug, Clone, Serialize, Deserialize)]\n #[serde(rename_all = \"camelCase\")]\n",
);
output.push_str(&format!(" pub struct {}Params {{\n", pascal_name));
for param in &cmd.parameters {
let field_name = to_snake_case(¶m.name);
let field_name = if is_rust_keyword(&field_name) {
format!("r#{}", field_name)
} else {
field_name
};
let rust_type = map_type_in_domain(param, &domain.domain, domain_types);
if param.optional {
output
.push_str(" #[serde(skip_serializing_if = \"Option::is_none\")]\n");
}
output.push_str(&format!(" pub {}: {},\n", field_name, rust_type));
}
output.push_str(" }\n\n");
}
if !cmd.returns.is_empty() {
output.push_str(
" #[derive(Debug, Clone, Serialize, Deserialize)]\n #[serde(rename_all = \"camelCase\")]\n",
);
output.push_str(&format!(" pub struct {}Result {{\n", pascal_name));
for ret in &cmd.returns {
let field_name = to_snake_case(&ret.name);
let field_name = if is_rust_keyword(&field_name) {
format!("r#{}", field_name)
} else {
field_name
};
let rust_type = map_type_in_domain(ret, &domain.domain, domain_types);
if ret.optional {
output
.push_str(" #[serde(skip_serializing_if = \"Option::is_none\")]\n");
}
output.push_str(&format!(" pub {}: {},\n", field_name, rust_type));
}
output.push_str(" }\n\n");
}
}
for event in &domain.events {
if !event.parameters.is_empty() {
let pascal_name = to_pascal_case(&event.name);
output.push_str(
" #[derive(Debug, Clone, Serialize, Deserialize)]\n #[serde(rename_all = \"camelCase\")]\n",
);
output.push_str(&format!(" pub struct {}Event {{\n", pascal_name));
for param in &event.parameters {
let field_name = to_snake_case(¶m.name);
let field_name = if is_rust_keyword(&field_name) {
format!("r#{}", field_name)
} else {
field_name
};
let rust_type = map_type_in_domain(param, &domain.domain, domain_types);
if param.optional {
output
.push_str(" #[serde(skip_serializing_if = \"Option::is_none\")]\n");
}
output.push_str(&format!(" pub {}: {},\n", field_name, rust_type));
}
output.push_str(" }\n\n");
}
}
output.push_str("}\n\n");
}
================================================
FILE: cli/cdp-protocol/browser_protocol.json
================================================
{
"version": {
"major": "1",
"minor": "3"
},
"domains": [
{
"domain": "Accessibility",
"experimental": true,
"dependencies": [
"DOM"
],
"types": [
{
"id": "AXNodeId",
"description": "Unique accessibility node identifier.",
"type": "string"
},
{
"id": "AXValueType",
"description": "Enum of possible property types.",
"type": "string",
"enum": [
"boolean",
"tristate",
"booleanOrUndefined",
"idref",
"idrefList",
"integer",
"node",
"nodeList",
"number",
"string",
"computedString",
"token",
"tokenList",
"domRelation",
"role",
"internalRole",
"valueUndefined"
]
},
{
"id": "AXValueSourceType",
"description": "Enum of possible property sources.",
"type": "string",
"enum": [
"attribute",
"implicit",
"style",
"contents",
"placeholder",
"relatedElement"
]
},
{
"id": "AXValueNativeSourceType",
"description": "Enum of possible native property sources (as a subtype of a particular AXValueSourceType).",
"type": "string",
"enum": [
"description",
"figcaption",
"label",
"labelfor",
"labelwrapped",
"legend",
"rubyannotation",
"tablecaption",
"title",
"other"
]
},
{
"id": "AXValueSource",
"description": "A single source for a computed AX property.",
"type": "object",
"properties": [
{
"name": "type",
"description": "What type of source this is.",
"$ref": "AXValueSourceType"
},
{
"name": "value",
"description": "The value of this property source.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "attribute",
"description": "The name of the relevant attribute, if any.",
"optional": true,
"type": "string"
},
{
"name": "attributeValue",
"description": "The value of the relevant attribute, if any.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "superseded",
"description": "Whether this source is superseded by a higher priority source.",
"optional": true,
"type": "boolean"
},
{
"name": "nativeSource",
"description": "The native markup source for this value, e.g. a `<label>` element.",
"optional": true,
"$ref": "AXValueNativeSourceType"
},
{
"name": "nativeSourceValue",
"description": "The value, such as a node or node list, of the native source.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "invalid",
"description": "Whether the value for this property is invalid.",
"optional": true,
"type": "boolean"
},
{
"name": "invalidReason",
"description": "Reason for the value being invalid, if it is.",
"optional": true,
"type": "string"
}
]
},
{
"id": "AXRelatedNode",
"type": "object",
"properties": [
{
"name": "backendDOMNodeId",
"description": "The BackendNodeId of the related DOM node.",
"$ref": "DOM.BackendNodeId"
},
{
"name": "idref",
"description": "The IDRef value provided, if any.",
"optional": true,
"type": "string"
},
{
"name": "text",
"description": "The text alternative of this node in the current context.",
"optional": true,
"type": "string"
}
]
},
{
"id": "AXProperty",
"type": "object",
"properties": [
{
"name": "name",
"description": "The name of this property.",
"$ref": "AXPropertyName"
},
{
"name": "value",
"description": "The value of this property.",
"$ref": "AXValue"
}
]
},
{
"id": "AXValue",
"description": "A single computed AX property.",
"type": "object",
"properties": [
{
"name": "type",
"description": "The type of this value.",
"$ref": "AXValueType"
},
{
"name": "value",
"description": "The computed value of this property.",
"optional": true,
"type": "any"
},
{
"name": "relatedNodes",
"description": "One or more related nodes, if applicable.",
"optional": true,
"type": "array",
"items": {
"$ref": "AXRelatedNode"
}
},
{
"name": "sources",
"description": "The sources which contributed to the computation of this property.",
"optional": true,
"type": "array",
"items": {
"$ref": "AXValueSource"
}
}
]
},
{
"id": "AXPropertyName",
"description": "Values of AXProperty name:\n- from 'busy' to 'roledescription': states which apply to every AX node\n- from 'live' to 'root': attributes which apply to nodes in live regions\n- from 'autocomplete' to 'valuetext': attributes which apply to widgets\n- from 'checked' to 'selected': states which apply to widgets\n- from 'activedescendant' to 'owns': relationships between elements other than parent/child/sibling\n- from 'activeFullscreenElement' to 'uninteresting': reasons why this noode is hidden",
"type": "string",
"enum": [
"actions",
"busy",
"disabled",
"editable",
"focusable",
"focused",
"hidden",
"hiddenRoot",
"invalid",
"keyshortcuts",
"settable",
"roledescription",
"live",
"atomic",
"relevant",
"root",
"autocomplete",
"hasPopup",
"level",
"multiselectable",
"orientation",
"multiline",
"readonly",
"required",
"valuemin",
"valuemax",
"valuetext",
"checked",
"expanded",
"modal",
"pressed",
"selected",
"activedescendant",
"controls",
"describedby",
"details",
"errormessage",
"flowto",
"labelledby",
"owns",
"url",
"activeFullscreenElement",
"activeModalDialog",
"activeAriaModalDialog",
"ariaHiddenElement",
"ariaHiddenSubtree",
"emptyAlt",
"emptyText",
"inertElement",
"inertSubtree",
"labelContainer",
"labelFor",
"notRendered",
"notVisible",
"presentationalRole",
"probablyPresentational",
"inactiveCarouselTabContent",
"uninteresting"
]
},
{
"id": "AXNode",
"description": "A node in the accessibility tree.",
"type": "object",
"properties": [
{
"name": "nodeId",
"description": "Unique identifier for this node.",
"$ref": "AXNodeId"
},
{
"name": "ignored",
"description": "Whether this node is ignored for accessibility",
"type": "boolean"
},
{
"name": "ignoredReasons",
"description": "Collection of reasons why this node is hidden.",
"optional": true,
"type": "array",
"items": {
"$ref": "AXProperty"
}
},
{
"name": "role",
"description": "This `Node`'s role, whether explicit or implicit.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "chromeRole",
"description": "This `Node`'s Chrome raw role.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "name",
"description": "The accessible name for this `Node`.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "description",
"description": "The accessible description for this `Node`.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "value",
"description": "The value for this `Node`.",
"optional": true,
"$ref": "AXValue"
},
{
"name": "properties",
"description": "All other properties",
"optional": true,
"type": "array",
"items": {
"$ref": "AXProperty"
}
},
{
"name": "parentId",
"description": "ID for this node's parent.",
"optional": true,
"$ref": "AXNodeId"
},
{
"name": "childIds",
"description": "IDs for each of this node's child nodes.",
"optional": true,
"type": "array",
"items": {
"$ref": "AXNodeId"
}
},
{
"name": "backendDOMNodeId",
"description": "The backend ID for the associated DOM node, if any.",
"optional": true,
"$ref": "DOM.BackendNodeId"
},
{
"name": "frameId",
"description": "The frame ID for the frame associated with this nodes document.",
"optional": true,
"$ref": "Page.FrameId"
}
]
}
],
"commands": [
{
"name": "disable",
"description": "Disables the accessibility domain."
},
{
"name": "enable",
"description": "Enables the accessibility domain which causes `AXNodeId`s to remain consistent between method calls.\nThis turns on accessibility for the page, which can impact performance until accessibility is disabled."
},
{
"name": "getPartialAXTree",
"description": "Fetches the accessibility node and partial accessibility tree for this DOM node, if it exists.",
"experimental": true,
"parameters": [
{
"name": "nodeId",
"description": "Identifier of the node to get the partial accessibility tree for.",
"optional": true,
"$ref": "DOM.NodeId"
},
{
"name": "backendNodeId",
"description": "Identifier of the backend node to get the partial accessibility tree for.",
"optional": true,
"$ref": "DOM.BackendNodeId"
},
{
"name": "objectId",
"description": "JavaScript object id of the node wrapper to get the partial accessibility tree for.",
"optional": true,
"$ref": "Runtime.RemoteObjectId"
},
{
"name": "fetchRelatives",
"description": "Whether to fetch this node's ancestors, siblings and children. Defaults to true.",
"optional": true,
"type": "boolean"
}
],
"returns": [
{
"name": "nodes",
"description": "The `Accessibility.AXNode` for this DOM node, if it exists, plus its ancestors, siblings and\nchildren, if requested.",
"type": "array",
"items": {
"$ref": "AXNode"
}
}
]
},
{
"name": "getFullAXTree",
"description": "Fetches the entire accessibility tree for the root Document",
"experimental": true,
"parameters": [
{
"name": "depth",
"description": "The maximum depth at which descendants of the root node should be retrieved.\nIf omitted, the full tree is returned.",
"optional": true,
"type": "integer"
},
{
"name": "frameId",
"description": "The frame for whose document the AX tree should be retrieved.\nIf omitted, the root frame is used.",
"optional": true,
"$ref": "Page.FrameId"
}
],
"returns": [
{
"name": "nodes",
"type": "array",
"items": {
"$ref": "AXNode"
}
}
]
},
{
"name": "getRootAXNode",
"description": "Fetches the root node.\nRequires `enable()` to have been called previously.",
"experimental": true,
"parameters": [
{
"name": "frameId",
"description": "The frame in whose document the node resides.\nIf omitted, the root frame is used.",
"optional": true,
"$ref": "Page.FrameId"
}
],
"returns": [
{
"name": "node",
"$ref": "AXNode"
}
]
},
{
"name": "getAXNodeAndAncestors",
"description": "Fetches a node and all ancestors up to and including the root.\nRequires `enable()` to have been called previously.",
"experimental": true,
"parameters": [
{
"name": "nodeId",
"description": "Identifier of the node to get.",
"optional": true,
"$ref": "DOM.NodeId"
},
{
"name": "backendNodeId",
"description": "Identifier of the backend node to get.",
"optional": true,
"$ref": "DOM.BackendNodeId"
},
{
"name": "objectId",
"description": "JavaScript object id of the node wrapper to get.",
"optional": true,
"$ref": "Runtime.RemoteObjectId"
}
],
"returns": [
{
"name": "nodes",
"type": "array",
"items": {
"$ref": "AXNode"
}
}
]
},
{
"name": "getChildAXNodes",
"description": "Fetches a particular accessibility node by AXNodeId.\nRequires `enable()` to have been called previously.",
"experimental": true,
"parameters": [
{
"name": "id",
"$ref": "AXNodeId"
},
{
"name": "frameId",
"description": "The frame in whose document the node resides.\nIf omitted, the root frame is used.",
"optional": true,
"$ref": "Page.FrameId"
}
],
"returns": [
{
"name": "nodes",
"type": "array",
"items": {
"$ref": "AXNode"
}
}
]
},
{
"name": "queryAXTree",
"description": "Query a DOM node's accessibility subtree for accessible name and role.\nThis command computes the name and role for all nodes in the subtree, including those that are\nignored for accessibility, and returns those that match the specified name and role. If no DOM\nnode is specified, or the DOM node does not exist, the command returns an error. If neither\n`accessibleName` or `role` is specified, it returns all the accessibility nodes in the subtree.",
"experimental": true,
"parameters": [
{
"name": "nodeId",
"description": "Identifier of the node for the root to query.",
"optional": true,
"$ref": "DOM.NodeId"
},
{
"name": "backendNodeId",
"description": "Identifier of the backend node for the root to query.",
"optional": true,
"$ref": "DOM.BackendNodeId"
},
{
"name": "objectId",
"description": "JavaScript object id of the node wrapper for the root to query.",
"optional": true,
"$ref": "Runtime.RemoteObjectId"
},
{
"name": "accessibleName",
"description": "Find nodes with this computed name.",
"optional": true,
"type": "string"
},
{
"name": "role",
"description": "Find nodes with this computed role.",
"optional": true,
"type": "string"
}
],
"returns": [
{
"name": "nodes",
"description": "A list of `Accessibility.AXNode` matching the specified attributes,\nincluding nodes that are ignored for accessibility.",
"type": "array",
"items": {
"$ref": "AXNode"
}
}
]
}
],
"events": [
{
"name": "loadComplete",
"description": "The loadComplete event mirrors the load complete event sent by the browser to assistive\ntechnology when the web page has finished loading.",
"experimental": true,
"parameters": [
{
"name": "root",
"description": "New document root node.",
"$ref": "AXNode"
}
]
},
{
"name": "nodesUpdated",
"description": "The nodesUpdated event is sent every time a previously requested node has changed the in tree.",
"experimental": true,
"parameters": [
{
"name": "nodes",
"description": "Updated node data.",
"type": "array",
"items": {
"$ref": "AXNode"
}
}
]
}
]
},
{
"domain": "Animation",
"experimental": true,
"dependencies": [
"Runtime",
"DOM"
],
"types": [
{
"id": "Animation",
"description": "Animation instance.",
"type": "object",
"properties": [
{
"name": "id",
"description": "`Animation`'s id.",
"type": "string"
},
{
"name": "name",
"description": "`Animation`'s name.",
"type": "string"
},
{
"name": "pausedState",
"description": "`Animation`'s internal paused state.",
"type": "boolean"
},
{
"name": "playState",
"description": "`Animation`'s play state.",
"type": "string"
},
{
"name": "playbackRate",
"description": "`Animation`'s playback rate.",
"type": "number"
},
{
"name": "startTime",
"description": "`Animation`'s start time.\nMilliseconds for time based animations and\npercentage [0 - 100] for scroll driven animations\n(i.e. when viewOrScrollTimeline exists).",
"type": "number"
},
{
"name": "currentTime",
"description": "`Animation`'s current time.",
"type": "number"
},
{
"name": "type",
"description": "Animation type of `Animation`.",
"type": "string",
"enum": [
"CSSTransition",
"CSSAnimation",
"WebAnimation"
]
},
{
"name": "source",
"description": "`Animation`'s source animation node.",
"optional": true,
"$ref": "AnimationEffect"
},
{
"name": "cssId",
"description": "A unique ID for `Animation` representing the sources that triggered this CSS\nanimation/transition.",
"optional": true,
"type": "string"
},
{
"name": "viewOrScrollTimeline",
"description": "View or scroll timeline",
"optional": true,
"$ref": "ViewOrScrollTimeline"
}
]
},
{
"id": "ViewOrScrollTimeline",
"description": "Timeline instance",
"type
gitextract_aeoh496c/
├── .changeset/
│ ├── README.md
│ └── config.json
├── .claude-plugin/
│ └── marketplace.json
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .prettierrc
├── AGENTS.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── benchmarks/
│ ├── .gitignore
│ ├── README.md
│ ├── bench.ts
│ ├── package.json
│ ├── scenarios.ts
│ └── tsconfig.json
├── bin/
│ └── agent-browser.js
├── cli/
│ ├── Cargo.toml
│ ├── build.rs
│ ├── cdp-protocol/
│ │ ├── browser_protocol.json
│ │ └── js_protocol.json
│ └── src/
│ ├── color.rs
│ ├── commands.rs
│ ├── connection.rs
│ ├── flags.rs
│ ├── install.rs
│ ├── main.rs
│ ├── native/
│ │ ├── actions.rs
│ │ ├── auth.rs
│ │ ├── browser.rs
│ │ ├── cdp/
│ │ │ ├── chrome.rs
│ │ │ ├── client.rs
│ │ │ ├── discovery.rs
│ │ │ ├── lightpanda.rs
│ │ │ ├── mod.rs
│ │ │ └── types.rs
│ │ ├── cookies.rs
│ │ ├── daemon.rs
│ │ ├── diff.rs
│ │ ├── e2e_tests.rs
│ │ ├── element.rs
│ │ ├── inspect_server.rs
│ │ ├── interaction.rs
│ │ ├── mod.rs
│ │ ├── network.rs
│ │ ├── parity_tests.rs
│ │ ├── policy.rs
│ │ ├── providers.rs
│ │ ├── recording.rs
│ │ ├── screenshot.rs
│ │ ├── snapshot.rs
│ │ ├── state.rs
│ │ ├── storage.rs
│ │ ├── stream.rs
│ │ ├── test_fixtures/
│ │ │ ├── drag_probe.html
│ │ │ ├── html5_drag_probe.html
│ │ │ └── pointer_capture_probe.html
│ │ ├── tracing.rs
│ │ └── webdriver/
│ │ ├── appium.rs
│ │ ├── backend.rs
│ │ ├── client.rs
│ │ ├── ios.rs
│ │ ├── mod.rs
│ │ ├── safari.rs
│ │ └── types.rs
│ ├── output.rs
│ ├── test_utils.rs
│ ├── upgrade.rs
│ └── validation.rs
├── docker/
│ ├── Dockerfile.build
│ └── docker-compose.yml
├── docs/
│ ├── .gitignore
│ ├── components.json
│ ├── eslint.config.mjs
│ ├── mdx-components.tsx
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── src/
│ │ ├── app/
│ │ │ ├── api/
│ │ │ │ ├── docs-chat/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── docs-markdown/
│ │ │ │ │ └── route.ts
│ │ │ │ └── search/
│ │ │ │ └── route.ts
│ │ │ ├── cdp-mode/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── changelog/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── commands/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── configuration/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── diffing/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── engines/
│ │ │ │ ├── chrome/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ └── lightpanda/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── globals.css
│ │ │ ├── installation/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── ios/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── layout.tsx
│ │ │ ├── native-mode/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── next/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── og/
│ │ │ │ ├── [...slug]/
│ │ │ │ │ └── route.tsx
│ │ │ │ ├── og-image.tsx
│ │ │ │ └── route.tsx
│ │ │ ├── page.mdx
│ │ │ ├── profiler/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── providers/
│ │ │ │ ├── browser-use/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ ├── browserbase/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ ├── browserless/
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.mdx
│ │ │ │ └── kernel/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── quick-start/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── security/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── selectors/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── sessions/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── skills/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ ├── snapshots/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.mdx
│ │ │ └── streaming/
│ │ │ ├── layout.tsx
│ │ │ └── page.mdx
│ │ ├── components/
│ │ │ ├── code-block.tsx
│ │ │ ├── copy-button.tsx
│ │ │ ├── copy-page-button.tsx
│ │ │ ├── diff-demo.tsx
│ │ │ ├── docs-chat.tsx
│ │ │ ├── docs-mobile-nav.tsx
│ │ │ ├── docs-sidebar.tsx
│ │ │ ├── header.tsx
│ │ │ ├── search.tsx
│ │ │ ├── theme-provider.tsx
│ │ │ ├── theme-toggle.tsx
│ │ │ └── ui/
│ │ │ ├── dialog.tsx
│ │ │ └── sheet.tsx
│ │ └── lib/
│ │ ├── docs-navigation.ts
│ │ ├── mdx-to-markdown.ts
│ │ ├── page-metadata.ts
│ │ ├── page-titles.ts
│ │ ├── rate-limit.ts
│ │ ├── search-index.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── examples/
│ └── environments/
│ ├── .gitignore
│ ├── README.md
│ ├── app/
│ │ ├── actions/
│ │ │ └── browse.ts
│ │ ├── api/
│ │ │ └── browse/
│ │ │ └── route.ts
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components/
│ │ └── ui/
│ │ ├── alert.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── resizable.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── toggle-group.tsx
│ │ └── toggle.tsx
│ ├── components.json
│ ├── lib/
│ │ ├── agent-browser-sandbox.ts
│ │ ├── constants.ts
│ │ ├── rate-limit.ts
│ │ └── utils.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── scripts/
│ │ └── create-snapshot.ts
│ └── tsconfig.json
├── package.json
├── scripts/
│ ├── build-all-platforms.sh
│ ├── check-version-sync.js
│ ├── copy-native.js
│ ├── postinstall.js
│ └── sync-version.js
└── skills/
├── agent-browser/
│ ├── SKILL.md
│ ├── references/
│ │ ├── authentication.md
│ │ ├── commands.md
│ │ ├── profiling.md
│ │ ├── proxy-support.md
│ │ ├── session-management.md
│ │ ├── snapshot-refs.md
│ │ └── video-recording.md
│ └── templates/
│ ├── authenticated-session.sh
│ ├── capture-workflow.sh
│ └── form-automation.sh
├── dogfood/
│ ├── SKILL.md
│ ├── references/
│ │ └── issue-taxonomy.md
│ └── templates/
│ └── dogfood-report-template.md
├── electron/
│ └── SKILL.md
├── slack/
│ ├── SKILL.md
│ ├── references/
│ │ └── slack-tasks.md
│ └── templates/
│ └── slack-report-template.md
└── vercel-sandbox/
└── SKILL.md
SYMBOL INDEX (1653 symbols across 117 files)
FILE: benchmarks/bench.ts
function loadEnv (line 33) | function loadEnv() {
function parseArgs (line 72) | function parseArgs() {
constant TIMEOUT_MS (line 103) | const TIMEOUT_MS = 30 * 60 * 1000;
constant REPO_URL (line 104) | const REPO_URL = "https://github.com/vercel-labs/agent-browser.git";
constant CHROMIUM_SYSTEM_DEPS (line 106) | const CHROMIUM_SYSTEM_DEPS = [
type SandboxInstance (line 138) | type SandboxInstance = InstanceType<typeof Sandbox>;
function run (line 140) | async function run(
function shell (line 156) | async function shell(sandbox: SandboxInstance, script: string): Promise<...
function shellSafe (line 160) | async function shellSafe(sandbox: SandboxInstance, script: string): Prom...
type Stats (line 169) | interface Stats {
function computeStats (line 178) | function computeStats(samples: number[]): Stats {
type ProcessMetrics (line 198) | interface ProcessMetrics {
type DaemonMetrics (line 208) | interface DaemonMetrics {
function findDaemonPids (line 221) | async function findDaemonPids(
function collectProcessMetrics (line 245) | async function collectProcessMetrics(
function getPeakRssKb (line 278) | async function getPeakRssKb(
function getChildPids (line 289) | async function getChildPids(
function getAllDescendantPids (line 301) | async function getAllDescendantPids(
function collectDaemonMetrics (line 316) | async function collectDaemonMetrics(
function getBinarySize (line 384) | async function getBinarySize(
function getDistributionSize (line 395) | async function getDistributionSize(
function formatBytes (line 430) | function formatBytes(bytes: number): string {
function formatKb (line 436) | function formatKb(kb: number): string {
type DaemonMode (line 445) | type DaemonMode = "node" | "native";
function daemonEnv (line 447) | function daemonEnv(mode: DaemonMode): Record<string, string> {
function agentBrowser (line 451) | async function agentBrowser(
function timedAgentBrowser (line 470) | async function timedAgentBrowser(
type ScenarioResult (line 492) | interface ScenarioResult {
function runScenario (line 499) | async function runScenario(
type DaemonResults (line 554) | interface DaemonResults {
function benchmarkDaemon (line 561) | async function benchmarkDaemon(
function installChromiumDeps (line 645) | async function installChromiumDeps(sandbox: SandboxInstance) {
function installNodeDaemon (line 653) | async function installNodeDaemon(sandbox: SandboxInstance) {
function installNativeDaemon (line 661) | async function installNativeDaemon(sandbox: SandboxInstance, branch: str...
function printResults (line 707) | function printResults(node: DaemonResults, native: DaemonResults) {
function main (line 820) | async function main() {
FILE: benchmarks/scenarios.ts
type Scenario (line 9) | interface Scenario {
constant FORM_HTML (line 17) | const FORM_HTML = [
constant INJECT_FORM_SCRIPT (line 34) | const INJECT_FORM_SCRIPT = `document.open(); document.write(${JSON.strin...
constant SETUP_PAGE (line 36) | const SETUP_PAGE: string[][] = [
FILE: bin/agent-browser.js
function isMusl (line 20) | function isMusl() {
function getBinaryName (line 31) | function getBinaryName() {
function main (line 68) | function main() {
FILE: cli/build.rs
function main (line 6) | fn main() {
type ProtocolSpec (line 75) | struct ProtocolSpec {
type Domain (line 81) | struct Domain {
type TypeDef (line 93) | struct TypeDef {
type Command (line 107) | struct Command {
type Event (line 119) | struct Event {
type Property (line 129) | struct Property {
type ItemType (line 147) | struct ItemType {
function to_pascal_case (line 154) | fn to_pascal_case(s: &str) -> String {
function to_snake_case (line 170) | fn to_snake_case(s: &str) -> String {
function resolve_ref (line 190) | fn resolve_ref(
function map_type_in_domain (line 222) | fn map_type_in_domain(
function is_rust_keyword (line 274) | fn is_rust_keyword(s: &str) -> bool {
function generate_domain (line 318) | fn generate_domain(
FILE: cli/src/color.rs
function is_enabled (line 10) | pub fn is_enabled() -> bool {
function red (line 16) | pub fn red(text: &str) -> String {
function green (line 25) | pub fn green(text: &str) -> String {
function yellow (line 34) | pub fn yellow(text: &str) -> String {
function cyan (line 43) | pub fn cyan(text: &str) -> String {
function bold (line 52) | pub fn bold(text: &str) -> String {
function dim (line 61) | pub fn dim(text: &str) -> String {
function error_indicator (line 70) | pub fn error_indicator() -> &'static str {
function success_indicator (line 82) | pub fn success_indicator() -> &'static str {
function warning_indicator (line 94) | pub fn warning_indicator() -> &'static str {
function console_level_prefix (line 106) | pub fn console_level_prefix(level: &str) -> String {
function test_red_contains_ansi_codes (line 129) | fn test_red_contains_ansi_codes() {
function test_green_contains_ansi_codes (line 137) | fn test_green_contains_ansi_codes() {
function test_console_level_prefix_contains_level (line 143) | fn test_console_level_prefix_contains_level() {
function test_indicators_contain_symbols (line 152) | fn test_indicators_contain_symbols() {
FILE: cli/src/commands.rs
type ParseError (line 11) | pub enum ParseError {
method format (line 34) | pub fn format(&self) -> String {
function gen_id (line 63) | pub fn gen_id() -> String {
function parse_command (line 74) | pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, Pa...
function parse_diff (line 1344) | fn parse_diff(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_get (line 1617) | fn parse_get(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_is (line 1690) | fn parse_is(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_find (line 1726) | fn parse_find(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_mouse (line 1863) | fn parse_mouse(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_set (line 1915) | fn parse_set(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_network (line 2052) | fn parse_network(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function parse_storage (line 2115) | fn parse_storage(rest: &[&str], id: &str) -> Result<Value, ParseError> {
function default_flags (line 2170) | fn default_flags() -> Flags {
function args (line 2221) | fn args(s: &str) -> Vec<String> {
function test_cookies_get (line 2228) | fn test_cookies_get() {
function test_cookies_get_explicit (line 2234) | fn test_cookies_get_explicit() {
function test_cookies_set (line 2240) | fn test_cookies_set() {
function test_cookies_set_missing_value (line 2248) | fn test_cookies_set_missing_value() {
function test_cookies_clear (line 2254) | fn test_cookies_clear() {
function test_cookies_set_with_url (line 2260) | fn test_cookies_set_with_url() {
function test_cookies_set_with_domain (line 2273) | fn test_cookies_set_with_domain() {
function test_cookies_set_with_path (line 2286) | fn test_cookies_set_with_path() {
function test_cookies_set_with_httponly (line 2299) | fn test_cookies_set_with_httponly() {
function test_cookies_set_with_secure (line 2312) | fn test_cookies_set_with_secure() {
function test_cookies_set_with_samesite (line 2325) | fn test_cookies_set_with_samesite() {
function test_cookies_set_with_expires (line 2338) | fn test_cookies_set_with_expires() {
function test_cookies_set_with_multiple_flags (line 2351) | fn test_cookies_set_with_multiple_flags() {
function test_cookies_set_with_all_flags (line 2363) | fn test_cookies_set_with_all_flags() {
function test_cookies_set_invalid_samesite (line 2378) | fn test_cookies_set_invalid_samesite() {
function test_storage_local_get (line 2389) | fn test_storage_local_get() {
function test_storage_local_get_key (line 2397) | fn test_storage_local_get_key() {
function test_storage_local_get_implicit_key (line 2405) | fn test_storage_local_get_implicit_key() {
function test_storage_session_get (line 2413) | fn test_storage_session_get() {
function test_storage_session_get_implicit_key (line 2420) | fn test_storage_session_get_implicit_key() {
function test_storage_local_set (line 2428) | fn test_storage_local_set() {
function test_storage_session_set (line 2438) | fn test_storage_session_set() {
function test_storage_set_missing_value (line 2448) | fn test_storage_set_missing_value() {
function test_storage_local_clear (line 2454) | fn test_storage_local_clear() {
function test_storage_session_clear (line 2461) | fn test_storage_session_clear() {
function test_storage_invalid_type (line 2468) | fn test_storage_invalid_type() {
function test_navigate_with_https (line 2476) | fn test_navigate_with_https() {
function test_navigate_without_protocol (line 2483) | fn test_navigate_without_protocol() {
function test_navigate_with_headers (line 2490) | fn test_navigate_with_headers() {
function test_navigate_with_multiple_headers (line 2500) | fn test_navigate_with_multiple_headers() {
function test_navigate_without_headers_flag (line 2510) | fn test_navigate_without_headers_flag() {
function test_navigate_with_invalid_headers_json (line 2518) | fn test_navigate_with_invalid_headers_json() {
function test_navigate_chrome_extension_url (line 2530) | fn test_navigate_chrome_extension_url() {
function test_navigate_chrome_url (line 2541) | fn test_navigate_chrome_url() {
function test_set_headers_parses_json (line 2550) | fn test_set_headers_parses_json() {
function test_set_headers_with_multiple_values (line 2564) | fn test_set_headers_with_multiple_values() {
function test_set_headers_invalid_json_error (line 2576) | fn test_set_headers_invalid_json_error() {
function test_back (line 2587) | fn test_back() {
function test_forward (line 2593) | fn test_forward() {
function test_reload (line 2599) | fn test_reload() {
function test_click (line 2607) | fn test_click() {
function test_fill (line 2614) | fn test_fill() {
function test_type_command (line 2622) | fn test_type_command() {
function test_select (line 2630) | fn test_select() {
function test_select_multiple_values (line 2638) | fn test_select_multiple_values() {
function test_frame_main (line 2646) | fn test_frame_main() {
function test_tab_new (line 2654) | fn test_tab_new() {
function test_tab_new_with_url (line 2664) | fn test_tab_new_with_url() {
function test_tab_list (line 2671) | fn test_tab_list() {
function test_tab_switch (line 2677) | fn test_tab_switch() {
function test_tab_close (line 2684) | fn test_tab_close() {
function test_network_har_start (line 2692) | fn test_network_har_start() {
function test_network_har_stop_with_path (line 2698) | fn test_network_har_stop_with_path() {
function test_network_har_stop_without_path (line 2705) | fn test_network_har_stop_without_path() {
function test_network_har_requires_subcommand (line 2712) | fn test_network_har_requires_subcommand() {
function test_screenshot (line 2720) | fn test_screenshot() {
function test_screenshot_path (line 2728) | fn test_screenshot_path() {
function test_screenshot_full_page (line 2735) | fn test_screenshot_full_page() {
function test_screenshot_full_page_shorthand (line 2742) | fn test_screenshot_full_page_shorthand() {
function test_screenshot_with_ref (line 2749) | fn test_screenshot_with_ref() {
function test_screenshot_with_css_class (line 2757) | fn test_screenshot_with_css_class() {
function test_screenshot_with_css_id (line 2765) | fn test_screenshot_with_css_id() {
function test_screenshot_with_path (line 2773) | fn test_screenshot_with_path() {
function test_screenshot_with_selector_and_path (line 2781) | fn test_screenshot_with_selector_and_path() {
function test_snapshot (line 2791) | fn test_snapshot() {
function test_snapshot_interactive (line 2797) | fn test_snapshot_interactive() {
function test_snapshot_cursor (line 2804) | fn test_snapshot_cursor() {
function test_snapshot_interactive_cursor (line 2811) | fn test_snapshot_interactive_cursor() {
function test_snapshot_compact (line 2819) | fn test_snapshot_compact() {
function test_snapshot_depth (line 2826) | fn test_snapshot_depth() {
function test_wait_selector (line 2835) | fn test_wait_selector() {
function test_wait_timeout (line 2842) | fn test_wait_timeout() {
function test_wait_url (line 2849) | fn test_wait_url() {
function test_wait_load (line 2856) | fn test_wait_load() {
function test_wait_load_missing_state (line 2863) | fn test_wait_load_missing_state() {
function test_wait_fn (line 2873) | fn test_wait_fn() {
function test_wait_text (line 2880) | fn test_wait_text() {
function test_wait_text_with_timeout (line 2888) | fn test_wait_text_with_timeout() {
function test_clipboard_read_default (line 2902) | fn test_clipboard_read_default() {
function test_clipboard_read_explicit (line 2909) | fn test_clipboard_read_explicit() {
function test_clipboard_write (line 2916) | fn test_clipboard_write() {
function test_clipboard_write_multi_word (line 2924) | fn test_clipboard_write_multi_word() {
function test_clipboard_copy (line 2932) | fn test_clipboard_copy() {
function test_clipboard_paste (line 2939) | fn test_clipboard_paste() {
function test_clipboard_write_missing_text (line 2946) | fn test_clipboard_write_missing_text() {
function test_clipboard_unknown_subcommand (line 2952) | fn test_clipboard_unknown_subcommand() {
function test_record_start (line 2962) | fn test_record_start() {
function test_record_start_with_url (line 2970) | fn test_record_start_with_url() {
function test_record_start_with_url_no_protocol (line 2982) | fn test_record_start_with_url_no_protocol() {
function test_record_start_with_chrome_extension_url (line 2994) | fn test_record_start_with_chrome_extension_url() {
function test_record_start_missing_path (line 3006) | fn test_record_start_missing_path() {
function test_record_stop (line 3016) | fn test_record_stop() {
function test_record_restart (line 3022) | fn test_record_restart() {
function test_record_restart_with_url (line 3030) | fn test_record_restart_with_url() {
function test_record_restart_missing_path (line 3042) | fn test_record_restart_missing_path() {
function test_record_invalid_subcommand (line 3052) | fn test_record_invalid_subcommand() {
function test_record_missing_subcommand (line 3062) | fn test_record_missing_subcommand() {
function test_profiler_start (line 3074) | fn test_profiler_start() {
function test_profiler_start_with_categories (line 3081) | fn test_profiler_start_with_categories() {
function test_profiler_start_categories_missing_value (line 3095) | fn test_profiler_start_categories_missing_value() {
function test_profiler_stop_with_path (line 3105) | fn test_profiler_stop_with_path() {
function test_profiler_stop_no_path (line 3112) | fn test_profiler_stop_no_path() {
function test_profiler_invalid_subcommand (line 3119) | fn test_profiler_invalid_subcommand() {
function test_profiler_missing_subcommand (line 3129) | fn test_profiler_missing_subcommand() {
function test_eval_basic (line 3141) | fn test_eval_basic() {
function test_eval_base64_short_flag (line 3148) | fn test_eval_base64_short_flag() {
function test_eval_base64_long_flag (line 3156) | fn test_eval_base64_long_flag() {
function test_eval_base64_with_special_chars (line 3168) | fn test_eval_base64_with_special_chars() {
function test_eval_base64_invalid (line 3180) | fn test_eval_base64_invalid() {
function test_unknown_command (line 3189) | fn test_unknown_command() {
function test_empty_args (line 3199) | fn test_empty_args() {
function test_get_missing_subcommand (line 3211) | fn test_get_missing_subcommand() {
function test_get_unknown_subcommand (line 3220) | fn test_get_unknown_subcommand() {
function test_get_text_missing_selector (line 3230) | fn test_get_text_missing_selector() {
function test_mouse_wheel (line 3241) | fn test_mouse_wheel() {
function test_set_media (line 3249) | fn test_set_media() {
function test_set_media_reduced_motion (line 3257) | fn test_set_media_reduced_motion() {
function test_set_viewport (line 3265) | fn test_set_viewport() {
function test_set_viewport_with_scale (line 3274) | fn test_set_viewport_with_scale() {
function test_set_viewport_with_fractional_scale (line 3283) | fn test_set_viewport_with_fractional_scale() {
function test_set_viewport_missing_height (line 3292) | fn test_set_viewport_missing_height() {
function test_set_viewport_invalid_scale (line 3298) | fn test_set_viewport_invalid_scale() {
function test_find_first_no_value (line 3304) | fn test_find_first_no_value() {
function test_find_first_with_value (line 3312) | fn test_find_first_with_value() {
function test_find_nth_no_value (line 3320) | fn test_find_nth_no_value() {
function test_download (line 3330) | fn test_download() {
function test_download_with_ref (line 3338) | fn test_download_with_ref() {
function test_download_missing_path (line 3346) | fn test_download_missing_path() {
function test_download_missing_selector (line 3356) | fn test_download_missing_selector() {
function test_wait_download (line 3368) | fn test_wait_download() {
function test_wait_download_with_path (line 3375) | fn test_wait_download_with_path() {
function test_wait_download_with_timeout (line 3382) | fn test_wait_download_with_timeout() {
function test_wait_download_with_path_and_timeout (line 3390) | fn test_wait_download_with_path_and_timeout() {
function test_wait_download_short_flag (line 3402) | fn test_wait_download_short_flag() {
function test_connect_with_port (line 3411) | fn test_connect_with_port() {
function test_connect_with_ws_url (line 3419) | fn test_connect_with_ws_url() {
function test_connect_with_wss_url (line 3431) | fn test_connect_with_wss_url() {
function test_connect_with_http_url (line 3446) | fn test_connect_with_http_url() {
function test_connect_missing_argument (line 3455) | fn test_connect_missing_argument() {
function test_connect_invalid_port (line 3465) | fn test_connect_invalid_port() {
function test_connect_port_zero (line 3474) | fn test_connect_port_zero() {
function test_connect_port_out_of_range (line 3483) | fn test_connect_port_out_of_range() {
function test_connect_port_max_valid (line 3493) | fn test_connect_port_max_valid() {
function test_connect_port_min_valid (line 3500) | fn test_connect_port_min_valid() {
function test_trace_start (line 3509) | fn test_trace_start() {
function test_trace_stop_with_path (line 3515) | fn test_trace_stop_with_path() {
function test_trace_stop_without_path (line 3522) | fn test_trace_stop_without_path() {
function test_diff_snapshot_basic (line 3531) | fn test_diff_snapshot_basic() {
function test_diff_snapshot_baseline (line 3537) | fn test_diff_snapshot_baseline() {
function test_diff_snapshot_selector_compact_depth (line 3548) | fn test_diff_snapshot_selector_compact_depth() {
function test_diff_snapshot_short_flags (line 3561) | fn test_diff_snapshot_short_flags() {
function test_diff_screenshot_baseline (line 3575) | fn test_diff_screenshot_baseline() {
function test_diff_screenshot_all_options (line 3586) | fn test_diff_screenshot_all_options() {
function test_diff_screenshot_missing_baseline (line 3601) | fn test_diff_screenshot_missing_baseline() {
function test_diff_screenshot_command_full_flag (line 3611) | fn test_diff_screenshot_command_full_flag() {
function test_diff_screenshot_command_full_flag_shorthand (line 3622) | fn test_diff_screenshot_command_full_flag_shorthand() {
function test_diff_url_basic (line 3633) | fn test_diff_url_basic() {
function test_diff_url_with_screenshot_full (line 3645) | fn test_diff_url_with_screenshot_full() {
function test_diff_url_with_wait_until (line 3657) | fn test_diff_url_with_wait_until() {
function test_diff_url_command_full_flag (line 3668) | fn test_diff_url_command_full_flag() {
function test_diff_missing_subcommand (line 3678) | fn test_diff_missing_subcommand() {
function test_diff_unknown_subcommand (line 3688) | fn test_diff_unknown_subcommand() {
function test_diff_snapshot_baseline_missing_value (line 3698) | fn test_diff_snapshot_baseline_missing_value() {
function test_diff_snapshot_selector_missing_value (line 3708) | fn test_diff_snapshot_selector_missing_value() {
function test_diff_snapshot_depth_missing_value (line 3718) | fn test_diff_snapshot_depth_missing_value() {
function test_diff_screenshot_threshold_missing_value (line 3728) | fn test_diff_screenshot_threshold_missing_value() {
function test_diff_screenshot_output_missing_value (line 3741) | fn test_diff_screenshot_output_missing_value() {
function test_diff_url_wait_until_missing_value (line 3754) | fn test_diff_url_wait_until_missing_value() {
function test_diff_snapshot_unexpected_arg (line 3767) | fn test_diff_snapshot_unexpected_arg() {
function test_diff_screenshot_unexpected_arg (line 3777) | fn test_diff_screenshot_unexpected_arg() {
function test_diff_url_unexpected_arg (line 3790) | fn test_diff_url_unexpected_arg() {
function test_diff_snapshot_unknown_flag (line 3803) | fn test_diff_snapshot_unknown_flag() {
function test_diff_url_missing_urls (line 3813) | fn test_diff_url_missing_urls() {
function test_diff_url_missing_second_url (line 3823) | fn test_diff_url_missing_second_url() {
function test_diff_snapshot_depth_invalid_value (line 3833) | fn test_diff_snapshot_depth_invalid_value() {
function test_diff_screenshot_threshold_invalid_value (line 3843) | fn test_diff_screenshot_threshold_invalid_value() {
function test_diff_screenshot_threshold_out_of_range (line 3856) | fn test_diff_screenshot_threshold_out_of_range() {
function test_diff_screenshot_threshold_negative (line 3869) | fn test_diff_screenshot_threshold_negative() {
function test_diff_url_with_selector (line 3878) | fn test_diff_url_with_selector() {
function test_diff_url_with_compact_depth (line 3889) | fn test_diff_url_with_compact_depth() {
function test_diff_url_with_short_snapshot_flags (line 3901) | fn test_diff_url_with_short_snapshot_flags() {
function test_diff_url_depth_invalid_value (line 3914) | fn test_diff_url_depth_invalid_value() {
function test_diff_snapshot_depth_negative_value (line 3927) | fn test_diff_snapshot_depth_negative_value() {
function test_diff_url_depth_negative_value (line 3937) | fn test_diff_url_depth_negative_value() {
function test_diff_url_selector_missing_value (line 3950) | fn test_diff_url_selector_missing_value() {
function test_scroll_defaults (line 3965) | fn test_scroll_defaults() {
function test_scroll_direction_and_amount (line 3974) | fn test_scroll_direction_and_amount() {
function test_scroll_with_selector (line 3982) | fn test_scroll_with_selector() {
function test_scroll_with_selector_short_flag (line 3995) | fn test_scroll_with_selector_short_flag() {
function test_scroll_selector_before_positional (line 4004) | fn test_scroll_selector_before_positional() {
function test_scroll_selector_only (line 4014) | fn test_scroll_selector_only() {
function test_scroll_selector_missing_value (line 4023) | fn test_scroll_selector_missing_value() {
function test_inspect (line 4035) | fn test_inspect() {
function test_get_cdp_url (line 4041) | fn test_get_cdp_url() {
function test_batch_default (line 4049) | fn test_batch_default() {
function test_batch_with_bail (line 4056) | fn test_batch_with_bail() {
FILE: cli/src/connection.rs
type Request (line 17) | pub struct Request {
type Response (line 25) | pub struct Response {
type Connection (line 32) | pub enum Connection {
method set_read_timeout (line 67) | pub fn set_read_timeout(&self, dur: Option<Duration>) -> std::io::Resu...
method set_write_timeout (line 75) | pub fn set_write_timeout(&self, dur: Option<Duration>) -> std::io::Res...
method read (line 39) | fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
method write (line 49) | fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
method flush (line 57) | fn flush(&mut self) -> std::io::Result<()> {
function get_socket_dir (line 86) | pub fn get_socket_dir() -> PathBuf {
function get_socket_path (line 111) | fn get_socket_path(session: &str) -> PathBuf {
function get_pid_path (line 115) | fn get_pid_path(session: &str) -> PathBuf {
function cleanup_stale_files (line 120) | fn cleanup_stale_files(session: &str) {
function get_port_path (line 138) | fn get_port_path(session: &str) -> PathBuf {
function get_port_for_session (line 143) | fn get_port_for_session(session: &str) -> u16 {
function daemon_ready (line 153) | fn daemon_ready(session: &str) -> bool {
type DaemonResult (line 171) | pub struct DaemonResult {
type DaemonOptions (line 180) | pub struct DaemonOptions<'a> {
function apply_daemon_env (line 206) | fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptio...
function ensure_daemon (line 281) | pub fn ensure_daemon(session: &str, opts: &DaemonOptions) -> Result<Daem...
function connect (line 438) | fn connect(session: &str) -> Result<Connection, String> {
function send_command (line 455) | pub fn send_command(cmd: Value, session: &str) -> Result<Response, Strin...
function is_transient_error (line 492) | fn is_transient_error(error: &str) -> bool {
function send_command_once (line 510) | fn send_command_once(cmd: &Value, session: &str) -> Result<Response, Str...
function test_get_socket_dir_explicit_override (line 538) | fn test_get_socket_dir_explicit_override() {
function test_get_socket_dir_ignores_empty_socket_dir (line 548) | fn test_get_socket_dir_ignores_empty_socket_dir() {
function test_get_socket_dir_xdg_runtime (line 560) | fn test_get_socket_dir_xdg_runtime() {
function test_get_socket_dir_ignores_empty_xdg_runtime (line 573) | fn test_get_socket_dir_ignores_empty_xdg_runtime() {
function test_get_socket_dir_home_fallback (line 585) | fn test_get_socket_dir_home_fallback() {
function test_is_transient_error_eagain_macos (line 601) | fn test_is_transient_error_eagain_macos() {
function test_is_transient_error_eagain_linux (line 608) | fn test_is_transient_error_eagain_linux() {
function test_is_transient_error_would_block (line 615) | fn test_is_transient_error_would_block() {
function test_is_transient_error_resource_unavailable (line 620) | fn test_is_transient_error_resource_unavailable() {
function test_is_transient_error_eof (line 625) | fn test_is_transient_error_eof() {
function test_is_transient_error_empty_json (line 632) | fn test_is_transient_error_empty_json() {
function test_is_transient_error_connection_reset (line 639) | fn test_is_transient_error_connection_reset() {
function test_is_transient_error_broken_pipe (line 644) | fn test_is_transient_error_broken_pipe() {
function test_is_transient_error_connection_reset_macos (line 649) | fn test_is_transient_error_connection_reset_macos() {
function test_is_transient_error_connection_reset_linux (line 656) | fn test_is_transient_error_connection_reset_linux() {
function test_is_transient_error_socket_not_found (line 663) | fn test_is_transient_error_socket_not_found() {
function test_is_transient_error_connection_refused_macos (line 670) | fn test_is_transient_error_connection_refused_macos() {
function test_is_transient_error_connection_refused_linux (line 677) | fn test_is_transient_error_connection_refused_linux() {
function test_is_transient_error_connection_refused_windows (line 684) | fn test_is_transient_error_connection_refused_windows() {
function test_is_transient_error_connection_reset_windows (line 691) | fn test_is_transient_error_connection_reset_windows() {
function test_is_transient_error_non_transient (line 698) | fn test_is_transient_error_non_transient() {
function test_get_port_for_session (line 708) | fn test_get_port_for_session() {
FILE: cli/src/flags.rs
constant CONFIG_DIR (line 7) | const CONFIG_DIR: &str = ".agent-browser";
constant CONFIG_FILENAME (line 8) | const CONFIG_FILENAME: &str = "config.json";
constant PROJECT_CONFIG_FILENAME (line 9) | const PROJECT_CONFIG_FILENAME: &str = "agent-browser.json";
function parse_idle_timeout (line 13) | fn parse_idle_timeout(s: &str) -> Result<String, String> {
function parse_idle_timeout_value (line 38) | fn parse_idle_timeout_value(value: Option<String>, source: &str) -> Opti...
type Config (line 55) | pub struct Config {
method merge (line 93) | fn merge(self, other: Config) -> Config {
function read_config_file (line 139) | fn read_config_file(path: &Path) -> Option<Config> {
function env_var_is_truthy (line 163) | fn env_var_is_truthy(name: &str) -> bool {
function parse_bool_arg (line 172) | fn parse_bool_arg(args: &[String], i: usize) -> (bool, bool) {
function extract_config_path (line 192) | fn extract_config_path(args: &[String]) -> Option<Option<String>> {
function load_config (line 234) | pub fn load_config(args: &[String]) -> Result<Config, String> {
type Flags (line 266) | pub struct Flags {
function parse_flags (line 318) | pub fn parse_flags(args: &[String]) -> Flags {
function clean_args (line 707) | pub fn clean_args(args: &[String]) -> Vec<String> {
function args (line 786) | fn args(s: &str) -> Vec<String> {
function test_parse_headers_flag (line 791) | fn test_parse_headers_flag() {
function test_parse_idle_timeout_raw_ms (line 797) | fn test_parse_idle_timeout_raw_ms() {
function test_parse_idle_timeout_seconds (line 802) | fn test_parse_idle_timeout_seconds() {
function test_parse_idle_timeout_minutes (line 807) | fn test_parse_idle_timeout_minutes() {
function test_parse_idle_timeout_hours (line 812) | fn test_parse_idle_timeout_hours() {
function test_parse_idle_timeout_rejects_capital_m (line 817) | fn test_parse_idle_timeout_rejects_capital_m() {
function test_parse_idle_timeout_rejects_unknown_unit (line 822) | fn test_parse_idle_timeout_rejects_unknown_unit() {
function test_parse_headers_flag_with_spaces (line 827) | fn test_parse_headers_flag_with_spaces() {
function test_parse_no_headers_flag (line 843) | fn test_parse_no_headers_flag() {
function test_clean_args_removes_headers (line 849) | fn test_clean_args_removes_headers() {
function test_clean_args_removes_headers_at_start (line 861) | fn test_clean_args_removes_headers_at_start() {
function test_headers_with_other_flags (line 873) | fn test_headers_with_other_flags() {
function test_parse_executable_path_flag (line 892) | fn test_parse_executable_path_flag() {
function test_parse_executable_path_flag_no_value (line 900) | fn test_parse_executable_path_flag_no_value() {
function test_clean_args_removes_executable_path (line 906) | fn test_clean_args_removes_executable_path() {
function test_clean_args_removes_executable_path_with_other_flags (line 914) | fn test_clean_args_removes_executable_path_with_other_flags() {
function test_clean_args_removes_idle_timeout_before_command (line 922) | fn test_clean_args_removes_idle_timeout_before_command() {
function test_parse_idle_timeout_flag_converts_to_ms (line 928) | fn test_parse_idle_timeout_flag_converts_to_ms() {
function test_parse_flags_with_session_and_executable_path (line 934) | fn test_parse_flags_with_session_and_executable_path() {
function test_cli_executable_path_tracking (line 943) | fn test_cli_executable_path_tracking() {
function test_cli_executable_path_not_set_without_flag (line 951) | fn test_cli_executable_path_not_set_without_flag() {
function test_cli_extension_tracking (line 959) | fn test_cli_extension_tracking() {
function test_cli_profile_tracking (line 965) | fn test_cli_profile_tracking() {
function test_cli_annotate_tracking (line 971) | fn test_cli_annotate_tracking() {
function test_cli_annotate_not_set_without_flag (line 978) | fn test_cli_annotate_not_set_without_flag() {
function test_cli_download_path_tracking (line 984) | fn test_cli_download_path_tracking() {
function test_cli_download_path_not_set_without_flag (line 991) | fn test_cli_download_path_not_set_without_flag() {
function test_cli_multiple_flags_tracking (line 997) | fn test_cli_multiple_flags_tracking() {
function test_config_deserialize_full (line 1011) | fn test_config_deserialize_full() {
function test_config_deserialize_partial (line 1061) | fn test_config_deserialize_partial() {
function test_config_deserialize_empty (line 1072) | fn test_config_deserialize_empty() {
function test_config_ignores_unknown_keys (line 1080) | fn test_config_ignores_unknown_keys() {
function test_config_merge_project_overrides_user (line 1087) | fn test_config_merge_project_overrides_user() {
function test_config_merge_none_does_not_override (line 1107) | fn test_config_merge_none_does_not_override() {
function test_load_config_from_file (line 1120) | fn test_load_config_from_file() {
function test_load_config_from_file_parses_idle_timeout (line 1137) | fn test_load_config_from_file_parses_idle_timeout() {
function test_load_config_missing_file_returns_none (line 1153) | fn test_load_config_missing_file_returns_none() {
function test_load_config_malformed_json_returns_none (line 1159) | fn test_load_config_malformed_json_returns_none() {
function test_extract_config_path (line 1175) | fn test_extract_config_path() {
function test_extract_config_path_missing (line 1183) | fn test_extract_config_path_missing() {
function test_extract_config_path_no_value (line 1188) | fn test_extract_config_path_no_value() {
function test_extract_config_path_skips_flag_values (line 1193) | fn test_extract_config_path_skips_flag_values() {
function test_clean_args_removes_config (line 1198) | fn test_clean_args_removes_config() {
function test_load_config_with_config_flag (line 1204) | fn test_load_config_with_config_flag() {
function test_load_config_error_missing_config_value (line 1227) | fn test_load_config_error_missing_config_value() {
function test_load_config_error_nonexistent_file (line 1234) | fn test_load_config_error_nonexistent_file() {
function test_load_config_error_malformed_explicit (line 1241) | fn test_load_config_error_malformed_explicit() {
function test_headed_false (line 1264) | fn test_headed_false() {
function test_headed_true_explicit (line 1270) | fn test_headed_true_explicit() {
function test_headed_bare_defaults_true (line 1276) | fn test_headed_bare_defaults_true() {
function test_debug_false (line 1282) | fn test_debug_false() {
function test_json_false (line 1288) | fn test_json_false() {
function test_ignore_https_errors_false (line 1294) | fn test_ignore_https_errors_false() {
function test_allow_file_access_false (line 1300) | fn test_allow_file_access_false() {
function test_auto_connect_false (line 1307) | fn test_auto_connect_false() {
function test_clean_args_removes_bool_flag_with_value (line 1313) | fn test_clean_args_removes_bool_flag_with_value() {
function test_clean_args_removes_bare_bool_flag (line 1319) | fn test_clean_args_removes_bare_bool_flag() {
function test_config_merge_extensions_concatenated (line 1327) | fn test_config_merge_extensions_concatenated() {
function test_config_merge_extensions_user_only (line 1348) | fn test_config_merge_extensions_user_only() {
function test_config_merge_extensions_project_only (line 1359) | fn test_config_merge_extensions_project_only() {
FILE: cli/src/install.rs
constant LAST_KNOWN_GOOD_URL (line 7) | const LAST_KNOWN_GOOD_URL: &str =
function get_browsers_dir (line 10) | pub fn get_browsers_dir() -> PathBuf {
function find_installed_chrome (line 17) | pub fn find_installed_chrome() -> Option<PathBuf> {
function chrome_binary_in_dir (line 46) | fn chrome_binary_in_dir(dir: &Path) -> Option<PathBuf> {
function platform_key (line 99) | fn platform_key() -> &'static str {
function fetch_download_url (line 130) | async fn fetch_download_url() -> Result<(String, String), String> {
function download_bytes (line 171) | async fn download_bytes(url: &str) -> Result<Vec<u8>, String> {
function extract_zip (line 211) | fn extract_zip(bytes: Vec<u8>, dest: &Path) -> Result<(), String> {
function run_install (line 272) | pub fn run_install(with_deps: bool) {
function report_install_status (line 372) | fn report_install_status(status: io::Result<std::process::ExitStatus>) {
function install_linux_deps (line 392) | fn install_linux_deps() {
function which_exists (line 601) | fn which_exists(cmd: &str) -> bool {
function package_exists_apt (line 624) | fn package_exists_apt(pkg: &str) -> bool {
FILE: cli/src/main.rs
function serialize_json_value (line 32) | fn serialize_json_value(value: &serde_json::Value) -> String {
function print_json_value (line 38) | fn print_json_value(value: serde_json::Value) {
function print_json_error (line 42) | fn print_json_error(message: impl AsRef<str>) {
function print_json_error_with_type (line 49) | fn print_json_error_with_type(message: impl AsRef<str>, error_type: &str) {
function parse_proxy (line 57) | fn parse_proxy(proxy_str: &str) -> serde_json::Value {
function run_session (line 87) | fn run_session(args: &[String], session: &str, json_mode: bool) {
function main (line 168) | fn main() {
function run_batch (line 827) | fn run_batch(flags: &Flags, bail: bool) {
function test_parse_proxy_simple (line 976) | fn test_parse_proxy_simple() {
function test_parse_proxy_with_auth (line 984) | fn test_parse_proxy_with_auth() {
function test_parse_proxy_username_only (line 992) | fn test_parse_proxy_username_only() {
function test_parse_proxy_no_protocol (line 1000) | fn test_parse_proxy_no_protocol() {
function test_parse_proxy_socks5 (line 1007) | fn test_parse_proxy_socks5() {
function test_parse_proxy_socks5_with_auth (line 1014) | fn test_parse_proxy_socks5_with_auth() {
function test_parse_proxy_complex_password (line 1022) | fn test_parse_proxy_complex_password() {
function test_serialize_json_value_escapes_control_characters (line 1030) | fn test_serialize_json_value_escapes_control_characters() {
FILE: cli/src/native/actions.rs
type PendingConfirmation (line 40) | pub struct PendingConfirmation {
type HarEntry (line 46) | pub struct HarEntry {
type RouteEntry (line 74) | pub struct RouteEntry {
type RouteResponse (line 80) | pub struct RouteResponse {
type TrackedRequest (line 88) | pub struct TrackedRequest {
type FetchPausedRequest (line 97) | pub struct FetchPausedRequest {
type BackendType (line 107) | pub enum BackendType {
type MouseState (line 113) | pub struct MouseState {
type DaemonState (line 119) | pub struct DaemonState {
method new (line 160) | pub fn new() -> Self {
method reset_input_state (line 199) | fn reset_input_state(&mut self) {
method new_with_stream (line 205) | pub fn new_with_stream(
method subscribe_to_browser_events (line 215) | fn subscribe_to_browser_events(&mut self) {
method start_fetch_handler (line 224) | fn start_fetch_handler(&mut self) {
method update_stream_client (line 294) | pub async fn update_stream_client(&self) {
method start_recording_task (line 317) | async fn start_recording_task(
method stop_recording_task (line 337) | async fn stop_recording_task(&mut self) -> Result<(), String> {
method drain_cdp_events (line 341) | fn drain_cdp_events(&mut self) -> (Vec<i64>, Vec<TargetCreatedEvent>, ...
method drop (line 627) | fn drop(&mut self) {
function execute_command (line 636) | pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Va...
function connect_auto_with_fresh_tab (line 976) | async fn connect_auto_with_fresh_tab() -> Result<BrowserManager, String> {
function auto_launch (line 987) | async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
function launch_options_from_env (line 1022) | fn launch_options_from_env() -> LaunchOptions {
function daemon_state_from_env (line 1062) | async fn daemon_state_from_env(state: &mut DaemonState) {
function try_auto_restore_state (line 1079) | async fn try_auto_restore_state(state: &mut DaemonState) {
function handle_launch (line 1097) | async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<V...
function launch_ios (line 1312) | async fn launch_ios(cmd: &Value, state: &mut DaemonState) -> Result<Valu...
function launch_safari (line 1352) | async fn launch_safari(cmd: &Value, state: &mut DaemonState) -> Result<V...
function handle_navigate (line 1395) | async fn handle_navigate(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_url (line 1472) | async fn handle_url(state: &DaemonState) -> Result<Value, String> {
function handle_cdp_url (line 1484) | fn handle_cdp_url(state: &DaemonState) -> Result<Value, String> {
function handle_inspect (line 1489) | async fn handle_inspect(state: &mut DaemonState) -> Result<Value, String> {
function open_url_in_browser (line 1509) | fn open_url_in_browser(url: &str) {
function handle_title (line 1528) | async fn handle_title(state: &DaemonState) -> Result<Value, String> {
function handle_content (line 1540) | async fn handle_content(state: &DaemonState) -> Result<Value, String> {
function handle_evaluate (line 1554) | async fn handle_evaluate(cmd: &Value, state: &DaemonState) -> Result<Val...
function handle_close (line 1577) | async fn handle_close(state: &mut DaemonState) -> Result<Value, String> {
function handle_snapshot (line 1635) | async fn handle_snapshot(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_screenshot (line 1686) | async fn handle_screenshot(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_click (line 1788) | async fn handle_click(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_dblclick (line 1858) | async fn handle_dblclick(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_fill (line 1870) | async fn handle_fill(cmd: &Value, state: &mut DaemonState) -> Result<Val...
function handle_type (line 1894) | async fn handle_type(cmd: &Value, state: &mut DaemonState) -> Result<Val...
function handle_press (line 1921) | async fn handle_press(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_hover (line 1933) | async fn handle_hover(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_scroll (line 1945) | async fn handle_scroll(cmd: &Value, state: &mut DaemonState) -> Result<V...
function handle_select (line 1970) | async fn handle_select(cmd: &Value, state: &mut DaemonState) -> Result<V...
function handle_check (line 1995) | async fn handle_check(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_uncheck (line 2007) | async fn handle_uncheck(cmd: &Value, state: &mut DaemonState) -> Result<...
function handle_wait (line 2019) | async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Val...
function handle_gettext (line 2060) | async fn handle_gettext(cmd: &Value, state: &mut DaemonState) -> Result<...
function handle_getattribute (line 2074) | async fn handle_getattribute(cmd: &Value, state: &mut DaemonState) -> Re...
function handle_isvisible (line 2098) | async fn handle_isvisible(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_isenabled (line 2113) | async fn handle_isenabled(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_ischecked (line 2128) | async fn handle_ischecked(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_back (line 2143) | async fn handle_back(state: &mut DaemonState) -> Result<Value, String> {
function handle_forward (line 2161) | async fn handle_forward(state: &mut DaemonState) -> Result<Value, String> {
function handle_reload (line 2179) | async fn handle_reload(state: &mut DaemonState) -> Result<Value, String> {
function wait_for_selector (line 2223) | async fn wait_for_selector(
function wait_for_url (line 2263) | async fn wait_for_url(
function wait_for_text (line 2276) | async fn wait_for_text(
function wait_for_function (line 2289) | async fn wait_for_function(
function poll_until_true (line 2299) | async fn poll_until_true(
function handle_cookies_get (line 2342) | async fn handle_cookies_get(cmd: &Value, state: &DaemonState) -> Result<...
function handle_cookies_set (line 2362) | async fn handle_cookies_set(cmd: &Value, state: &DaemonState) -> Result<...
function handle_cookies_clear (line 2387) | async fn handle_cookies_clear(state: &DaemonState) -> Result<Value, Stri...
function handle_storage_get (line 2394) | async fn handle_storage_get(cmd: &Value, state: &DaemonState) -> Result<...
function handle_storage_set (line 2402) | async fn handle_storage_set(cmd: &Value, state: &DaemonState) -> Result<...
function handle_storage_clear (line 2418) | async fn handle_storage_clear(cmd: &Value, state: &DaemonState) -> Resul...
function handle_setcontent (line 2426) | async fn handle_setcontent(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_headers (line 2437) | async fn handle_headers(cmd: &Value, state: &DaemonState) -> Result<Valu...
function handle_offline (line 2456) | async fn handle_offline(cmd: &Value, state: &DaemonState) -> Result<Valu...
function handle_console (line 2464) | async fn handle_console(state: &DaemonState) -> Result<Value, String> {
function handle_errors (line 2468) | async fn handle_errors(state: &DaemonState) -> Result<Value, String> {
function handle_state_save (line 2472) | async fn handle_state_save(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_state_load (line 2489) | async fn handle_state_load(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_state_list (line 2501) | async fn handle_state_list() -> Result<Value, String> {
function handle_state_show (line 2505) | async fn handle_state_show(cmd: &Value) -> Result<Value, String> {
function handle_state_clear (line 2513) | async fn handle_state_clear(cmd: &Value) -> Result<Value, String> {
function handle_state_clean (line 2518) | async fn handle_state_clean(cmd: &Value) -> Result<Value, String> {
function handle_state_rename (line 2523) | async fn handle_state_rename(cmd: &Value) -> Result<Value, String> {
function handle_diff_snapshot (line 2539) | async fn handle_diff_snapshot(cmd: &Value, state: &mut DaemonState) -> R...
function handle_diff_url (line 2591) | async fn handle_diff_url(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_credentials_set (line 2634) | async fn handle_credentials_set(cmd: &Value) -> Result<Value, String> {
function handle_credentials_get (line 2651) | async fn handle_credentials_get(cmd: &Value) -> Result<Value, String> {
function handle_credentials_delete (line 2659) | async fn handle_credentials_delete(cmd: &Value) -> Result<Value, String> {
function handle_credentials_list (line 2667) | async fn handle_credentials_list() -> Result<Value, String> {
function handle_auth_show (line 2671) | async fn handle_auth_show(cmd: &Value) -> Result<Value, String> {
function handle_mouse (line 2679) | async fn handle_mouse(cmd: &Value, state: &DaemonState) -> Result<Value,...
function handle_keyboard (line 2709) | async fn handle_keyboard(cmd: &Value, state: &DaemonState) -> Result<Val...
function handle_tab_list (line 2743) | async fn handle_tab_list(state: &DaemonState) -> Result<Value, String> {
function handle_tab_new (line 2749) | async fn handle_tab_new(cmd: &Value, state: &mut DaemonState) -> Result<...
function handle_tab_switch (line 2756) | async fn handle_tab_switch(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_tab_close (line 2766) | async fn handle_tab_close(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_viewport (line 2776) | async fn handle_viewport(cmd: &Value, state: &DaemonState) -> Result<Val...
function handle_user_agent (line 2790) | async fn handle_user_agent(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_set_media (line 2800) | async fn handle_set_media(cmd: &Value, state: &DaemonState) -> Result<Va...
function handle_download (line 2814) | async fn handle_download(cmd: &Value, state: &DaemonState) -> Result<Val...
function handle_trace_start (line 2828) | async fn handle_trace_start(state: &mut DaemonState) -> Result<Value, St...
function handle_trace_stop (line 2834) | async fn handle_trace_stop(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_profiler_start (line 2841) | async fn handle_profiler_start(cmd: &Value, state: &mut DaemonState) -> ...
function handle_profiler_stop (line 2858) | async fn handle_profiler_stop(cmd: &Value, state: &mut DaemonState) -> R...
function handle_recording_start (line 2865) | async fn handle_recording_start(cmd: &Value, state: &mut DaemonState) ->...
function handle_recording_stop (line 2979) | async fn handle_recording_stop(state: &mut DaemonState) -> Result<Value,...
function handle_recording_restart (line 2984) | async fn handle_recording_restart(cmd: &Value, state: &mut DaemonState) ...
function handle_pdf (line 3003) | async fn handle_pdf(cmd: &Value, state: &DaemonState) -> Result<Value, S...
function handle_focus (line 3054) | async fn handle_focus(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_clear (line 3066) | async fn handle_clear(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_selectall (line 3078) | async fn handle_selectall(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_scrollintoview (line 3090) | async fn handle_scrollintoview(cmd: &Value, state: &mut DaemonState) -> ...
function handle_dispatch (line 3102) | async fn handle_dispatch(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_highlight (line 3128) | async fn handle_highlight(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_tap (line 3140) | async fn handle_tap(cmd: &Value, state: &mut DaemonState) -> Result<Valu...
function handle_boundingbox (line 3161) | async fn handle_boundingbox(cmd: &Value, state: &mut DaemonState) -> Res...
function handle_innertext (line 3179) | async fn handle_innertext(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_innerhtml (line 3193) | async fn handle_innerhtml(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_inputvalue (line 3207) | async fn handle_inputvalue(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_setvalue (line 3221) | async fn handle_setvalue(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_count (line 3238) | async fn handle_count(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_styles (line 3250) | async fn handle_styles(cmd: &Value, state: &mut DaemonState) -> Result<V...
function handle_bringtofront (line 3275) | async fn handle_bringtofront(state: &DaemonState) -> Result<Value, Strin...
function handle_timezone (line 3281) | async fn handle_timezone(cmd: &Value, state: &DaemonState) -> Result<Val...
function handle_locale (line 3292) | async fn handle_locale(cmd: &Value, state: &DaemonState) -> Result<Value...
function handle_geolocation (line 3302) | async fn handle_geolocation(cmd: &Value, state: &DaemonState) -> Result<...
function handle_permissions (line 3318) | async fn handle_permissions(cmd: &Value, state: &DaemonState) -> Result<...
function handle_dialog (line 3334) | async fn handle_dialog(cmd: &Value, state: &DaemonState) -> Result<Value...
function handle_upload (line 3348) | async fn handle_upload(cmd: &Value, state: &DaemonState) -> Result<Value...
function handle_addscript (line 3374) | async fn handle_addscript(cmd: &Value, state: &DaemonState) -> Result<Va...
function handle_addinitscript (line 3414) | async fn handle_addinitscript(cmd: &Value, state: &DaemonState) -> Resul...
function handle_addstyle (line 3427) | async fn handle_addstyle(cmd: &Value, state: &DaemonState) -> Result<Val...
function handle_clipboard (line 3467) | async fn handle_clipboard(cmd: &Value, state: &DaemonState) -> Result<Va...
function handle_wheel (line 3511) | async fn handle_wheel(cmd: &Value, state: &DaemonState) -> Result<Value,...
function handle_device (line 3536) | async fn handle_device(cmd: &Value, state: &DaemonState) -> Result<Value...
function handle_screencast_start (line 3572) | async fn handle_screencast_start(cmd: &Value, state: &mut DaemonState) -...
function handle_screencast_stop (line 3603) | async fn handle_screencast_stop(state: &mut DaemonState) -> Result<Value...
function handle_waitforurl (line 3625) | async fn handle_waitforurl(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_waitforloadstate (line 3639) | async fn handle_waitforloadstate(cmd: &Value, state: &DaemonState) -> Re...
function handle_waitforfunction (line 3656) | async fn handle_waitforfunction(cmd: &Value, state: &DaemonState) -> Res...
function handle_frame (line 3687) | async fn handle_frame(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_mainframe (line 3828) | async fn handle_mainframe(state: &mut DaemonState) -> Result<Value, Stri...
function execute_subaction (line 3837) | async fn execute_subaction(
function build_role_selector (line 3892) | fn build_role_selector(role: &str, name: Option<&str>, exact: bool) -> S...
function resolve_semantic_locator (line 3902) | async fn resolve_semantic_locator(
function handle_getbyrole (line 3971) | async fn handle_getbyrole(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_semantic_locator (line 4056) | async fn handle_semantic_locator(
function handle_getbytext (line 4182) | async fn handle_getbytext(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_getbylabel (line 4186) | async fn handle_getbylabel(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_getbyplaceholder (line 4190) | async fn handle_getbyplaceholder(cmd: &Value, state: &mut DaemonState) -...
function handle_getbyalttext (line 4194) | async fn handle_getbyalttext(cmd: &Value, state: &mut DaemonState) -> Re...
function handle_getbytitle (line 4198) | async fn handle_getbytitle(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_getbytestid (line 4202) | async fn handle_getbytestid(cmd: &Value, state: &mut DaemonState) -> Res...
function handle_nth (line 4206) | async fn handle_nth(cmd: &Value, state: &mut DaemonState) -> Result<Valu...
function handle_find (line 4271) | async fn handle_find(cmd: &Value, state: &DaemonState) -> Result<Value, ...
function handle_evalhandle (line 4296) | async fn handle_evalhandle(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_drag (line 4325) | async fn handle_drag(cmd: &Value, state: &mut DaemonState) -> Result<Val...
function handle_expose (line 4387) | async fn handle_expose(cmd: &Value, state: &DaemonState) -> Result<Value...
function handle_pause (line 4406) | async fn handle_pause(_state: &DaemonState) -> Result<Value, String> {
function handle_multiselect (line 4410) | async fn handle_multiselect(cmd: &Value, state: &DaemonState) -> Result<...
function handle_responsebody (line 4447) | async fn handle_responsebody(cmd: &Value, state: &DaemonState) -> Result...
function handle_waitfordownload (line 4530) | async fn handle_waitfordownload(cmd: &Value, state: &DaemonState) -> Res...
function handle_window_new (line 4564) | async fn handle_window_new(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_diff_screenshot (line 4625) | async fn handle_diff_screenshot(cmd: &Value, state: &DaemonState) -> Res...
function handle_video_start (line 4683) | async fn handle_video_start(cmd: &Value, state: &mut DaemonState) -> Res...
function handle_video_stop (line 4707) | async fn handle_video_stop(state: &mut DaemonState) -> Result<Value, Str...
function handle_har_start (line 4720) | async fn handle_har_start(state: &mut DaemonState) -> Result<Value, Stri...
function handle_har_stop (line 4732) | async fn handle_har_stop(cmd: &Value, state: &mut DaemonState) -> Result...
function har_entry_to_json (line 4766) | fn har_entry_to_json(e: HarEntry) -> Value {
function har_extract_headers (line 4860) | fn har_extract_headers(headers_val: Option<&Value>) -> Vec<(String, Stri...
function har_cdp_protocol_to_http_version (line 4873) | fn har_cdp_protocol_to_http_version(protocol: &str) -> String {
function har_parse_query_string (line 4883) | fn har_parse_query_string(url_str: &str) -> Vec<Value> {
function har_parse_request_cookies (line 4894) | fn har_parse_request_cookies(cookie_header: &str) -> Vec<Value> {
function har_compute_timings (line 4913) | fn har_compute_timings(
function har_wall_time_to_rfc3339 (line 5014) | fn har_wall_time_to_rfc3339(wall_time: f64) -> String {
function har_output_path (line 5028) | fn har_output_path(explicit_path: Option<&str>) -> String {
function get_har_dir (line 5041) | fn get_har_dir() -> PathBuf {
function unix_timestamp_millis (line 5049) | fn unix_timestamp_millis() -> u128 {
function har_browser_metadata (line 5056) | async fn har_browser_metadata(state: &DaemonState) -> Option<Value> {
function browser_metadata_from_version (line 5070) | fn browser_metadata_from_version(version: &Value) -> Option<Value> {
function resolve_fetch_paused (line 5083) | async fn resolve_fetch_paused(
function build_fetch_patterns (line 5274) | async fn build_fetch_patterns(state: &DaemonState) -> Vec<Value> {
function handle_route (line 5289) | async fn handle_route(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_unroute (line 5341) | async fn handle_unroute(cmd: &Value, state: &mut DaemonState) -> Result<...
function handle_requests (line 5378) | async fn handle_requests(cmd: &Value, state: &mut DaemonState) -> Result...
function handle_http_credentials (line 5410) | async fn handle_http_credentials(cmd: &Value, state: &DaemonState) -> Re...
function handle_auth_save (line 5438) | async fn handle_auth_save(cmd: &Value) -> Result<Value, String> {
function handle_auth_login (line 5469) | async fn handle_auth_login(cmd: &Value, state: &mut DaemonState) -> Resu...
function handle_confirm (line 5619) | async fn handle_confirm(_cmd: &Value, state: &mut DaemonState) -> Result...
function handle_deny (line 5635) | async fn handle_deny(_cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_swipe (line 5648) | async fn handle_swipe(cmd: &Value, state: &mut DaemonState) -> Result<Va...
function handle_device_list (line 5776) | async fn handle_device_list() -> Result<Value, String> {
function mouse_button_mask (line 5794) | fn mouse_button_mask(button: &str) -> i32 {
function primary_button_from_mask (line 5805) | fn primary_button_from_mask(buttons: i32) -> &'static str {
function build_mouse_event_params (line 5822) | fn build_mouse_event_params(
function handle_input_mouse (line 5870) | async fn handle_input_mouse(cmd: &Value, state: &mut DaemonState) -> Res...
function handle_input_keyboard (line 5902) | async fn handle_input_keyboard(cmd: &Value, state: &DaemonState) -> Resu...
function handle_input_touch (line 5923) | async fn handle_input_touch(cmd: &Value, state: &DaemonState) -> Result<...
function handle_keydown (line 5944) | async fn handle_keydown(cmd: &Value, state: &DaemonState) -> Result<Valu...
function handle_keyup (line 5962) | async fn handle_keyup(cmd: &Value, state: &DaemonState) -> Result<Value,...
function handle_inserttext (line 5980) | async fn handle_inserttext(cmd: &Value, state: &DaemonState) -> Result<V...
function handle_mousemove (line 5998) | async fn handle_mousemove(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_mousedown (line 6022) | async fn handle_mousedown(cmd: &Value, state: &mut DaemonState) -> Resul...
function handle_mouseup (line 6045) | async fn handle_mouseup(cmd: &Value, state: &mut DaemonState) -> Result<...
function success_response (line 6072) | fn success_response(id: &str, data: Value) -> Value {
function error_response (line 6080) | fn error_response(id: &str, error: &str) -> Value {
function test_success_response_structure (line 6095) | fn test_success_response_structure() {
function test_error_response_structure (line 6104) | fn test_error_response_structure() {
function test_daemon_state_new (line 6112) | async fn test_daemon_state_new() {
function test_mouse_event_params_preserve_position_and_buttons (line 6125) | fn test_mouse_event_params_preserve_position_and_buttons() {
function test_reset_input_state_clears_mouse_state (line 6198) | fn test_reset_input_state_clears_mouse_state() {
function test_launch_options_from_env_defaults (line 6212) | fn test_launch_options_from_env_defaults() {
function test_launch_options_from_env_headed_flag (line 6221) | fn test_launch_options_from_env_headed_flag() {
function test_har_entry_to_json_enriches_request_and_response (line 6232) | fn test_har_entry_to_json_enriches_request_and_response() {
function test_har_wall_time_to_rfc3339_epoch (line 6292) | fn test_har_wall_time_to_rfc3339_epoch() {
function test_har_wall_time_to_rfc3339_fractional_seconds (line 6299) | fn test_har_wall_time_to_rfc3339_fractional_seconds() {
function test_har_cdp_protocol_to_http_version (line 6305) | fn test_har_cdp_protocol_to_http_version() {
function test_har_parse_request_cookies (line 6314) | fn test_har_parse_request_cookies() {
function test_har_set_cookie_strips_attributes_before_equal_split (line 6326) | fn test_har_set_cookie_strips_attributes_before_equal_split() {
function test_har_compute_timings_no_cdp_timing (line 6355) | fn test_har_compute_timings_no_cdp_timing() {
function test_har_compute_timings_with_cdp_timing (line 6364) | fn test_har_compute_timings_with_cdp_timing() {
function test_handle_har_stop_without_path_uses_default_location (line 6382) | async fn test_handle_har_stop_without_path_uses_default_location() {
function test_execute_har_stop_skips_browser_auto_launch (line 6426) | async fn test_execute_har_stop_skips_browser_auto_launch() {
function test_browser_metadata_from_version_parses_product (line 6468) | fn test_browser_metadata_from_version_parses_product() {
function test_execute_unknown_command (line 6479) | async fn test_execute_unknown_command() {
function test_execute_empty_action (line 6493) | async fn test_execute_empty_action() {
function test_execute_close_without_browser (line 6502) | async fn test_execute_close_without_browser() {
function test_navigate_without_browser (line 6511) | async fn test_navigate_without_browser() {
function test_credentials_roundtrip_via_actions (line 6529) | async fn test_credentials_roundtrip_via_actions() {
function test_state_list_via_actions (line 6577) | async fn test_state_list_via_actions() {
function test_build_fetch_patterns_empty_state (line 6586) | async fn test_build_fetch_patterns_empty_state() {
function test_build_fetch_patterns_with_routes (line 6596) | async fn test_build_fetch_patterns_with_routes() {
function test_build_fetch_patterns_adds_wildcard_for_domain_filter (line 6612) | async fn test_build_fetch_patterns_adds_wildcard_for_domain_filter() {
function test_build_fetch_patterns_adds_wildcard_for_origin_headers (line 6624) | async fn test_build_fetch_patterns_adds_wildcard_for_origin_headers() {
function test_build_fetch_patterns_no_duplicate_wildcard (line 6638) | async fn test_build_fetch_patterns_no_duplicate_wildcard() {
FILE: cli/src/native/auth.rs
type AuthProfile (line 11) | pub struct AuthProfile {
type Credential (line 29) | pub type Credential = AuthProfile;
function validate_profile_name (line 31) | fn validate_profile_name(name: &str) -> Result<(), String> {
function get_auth_dir (line 45) | fn get_auth_dir() -> PathBuf {
function get_profile_path (line 53) | fn get_profile_path(name: &str) -> PathBuf {
constant ENCRYPTION_KEY_ENV (line 57) | const ENCRYPTION_KEY_ENV: &str = "AGENT_BROWSER_ENCRYPTION_KEY";
constant KEY_FILE_NAME (line 58) | const KEY_FILE_NAME: &str = ".encryption-key";
function get_agent_browser_dir (line 60) | fn get_agent_browser_dir() -> PathBuf {
function get_key_file_path (line 68) | fn get_key_file_path() -> PathBuf {
function parse_key_hex (line 72) | fn parse_key_hex(hex_str: &str) -> Option<Vec<u8>> {
function get_encryption_key (line 85) | fn get_encryption_key() -> Result<Vec<u8>, String> {
function ensure_encryption_key (line 115) | fn ensure_encryption_key() -> Result<Vec<u8>, String> {
function encrypt_profile (line 152) | fn encrypt_profile(profile: &AuthProfile) -> Result<String, String> {
type EncryptedPayload (line 186) | struct EncryptedPayload {
function decrypt_profile (line 197) | fn decrypt_profile(data: &[u8]) -> Result<AuthProfile, String> {
function save_profile (line 236) | fn save_profile(profile: &AuthProfile) -> Result<(), String> {
function load_profile (line 256) | fn load_profile(name: &str) -> Result<AuthProfile, String> {
function credentials_set (line 265) | pub fn credentials_set(
function auth_save (line 287) | pub fn auth_save(
function credentials_get (line 312) | pub fn credentials_get(name: &str) -> Result<Value, String> {
function credentials_get_full (line 322) | pub fn credentials_get_full(name: &str) -> Result<AuthProfile, String> {
function credentials_delete (line 326) | pub fn credentials_delete(name: &str) -> Result<Value, String> {
function credentials_list (line 336) | pub fn credentials_list() -> Result<Value, String> {
function auth_show (line 374) | pub fn auth_show(name: &str) -> Result<Value, String> {
function with_test_key (line 396) | fn with_test_key<F: FnOnce()>(f: F) {
function test_validate_profile_name (line 411) | fn test_validate_profile_name() {
function test_auth_profile_serialization (line 422) | fn test_auth_profile_serialization() {
function test_encrypt_decrypt_roundtrip (line 445) | fn test_encrypt_decrypt_roundtrip() {
function test_get_encryption_key_from_env (line 466) | fn test_get_encryption_key_from_env() {
function test_parse_key_hex_valid (line 475) | fn test_parse_key_hex_valid() {
function test_parse_key_hex_invalid (line 483) | fn test_parse_key_hex_invalid() {
function test_decrypt_json_payload_format (line 490) | fn test_decrypt_json_payload_format() {
function test_encrypted_output_is_json_format (line 534) | fn test_encrypted_output_is_json_format() {
FILE: cli/src/native/browser.rs
function validate_launch_options (line 20) | pub fn validate_launch_options(
function validate_lightpanda_options (line 61) | fn validate_lightpanda_options(options: &LaunchOptions) -> Result<(), St...
function is_internal_chrome_target (line 92) | fn is_internal_chrome_target(url: &str) -> bool {
function to_ai_friendly_error (line 99) | pub fn to_ai_friendly_error(error: &str) -> String {
type PageInfo (line 124) | pub struct PageInfo {
type WaitUntil (line 133) | pub enum WaitUntil {
method from_str (line 140) | pub fn from_str(s: &str) -> Self {
type BrowserProcess (line 149) | pub enum BrowserProcess {
method kill (line 155) | pub fn kill(&mut self) {
method wait_or_kill (line 162) | pub fn wait_or_kill(&mut self, timeout: std::time::Duration) {
type BrowserManager (line 170) | pub struct BrowserManager {
method launch (line 184) | pub async fn launch(options: LaunchOptions, engine: Option<&str>) -> R...
method connect_cdp (line 299) | pub async fn connect_cdp(url: &str) -> Result<Self, String> {
method connect_auto (line 315) | pub async fn connect_auto() -> Result<Self, String> {
method discover_and_attach_targets (line 320) | async fn discover_and_attach_targets(&mut self) -> Result<(), String> {
method enable_domains_pub (line 409) | pub async fn enable_domains_pub(&self, session_id: &str) -> Result<(),...
method enable_domains (line 413) | async fn enable_domains(&self, session_id: &str) -> Result<(), String> {
method active_session_id (line 426) | pub fn active_session_id(&self) -> Result<&str, String> {
method navigate (line 433) | pub async fn navigate(&mut self, url: &str, wait_until: WaitUntil) -> ...
method wait_for_lifecycle (line 467) | async fn wait_for_lifecycle(
method wait_for_network_idle (line 501) | async fn wait_for_network_idle(
method get_url (line 510) | pub async fn get_url(&self) -> Result<String, String> {
method get_title (line 515) | pub async fn get_title(&self) -> Result<String, String> {
method get_content (line 520) | pub async fn get_content(&self) -> Result<String, String> {
method evaluate (line 527) | pub async fn evaluate(&self, script: &str, _args: Option<Value>) -> Re...
method evaluate_simple (line 555) | async fn evaluate_simple(&self, expression: &str) -> Result<Value, Str...
method wait_for_lifecycle_external (line 559) | pub async fn wait_for_lifecycle_external(
method close (line 569) | pub async fn close(&mut self) -> Result<(), String> {
method has_pages (line 591) | pub fn has_pages(&self) -> bool {
method is_connection_alive (line 597) | pub async fn is_connection_alive(&self) -> bool {
method get_cdp_url (line 612) | pub fn get_cdp_url(&self) -> &str {
method chrome_host_port (line 617) | pub fn chrome_host_port(&self) -> &str {
method active_target_id (line 626) | pub fn active_target_id(&self) -> Result<&str, String> {
method is_cdp_connection (line 634) | pub fn is_cdp_connection(&self) -> bool {
method ensure_page (line 640) | pub async fn ensure_page(&mut self) -> Result<(), String> {
method update_active_page_if_needed (line 687) | pub fn update_active_page_if_needed(&mut self) {
method tab_list (line 697) | pub fn tab_list(&self) -> Vec<Value> {
method tab_new (line 713) | pub async fn tab_new(&mut self, url: Option<&str>) -> Result<Value, St...
method tab_switch (line 754) | pub async fn tab_switch(&mut self, index: usize) -> Result<Value, Stri...
method tab_close (line 784) | pub async fn tab_close(&mut self, index: Option<usize>) -> Result<Valu...
method set_viewport (line 821) | pub async fn set_viewport(
method set_user_agent (line 844) | pub async fn set_user_agent(&self, user_agent: &str) -> Result<(), Str...
method set_emulated_media (line 856) | pub async fn set_emulated_media(
method bring_to_front (line 879) | pub async fn bring_to_front(&self) -> Result<(), String> {
method set_timezone (line 887) | pub async fn set_timezone(&self, timezone_id: &str) -> Result<(), Stri...
method set_locale (line 899) | pub async fn set_locale(&self, locale: &str) -> Result<(), String> {
method set_geolocation (line 911) | pub async fn set_geolocation(
method grant_permissions (line 932) | pub async fn grant_permissions(&self, permissions: &[String]) -> Resul...
method handle_dialog (line 943) | pub async fn handle_dialog(
method upload_files (line 963) | pub async fn upload_files(&self, selector: &str, files: &[String]) -> ...
method add_script_to_evaluate (line 1033) | pub async fn add_script_to_evaluate(&self, source: &str) -> Result<Str...
method add_page (line 1050) | pub fn add_page(&mut self, page: PageInfo) {
method remove_page_by_target_id (line 1056) | pub fn remove_page_by_target_id(&mut self, target_id: &str) {
method has_target (line 1063) | pub fn has_target(&self, target_id: &str) -> bool {
method page_count (line 1067) | pub fn page_count(&self) -> usize {
method pages_list (line 1071) | pub fn pages_list(&self) -> Vec<PageInfo> {
method set_download_behavior (line 1075) | pub async fn set_download_behavior(&self, download_path: &str) -> Resu...
constant LIGHTPANDA_CDP_CONNECT_TIMEOUT (line 179) | const LIGHTPANDA_CDP_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
constant LIGHTPANDA_CDP_CONNECT_POLL_INTERVAL (line 180) | const LIGHTPANDA_CDP_CONNECT_POLL_INTERVAL: Duration = Duration::from_mi...
constant LIGHTPANDA_TARGET_INIT_TIMEOUT (line 181) | const LIGHTPANDA_TARGET_INIT_TIMEOUT: Duration = Duration::from_secs(10);
function poll_network_idle (line 1097) | async fn poll_network_idle(
function connect_cdp_with_retry (line 1168) | async fn connect_cdp_with_retry(
function initialize_lightpanda_manager (line 1189) | async fn initialize_lightpanda_manager(
function discover_and_attach_lightpanda_targets (line 1238) | async fn discover_and_attach_lightpanda_targets(
function remaining_until (line 1250) | fn remaining_until(deadline: Instant) -> Option<Duration> {
function run_with_lightpanda_deadline (line 1254) | async fn run_with_lightpanda_deadline<F, T>(
function lightpanda_target_init_timeout (line 1271) | fn lightpanda_target_init_timeout(last_error: Option<&str>) -> String {
function resolve_cdp_url (line 1282) | async fn resolve_cdp_url(input: &str) -> Result<String, String> {
function test_validate_launch_options_extensions_and_cdp (line 1313) | fn test_validate_launch_options_extensions_and_cdp() {
function test_validate_launch_options_profile_and_cdp (line 1319) | fn test_validate_launch_options_profile_and_cdp() {
function test_validate_launch_options_storage_state_and_profile (line 1324) | fn test_validate_launch_options_storage_state_and_profile() {
function test_validate_launch_options_storage_state_and_extensions (line 1337) | fn test_validate_launch_options_storage_state_and_extensions() {
function test_validate_launch_options_allow_file_access_firefox (line 1346) | fn test_validate_launch_options_allow_file_access_firefox() {
function test_validate_launch_options_valid (line 1354) | fn test_validate_launch_options_valid() {
function test_to_ai_friendly_error_strict_mode (line 1359) | fn test_to_ai_friendly_error_strict_mode() {
function test_to_ai_friendly_error_not_visible (line 1367) | fn test_to_ai_friendly_error_not_visible() {
function test_to_ai_friendly_error_intercept (line 1375) | fn test_to_ai_friendly_error_intercept() {
function test_to_ai_friendly_error_timeout (line 1383) | fn test_to_ai_friendly_error_timeout() {
function test_to_ai_friendly_error_not_found (line 1391) | fn test_to_ai_friendly_error_not_found() {
function test_to_ai_friendly_error_unknown (line 1399) | fn test_to_ai_friendly_error_unknown() {
function test_to_ai_friendly_error_ignores_non_element_not_found (line 1406) | fn test_to_ai_friendly_error_ignores_non_element_not_found() {
function test_to_ai_friendly_error_catches_no_element (line 1412) | fn test_to_ai_friendly_error_catches_no_element() {
function test_remaining_until_returns_none_for_past_deadline (line 1419) | fn test_remaining_until_returns_none_for_past_deadline() {
function test_run_with_lightpanda_deadline_enforces_timeout (line 1427) | async fn test_run_with_lightpanda_deadline_enforces_timeout() {
function test_run_with_lightpanda_deadline_returns_operation_error (line 1451) | async fn test_run_with_lightpanda_deadline_returns_operation_error() {
function test_lightpanda_target_init_timeout_includes_last_error (line 1465) | fn test_lightpanda_target_init_timeout_includes_last_error() {
function test_is_internal_chrome_target (line 1474) | fn test_is_internal_chrome_target() {
function cdp_event (line 1494) | fn cdp_event(method: &str, session_id: &str, params: Value) -> CdpEvent {
function test_network_idle_no_events_does_not_return_instantly (line 1506) | async fn test_network_idle_no_events_does_not_return_instantly() {
function test_network_idle_after_requests_complete (line 1532) | async fn test_network_idle_after_requests_complete() {
function test_network_idle_resets_on_new_request (line 1571) | async fn test_network_idle_resets_on_new_request() {
function test_network_idle_overall_timeout (line 1625) | async fn test_network_idle_overall_timeout() {
FILE: cli/src/native/cdp/chrome.rs
type ChromeProcess (line 8) | pub struct ChromeProcess {
method kill (line 15) | pub fn kill(&mut self) {
method wait_or_kill (line 23) | pub fn wait_or_kill(&mut self, timeout: Duration) {
method drop (line 40) | fn drop(&mut self) {
type LaunchOptions (line 65) | pub struct LaunchOptions {
method default (line 82) | fn default() -> Self {
type ChromeArgs (line 101) | struct ChromeArgs {
function build_chrome_args (line 107) | fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, Stri...
function launch_chrome (line 204) | pub fn launch_chrome(options: &LaunchOptions) -> Result<ChromeProcess, S...
function try_launch_chrome (line 238) | fn try_launch_chrome(chrome_path: &Path, options: &LaunchOptions) -> Res...
function wait_for_devtools_active_port (line 303) | fn wait_for_devtools_active_port(
function wait_for_ws_url_until (line 335) | fn wait_for_ws_url_until(
function chrome_launch_error (line 362) | fn chrome_launch_error(message: &str, stderr_lines: &[String]) -> String {
function find_chrome (line 420) | pub fn find_chrome() -> Option<PathBuf> {
function read_devtools_active_port (line 498) | pub fn read_devtools_active_port(user_data_dir: &Path) -> Option<(u16, S...
function auto_connect_cdp (line 511) | pub async fn auto_connect_cdp() -> Result<String, String> {
function is_port_reachable (line 544) | fn is_port_reachable(port: u16) -> bool {
function get_chrome_user_data_dirs (line 550) | fn get_chrome_user_data_dirs() -> Vec<PathBuf> {
function should_disable_sandbox (line 603) | fn should_disable_sandbox(existing_args: &[String]) -> bool {
function should_disable_dev_shm (line 645) | fn should_disable_dev_shm(existing_args: &[String]) -> bool {
function find_playwright_chromium (line 674) | fn find_playwright_chromium() -> Option<PathBuf> {
function build_playwright_binary_path (line 720) | fn build_playwright_binary_path(chromium_dir: &Path) -> PathBuf {
function build_playwright_binary_path (line 725) | fn build_playwright_binary_path(chromium_dir: &Path) -> PathBuf {
function build_playwright_binary_path (line 730) | fn build_playwright_binary_path(chromium_dir: &Path) -> PathBuf {
function expand_tilde (line 734) | fn expand_tilde(path: &str) -> String {
function spawn_noop_child (line 752) | fn spawn_noop_child() -> Child {
function spawn_noop_child (line 763) | fn spawn_noop_child() -> Child {
function test_find_chrome_returns_some_on_host (line 774) | fn test_find_chrome_returns_some_on_host() {
function test_expand_tilde (line 786) | fn test_expand_tilde() {
function test_expand_tilde_no_tilde (line 793) | fn test_expand_tilde_no_tilde() {
function test_read_devtools_active_port_missing (line 798) | fn test_read_devtools_active_port_missing() {
function test_should_disable_sandbox_skips_if_already_set (line 804) | fn test_should_disable_sandbox_skips_if_already_set() {
function test_chrome_launch_error_no_stderr (line 810) | fn test_chrome_launch_error_no_stderr() {
function test_chrome_launch_error_with_sandbox_hint (line 818) | fn test_chrome_launch_error_with_sandbox_hint() {
function test_chrome_launch_error_generic (line 830) | fn test_chrome_launch_error_generic() {
function test_find_playwright_chromium_nonexistent (line 837) | fn test_find_playwright_chromium_nonexistent() {
function test_build_args_headless_includes_headless_flag (line 845) | fn test_build_args_headless_includes_headless_flag() {
function test_build_args_headed_no_headless_flag (line 865) | fn test_build_args_headed_no_headless_flag() {
function test_build_args_temp_user_data_dir_created (line 885) | fn test_build_args_temp_user_data_dir_created() {
function test_build_args_profile_no_temp_dir (line 898) | fn test_build_args_profile_no_temp_dir() {
function test_build_args_custom_window_size_not_overridden (line 912) | fn test_build_args_custom_window_size_not_overridden() {
function test_build_args_start_maximized_suppresses_default_window_size (line 927) | fn test_build_args_start_maximized_suppresses_default_window_size() {
function test_build_args_disables_translate (line 942) | fn test_build_args_disables_translate() {
function test_build_args_headless_with_extensions_skips_headless_flag (line 955) | fn test_build_args_headless_with_extensions_skips_headless_flag() {
function test_build_args_headed_with_extensions_no_headless_flag (line 980) | fn test_build_args_headed_with_extensions_no_headless_flag() {
function test_chrome_process_drop_cleans_temp_dir (line 1001) | fn test_chrome_process_drop_cleans_temp_dir() {
FILE: cli/src/native/cdp/client.rs
type PendingMap (line 13) | type PendingMap = Arc<Mutex<HashMap<u64, oneshot::Sender<CdpMessage>>>>;
constant WS_KEEPALIVE_INTERVAL_SECS (line 17) | const WS_KEEPALIVE_INTERVAL_SECS: u64 = 30;
type RawCdpMessage (line 22) | pub struct RawCdpMessage {
type CdpClient (line 27) | pub struct CdpClient {
method connect (line 47) | pub async fn connect(url: &str) -> Result<Self, String> {
method send_command (line 174) | pub async fn send_command(
method subscribe (line 223) | pub fn subscribe(&self) -> broadcast::Receiver<CdpEvent> {
method subscribe_raw (line 229) | pub fn subscribe_raw(&self) -> broadcast::Receiver<RawCdpMessage> {
method inspect_handle (line 235) | pub fn inspect_handle(&self) -> InspectProxyHandle {
method send_command_typed (line 242) | pub async fn send_command_typed<P: serde::Serialize, R: serde::de::Des...
method send_command_no_params (line 257) | pub async fn send_command_no_params(
method send_raw (line 267) | pub async fn send_raw(&self, json: String) -> Result<(), String> {
type WsTx (line 276) | type WsTx = Arc<
type InspectProxyHandle (line 289) | pub struct InspectProxyHandle {
method send_raw (line 295) | pub async fn send_raw(&self, json: String) -> Result<(), String> {
method subscribe_raw (line 303) | pub fn subscribe_raw(&self) -> broadcast::Receiver<RawCdpMessage> {
function enable_tcp_keepalive (line 311) | fn enable_tcp_keepalive(stream: &tokio_tungstenite::MaybeTlsStream<tokio...
FILE: cli/src/native/cdp/discovery.rs
constant DEFAULT_DISCOVERY_TIMEOUT (line 9) | const DEFAULT_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(2);
function discover_cdp_url (line 16) | pub async fn discover_cdp_url(host: &str, port: u16) -> Result<String, S...
function discover_cdp_url_with_timeout (line 21) | pub async fn discover_cdp_url_with_timeout(
function bracket_ipv6 (line 59) | fn bracket_ipv6(host: &str) -> String {
function fetch_cdp_info (line 68) | async fn fetch_cdp_info(
function rewrite_ws_host (line 87) | fn rewrite_ws_host(ws_url: &str, host: &str, port: u16) -> String {
function fetch_cdp_list (line 99) | async fn fetch_cdp_list(host: &str, port: u16, timeout: Duration) -> Res...
function discover_cdp_ws (line 132) | async fn discover_cdp_ws(host: &str, port: u16, timeout: Duration) -> Re...
function reqwest_get_string (line 173) | async fn reqwest_get_string(url: &str) -> Result<String, String> {
constant HTTP_404 (line 184) | const HTTP_404: &str =
function http_200 (line 187) | fn http_200(body: &str) -> String {
function accept_http (line 194) | async fn accept_http(listener: &TcpListener, response: &str) {
function discovers_ws_url_from_json_version (line 202) | async fn discovers_ws_url_from_json_version() {
function returns_error_when_version_returns_invalid_json (line 219) | async fn returns_error_when_version_returns_invalid_json() {
function falls_back_to_json_list_on_version_404 (line 233) | async fn falls_back_to_json_list_on_version_404() {
function falls_back_to_ws_when_http_returns_404 (line 251) | async fn falls_back_to_ws_when_http_returns_404() {
function rewrite_ws_host_replaces_host_and_port (line 280) | fn rewrite_ws_host_replaces_host_and_port() {
function rewrite_ws_host_handles_ipv6 (line 287) | fn rewrite_ws_host_handles_ipv6() {
FILE: cli/src/native/cdp/lightpanda.rs
constant LIGHTPANDA_STARTUP_TIMEOUT (line 11) | const LIGHTPANDA_STARTUP_TIMEOUT: Duration = Duration::from_secs(10);
constant LIGHTPANDA_POLL_INTERVAL (line 12) | const LIGHTPANDA_POLL_INTERVAL: Duration = Duration::from_millis(100);
constant LIGHTPANDA_DISCOVERY_TIMEOUT (line 13) | const LIGHTPANDA_DISCOVERY_TIMEOUT: Duration = Duration::from_millis(500);
constant LIGHTPANDA_SESSION_TIMEOUT_SECS (line 14) | const LIGHTPANDA_SESSION_TIMEOUT_SECS: u64 = 604800;
constant MAX_LOG_LINES (line 15) | const MAX_LOG_LINES: usize = 40;
type LightpandaProcess (line 17) | pub struct LightpandaProcess {
method kill (line 24) | pub fn kill(&mut self) {
method drop (line 31) | fn drop(&mut self) {
type LightpandaLaunchOptions (line 37) | pub struct LightpandaLaunchOptions {
function build_lightpanda_serve_args (line 43) | fn build_lightpanda_serve_args(port: u16, proxy: Option<&str>) -> Vec<St...
type LaunchLogBuffer (line 63) | struct LaunchLogBuffer {
method push_stdout (line 69) | fn push_stdout(&self, line: String) {
method push_stderr (line 73) | fn push_stderr(&self, line: String) {
method snapshot_stdout (line 77) | fn snapshot_stdout(&self) -> Vec<String> {
method snapshot_stderr (line 86) | fn snapshot_stderr(&self) -> Vec<String> {
function push_bounded (line 96) | fn push_bounded(buffer: &Mutex<VecDeque<String>>, line: String) {
function find_lightpanda (line 104) | pub fn find_lightpanda() -> Option<PathBuf> {
function launch_lightpanda (line 149) | pub async fn launch_lightpanda(
function start_log_drainers (line 197) | fn start_log_drainers(
function drain_reader (line 221) | fn drain_reader<R, F>(reader: R, mut push: F)
function wait_for_lightpanda_ready (line 234) | async fn wait_for_lightpanda_ready(
function lightpanda_launch_error (line 281) | fn lightpanda_launch_error(
function unused_port (line 323) | fn unused_port() -> u16 {
function serve_json_version_once_after_delay (line 331) | async fn serve_json_version_once_after_delay(port: u16, delay_ms: u64, b...
function waits_for_ready_without_logs (line 347) | async fn waits_for_ready_without_logs() {
function child_exit_surfaces_logs (line 375) | async fn child_exit_surfaces_logs() {
function timeout_reports_last_probe_error (line 396) | async fn timeout_reports_last_probe_error() {
function test_find_lightpanda_returns_none_when_missing (line 426) | fn test_find_lightpanda_returns_none_when_missing() {
function test_lightpanda_launch_error_no_logs (line 431) | fn test_lightpanda_launch_error_no_logs() {
function test_lightpanda_launch_error_with_lines (line 438) | fn test_lightpanda_launch_error_with_lines() {
function test_default_options (line 449) | fn test_default_options() {
function test_build_lightpanda_serve_args_sets_explicit_session_timeout (line 457) | fn test_build_lightpanda_serve_args_sets_explicit_session_timeout() {
function test_build_lightpanda_serve_args_with_proxy (line 475) | fn test_build_lightpanda_serve_args_with_proxy() {
FILE: cli/src/native/cdp/types.rs
function string_or_int (line 7) | fn string_or_int<'de, D>(deserializer: D) -> Result<String, D::Error>
function opt_vec_string_or_int (line 23) | fn opt_vec_string_or_int<'de, D>(deserializer: D) -> Result<Option<Vec<S...
type CdpCommand (line 55) | pub struct CdpCommand {
type CdpMessage (line 66) | pub struct CdpMessage {
type CdpError (line 76) | pub struct CdpError {
method fmt (line 83) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type CdpEvent (line 93) | pub struct CdpEvent {
type TargetInfo (line 105) | pub struct TargetInfo {
type GetTargetsResult (line 117) | pub struct GetTargetsResult {
type AttachToTargetParams (line 123) | pub struct AttachToTargetParams {
type AttachToTargetResult (line 130) | pub struct AttachToTargetResult {
type SetDiscoverTargetsParams (line 136) | pub struct SetDiscoverTargetsParams {
type CreateTargetParams (line 142) | pub struct CreateTargetParams {
type CreateTargetResult (line 148) | pub struct CreateTargetResult {
type CloseTargetParams (line 154) | pub struct CloseTargetParams {
type TargetCreatedEvent (line 161) | pub struct TargetCreatedEvent {
type TargetDestroyedEvent (line 167) | pub struct TargetDestroyedEvent {
type TargetInfoChangedEvent (line 173) | pub struct TargetInfoChangedEvent {
type PageNavigateParams (line 183) | pub struct PageNavigateParams {
type PageNavigateResult (line 191) | pub struct PageNavigateResult {
type FrameNavigatedEvent (line 199) | pub struct FrameNavigatedEvent {
type FrameInfo (line 205) | pub struct FrameInfo {
type JavascriptDialogOpeningEvent (line 215) | pub struct JavascriptDialogOpeningEvent {
type HandleJavaScriptDialogParams (line 225) | pub struct HandleJavaScriptDialogParams {
type EvaluateParams (line 237) | pub struct EvaluateParams {
type EvaluateResult (line 247) | pub struct EvaluateResult {
type RemoteObject (line 254) | pub struct RemoteObject {
type ExceptionDetails (line 267) | pub struct ExceptionDetails {
type ConsoleApiCalledEvent (line 277) | pub struct ConsoleApiCalledEvent {
type ExceptionThrownEvent (line 287) | pub struct ExceptionThrownEvent {
type GetFullAXTreeResult (line 298) | pub struct GetFullAXTreeResult {
type AXNode (line 304) | pub struct AXNode {
type AXValue (line 320) | pub struct AXValue {
type AXProperty (line 328) | pub struct AXProperty {
type RequestWillBeSentEvent (line 339) | pub struct RequestWillBeSentEvent {
type NetworkRequest (line 346) | pub struct NetworkRequest {
type LoadingFinishedEvent (line 353) | pub struct LoadingFinishedEvent {
type LoadingFailedEvent (line 359) | pub struct LoadingFailedEvent {
type DomResolveNodeParams (line 369) | pub struct DomResolveNodeParams {
type DomResolveNodeResult (line 380) | pub struct DomResolveNodeResult {
type DomGetBoxModelParams (line 386) | pub struct DomGetBoxModelParams {
type DomGetBoxModelResult (line 397) | pub struct DomGetBoxModelResult {
type BoxModel (line 403) | pub struct BoxModel {
type DomQuerySelectorParams (line 414) | pub struct DomQuerySelectorParams {
type DomQuerySelectorResult (line 421) | pub struct DomQuerySelectorResult {
type DomGetDocumentParams (line 427) | pub struct DomGetDocumentParams {
type DomGetDocumentResult (line 434) | pub struct DomGetDocumentResult {
type DomNode (line 440) | pub struct DomNode {
type DispatchMouseEventParams (line 454) | pub struct DispatchMouseEventParams {
type DispatchKeyEventParams (line 475) | pub struct DispatchKeyEventParams {
type InsertTextParams (line 496) | pub struct InsertTextParams {
type CaptureScreenshotParams (line 506) | pub struct CaptureScreenshotParams {
type Viewport (line 521) | pub struct Viewport {
type CaptureScreenshotResult (line 531) | pub struct CaptureScreenshotResult {
type CallFunctionOnParams (line 541) | pub struct CallFunctionOnParams {
type CallArgument (line 555) | pub struct CallArgument {
type BrowserVersionInfo (line 568) | pub struct BrowserVersionInfo {
FILE: cli/src/native/cookies.rs
type Cookie (line 8) | pub struct Cookie {
function get_cookies (line 27) | pub async fn get_cookies(
function set_cookies (line 49) | pub async fn set_cookies(
function clear_cookies (line 82) | pub async fn clear_cookies(client: &CdpClient, session_id: &str) -> Resu...
FILE: cli/src/native/daemon.rs
function run_daemon (line 19) | pub async fn run_daemon(session: &str) {
function run_socket_server (line 93) | async fn run_socket_server(
function run_socket_server (line 161) | async fn run_socket_server(
function handle_connection (line 236) | async fn handle_connection<S>(
function looks_like_http (line 302) | fn looks_like_http(line: &str) -> bool {
function shutdown_signal (line 309) | async fn shutdown_signal() {
function get_daemon_socket_dir (line 354) | fn get_daemon_socket_dir() -> PathBuf {
function get_port_for_session (line 375) | fn get_port_for_session(session: &str) -> u16 {
function test_port_matches_client_algorithm (line 389) | fn test_port_matches_client_algorithm() {
FILE: cli/src/native/diff.rs
type ScreenshotDiffResult (line 4) | pub struct ScreenshotDiffResult {
type SnapshotDiffResult (line 13) | pub struct SnapshotDiffResult {
function diff_screenshot (line 21) | pub fn diff_screenshot(
function diff_snapshots (line 103) | pub fn diff_snapshots(before: &str, after: &str) -> SnapshotDiffResult {
function diff_text (line 151) | pub fn diff_text(a: &str, b: &str) -> Value {
function diff_unified (line 163) | pub fn diff_unified(a: &str, b: &str) -> String {
function test_diff_identical (line 172) | fn test_diff_identical() {
function test_diff_additions (line 180) | fn test_diff_additions() {
function test_diff_deletions (line 188) | fn test_diff_deletions() {
function test_diff_unified_output (line 195) | fn test_diff_unified_output() {
function test_snapshot_diff_struct (line 202) | fn test_snapshot_diff_struct() {
function test_diff_snapshots_identical_fast_path (line 212) | fn test_diff_snapshots_identical_fast_path() {
function bench_diff_snapshots_identical_and_changed (line 224) | fn bench_diff_snapshots_identical_and_changed() {
FILE: cli/src/native/e2e_tests.rs
function assert_success (line 16) | fn assert_success(resp: &Value) {
function get_data (line 25) | fn get_data(resp: &Value) -> &Value {
function native_test_fixture_html (line 29) | fn native_test_fixture_html(name: &str) -> &'static str {
function native_test_fixture_url (line 38) | fn native_test_fixture_url(name: &str) -> String {
function e2e_launch_navigate_evaluate_close (line 51) | async fn e2e_launch_navigate_evaluate_close() {
function e2e_lightpanda_launch_can_open_page (line 109) | async fn e2e_lightpanda_launch_can_open_page() {
function e2e_lightpanda_auto_launch_can_open_page (line 152) | async fn e2e_lightpanda_auto_launch_can_open_page() {
function e2e_snapshot_and_click_ref (line 199) | async fn e2e_snapshot_and_click_ref() {
function e2e_screenshot (line 262) | async fn e2e_screenshot() {
function e2e_form_interaction (line 382) | async fn e2e_form_interaction() {
function e2e_navigation_history (line 512) | async fn e2e_navigation_history() {
function e2e_cookies (line 584) | async fn e2e_cookies() {
function e2e_storage (line 644) | async fn e2e_storage() {
function e2e_tabs (line 718) | async fn e2e_tabs() {
function e2e_element_queries (line 798) | async fn e2e_element_queries() {
function e2e_wait (line 895) | async fn e2e_wait() {
function e2e_viewport_scale_factor (line 958) | async fn e2e_viewport_scale_factor() {
function e2e_viewport_emulation (line 1029) | async fn e2e_viewport_emulation() {
function e2e_hover_scroll_press (line 1111) | async fn e2e_hover_scroll_press() {
function e2e_mouse_down_move_up_preserves_drag_state (line 1179) | async fn e2e_mouse_down_move_up_preserves_drag_state() {
function e2e_mouse_drag_reaches_pointer_capture_target (line 1304) | async fn e2e_mouse_drag_reaches_pointer_capture_target() {
function e2e_state_management (line 1418) | async fn e2e_state_management() {
function e2e_domain_filter (line 1484) | async fn e2e_domain_filter() {
function e2e_diff_snapshot (line 1561) | async fn e2e_diff_snapshot() {
function e2e_phase8_commands (line 1611) | async fn e2e_phase8_commands() {
function e2e_auto_launch (line 1720) | async fn e2e_auto_launch() {
function e2e_error_handling (line 1750) | async fn e2e_error_handling() {
function e2e_profile_cookie_persistence (line 1815) | async fn e2e_profile_cookie_persistence() {
function e2e_get_cdp_url (line 1927) | async fn e2e_get_cdp_url() {
function e2e_inspect (line 1954) | async fn e2e_inspect() {
function e2e_click_stale_ref_falls_back_to_role_name (line 2011) | async fn e2e_click_stale_ref_falls_back_to_role_name() {
function e2e_material_checkbox_check_uncheck (line 2108) | async fn e2e_material_checkbox_check_uncheck() {
function e2e_snapshot_cursor_interactive (line 2285) | async fn e2e_snapshot_cursor_interactive() {
function e2e_screenshot_annotate_many_elements (line 2382) | async fn e2e_screenshot_annotate_many_elements() {
function e2e_snapshot_cursor_many_elements (line 2441) | async fn e2e_snapshot_cursor_many_elements() {
function e2e_snapshot_inline_text_box_filtered (line 2507) | async fn e2e_snapshot_inline_text_box_filtered() {
function start_echo_server (line 2573) | async fn start_echo_server() -> (String, tokio::task::JoinHandle<()>) {
function e2e_headers_persist_same_origin_navigation (line 2625) | async fn e2e_headers_persist_same_origin_navigation() {
function e2e_headers_persist_same_origin_fetch (line 2683) | async fn e2e_headers_persist_same_origin_fetch() {
function e2e_headers_do_not_leak_cross_origin (line 2730) | async fn e2e_headers_do_not_leak_cross_origin() {
function e2e_headers_do_not_leak_cross_origin_fetch (line 2789) | async fn e2e_headers_do_not_leak_cross_origin_fetch() {
function e2e_set_headers_not_regressed (line 2840) | async fn e2e_set_headers_not_regressed() {
function e2e_headers_multiple_origins_independent (line 2895) | async fn e2e_headers_multiple_origins_independent() {
function e2e_headers_persist_after_roundtrip (line 2963) | async fn e2e_headers_persist_after_roundtrip() {
function e2e_headers_override_same_origin (line 3022) | async fn e2e_headers_override_same_origin() {
function e2e_global_and_scoped_headers_stack (line 3092) | async fn e2e_global_and_scoped_headers_stack() {
function e2e_headers_case_insensitive_no_duplicates (line 3151) | async fn e2e_headers_case_insensitive_no_duplicates() {
FILE: cli/src/native/element.rs
type RefEntry (line 9) | pub struct RefEntry {
type RefMap (line 18) | pub struct RefMap {
method new (line 24) | pub fn new() -> Self {
method add (line 31) | pub fn add(
method add_with_frame (line 42) | pub fn add_with_frame(
method add_selector (line 64) | pub fn add_selector(
method get (line 85) | pub fn get(&self, ref_id: &str) -> Option<&RefEntry> {
method entries_sorted (line 89) | pub fn entries_sorted(&self) -> Vec<(String, RefEntry)> {
method clear (line 106) | pub fn clear(&mut self) {
method next_ref_num (line 111) | pub fn next_ref_num(&self) -> usize {
method set_next_ref_num (line 115) | pub fn set_next_ref_num(&mut self, n: usize) {
function parse_ref (line 120) | pub fn parse_ref(input: &str) -> Option<String> {
function resolve_element_center (line 145) | pub async fn resolve_element_center(
function resolve_element_object_id (line 205) | pub async fn resolve_element_object_id(
function find_node_id_by_role_name (line 290) | async fn find_node_id_by_role_name(
function extract_ax_string (line 335) | fn extract_ax_string(value: &Option<AXValue>) -> String {
function build_find_element_js (line 348) | fn build_find_element_js(selector: &str) -> String {
function build_count_elements_js (line 363) | fn build_count_elements_js(selector: &str) -> String {
function build_selector_js (line 377) | fn build_selector_js(selector: &str) -> String {
function resolve_by_selector (line 389) | async fn resolve_by_selector(
function box_model_center (line 418) | fn box_model_center(model: &BoxModel) -> (f64, f64) {
function get_element_text (line 429) | pub async fn get_element_text(
function get_element_attribute (line 459) | pub async fn get_element_attribute(
function is_element_visible (line 488) | pub async fn is_element_visible(
function is_element_enabled (line 525) | pub async fn is_element_enabled(
function is_element_checked (line 554) | pub async fn is_element_checked(
function get_element_inner_text (line 616) | pub async fn get_element_inner_text(
function get_element_inner_html (line 645) | pub async fn get_element_inner_html(
function get_element_input_value (line 674) | pub async fn get_element_input_value(
function set_element_value (line 705) | pub async fn set_element_value(
function get_element_bounding_box (line 736) | pub async fn get_element_bounding_box(
function get_element_count (line 768) | pub async fn get_element_count(
function get_element_styles (line 790) | pub async fn get_element_styles(
function test_parse_ref_at_prefix (line 847) | fn test_parse_ref_at_prefix() {
function test_parse_ref_equals_prefix (line 853) | fn test_parse_ref_equals_prefix() {
function test_parse_ref_bare (line 858) | fn test_parse_ref_bare() {
function test_parse_ref_invalid (line 864) | fn test_parse_ref_invalid() {
function test_ref_map_basic (line 872) | fn test_ref_map_basic() {
function test_build_selector_js_css (line 881) | fn test_build_selector_js_css() {
function test_build_selector_js_xpath (line 888) | fn test_build_selector_js_xpath() {
function test_build_selector_js_xpath_empty (line 895) | fn test_build_selector_js_xpath_empty() {
function test_build_selector_js_not_xpath_prefix (line 901) | fn test_build_selector_js_not_xpath_prefix() {
function test_build_count_elements_js_css (line 908) | fn test_build_count_elements_js_css() {
function test_build_count_elements_js_xpath (line 915) | fn test_build_count_elements_js_xpath() {
function test_box_model_center (line 922) | fn test_box_model_center() {
FILE: cli/src/native/inspect_server.rs
type InspectServer (line 23) | pub struct InspectServer {
method start (line 34) | pub async fn start(
method port (line 63) | pub fn port(&self) -> u16 {
method shutdown (line 67) | pub fn shutdown(self) {
function accept_loop (line 72) | async fn accept_loop(
function handle_connection (line 97) | async fn handle_connection(
constant MAX_HEADER_BYTES (line 135) | const MAX_HEADER_BYTES: usize = 8192;
function handle_http_redirect (line 137) | async fn handle_http_redirect(
function handle_ws_proxy (line 175) | async fn handle_ws_proxy(
function inject_session_id (line 298) | fn inject_session_id(json: &str, session_id: &str) -> String {
function strip_session_id (line 312) | fn strip_session_id(json: &str) -> String {
function test_inject_session_id (line 328) | fn test_inject_session_id() {
function test_inject_session_id_empty_object (line 338) | fn test_inject_session_id_empty_object() {
function test_strip_session_id (line 345) | fn test_strip_session_id() {
function test_inject_then_strip_roundtrip (line 354) | fn test_inject_then_strip_roundtrip() {
FILE: cli/src/native/interaction.rs
function click (line 7) | pub async fn click(
function dblclick (line 19) | pub async fn dblclick(
function hover (line 28) | pub async fn hover(
function fill (line 55) | pub async fn fill(
function type_text (line 114) | pub async fn type_text(
function press_key (line 223) | pub async fn press_key(client: &CdpClient, session_id: &str, key: &str) ...
function press_key_with_modifiers (line 234) | pub async fn press_key_with_modifiers(
function scroll (line 279) | pub async fn scroll(
function select_option (line 329) | pub async fn select_option(
function check (line 367) | pub async fn check(
function uncheck (line 389) | pub async fn uncheck(
function js_click_checkbox (line 417) | async fn js_click_checkbox(
function focus (line 466) | pub async fn focus(
function clear (line 491) | pub async fn clear(
function select_all (line 522) | pub async fn select_all(
function scroll_into_view (line 559) | pub async fn scroll_into_view(
function dispatch_event (line 586) | pub async fn dispatch_event(
function highlight (line 623) | pub async fn highlight(
function tap_touch (line 657) | pub async fn tap_touch(
function dispatch_click (line 690) | async fn dispatch_click(
function char_to_key_info (line 764) | fn char_to_key_info(ch: char) -> (String, String, i32) {
function punctuation_key_info (line 796) | fn punctuation_key_info(ch: char) -> (&'static str, i32) {
function named_key_info (line 824) | fn named_key_info(key: &str) -> (String, String, i32) {
function test_char_to_key_info_matches_playwright_layout (line 861) | fn test_char_to_key_info_matches_playwright_layout() {
function test_period_is_not_vk_delete (line 923) | fn test_period_is_not_vk_delete() {
function test_unmapped_chars_return_zero_keycode (line 935) | fn test_unmapped_chars_return_zero_keycode() {
FILE: cli/src/native/network.rs
function set_extra_headers (line 6) | pub async fn set_extra_headers(
function set_offline (line 28) | pub async fn set_offline(
function set_content (line 48) | pub async fn set_content(client: &CdpClient, session_id: &str, html: &st...
type DomainFilter (line 80) | pub struct DomainFilter {
method new (line 85) | pub fn new(domains: &str) -> Self {
method is_allowed (line 92) | pub fn is_allowed(&self, hostname: &str) -> bool {
method check_url (line 109) | pub fn check_url(&self, url: &str) -> Result<(), String> {
function parse_domain_list (line 128) | fn parse_domain_list(input: &str) -> Vec<String> {
function sanitize_existing_pages (line 136) | pub async fn sanitize_existing_pages(
function install_domain_filter_script (line 161) | pub async fn install_domain_filter_script(
function install_domain_filter_fetch (line 233) | pub async fn install_domain_filter_fetch(
function install_domain_filter (line 252) | pub async fn install_domain_filter(
type ConsoleEntry (line 267) | pub struct ConsoleEntry {
type ErrorEntry (line 273) | pub struct ErrorEntry {
type EventTracker (line 280) | pub struct EventTracker {
method new (line 287) | pub fn new() -> Self {
method add_console (line 295) | pub fn add_console(&mut self, level: &str, text: &str) {
method add_error (line 305) | pub fn add_error(
method get_console_json (line 323) | pub fn get_console_json(&self) -> Value {
method get_errors_json (line 332) | pub fn get_errors_json(&self) -> Value {
function test_domain_filter_exact (line 354) | fn test_domain_filter_exact() {
function test_domain_filter_wildcard (line 361) | fn test_domain_filter_wildcard() {
function test_domain_filter_empty (line 370) | fn test_domain_filter_empty() {
function test_domain_filter_multiple (line 376) | fn test_domain_filter_multiple() {
function test_parse_domain_list (line 385) | fn test_parse_domain_list() {
function test_event_tracker (line 391) | fn test_event_tracker() {
FILE: cli/src/native/parity_tests.rs
constant ENCRYPTION_KEY_ENV (line 12) | const ENCRYPTION_KEY_ENV: &str = "AGENT_BROWSER_ENCRYPTION_KEY";
type TestKeyGuard (line 14) | struct TestKeyGuard {
method new (line 20) | fn new() -> Self {
method drop (line 35) | fn drop(&mut self) {
constant DOCUMENTED_ACTIONS (line 45) | const DOCUMENTED_ACTIONS: &[&str] = &[
function minimal_command (line 196) | fn minimal_command(action: &str, id: &str) -> Value {
function test_all_documented_actions_are_handled (line 377) | async fn test_all_documented_actions_are_handled() {
function test_success_response_format (line 406) | async fn test_success_response_format() {
function test_error_response_format (line 418) | async fn test_error_response_format() {
function test_state_list_without_browser (line 433) | async fn test_state_list_without_browser() {
function test_credentials_list_without_browser (line 443) | async fn test_credentials_list_without_browser() {
function test_auth_profile_name_validation (line 457) | async fn test_auth_profile_name_validation() {
function test_auth_save_and_show (line 473) | async fn test_auth_save_and_show() {
function test_har_start_stop_without_browser (line 502) | async fn test_har_start_stop_without_browser() {
function test_state_clean_action (line 518) | async fn test_state_clean_action() {
function test_daemon_state_new_defaults (line 526) | async fn test_daemon_state_new_defaults() {
function test_tracked_request_struct (line 540) | async fn test_tracked_request_struct() {
function test_request_tracking_state (line 557) | async fn test_request_tracking_state() {
function test_addscript_and_addinitscript_separate_dispatch (line 593) | async fn test_addscript_and_addinitscript_separate_dispatch() {
function test_frame_context_management (line 615) | async fn test_frame_context_management() {
function test_addstyle_supports_content_and_url (line 629) | async fn test_addstyle_supports_content_and_url() {
function test_domain_filter_sanitize (line 646) | async fn test_domain_filter_sanitize() {
function test_state_find_auto_returns_none_for_nonexistent (line 656) | async fn test_state_find_auto_returns_none_for_nonexistent() {
FILE: cli/src/native/policy.rs
type PolicyResult (line 9) | pub enum PolicyResult {
type ActionPolicy (line 20) | pub struct ActionPolicy {
method load (line 64) | pub fn load(path: &str) -> Result<Self, String> {
method load_if_exists (line 76) | pub fn load_if_exists() -> Option<Self> {
method check (line 84) | pub fn check(&self, action: &str) -> PolicyResult {
method reload (line 124) | pub fn reload(&mut self) -> Result<(), String> {
type ConfirmActions (line 35) | pub struct ConfirmActions {
method from_env (line 40) | pub fn from_env() -> Option<Self> {
method requires_confirmation (line 57) | pub fn requires_confirmation(&self, action: &str) -> bool {
function test_policy_allow_whitelist (line 141) | fn test_policy_allow_whitelist() {
function test_policy_deny (line 150) | fn test_policy_deny() {
function test_policy_confirm (line 157) | fn test_policy_confirm() {
function test_policy_deny_takes_precedence (line 164) | fn test_policy_deny_takes_precedence() {
function test_policy_confirm_takes_precedence_over_allow (line 171) | fn test_policy_confirm_takes_precedence_over_allow() {
function test_policy_empty_allow_allows_all (line 178) | fn test_policy_empty_allow_allows_all() {
function test_policy_missing_allow_allows_all (line 185) | fn test_policy_missing_allow_allows_all() {
function test_policy_default_allow (line 192) | fn test_policy_default_allow() {
function test_policy_default_deny (line 200) | fn test_policy_default_deny() {
function test_confirm_actions_from_env (line 208) | fn test_confirm_actions_from_env() {
FILE: cli/src/native/providers.rs
type ProviderSession (line 10) | pub struct ProviderSession {
function connect_provider (line 17) | pub async fn connect_provider(
function close_provider_session (line 33) | pub async fn close_provider_session(session: &ProviderSession) {
function connect_browserbase (line 87) | async fn connect_browserbase() -> Result<(String, Option<ProviderSession...
function connect_browserless (line 137) | async fn connect_browserless() -> Result<(String, Option<ProviderSession...
function connect_browser_use (line 218) | async fn connect_browser_use() -> Result<(String, Option<ProviderSession...
function connect_kernel (line 271) | async fn connect_kernel() -> Result<(String, Option<ProviderSession>), S...
FILE: cli/src/native/recording.rs
constant CAPTURE_INTERVAL_MS (line 12) | const CAPTURE_INTERVAL_MS: u64 = 100;
constant CAPTURE_FPS (line 13) | const CAPTURE_FPS: u32 = 10;
type RecordingState (line 15) | pub struct RecordingState {
method new (line 25) | pub fn new() -> Self {
function recording_start (line 37) | pub fn recording_start(state: &mut RecordingState, path: &str) -> Result...
function recording_stop (line 49) | pub fn recording_stop(state: &mut RecordingState) -> Result<Value, Strin...
function recording_restart (line 63) | pub fn recording_restart(state: &mut RecordingState, path: &str) -> Resu...
function build_ffmpeg_command (line 82) | fn build_ffmpeg_command(output_path: &str) -> tokio::process::Command {
function spawn_recording_task (line 125) | pub fn spawn_recording_task(
function stop_recording_task (line 211) | pub async fn stop_recording_task(state: &mut RecordingState) -> Result<(...
function test_recording_state_new (line 241) | fn test_recording_state_new() {
function test_recording_start_sets_active (line 249) | fn test_recording_start_sets_active() {
function test_recording_start_while_active (line 259) | fn test_recording_start_while_active() {
function test_recording_stop_not_active (line 268) | fn test_recording_stop_not_active() {
function test_recording_stop_no_frames (line 276) | fn test_recording_stop_no_frames() {
function test_recording_restart_while_inactive (line 286) | fn test_recording_restart_while_inactive() {
function test_recording_restart_while_active (line 295) | fn test_recording_restart_while_active() {
function test_build_ffmpeg_command_webm (line 307) | fn test_build_ffmpeg_command_webm() {
function test_build_ffmpeg_command_mp4 (line 316) | fn test_build_ffmpeg_command_mp4() {
FILE: cli/src/native/screenshot.rs
constant ANNOTATION_OVERLAY_ID (line 9) | const ANNOTATION_OVERLAY_ID: &str = "__agent_browser_annotations__";
type Rect (line 12) | struct Rect {
type RawAnnotation (line 20) | struct RawAnnotation {
type AnnotationBox (line 29) | pub struct AnnotationBox {
type ScreenshotAnnotation (line 37) | pub struct ScreenshotAnnotation {
type ScreenshotResult (line 46) | pub struct ScreenshotResult {
type ScreenshotOptions (line 53) | pub struct ScreenshotOptions {
method default (line 64) | fn default() -> Self {
method serialize (line 78) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
function take_screenshot (line 98) | pub async fn take_screenshot(
function capture_screenshot_base64 (line 164) | async fn capture_screenshot_base64(
function collect_annotations (line 221) | async fn collect_annotations(
function get_rect_for_selector (line 314) | async fn get_rect_for_selector(
function get_rect_for_object (line 325) | async fn get_rect_for_object(
function parse_rect (line 351) | fn parse_rect(value: &Value) -> Option<Rect> {
function filter_annotations (line 360) | fn filter_annotations(
function overlaps (line 376) | fn overlaps(left: &Rect, right: &Rect) -> bool {
function inject_annotation_overlay (line 385) | async fn inject_annotation_overlay(
function remove_annotation_overlay (line 450) | async fn remove_annotation_overlay(client: &CdpClient, session_id: &str)...
function get_scroll_offsets (line 476) | async fn get_scroll_offsets(client: &CdpClient, session_id: &str) -> Res...
function project_annotations (line 495) | fn project_annotations(
function save_screenshot (line 537) | fn save_screenshot(
function round (line 569) | fn round(value: f64) -> i64 {
function get_screenshot_dir (line 573) | fn get_screenshot_dir() -> PathBuf {
function filters_annotations_to_target_overlap (line 588) | fn filters_annotations_to_target_overlap() {
function projects_selector_annotations_relative_to_target (line 629) | fn projects_selector_annotations_relative_to_target() {
function projects_full_page_annotations_to_document_space (line 656) | fn projects_full_page_annotations_to_document_space() {
FILE: cli/src/native/snapshot.rs
constant INTERACTIVE_ROLES (line 11) | const INTERACTIVE_ROLES: &[&str] = &[
constant CONTENT_ROLES (line 32) | const CONTENT_ROLES: &[&str] = &[
constant STRUCTURAL_ROLES (line 45) | const STRUCTURAL_ROLES: &[&str] = &[
type SnapshotOptions (line 69) | pub struct SnapshotOptions {
type TreeNode (line 77) | struct TreeNode {
type CursorElementInfo (line 99) | struct CursorElementInfo {
type RoleNameTracker (line 105) | struct RoleNameTracker {
method new (line 111) | fn new() -> Self {
method track (line 118) | fn track(&mut self, role: &str, name: &str, node_idx: usize) -> usize {
method get_duplicates (line 127) | fn get_duplicates(&self) -> HashMap<String, usize> {
function take_snapshot (line 136) | pub async fn take_snapshot(
function resolve_iframe_frame_id (line 415) | async fn resolve_iframe_frame_id(
function find_cursor_interactive_elements (line 448) | async fn find_cursor_interactive_elements(
function build_tree (line 703) | fn build_tree(nodes: &[AXNode]) -> (Vec<TreeNode>, Vec<usize>) {
function render_tree (line 801) | fn render_tree(
function compact_tree (line 917) | fn compact_tree(tree: &str, interactive: bool) -> String {
function count_indent (line 956) | fn count_indent(line: &str) -> usize {
function extract_ax_string (line 961) | fn extract_ax_string(value: &Option<AXValue>) -> String {
function extract_ax_string_opt (line 973) | fn extract_ax_string_opt(value: &Option<AXValue>) -> Option<String> {
type NodeProperties (line 984) | type NodeProperties = (
function extract_properties (line 993) | fn extract_properties(props: &Option<Vec<AXProperty>>) -> NodeProperties {
function build_dedup_set (line 1039) | fn build_dedup_set(ref_map: &RefMap) -> std::collections::HashSet<String> {
function collect_backend_node_ids (line 1050) | fn collect_backend_node_ids(node: &Value, ids: &mut std::collections::Ha...
function test_interactive_roles (line 1075) | fn test_interactive_roles() {
function test_content_roles (line 1082) | fn test_content_roles() {
function test_compact_tree_basic (line 1088) | fn test_compact_tree_basic() {
function test_compact_tree_empty_interactive (line 1097) | fn test_compact_tree_empty_interactive() {
function test_count_indent (line 1103) | fn test_count_indent() {
function test_role_name_tracker (line 1110) | fn test_role_name_tracker() {
function test_dedup_set_from_ref_map_names (line 1126) | fn test_dedup_set_from_ref_map_names() {
function test_dedup_set_case_insensitive (line 1138) | fn test_dedup_set_case_insensitive() {
function test_dedup_set_empty_inputs (line 1148) | fn test_dedup_set_empty_inputs() {
function test_dedup_set_skips_empty_names (line 1155) | fn test_dedup_set_skips_empty_names() {
FILE: cli/src/native/state.rs
type StorageState (line 14) | pub struct StorageState {
type OriginStorage (line 21) | pub struct OriginStorage {
type StorageEntry (line 30) | pub struct StorageEntry {
function save_state (line 35) | pub async fn save_state(
function load_state (line 132) | pub async fn load_state(client: &CdpClient, session_id: &str, path: &str...
function is_state_file (line 236) | fn is_state_file(path: &std::path::Path) -> bool {
function is_encrypted_state (line 245) | fn is_encrypted_state(path: &std::path::Path) -> bool {
function state_list (line 249) | pub fn state_list() -> Result<Value, String> {
function state_show (line 290) | pub fn state_show(path: &str) -> Result<Value, String> {
function state_clear (line 329) | pub fn state_clear(path: Option<&str>) -> Result<Value, String> {
function state_clean (line 354) | pub fn state_clean(max_age_days: u64) -> Result<Value, String> {
function state_rename (line 390) | pub fn state_rename(old_path: &str, new_name: &str) -> Result<Value, Str...
function encrypt_data (line 409) | fn encrypt_data(data: &[u8], key_str: &str) -> Result<Vec<u8>, String> {
function decrypt_data (line 428) | fn decrypt_data(data: &[u8], key_str: &str) -> Result<Vec<u8>, String> {
function find_auto_state_file (line 445) | pub fn find_auto_state_file(session_name: &str) -> Option<String> {
function get_sessions_dir (line 478) | pub fn get_sessions_dir() -> PathBuf {
function test_storage_state_serialization (line 491) | fn test_storage_state_serialization() {
function test_storage_state_empty (line 524) | fn test_storage_state_empty() {
function test_state_show_nonexistent_file (line 536) | fn test_state_show_nonexistent_file() {
function test_state_clear_nonexistent_file (line 542) | fn test_state_clear_nonexistent_file() {
function test_state_rename_nonexistent (line 548) | fn test_state_rename_nonexistent() {
function test_state_list_returns_json (line 555) | fn test_state_list_returns_json() {
function test_sessions_dir_path (line 562) | fn test_sessions_dir_path() {
function test_encrypt_decrypt_roundtrip (line 568) | fn test_encrypt_decrypt_roundtrip() {
function test_decrypt_wrong_key_fails (line 579) | fn test_decrypt_wrong_key_fails() {
function test_cookie_serde_roundtrip (line 587) | fn test_cookie_serde_roundtrip() {
FILE: cli/src/native/storage.rs
function storage_get (line 6) | pub async fn storage_get(
function storage_set (line 40) | pub async fn storage_set(
function storage_clear (line 58) | pub async fn storage_clear(
function storage_js_name (line 69) | fn storage_js_name(storage_type: &str) -> &str {
function eval_simple (line 76) | async fn eval_simple(client: &CdpClient, session_id: &str, js: &str) -> ...
FILE: cli/src/native/stream.rs
type FrameMetadata (line 14) | pub struct FrameMetadata {
method default (line 25) | fn default() -> Self {
type StreamServer (line 38) | pub struct StreamServer {
method start (line 50) | pub async fn start(
method start_without_client (line 63) | pub async fn start_without_client(
method notify_client_changed (line 72) | pub fn notify_client_changed(&self) {
method set_cdp_session_id (line 77) | pub async fn set_cdp_session_id(&self, session_id: Option<String>) {
method is_screencasting (line 83) | pub async fn is_screencasting(&self) -> bool {
method start_inner (line 87) | async fn start_inner(
method port (line 162) | pub fn port(&self) -> u16 {
method broadcast_frame (line 167) | pub fn broadcast_frame(&self, frame_json: &str) {
method broadcast_screencast_frame (line 172) | pub fn broadcast_screencast_frame(&self, base64_data: &str, metadata: ...
method broadcast_status (line 190) | pub fn broadcast_status(
method broadcast_error (line 208) | pub fn broadcast_error(&self, message: &str) {
function accept_loop (line 218) | async fn accept_loop(
function handle_ws_client (line 252) | async fn handle_ws_client(
function cdp_event_loop (line 354) | async fn cdp_event_loop(
function handle_client_message (line 506) | async fn handle_client_message(msg: &str, client: &CdpClient, session_id...
function is_allowed_origin (line 568) | pub fn is_allowed_origin(origin: Option<&str>) -> bool {
function start_screencast (line 585) | pub async fn start_screencast(
function stop_screencast (line 609) | pub async fn stop_screencast(client: &CdpClient, session_id: &str) -> Re...
function ack_screencast_frame (line 616) | pub async fn ack_screencast_frame(
function test_allowed_origin_none (line 636) | fn test_allowed_origin_none() {
function test_allowed_origin_file (line 641) | fn test_allowed_origin_file() {
function test_allowed_origin_localhost (line 646) | fn test_allowed_origin_localhost() {
function test_disallowed_origin (line 652) | fn test_disallowed_origin() {
function test_frame_metadata_default (line 657) | fn test_frame_metadata_default() {
FILE: cli/src/native/tracing.rs
constant MAX_PROFILE_EVENTS (line 6) | const MAX_PROFILE_EVENTS: usize = 5_000_000;
constant DEFAULT_PROFILER_CATEGORIES (line 8) | const DEFAULT_PROFILER_CATEGORIES: &[&str] = &[
type TracingState (line 26) | pub struct TracingState {
method new (line 33) | pub fn new() -> Self {
function trace_start (line 42) | pub async fn trace_start(
function trace_stop (line 71) | pub async fn trace_stop(
function profiler_start (line 183) | pub async fn profiler_start(
function profiler_stop (line 221) | pub async fn profiler_stop(
function read_io_stream (line 319) | async fn read_io_stream(
function get_clock_domain (line 349) | fn get_clock_domain() -> Option<&'static str> {
function get_traces_dir (line 359) | fn get_traces_dir() -> PathBuf {
function get_profiles_dir (line 367) | fn get_profiles_dir() -> PathBuf {
FILE: cli/src/native/webdriver/appium.rs
constant APPIUM_DEFAULT_PORT (line 7) | const APPIUM_DEFAULT_PORT: u16 = 4723;
constant APPIUM_STARTUP_TIMEOUT_SECS (line 8) | const APPIUM_STARTUP_TIMEOUT_SECS: u64 = 30;
type AppiumManager (line 10) | pub struct AppiumManager {
method connect_or_launch (line 17) | pub async fn connect_or_launch(device_udid: Option<&str>) -> Result<Se...
method build_ios_capabilities (line 43) | pub fn build_ios_capabilities(
method create_ios_session (line 72) | pub async fn create_ios_session(
method tap (line 85) | pub async fn tap(&self, x: f64, y: f64) -> Result<(), String> {
method swipe (line 107) | pub async fn swipe(
method close (line 136) | pub async fn close(&mut self) -> Result<(), String> {
method drop (line 147) | fn drop(&mut self) {
function is_appium_running (line 155) | async fn is_appium_running(port: u16) -> bool {
function launch_appium (line 166) | fn launch_appium(port: u16) -> Result<Child, String> {
function wait_for_appium (line 192) | async fn wait_for_appium(port: u16, timeout_secs: u64) -> Result<(), Str...
function test_appium_constants (line 210) | fn test_appium_constants() {
function test_ios_capabilities_use_vendor_prefix (line 216) | fn test_ios_capabilities_use_vendor_prefix() {
FILE: cli/src/native/webdriver/backend.rs
type BrowserBackend (line 8) | pub trait BrowserBackend: Send + Sync {
method navigate (line 9) | async fn navigate(&self, url: &str) -> Result<(), String>;
method get_url (line 10) | async fn get_url(&self) -> Result<String, String>;
method get_title (line 11) | async fn get_title(&self) -> Result<String, String>;
method get_content (line 12) | async fn get_content(&self) -> Result<String, String>;
method evaluate (line 13) | async fn evaluate(&self, script: &str) -> Result<Value, String>;
method screenshot (line 14) | async fn screenshot(&self) -> Result<String, String>;
method click (line 15) | async fn click(&self, selector: &str) -> Result<(), String>;
method fill (line 16) | async fn fill(&self, selector: &str, value: &str) -> Result<(), String>;
method close (line 17) | async fn close(&mut self) -> Result<(), String>;
method back (line 18) | async fn back(&self) -> Result<(), String>;
method forward (line 19) | async fn forward(&self) -> Result<(), String>;
method reload (line 20) | async fn reload(&self) -> Result<(), String>;
method get_cookies (line 21) | async fn get_cookies(&self) -> Result<Value, String>;
method backend_type (line 22) | fn backend_type(&self) -> &str;
method supports (line 24) | fn supports(&self, feature: &str) -> bool {
method unsupported_error (line 32) | fn unsupported_error(&self, action: &str) -> String {
method navigate (line 54) | async fn navigate(&self, url: &str) -> Result<(), String> {
method get_url (line 58) | async fn get_url(&self) -> Result<String, String> {
method get_title (line 62) | async fn get_title(&self) -> Result<String, String> {
method get_content (line 66) | async fn get_content(&self) -> Result<String, String> {
method evaluate (line 70) | async fn evaluate(&self, script: &str) -> Result<Value, String> {
method screenshot (line 74) | async fn screenshot(&self) -> Result<String, String> {
method click (line 78) | async fn click(&self, selector: &str) -> Result<(), String> {
method fill (line 83) | async fn fill(&self, selector: &str, value: &str) -> Result<(), String> {
method close (line 89) | async fn close(&mut self) -> Result<(), String> {
method back (line 93) | async fn back(&self) -> Result<(), String> {
method forward (line 97) | async fn forward(&self) -> Result<(), String> {
method reload (line 101) | async fn reload(&self) -> Result<(), String> {
method get_cookies (line 105) | async fn get_cookies(&self) -> Result<Value, String> {
method backend_type (line 109) | fn backend_type(&self) -> &str {
type WebDriverBackend (line 42) | pub struct WebDriverBackend {
method new (line 47) | pub fn new(client: super::client::WebDriverClient) -> Self {
constant WEBDRIVER_UNSUPPORTED_ACTIONS (line 115) | pub const WEBDRIVER_UNSUPPORTED_ACTIONS: &[&str] = &[
function test_unsupported_actions (line 137) | fn test_unsupported_actions() {
FILE: cli/src/native/webdriver/client.rs
type WebDriverClient (line 4) | pub struct WebDriverClient {
method new (line 10) | pub fn new(port: u16) -> Self {
method create_session (line 17) | pub async fn create_session(&mut self, capabilities: Value) -> Result<...
method delete_session (line 37) | pub async fn delete_session(&mut self) -> Result<(), String> {
method navigate (line 45) | pub async fn navigate(&self, url: &str) -> Result<(), String> {
method get_url (line 52) | pub async fn get_url(&self) -> Result<String, String> {
method get_title (line 62) | pub async fn get_title(&self) -> Result<String, String> {
method find_element (line 72) | pub async fn find_element(&self, using: &str, value: &str) -> Result<S...
method click_element (line 91) | pub async fn click_element(&self, element_id: &str) -> Result<(), Stri...
method send_keys (line 101) | pub async fn send_keys(&self, element_id: &str, text: &str) -> Result<...
method clear_element (line 111) | pub async fn clear_element(&self, element_id: &str) -> Result<(), Stri...
method execute_script (line 121) | pub async fn execute_script(&self, script: &str, args: Vec<Value>) -> ...
method screenshot (line 132) | pub async fn screenshot(&self) -> Result<String, String> {
method get_cookies (line 142) | pub async fn get_cookies(&self) -> Result<Value, String> {
method get_page_source (line 148) | pub async fn get_page_source(&self) -> Result<String, String> {
method back (line 158) | pub async fn back(&self) -> Result<(), String> {
method forward (line 165) | pub async fn forward(&self) -> Result<(), String> {
method refresh (line 172) | pub async fn refresh(&self) -> Result<(), String> {
method session_id_pub (line 179) | pub fn session_id_pub(&self) -> Option<&str> {
method new_with_session (line 183) | pub fn new_with_session(port: u16, session_id: String) -> Self {
method execute_actions (line 190) | pub async fn execute_actions(&self, session_id: &str, actions: &Value)...
method session_id (line 196) | fn session_id(&self) -> Result<&str, String> {
method get (line 202) | async fn get(&self, path: &str) -> Result<Value, String> {
method post (line 206) | async fn post(&self, path: &str, body: &Value) -> Result<Value, String> {
method delete (line 210) | async fn delete(&self, path: &str) -> Result<Value, String> {
function http_request (line 215) | async fn http_request(method: &str, url: &str, body: Option<&Value>) -> ...
function test_client_new (line 299) | fn test_client_new() {
function test_session_id_none (line 306) | fn test_session_id_none() {
function test_client_custom_port (line 314) | fn test_client_custom_port() {
FILE: cli/src/native/webdriver/ios.rs
type IosDevice (line 5) | pub struct IosDevice {
function list_simulators (line 13) | pub fn list_simulators() -> Result<Vec<IosDevice>, String> {
function list_real_devices (line 61) | pub fn list_real_devices() -> Result<Vec<IosDevice>, String> {
function list_all_devices (line 113) | pub fn list_all_devices() -> Result<Vec<IosDevice>, String> {
function boot_simulator (line 119) | pub fn boot_simulator(udid: &str) -> Result<(), String> {
function shutdown_simulator (line 135) | pub fn shutdown_simulator(udid: &str) -> Result<(), String> {
function select_device (line 151) | pub fn select_device(device_name: Option<&str>, udid: Option<&str>) -> R...
function to_device_json (line 190) | pub fn to_device_json(devices: &[IosDevice]) -> Value {
function test_ios_device_struct (line 211) | fn test_ios_device_struct() {
function test_to_device_json (line 224) | fn test_to_device_json() {
FILE: cli/src/native/webdriver/safari.rs
type SafariDriverProcess (line 5) | pub struct SafariDriverProcess {
method kill (line 11) | pub fn kill(&mut self) {
method drop (line 18) | fn drop(&mut self) {
function find_safaridriver (line 23) | pub fn find_safaridriver() -> Option<PathBuf> {
function launch_safaridriver (line 46) | pub fn launch_safaridriver(port: u16) -> Result<SafariDriverProcess, Str...
function test_find_safaridriver (line 70) | fn test_find_safaridriver() {
FILE: cli/src/native/webdriver/types.rs
type NewSessionRequest (line 6) | pub struct NewSessionRequest {
type Capabilities (line 12) | pub struct Capabilities {
type SessionResponse (line 18) | pub struct SessionResponse {
type SessionValue (line 24) | pub struct SessionValue {
type WebDriverResponse (line 30) | pub struct WebDriverResponse {
type WebDriverError (line 35) | pub struct WebDriverError {
type ElementResponse (line 42) | pub struct ElementResponse {
type ElementValue (line 47) | pub struct ElementValue {
method id (line 55) | pub fn id(&self) -> Option<&str> {
type FindElementRequest (line 63) | pub struct FindElementRequest {
type ExecuteScriptRequest (line 69) | pub struct ExecuteScriptRequest {
type CookieRequest (line 76) | pub struct CookieRequest {
type CookieData (line 82) | pub struct CookieData {
FILE: cli/src/output.rs
function get_boundary_nonce (line 11) | fn get_boundary_nonce() -> &'static str {
type OutputOptions (line 20) | pub struct OutputOptions {
function truncate_if_needed (line 26) | fn truncate_if_needed(content: &str, max: Option<usize>) -> String {
function print_with_boundaries (line 51) | fn print_with_boundaries(content: &str, origin: Option<&str>, opts: &Out...
function format_storage_value (line 67) | fn format_storage_value(value: &serde_json::Value) -> String {
function format_storage_text (line 74) | fn format_storage_text(data: &serde_json::Value) -> Option<String> {
function print_response_with_opts (line 92) | pub fn print_response_with_opts(resp: &Response, action: Option<&str>, o...
function print_command_help (line 876) | pub fn print_command_help(command: &str) -> bool {
function print_help (line 2484) | pub fn print_help() {
function print_snapshot_diff (line 2740) | fn print_snapshot_diff(data: &serde_json::Map<String, serde_json::Value>) {
function print_screenshot_diff (line 2771) | fn print_screenshot_diff(data: &serde_json::Map<String, serde_json::Valu...
function print_version (line 2816) | pub fn print_version() {
function test_format_storage_text_for_all_entries (line 2826) | fn test_format_storage_text_for_all_entries() {
function test_format_storage_text_for_key_lookup (line 2840) | fn test_format_storage_text_for_key_lookup() {
function test_format_storage_text_for_empty_store (line 2852) | fn test_format_storage_text_for_empty_store() {
FILE: cli/src/test_utils.rs
type EnvGuard (line 8) | pub struct EnvGuard<'a> {
function new (line 14) | pub fn new(var_names: &[&str]) -> Self {
function set (line 23) | pub fn set(&self, name: &str, value: &str) {
function remove (line 31) | pub fn remove(&self, name: &str) {
method drop (line 41) | fn drop(&mut self) {
FILE: cli/src/upgrade.rs
constant CURRENT_VERSION (line 4) | const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
constant NPM_REGISTRY_URL (line 5) | const NPM_REGISTRY_URL: &str = "https://registry.npmjs.org/agent-browser...
type InstallMethod (line 7) | enum InstallMethod {
function fetch_latest_version (line 14) | async fn fetch_latest_version() -> Result<String, String> {
function detect_install_method (line 30) | fn detect_install_method() -> InstallMethod {
function run_upgrade_command (line 65) | fn run_upgrade_command(method: &InstallMethod) -> bool {
function run_upgrade (line 95) | pub fn run_upgrade() {
FILE: cli/src/validation.rs
function is_valid_session_name (line 2) | pub fn is_valid_session_name(name: &str) -> bool {
function session_name_error (line 10) | pub fn session_name_error(name: &str) -> String {
FILE: docs/mdx-components.tsx
function slugify (line 5) | function slugify(text: string): string {
function extractText (line 13) | function extractText(children: React.ReactNode): string {
function useMDXComponents (line 27) | function useMDXComponents(components: MDXComponents): MDXComponents {
FILE: docs/src/app/api/docs-chat/route.ts
constant DEFAULT_MODEL (line 13) | const DEFAULT_MODEL = "anthropic/claude-haiku-4.5";
constant SYSTEM_PROMPT (line 15) | const SYSTEM_PROMPT = `You are a helpful documentation assistant for age...
function loadDocsFiles (line 33) | async function loadDocsFiles(): Promise<Record<string, string>> {
function addCacheControl (line 59) | function addCacheControl(messages: ModelMessage[]): ModelMessage[] {
function POST (line 75) | async function POST(req: Request) {
FILE: docs/src/app/api/docs-markdown/route.ts
function GET (line 6) | async function GET(req: NextRequest) {
FILE: docs/src/app/api/search/route.ts
function GET (line 4) | async function GET(req: NextRequest) {
FILE: docs/src/app/cdp-mode/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/changelog/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/commands/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/configuration/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/diffing/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/engines/chrome/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/engines/lightpanda/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/installation/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/ios/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/layout.tsx
function RootLayout (line 49) | async function RootLayout({
FILE: docs/src/app/native-mode/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/next/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/og/[...slug]/route.tsx
function GET (line 4) | async function GET(
FILE: docs/src/app/og/og-image.tsx
function loadFonts (line 10) | async function loadFonts() {
function renderOgImage (line 20) | async function renderOgImage(title: string) {
FILE: docs/src/app/og/route.tsx
function GET (line 3) | async function GET() {
FILE: docs/src/app/profiler/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/providers/browser-use/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/providers/browserbase/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/providers/browserless/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/providers/kernel/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/quick-start/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/security/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/selectors/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/sessions/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/skills/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/snapshots/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/app/streaming/layout.tsx
function Layout (line 5) | function Layout({ children }: { children: React.ReactNode }) {
FILE: docs/src/components/code-block.tsx
constant PLACEHOLDER_PREFIX (line 140) | const PLACEHOLDER_PREFIX = "\u200B\u200B";
constant PLACEHOLDER_SUFFIX (line 141) | const PLACEHOLDER_SUFFIX = "\u200B\u200B";
function shieldPlaceholders (line 143) | function shieldPlaceholders(code: string): string {
function restorePlaceholders (line 147) | function restorePlaceholders(html: string): string {
type CodeBlockProps (line 154) | interface CodeBlockProps {
function CodeBlock (line 159) | async function CodeBlock({ code, lang = "bash" }: CodeBlockProps) {
FILE: docs/src/components/copy-button.tsx
type CopyButtonProps (line 5) | interface CopyButtonProps {
function CopyButton (line 9) | function CopyButton({ code }: CopyButtonProps) {
FILE: docs/src/components/copy-page-button.tsx
function CopyPageButton (line 6) | function CopyPageButton() {
FILE: docs/src/components/diff-demo.tsx
function DiffLine (line 3) | function DiffLine({ line }: { line: string }) {
function CommandLine (line 13) | function CommandLine({ children }: { children: string }) {
function Terminal (line 22) | function Terminal({ children }: { children: React.ReactNode }) {
function PageMockup (line 37) | function PageMockup({
function DiffDemo (line 206) | function DiffDemo() {
FILE: docs/src/components/docs-chat.tsx
constant STORAGE_KEY (line 16) | const STORAGE_KEY = "docs-chat-messages";
constant DESKTOP_DEFAULT_WIDTH (line 19) | const DESKTOP_DEFAULT_WIDTH = 400;
constant DESKTOP_MIN_WIDTH (line 20) | const DESKTOP_MIN_WIDTH = 300;
constant DESKTOP_MAX_WIDTH (line 21) | const DESKTOP_MAX_WIDTH = 700;
function setCookie (line 23) | function setCookie(name: string, value: string) {
constant TOOL_LABELS (line 27) | const TOOL_LABELS: Record<
function isToolPart (line 35) | function isToolPart(part: { type: string }): part is {
function getToolName (line 47) | function getToolName(part: { type: string; toolName?: string }): string {
function ToolCallDisplay (line 52) | function ToolCallDisplay({
constant SUGGESTIONS (line 117) | const SUGGESTIONS = [
function DocsChat (line 125) | function DocsChat({
FILE: docs/src/components/docs-mobile-nav.tsx
function DocsMobileNav (line 14) | function DocsMobileNav() {
FILE: docs/src/components/docs-sidebar.tsx
function DocsSidebar (line 8) | function DocsSidebar() {
FILE: docs/src/components/header.tsx
function Header (line 7) | function Header() {
FILE: docs/src/components/search.tsx
type SearchResult (line 8) | type SearchResult = {
function Search (line 15) | function Search() {
FILE: docs/src/components/theme-provider.tsx
function ThemeProvider (line 5) | function ThemeProvider({ children }: { children: React.ReactNode }) {
FILE: docs/src/components/theme-toggle.tsx
function ThemeToggle (line 6) | function ThemeToggle() {
FILE: docs/src/components/ui/dialog.tsx
function Dialog (line 8) | function Dialog({
function DialogPortal (line 14) | function DialogPortal({
function DialogOverlay (line 20) | function DialogOverlay({
function DialogContent (line 36) | function DialogContent({
function DialogTitle (line 79) | function DialogTitle({
FILE: docs/src/components/ui/sheet.tsx
function Sheet (line 8) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
function SheetTrigger (line 12) | function SheetTrigger({
function SheetClose (line 18) | function SheetClose({
function SheetPortal (line 24) | function SheetPortal({
function SheetOverlay (line 30) | function SheetOverlay({
function SheetContent (line 46) | function SheetContent({
function SheetHeader (line 92) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
function SheetFooter (line 102) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
function SheetTitle (line 112) | function SheetTitle({
function SheetDescription (line 125) | function SheetDescription({
FILE: docs/src/lib/docs-navigation.ts
type NavItem (line 1) | type NavItem = {
type NavSection (line 6) | type NavSection = {
FILE: docs/src/lib/mdx-to-markdown.ts
function mdxToCleanMarkdown (line 7) | function mdxToCleanMarkdown(raw: string): string {
FILE: docs/src/lib/page-metadata.ts
constant DESCRIPTION (line 4) | const DESCRIPTION =
function pageMetadata (line 7) | function pageMetadata(slug: string): Metadata {
FILE: docs/src/lib/page-titles.ts
constant PAGE_TITLES (line 1) | const PAGE_TITLES: Record<string, string> = {
function getPageTitle (line 28) | function getPageTitle(slug: string): string | null {
FILE: docs/src/lib/rate-limit.ts
function getRedis (line 8) | function getRedis(): Redis | null {
constant MINUTE_LIMIT (line 24) | const MINUTE_LIMIT = Number(process.env.RATE_LIMIT_PER_MINUTE) || 10;
constant DAILY_LIMIT (line 25) | const DAILY_LIMIT = Number(process.env.RATE_LIMIT_PER_DAY) || 100;
FILE: docs/src/lib/search-index.ts
type IndexEntry (line 6) | type IndexEntry = {
function stripMarkdown (line 15) | function stripMarkdown(md: string): string {
function mdxFileForSlug (line 27) | function mdxFileForSlug(slug: string): string {
function getSearchIndex (line 36) | async function getSearchIndex(): Promise<IndexEntry[]> {
FILE: docs/src/lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: examples/environments/app/actions/browse.ts
type EnvStatus (line 3) | type EnvStatus = {
function getEnvStatus (line 9) | async function getEnvStatus(): Promise<EnvStatus> {
FILE: examples/environments/app/api/browse/route.ts
function POST (line 7) | async function POST(req: NextRequest) {
FILE: examples/environments/app/layout.tsx
function RootLayout (line 11) | function RootLayout({
FILE: examples/environments/app/page.tsx
constant MOBILE_QUERY (line 25) | const MOBILE_QUERY = "(max-width: 767px)";
function useIsMobile (line 34) | function useIsMobile() {
function useTheme (line 38) | function useTheme() {
type Action (line 62) | type Action = "screenshot" | "snapshot";
type StepInfo (line 64) | type StepInfo = {
type BrowseResult (line 70) | type BrowseResult = {
function formatError (line 78) | function formatError(raw: string): string {
function SegmentedControl (line 86) | function SegmentedControl<T extends string>({
function StepIndicator (line 118) | function StepIndicator({ step }: { step: StepInfo }) {
function ErrorDisplay (line 150) | function ErrorDisplay({ error }: { error: string }) {
function streamBrowse (line 176) | async function streamBrowse(
function Home (line 231) | function Home() {
FILE: examples/environments/components/ui/alert.tsx
function Alert (line 22) | function Alert({
function AlertTitle (line 37) | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
function AlertDescription (line 50) | function AlertDescription({
function AlertAction (line 66) | function AlertAction({ className, ...props }: React.ComponentProps<"div"...
FILE: examples/environments/components/ui/badge.tsx
function Badge (line 30) | function Badge({
FILE: examples/environments/components/ui/button.tsx
function Button (line 45) | function Button({
FILE: examples/environments/components/ui/input.tsx
function Input (line 6) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...
FILE: examples/environments/components/ui/label.tsx
function Label (line 7) | function Label({ className, ...props }: React.ComponentProps<"label">) {
FILE: examples/environments/components/ui/resizable.tsx
function ResizablePanelGroup (line 7) | function ResizablePanelGroup({
function ResizablePanel (line 23) | function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
function ResizableHandle (line 27) | function ResizableHandle({
FILE: examples/environments/components/ui/select.tsx
function SelectGroup (line 11) | function SelectGroup({ className, ...props }: SelectPrimitive.Group.Prop...
function SelectValue (line 21) | function SelectValue({ className, ...props }: SelectPrimitive.Value.Prop...
function SelectTrigger (line 31) | function SelectTrigger({
function SelectContent (line 59) | function SelectContent({
function SelectLabel (line 98) | function SelectLabel({
function SelectItem (line 111) | function SelectItem({
function SelectSeparator (line 139) | function SelectSeparator({
function SelectScrollUpButton (line 152) | function SelectScrollUpButton({
function SelectScrollDownButton (line 171) | function SelectScrollDownButton({
FILE: examples/environments/components/ui/separator.tsx
function Separator (line 7) | function Separator({
FILE: examples/environments/components/ui/toggle-group.tsx
function ToggleGroup (line 23) | function ToggleGroup({
function ToggleGroupItem (line 59) | function ToggleGroupItem({
FILE: examples/environments/components/ui/toggle.tsx
function Toggle (line 29) | function Toggle({
FILE: examples/environments/lib/agent-browser-sandbox.ts
type SandboxResult (line 13) | type SandboxResult = {
type StepEvent (line 19) | type StepEvent = {
type OnStep (line 25) | type OnStep = (event: StepEvent) => void;
constant SNAPSHOT_ID (line 27) | const SNAPSHOT_ID = process.env.AGENT_BROWSER_SNAPSHOT_ID;
constant CHROMIUM_SYSTEM_DEPS (line 29) | const CHROMIUM_SYSTEM_DEPS = [
function getSandboxCredentials (line 62) | function getSandboxCredentials():
function runStep (line 79) | async function runStep<T>(
function bootstrapSandbox (line 100) | async function bootstrapSandbox(
function createSandbox (line 117) | async function createSandbox(
function exec (line 145) | async function exec(
function screenshotUrl (line 173) | async function screenshotUrl(
function snapshotUrl (line 235) | async function snapshotUrl(
function runCommands (line 282) | async function runCommands(
function createSnapshot (line 303) | async function createSnapshot(): Promise<string> {
function tryParseJson (line 317) | function tryParseJson(str: string): any {
FILE: examples/environments/lib/constants.ts
constant ALLOWED_URLS (line 1) | const ALLOWED_URLS = [
type AllowedUrl (line 8) | type AllowedUrl = (typeof ALLOWED_URLS)[number];
FILE: examples/environments/lib/rate-limit.ts
function getRedis (line 7) | function getRedis(): Redis | null {
constant MINUTE_LIMIT (line 22) | const MINUTE_LIMIT = Number(process.env.RATE_LIMIT_PER_MINUTE) || 10;
constant DAILY_LIMIT (line 23) | const DAILY_LIMIT = Number(process.env.RATE_LIMIT_PER_DAY) || 100;
FILE: examples/environments/lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: examples/environments/scripts/create-snapshot.ts
function main (line 40) | async function main() {
FILE: scripts/postinstall.js
function isMusl (line 24) | function isMusl() {
constant GITHUB_REPO (line 48) | const GITHUB_REPO = 'vercel-labs/agent-browser';
constant DOWNLOAD_URL (line 49) | const DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/downloa...
function downloadFile (line 51) | async function downloadFile(url, dest) {
function main (line 83) | async function main() {
function findSystemChrome (line 131) | function findSystemChrome() {
function showInstallReminder (line 162) | function showInstallReminder() {
function fixGlobalInstallBin (line 192) | async function fixGlobalInstallBin() {
function fixUnixSymlink (line 204) | async function fixUnixSymlink() {
function fixWindowsShims (line 243) | async function fixWindowsShims() {
Condensed preview — 208 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,627K chars).
[
{
"path": ".changeset/README.md",
"chars": 632,
"preview": "# Changesets\n\nThis project uses [Changesets](https://github.com/changesets/changesets) for versioning and changelog gene"
},
{
"path": ".changeset/config.json",
"chars": 271,
"preview": "{\n \"$schema\": \"https://unpkg.com/@changesets/config@3.1.1/schema.json\",\n \"changelog\": \"@changesets/cli/changelog\",\n \""
},
{
"path": ".claude-plugin/marketplace.json",
"chars": 543,
"preview": "{\n \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n \"name\": \"agent-browser\",\n \"description\": "
},
{
"path": ".github/workflows/ci.yml",
"chars": 7332,
"preview": "name: CI\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n workflow_dispatch:\n\njobs:\n version-sy"
},
{
"path": ".github/workflows/release.yml",
"chars": 8599,
"preview": "name: Release\n\non:\n push:\n branches:\n - main\n workflow_dispatch:\n\nconcurrency: ${{ github.workflow }}-${{ gith"
},
{
"path": ".gitignore",
"chars": 610,
"preview": "# Dependencies\nnode_modules/\n\n# Build output\ndist/\n\n# Native binaries (keep the launcher scripts)\nbin/agent-browser-*\n!b"
},
{
"path": ".husky/pre-commit",
"chars": 67,
"preview": "node scripts/sync-version.js\ngit add cli/Cargo.toml cli/Cargo.lock\n"
},
{
"path": ".prettierrc",
"chars": 106,
"preview": "{\n \"semi\": true,\n \"singleQuote\": true,\n \"trailingComma\": \"es5\",\n \"printWidth\": 100,\n \"tabWidth\": 2\n}\n"
},
{
"path": "AGENTS.md",
"chars": 3495,
"preview": "# AGENTS.md\n\nInstructions for AI coding agents working with this codebase.\n\n## Package Manager\n\nThis project uses **pnpm"
},
{
"path": "CHANGELOG.md",
"chars": 31939,
"preview": "# agent-browser\n\n## 0.21.2\n\n### Patch Changes\n\n- 757626f: ### Bug Fixes\n\n - **Deduplicate text content in snapshots** -"
},
{
"path": "LICENSE",
"chars": 10931,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 47545,
"preview": "# agent-browser\n\nHeadless browser automation CLI for AI agents. Fast native Rust CLI.\n\n## Installation\n\n### Global Insta"
},
{
"path": "benchmarks/.gitignore",
"chars": 27,
"preview": "node_modules/\nresults.json\n"
},
{
"path": "benchmarks/README.md",
"chars": 3222,
"preview": "# agent-browser Daemon Benchmarks\n\nCompares command latency and system metrics between the **Node.js daemon** (published"
},
{
"path": "benchmarks/bench.ts",
"chars": 27076,
"preview": "/**\n * Node.js Daemon vs Rust Native Daemon benchmark.\n *\n * Compares the last published npm version (Node.js daemon) ag"
},
{
"path": "benchmarks/package.json",
"chars": 229,
"preview": "{\n \"name\": \"agent-browser-benchmarks\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n "
},
{
"path": "benchmarks/scenarios.ts",
"chars": 2948,
"preview": "/**\n * Benchmark scenarios for comparing Node.js daemon vs Rust native daemon.\n *\n * Each scenario defines CLI commands "
},
{
"path": "benchmarks/tsconfig.json",
"chars": 258,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"esModul"
},
{
"path": "bin/agent-browser.js",
"chars": 3184,
"preview": "#!/usr/bin/env node\n\n/**\n * Cross-platform CLI wrapper for agent-browser\n * \n * This wrapper enables npx support on Wind"
},
{
"path": "cli/Cargo.toml",
"chars": 1551,
"preview": "[package]\nname = \"agent-browser\"\nversion = \"0.21.2\"\nedition = \"2021\"\ndescription = \"Fast browser automation CLI for AI a"
},
{
"path": "cli/build.rs",
"chars": 16311,
"preview": "use std::collections::HashSet;\nuse std::env;\nuse std::fs;\nuse std::path::Path;\n\nfn main() {\n let protocol_dir = Path:"
},
{
"path": "cli/cdp-protocol/browser_protocol.json",
"chars": 1418351,
"preview": "{\n \"version\": {\n \"major\": \"1\",\n \"minor\": \"3\"\n },\n \"domains\": [\n {\n \"domain\": \"A"
},
{
"path": "cli/cdp-protocol/js_protocol.json",
"chars": 181640,
"preview": "{\n \"version\": {\n \"major\": \"1\",\n \"minor\": \"3\"\n },\n \"domains\": [\n {\n \"domain\": \"C"
},
{
"path": "cli/src/color.rs",
"chars": 4090,
"preview": "//! Color output utilities respecting NO_COLOR environment variable.\n//!\n//! When the NO_COLOR environment variable is p"
},
{
"path": "cli/src/commands.rs",
"chars": 155815,
"preview": "use base64::{engine::general_purpose::STANDARD, Engine};\nuse serde_json::{json, Value};\nuse std::io::{self, BufRead};\n\nu"
},
{
"path": "cli/src/connection.rs",
"chars": 22481,
"preview": "use serde::{Deserialize, Serialize};\nuse serde_json::Value;\nuse std::env;\nuse std::fs;\nuse std::io::{BufRead, BufReader,"
},
{
"path": "cli/src/flags.rs",
"chars": 47003,
"preview": "use crate::color;\nuse serde::Deserialize;\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\nconst CONFIG_DIR: "
},
{
"path": "cli/src/install.rs",
"chars": 19978,
"preview": "use crate::color;\nuse std::fs;\nuse std::io::{self, Write};\nuse std::path::{Path, PathBuf};\nuse std::process::{exit, Comm"
},
{
"path": "cli/src/main.rs",
"chars": 35071,
"preview": "mod color;\nmod commands;\nmod connection;\nmod flags;\nmod install;\nmod native;\nmod output;\n#[cfg(test)]\nmod test_utils;\nmo"
},
{
"path": "cli/src/native/actions.rs",
"chars": 242430,
"preview": "use serde_json::{json, Value};\nuse std::collections::HashMap;\nuse std::env;\nuse std::io::Write;\nuse std::path::PathBuf;\n"
},
{
"path": "cli/src/native/auth.rs",
"chars": 18976,
"preview": "use aes_gcm::{aead::Aead, aead::KeyInit, Aes256Gcm};\nuse base64::{engine::general_purpose::STANDARD, Engine};\nuse serde:"
},
{
"path": "cli/src/native/browser.rs",
"chars": 54483,
"preview": "use serde_json::{json, Value};\nuse std::collections::HashSet;\nuse std::future::Future;\nuse std::sync::Arc;\nuse std::time"
},
{
"path": "cli/src/native/cdp/chrome.rs",
"chars": 33673,
"preview": "use std::io::{BufRead, BufReader, Write};\nuse std::path::{Path, PathBuf};\nuse std::process::{Child, Command, Stdio};\nuse"
},
{
"path": "cli/src/native/cdp/client.rs",
"chars": 12561,
"preview": "use std::collections::HashMap;\nuse std::sync::atomic::{AtomicU64, Ordering};\nuse std::sync::Arc;\n\nuse futures_util::{Sin"
},
{
"path": "cli/src/native/cdp/discovery.rs",
"chars": 11207,
"preview": "use std::time::Duration;\n\nuse futures_util::{SinkExt, StreamExt};\nuse tokio_tungstenite::tungstenite::Message;\n\nuse supe"
},
{
"path": "cli/src/native/cdp/lightpanda.rs",
"chars": 14886,
"preview": "use std::collections::VecDeque;\nuse std::io::{BufRead, BufReader};\nuse std::net::TcpListener;\nuse std::path::PathBuf;\nus"
},
{
"path": "cli/src/native/cdp/mod.rs",
"chars": 86,
"preview": "pub mod chrome;\npub mod client;\npub mod discovery;\npub mod lightpanda;\npub mod types;\n"
},
{
"path": "cli/src/native/cdp/types.rs",
"chars": 16918,
"preview": "use serde::{Deserialize, Deserializer, Serialize};\nuse serde_json::Value;\n\n/// Deserialize a value that may be either a "
},
{
"path": "cli/src/native/cookies.rs",
"chars": 2248,
"preview": "use serde::{Deserialize, Serialize};\nuse serde_json::{json, Value};\n\nuse super::cdp::client::CdpClient;\n\n#[derive(Debug,"
},
{
"path": "cli/src/native/daemon.rs",
"chars": 13199,
"preview": "use serde_json::Value;\nuse std::env;\nuse std::fs;\nuse std::io::Write;\nuse std::path::PathBuf;\nuse std::process;\nuse std:"
},
{
"path": "cli/src/native/diff.rs",
"chars": 8468,
"preview": "use serde_json::{json, Value};\nuse similar::{ChangeTag, TextDiff};\n\npub struct ScreenshotDiffResult {\n pub total_pixe"
},
{
"path": "cli/src/native/e2e_tests.rs",
"chars": 99288,
"preview": "//! End-to-end tests for the native daemon.\n//!\n//! These tests launch a real Chrome instance and exercise the full comm"
},
{
"path": "cli/src/native/element.rs",
"chars": 29124,
"preview": "use std::collections::HashMap;\n\nuse serde_json::Value;\n\nuse super::cdp::client::CdpClient;\nuse super::cdp::types::*;\n\n#["
},
{
"path": "cli/src/native/inspect_server.rs",
"chars": 12042,
"preview": "use std::io::Write;\nuse std::sync::atomic::{AtomicI64, Ordering};\nuse std::sync::Arc;\n\nuse futures_util::{SinkExt, Strea"
},
{
"path": "cli/src/native/interaction.rs",
"chars": 31370,
"preview": "use serde_json::Value;\n\nuse super::cdp::client::CdpClient;\nuse super::cdp::types::*;\nuse super::element::{resolve_elemen"
},
{
"path": "cli/src/native/mod.rs",
"chars": 843,
"preview": "#[allow(dead_code)]\npub mod actions;\n#[allow(dead_code)]\npub mod auth;\n#[allow(dead_code)]\npub mod browser;\n#[allow(dead"
},
{
"path": "cli/src/native/network.rs",
"chars": 11845,
"preview": "use serde_json::{json, Value};\nuse std::collections::HashMap;\n\nuse super::cdp::client::CdpClient;\n\npub async fn set_extr"
},
{
"path": "cli/src/native/parity_tests.rs",
"chars": 21006,
"preview": "//! Parity tests for the native daemon's command interface.\n//!\n//! These unit tests verify:\n//! - All documented action"
},
{
"path": "cli/src/native/policy.rs",
"chars": 7636,
"preview": "use serde::{Deserialize, Serialize};\nuse std::collections::HashSet;\nuse std::env;\nuse std::fs;\nuse std::path::PathBuf;\n\n"
},
{
"path": "cli/src/native/providers.rs",
"chars": 11452,
"preview": "//! Browser provider connections for remote CDP sessions.\n//!\n//! Supports Browserbase, Browserless, Browser Use, and Ke"
},
{
"path": "cli/src/native/recording.rs",
"chars": 9579,
"preview": "use serde_json::{json, Value};\nuse std::process::Stdio;\nuse std::sync::atomic::{AtomicU64, Ordering};\nuse std::sync::Arc"
},
{
"path": "cli/src/native/screenshot.rs",
"chars": 20307,
"preview": "use serde::Serialize;\nuse serde_json::Value;\nuse std::path::PathBuf;\n\nuse super::cdp::client::CdpClient;\nuse super::cdp:"
},
{
"path": "cli/src/native/snapshot.rs",
"chars": 36750,
"preview": "use std::collections::HashMap;\n\nuse serde_json::Value;\n\nuse super::cdp::client::CdpClient;\nuse super::cdp::types::{\n "
},
{
"path": "cli/src/native/state.rs",
"chars": 20332,
"preview": "use aes_gcm::{aead::Aead, aead::KeyInit, Aes256Gcm};\nuse serde::{Deserialize, Serialize};\nuse serde_json::{json, Value};"
},
{
"path": "cli/src/native/storage.rs",
"chars": 2565,
"preview": "use serde_json::{json, Value};\n\nuse super::cdp::client::CdpClient;\nuse super::cdp::types::EvaluateParams;\n\npub async fn "
},
{
"path": "cli/src/native/stream.rs",
"chars": 24850,
"preview": "use serde_json::{json, Value};\nuse std::net::SocketAddr;\nuse std::sync::Arc;\n\nuse futures_util::{SinkExt, StreamExt};\nus"
},
{
"path": "cli/src/native/test_fixtures/drag_probe.html",
"chars": 3433,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Drag Probe</title>\n <style>\n body {\n "
},
{
"path": "cli/src/native/test_fixtures/html5_drag_probe.html",
"chars": 2336,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>HTML5 Drag Probe</title>\n <style>\n body {\n"
},
{
"path": "cli/src/native/test_fixtures/pointer_capture_probe.html",
"chars": 3213,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Pointer Capture Probe</title>\n <style>\n bo"
},
{
"path": "cli/src/native/tracing.rs",
"chars": 11134,
"preview": "use serde_json::{json, Value};\nuse std::path::PathBuf;\n\nuse super::cdp::client::CdpClient;\n\nconst MAX_PROFILE_EVENTS: us"
},
{
"path": "cli/src/native/webdriver/appium.rs",
"chars": 7355,
"preview": "use serde_json::{json, Value};\nuse std::process::{Child, Command, Stdio};\nuse std::time::Duration;\n\nuse super::client::W"
},
{
"path": "cli/src/native/webdriver/backend.rs",
"chars": 4298,
"preview": "use async_trait::async_trait;\nuse serde_json::Value;\n\n/// Abstract backend for browser automation. CDP (Chromium) and We"
},
{
"path": "cli/src/native/webdriver/client.rs",
"chars": 9868,
"preview": "use serde_json::{json, Value};\nuse std::time::Duration;\n\npub struct WebDriverClient {\n base_url: String,\n session_"
},
{
"path": "cli/src/native/webdriver/ios.rs",
"chars": 7340,
"preview": "use serde_json::{json, Value};\nuse std::process::Command;\n\n#[derive(Debug, Clone)]\npub struct IosDevice {\n pub name: "
},
{
"path": "cli/src/native/webdriver/mod.rs",
"chars": 93,
"preview": "pub mod appium;\npub mod backend;\npub mod client;\npub mod ios;\npub mod safari;\npub mod types;\n"
},
{
"path": "cli/src/native/webdriver/safari.rs",
"chars": 1964,
"preview": "use std::path::PathBuf;\nuse std::process::{Child, Command, Stdio};\nuse std::time::Duration;\n\npub struct SafariDriverProc"
},
{
"path": "cli/src/native/webdriver/types.rs",
"chars": 2311,
"preview": "use serde::{Deserialize, Serialize};\nuse serde_json::Value;\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase"
},
{
"path": "cli/src/output.rs",
"chars": 97043,
"preview": "use std::sync::OnceLock;\n\nuse crate::color;\nuse crate::connection::Response;\n\nstatic BOUNDARY_NONCE: OnceLock<String> = "
},
{
"path": "cli/src/test_utils.rs",
"chars": 1472,
"preview": "use std::sync::{Mutex, MutexGuard};\n\n/// Global mutex shared across all test modules to prevent parallel tests from\n/// "
},
{
"path": "cli/src/upgrade.rs",
"chars": 5436,
"preview": "use crate::color;\nuse std::process::{exit, Command, Stdio};\n\nconst CURRENT_VERSION: &str = env!(\"CARGO_PKG_VERSION\");\nco"
},
{
"path": "cli/src/validation.rs",
"chars": 503,
"preview": "/// Check if a session name is valid (alphanumeric, hyphens, and underscores only)\npub fn is_valid_session_name(name: &s"
},
{
"path": "docker/Dockerfile.build",
"chars": 1071,
"preview": "# Multi-platform Rust cross-compilation image\nFROM rust:1.85-bookworm\n\n# Install cross-compilation toolchains\nRUN apt-ge"
},
{
"path": "docker/docker-compose.yml",
"chars": 2513,
"preview": "# Docker Compose for building agent-browser\n# Usage: docker compose -f docker/docker-compose.yml run build-linux\n# "
},
{
"path": "docs/.gitignore",
"chars": 480,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "docs/components.json",
"chars": 451,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {"
},
{
"path": "docs/eslint.config.mjs",
"chars": 465,
"preview": "import { defineConfig, globalIgnores } from \"eslint/config\";\nimport nextVitals from \"eslint-config-next/core-web-vitals\""
},
{
"path": "docs/mdx-components.tsx",
"chars": 2365,
"preview": "import type { MDXComponents } from \"mdx/types\";\nimport Link from \"next/link\";\nimport { CodeBlock } from \"@/components/co"
},
{
"path": "docs/next.config.mjs",
"chars": 283,
"preview": "import createMDX from \"@next/mdx\";\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n pageExtensions: [\"js"
},
{
"path": "docs/package.json",
"chars": 1205,
"preview": "{\n \"name\": \"docs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"portless agent-browser next dev\""
},
{
"path": "docs/postcss.config.mjs",
"chars": 94,
"preview": "const config = {\n plugins: {\n \"@tailwindcss/postcss\": {},\n },\n};\n\nexport default config;\n"
},
{
"path": "docs/src/app/api/docs-chat/route.ts",
"chars": 4128,
"preview": "import { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { convertToModelMessages, stepCountIs, strea"
},
{
"path": "docs/src/app/api/docs-markdown/route.ts",
"chars": 1125,
"preview": "import { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { NextRequest, NextResponse } from \"next/ser"
},
{
"path": "docs/src/app/api/search/route.ts",
"chars": 1922,
"preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getSearchIndex } from \"@/lib/search-index\";\n\nexport as"
},
{
"path": "docs/src/app/cdp-mode/layout.tsx",
"chars": 203,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"cdp-mode\");\n\nexport default f"
},
{
"path": "docs/src/app/cdp-mode/page.mdx",
"chars": 4605,
"preview": "# CDP Mode\n\nConnect to an existing browser via Chrome DevTools Protocol:\n\n```bash\n# Start Chrome with: google-chrome --r"
},
{
"path": "docs/src/app/changelog/layout.tsx",
"chars": 204,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"changelog\");\n\nexport default "
},
{
"path": "docs/src/app/changelog/page.mdx",
"chars": 28334,
"preview": "# Changelog\n\n## v0.21.0\n\n<p className=\"text-[#888] text-sm\">March 2026</p>\n\n### New Commands\n\n- **`batch`** -- Execute m"
},
{
"path": "docs/src/app/commands/layout.tsx",
"chars": 203,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"commands\");\n\nexport default f"
},
{
"path": "docs/src/app/commands/page.mdx",
"chars": 15898,
"preview": "# Commands\n\n## Core\n\n```bash\nagent-browser open <url> # Navigate (aliases: goto, navigate)\nagent-browser cl"
},
{
"path": "docs/src/app/configuration/layout.tsx",
"chars": 208,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"configuration\");\n\nexport defa"
},
{
"path": "docs/src/app/configuration/page.mdx",
"chars": 10254,
"preview": "# Configuration\n\nCreate an `agent-browser.json` file to set persistent defaults instead of repeating flags on every comm"
},
{
"path": "docs/src/app/diffing/layout.tsx",
"chars": 202,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"diffing\");\n\nexport default fu"
},
{
"path": "docs/src/app/diffing/page.mdx",
"chars": 6470,
"preview": "import { DiffDemo } from \"@/components/diff-demo\"\n\n# Diffing\n\nCompare page states to detect changes -- structurally via "
},
{
"path": "docs/src/app/engines/chrome/layout.tsx",
"chars": 209,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"engines/chrome\");\n\nexport def"
},
{
"path": "docs/src/app/engines/chrome/page.mdx",
"chars": 2807,
"preview": "# Chrome\n\nChrome (and Chromium) is the default browser engine. agent-browser discovers, launches, and manages the Chrome"
},
{
"path": "docs/src/app/engines/lightpanda/layout.tsx",
"chars": 213,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"engines/lightpanda\");\n\nexport"
},
{
"path": "docs/src/app/engines/lightpanda/page.mdx",
"chars": 3081,
"preview": "# Lightpanda\n\n[Lightpanda](https://lightpanda.io/) is a headless browser engine built from scratch in Zig for machines. "
},
{
"path": "docs/src/app/globals.css",
"chars": 6009,
"preview": "@import \"tailwindcss\";\n@plugin \"tailwindcss-animate\";\n\n@source \"../../node_modules/streamdown/dist/index.js\";\n\n@custom-v"
},
{
"path": "docs/src/app/installation/layout.tsx",
"chars": 207,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"installation\");\n\nexport defau"
},
{
"path": "docs/src/app/installation/page.mdx",
"chars": 3296,
"preview": "# Installation\n\n## Global installation (recommended)\n\nInstalls the native Rust binary for maximum performance:\n\n```bash\n"
},
{
"path": "docs/src/app/ios/layout.tsx",
"chars": 198,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"ios\");\n\nexport default functi"
},
{
"path": "docs/src/app/ios/page.mdx",
"chars": 5307,
"preview": "# iOS Simulator\n\nControl real Mobile Safari in the iOS Simulator for authentic mobile\nweb testing. Uses Appium with XCUI"
},
{
"path": "docs/src/app/layout.tsx",
"chars": 3184,
"preview": "import type { Metadata } from \"next\";\nimport { Inter, Geist_Mono } from \"next/font/google\";\nimport { GeistPixelSquare } "
},
{
"path": "docs/src/app/native-mode/layout.tsx",
"chars": 206,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"native-mode\");\n\nexport defaul"
},
{
"path": "docs/src/app/native-mode/page.mdx",
"chars": 215,
"preview": "# Native Mode\n\nagent-browser is now 100% native Rust by default. The Node.js/Playwright daemon has been removed.\n\nThis p"
},
{
"path": "docs/src/app/next/layout.tsx",
"chars": 199,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"next\");\n\nexport default funct"
},
{
"path": "docs/src/app/next/page.mdx",
"chars": 6344,
"preview": "# Next.js + Vercel\n\nRun agent-browser from a Next.js app on Vercel using Vercel Sandbox.\nA Linux microVM spins up on dem"
},
{
"path": "docs/src/app/og/[...slug]/route.tsx",
"chars": 416,
"preview": "import { NextResponse } from \"next/server\";\nimport { getPageTitle, renderOgImage } from \"../og-image\";\n\nexport async fun"
},
{
"path": "docs/src/app/og/og-image.tsx",
"chars": 2698,
"preview": "import { ImageResponse } from \"next/og\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n"
},
{
"path": "docs/src/app/og/route.tsx",
"chars": 157,
"preview": "import { getPageTitle, renderOgImage } from \"./og-image\";\n\nexport async function GET() {\n const title = getPageTitle(\"\""
},
{
"path": "docs/src/app/page.mdx",
"chars": 2069,
"preview": "# agent-browser\n\nBrowser automation CLI designed for AI agents. Compact text output minimizes context usage. 100% native"
},
{
"path": "docs/src/app/profiler/layout.tsx",
"chars": 203,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"profiler\");\n\nexport default f"
},
{
"path": "docs/src/app/profiler/page.mdx",
"chars": 3747,
"preview": "# Profiler\n\nCapture Chrome DevTools performance profiles during browser automation.\nUse profiles to diagnose slow page l"
},
{
"path": "docs/src/app/providers/browser-use/layout.tsx",
"chars": 216,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"providers/browser-use\");\n\nexp"
},
{
"path": "docs/src/app/providers/browser-use/page.mdx",
"chars": 910,
"preview": "# Browser Use\n\n[Browser Use](https://browser-use.com) provides cloud browser infrastructure for AI agents. Use it when r"
},
{
"path": "docs/src/app/providers/browserbase/layout.tsx",
"chars": 216,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"providers/browserbase\");\n\nexp"
},
{
"path": "docs/src/app/providers/browserbase/page.mdx",
"chars": 815,
"preview": "# Browserbase\n\n[Browserbase](https://browserbase.com) provides remote browser infrastructure to make deployment of agent"
},
{
"path": "docs/src/app/providers/browserless/layout.tsx",
"chars": 216,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"providers/browserless\");\n\nexp"
},
{
"path": "docs/src/app/providers/browserless/page.mdx",
"chars": 1586,
"preview": "# Browserless\n\n[Browserless](https://browserless.io) provides cloud browser infrastructure with a Sessions API. Use it w"
},
{
"path": "docs/src/app/providers/kernel/layout.tsx",
"chars": 211,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"providers/kernel\");\n\nexport d"
},
{
"path": "docs/src/app/providers/kernel/page.mdx",
"chars": 1697,
"preview": "# Kernel\n\n[Kernel](https://www.kernel.sh) provides cloud browser infrastructure for AI agents with features like stealth"
},
{
"path": "docs/src/app/quick-start/layout.tsx",
"chars": 206,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"quick-start\");\n\nexport defaul"
},
{
"path": "docs/src/app/quick-start/page.mdx",
"chars": 2415,
"preview": "# Quick Start\n\n## Core workflow\n\nEvery browser automation follows this pattern:\n\n```bash\n# 1. Navigate\nagent-browser ope"
},
{
"path": "docs/src/app/security/layout.tsx",
"chars": 203,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"security\");\n\nexport default f"
},
{
"path": "docs/src/app/security/page.mdx",
"chars": 11715,
"preview": "# Security\n\nagent-browser includes security features to protect against credential exposure, prompt injection via untrus"
},
{
"path": "docs/src/app/selectors/layout.tsx",
"chars": 204,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"selectors\");\n\nexport default "
},
{
"path": "docs/src/app/selectors/page.mdx",
"chars": 1357,
"preview": "# Selectors\n\n## Refs (recommended)\n\nRefs provide deterministic element selection from snapshots. Best for AI agents.\n\n``"
},
{
"path": "docs/src/app/sessions/layout.tsx",
"chars": 203,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"sessions\");\n\nexport default f"
},
{
"path": "docs/src/app/sessions/page.mdx",
"chars": 6373,
"preview": "# Sessions\n\nRun multiple isolated browser instances:\n\n```bash\n# Different sessions\nagent-browser --session agent1 open s"
},
{
"path": "docs/src/app/skills/layout.tsx",
"chars": 201,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"skills\");\n\nexport default fun"
},
{
"path": "docs/src/app/skills/page.mdx",
"chars": 4450,
"preview": "# Skills\n\nagent-browser ships with skills that teach AI coding agents how to use it for specific workflows. Install a sk"
},
{
"path": "docs/src/app/snapshots/layout.tsx",
"chars": 204,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"snapshots\");\n\nexport default "
},
{
"path": "docs/src/app/snapshots/page.mdx",
"chars": 4800,
"preview": "# Snapshots\n\nThe `snapshot` command returns a compact accessibility tree with refs for element interaction.\n\n## Options\n"
},
{
"path": "docs/src/app/streaming/layout.tsx",
"chars": 204,
"preview": "import { pageMetadata } from \"@/lib/page-metadata\";\n\nexport const metadata = pageMetadata(\"streaming\");\n\nexport default "
},
{
"path": "docs/src/app/streaming/page.mdx",
"chars": 4065,
"preview": "# Streaming\n\nStream the browser viewport via WebSocket for live preview or \"pair browsing\"\nwhere a human can watch and i"
},
{
"path": "docs/src/components/code-block.tsx",
"chars": 4846,
"preview": "import { codeToHtml } from \"shiki\";\nimport { CopyButton } from \"./copy-button\";\n\nconst vercelDarkTheme = {\n name: \"verc"
},
{
"path": "docs/src/components/copy-button.tsx",
"chars": 1350,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\n\ninterface CopyButtonProps {\n code: string;\n}\n\nexport function CopyBut"
},
{
"path": "docs/src/components/copy-page-button.tsx",
"chars": 1976,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { usePathname } from \"next/navigation\";\n\nexport function CopyPag"
},
{
"path": "docs/src/components/diff-demo.tsx",
"chars": 6969,
"preview": "\"use client\";\n\nfunction DiffLine({ line }: { line: string }) {\n if (line.startsWith(\"+ \")) {\n return <div className="
},
{
"path": "docs/src/components/docs-chat.tsx",
"chars": 17449,
"preview": "\"use client\";\n\nimport {\n useRef,\n useEffect,\n useState,\n useCallback,\n type PointerEvent as ReactPointerEvent,\n} fr"
},
{
"path": "docs/src/components/docs-mobile-nav.tsx",
"chars": 2900,
"preview": "\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport Link from \"next/link\";\nimport { usePathname } from \"nex"
},
{
"path": "docs/src/components/docs-sidebar.tsx",
"chars": 1315,
"preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { cn } from \"@/lib/ut"
},
{
"path": "docs/src/components/header.tsx",
"chars": 3463,
"preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { ThemeToggle } from \"./theme-toggle\";\nimport { Search } from \"./sea"
},
{
"path": "docs/src/components/search.tsx",
"chars": 8432,
"preview": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useRouter } from \"next/navigat"
},
{
"path": "docs/src/components/theme-provider.tsx",
"chars": 352,
"preview": "\"use client\";\n\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\n\nexport function ThemeProvider({ child"
},
{
"path": "docs/src/components/theme-toggle.tsx",
"chars": 1684,
"preview": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { useEffect, useState } from \"react\";\n\nexport function The"
},
{
"path": "docs/src/components/ui/dialog.tsx",
"chars": 2914,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\n\nimport { cn } from"
},
{
"path": "docs/src/components/ui/sheet.tsx",
"chars": 4485,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Dialog as SheetPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/"
},
{
"path": "docs/src/lib/docs-navigation.ts",
"chars": 1790,
"preview": "export type NavItem = {\n name: string;\n href: string;\n};\n\nexport type NavSection = {\n title: string | null;\n items: "
},
{
"path": "docs/src/lib/mdx-to-markdown.ts",
"chars": 1136,
"preview": "/**\n * Converts raw MDX content to clean Markdown suitable for AI agents.\n *\n * Strips export/import statements and stan"
},
{
"path": "docs/src/lib/page-metadata.ts",
"chars": 941,
"preview": "import type { Metadata } from \"next\";\nimport { PAGE_TITLES } from \"./page-titles\";\n\nconst DESCRIPTION =\n \"Headless brow"
},
{
"path": "docs/src/lib/page-titles.ts",
"chars": 898,
"preview": "export const PAGE_TITLES: Record<string, string> = {\n \"\": \"Headless Browser\\nAutomation for AI\",\n installation: \"Insta"
},
{
"path": "docs/src/lib/rate-limit.ts",
"chars": 1662,
"preview": "import { Ratelimit } from \"@upstash/ratelimit\";\nimport { Redis } from \"@upstash/redis\";\n\n// Lazy initialization to avoid"
},
{
"path": "docs/src/lib/search-index.ts",
"chars": 1663,
"preview": "import { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { navigation } from \"./docs-navigation\";\nimp"
},
{
"path": "docs/src/lib/utils.ts",
"chars": 169,
"preview": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
},
{
"path": "docs/tsconfig.json",
"chars": 670,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n "
},
{
"path": "examples/environments/.gitignore",
"chars": 63,
"preview": "node_modules/\n.next/\n.env\n.env.local\n.env*.local\nnext-env.d.ts\n"
},
{
"path": "examples/environments/README.md",
"chars": 2638,
"preview": "# agent-browser Environments\n\nA demo of agent-browser running in a Vercel Sandbox. Pick a URL, take a screenshot or acce"
},
{
"path": "examples/environments/app/actions/browse.ts",
"chars": 248,
"preview": "\"use server\";\n\nexport type EnvStatus = {\n sandbox: {\n hasSnapshot: boolean;\n };\n};\n\nexport async function getEnvSta"
},
{
"path": "examples/environments/app/api/browse/route.ts",
"chars": 2548,
"preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport * as sandbox from \"@/lib/agent-browser-sandbox\";\nimport "
},
{
"path": "examples/environments/app/globals.css",
"chars": 4621,
"preview": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n@import \"shadcn/tailwind.css\";\n\n@custom-variant dark (&:is(.dark *));\n\n"
},
{
"path": "examples/environments/app/layout.tsx",
"chars": 576,
"preview": "import type { Metadata } from \"next\";\nimport { GeistSans } from \"geist/font/sans\";\nimport { GeistMono } from \"geist/font"
},
{
"path": "examples/environments/app/page.tsx",
"chars": 16495,
"preview": "\"use client\";\n\nimport { useState, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport { getEnvStatus } from \""
},
{
"path": "examples/environments/components/ui/alert.tsx",
"chars": 2048,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
},
{
"path": "examples/environments/components/ui/badge.tsx",
"chars": 1925,
"preview": "import { mergeProps } from \"@base-ui/react/merge-props\"\nimport { useRender } from \"@base-ui/react/use-render\"\nimport { c"
},
{
"path": "examples/environments/components/ui/button.tsx",
"chars": 3169,
"preview": "\"use client\"\n\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { cva, type VariantProps } from \""
},
{
"path": "examples/environments/components/ui/input.tsx",
"chars": 1040,
"preview": "import * as React from \"react\"\nimport { Input as InputPrimitive } from \"@base-ui/react/input\"\n\nimport { cn } from \"@/lib"
},
{
"path": "examples/environments/components/ui/label.tsx",
"chars": 518,
"preview": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Label({ className, ...props }: "
},
{
"path": "examples/environments/components/ui/resizable.tsx",
"chars": 1671,
"preview": "\"use client\"\n\nimport * as ResizablePrimitive from \"react-resizable-panels\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction R"
},
{
"path": "examples/environments/components/ui/select.tsx",
"chars": 6655,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\"\n\nimport {"
},
{
"path": "examples/environments/components/ui/separator.tsx",
"chars": 545,
"preview": "\"use client\"\n\nimport { Separator as SeparatorPrimitive } from \"@base-ui/react/separator\"\n\nimport { cn } from \"@/lib/util"
},
{
"path": "examples/environments/components/ui/toggle-group.tsx",
"chars": 3041,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Toggle as TogglePrimitive } from \"@base-ui/react/toggle\"\nimport { "
},
{
"path": "examples/environments/components/ui/toggle.tsx",
"chars": 1509,
"preview": "\"use client\"\n\nimport { Toggle as TogglePrimitive } from \"@base-ui/react/toggle\"\nimport { cva, type VariantProps } from \""
},
{
"path": "examples/environments/components.json",
"chars": 516,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"base-nova\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": "
},
{
"path": "examples/environments/lib/agent-browser-sandbox.ts",
"chars": 8436,
"preview": "/**\n * Run agent-browser inside a Vercel Sandbox.\n *\n * No external server needed -- a Linux microVM spins up on demand,"
},
{
"path": "examples/environments/lib/constants.ts",
"chars": 201,
"preview": "export const ALLOWED_URLS = [\n \"https://example.com\",\n \"https://ai-sdk.dev\",\n \"https://useworkflow.dev\",\n \"https://v"
},
{
"path": "examples/environments/lib/rate-limit.ts",
"chars": 1454,
"preview": "import { Ratelimit } from \"@upstash/ratelimit\";\nimport { Redis } from \"@upstash/redis\";\n\nlet _minuteRateLimit: Ratelimit"
},
{
"path": "examples/environments/lib/utils.ts",
"chars": 166,
"preview": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: Cla"
},
{
"path": "examples/environments/next.config.ts",
"chars": 104,
"preview": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n"
},
{
"path": "examples/environments/package.json",
"chars": 954,
"preview": "{\n \"name\": \"agent-browser-environments\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\","
},
{
"path": "examples/environments/postcss.config.mjs",
"chars": 94,
"preview": "const config = {\n plugins: {\n \"@tailwindcss/postcss\": {},\n },\n};\n\nexport default config;\n"
},
{
"path": "examples/environments/scripts/create-snapshot.ts",
"chars": 1647,
"preview": "/**\n * Create a Vercel Sandbox snapshot with agent-browser + Chromium pre-installed.\n *\n * Run once: npx tsx scripts/c"
},
{
"path": "examples/environments/tsconfig.json",
"chars": 712,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\n \"dom\",\n \"dom.iterable\",\n \"esnext\"\n ],\n "
},
{
"path": "package.json",
"chars": 2167,
"preview": "{\n \"name\": \"agent-browser\",\n \"version\": \"0.21.2\",\n \"description\": \"Headless browser automation CLI for AI agents\",\n "
},
{
"path": "scripts/build-all-platforms.sh",
"chars": 2172,
"preview": "#!/bin/bash\nset -e\n\n# Build agent-browser for all platforms using Docker\n# Usage: ./scripts/build-all-platforms.sh\n\nSCRI"
},
{
"path": "scripts/check-version-sync.js",
"chars": 1195,
"preview": "#!/usr/bin/env node\n\n/**\n * Verifies that package.json and cli/Cargo.toml have the same version.\n * Used in CI to catch "
},
{
"path": "scripts/copy-native.js",
"chars": 1162,
"preview": "#!/usr/bin/env node\n\n/**\n * Copies the compiled Rust binary to bin/ with platform-specific naming\n */\n\nimport { copyFile"
},
{
"path": "scripts/postinstall.js",
"chars": 9153,
"preview": "#!/usr/bin/env node\n\n/**\n * Postinstall script for agent-browser\n * \n * Downloads the platform-specific native binary if"
},
{
"path": "scripts/sync-version.js",
"chars": 2107,
"preview": "#!/usr/bin/env node\n\n/**\n * Syncs the version from package.json to all other config files.\n * Run this script before bui"
},
{
"path": "skills/agent-browser/SKILL.md",
"chars": 26368,
"preview": "---\nname: agent-browser\ndescription: Browser automation CLI for AI agents. Use when the user needs to interact with webs"
},
{
"path": "skills/agent-browser/references/authentication.md",
"chars": 8432,
"preview": "# Authentication Patterns\n\nLogin flows, session persistence, OAuth, 2FA, and authenticated browsing.\n\n**Related**: [sess"
},
{
"path": "skills/agent-browser/references/commands.md",
"chars": 11151,
"preview": "# Command Reference\n\nComplete reference for all agent-browser commands. For quick start and common patterns, see SKILL.m"
},
{
"path": "skills/agent-browser/references/profiling.md",
"chars": 3405,
"preview": "# Profiling\n\nCapture Chrome DevTools performance profiles during browser automation for performance analysis.\n\n**Related"
},
{
"path": "skills/agent-browser/references/proxy-support.md",
"chars": 4971,
"preview": "# Proxy Support\n\nProxy configuration for geo-testing, rate limiting avoidance, and corporate environments.\n\n**Related**:"
},
{
"path": "skills/agent-browser/references/session-management.md",
"chars": 4337,
"preview": "# Session Management\n\nMultiple isolated browser sessions with state persistence and concurrent browsing.\n\n**Related**: ["
},
{
"path": "skills/agent-browser/references/snapshot-refs.md",
"chars": 5315,
"preview": "# Snapshot and Refs\n\nCompact element references that reduce context usage dramatically for AI agents.\n\n**Related**: [com"
},
{
"path": "skills/agent-browser/references/video-recording.md",
"chars": 3569,
"preview": "# Video Recording\n\nCapture browser automation as video for debugging, documentation, or verification.\n\n**Related**: [com"
},
{
"path": "skills/agent-browser/templates/authenticated-session.sh",
"chars": 3678,
"preview": "#!/bin/bash\n# Template: Authenticated Session Workflow\n# Purpose: Login once, save state, reuse for subsequent runs\n# Us"
},
{
"path": "skills/agent-browser/templates/capture-workflow.sh",
"chars": 1832,
"preview": "#!/bin/bash\n# Template: Content Capture Workflow\n# Purpose: Extract content from web pages (text, screenshots, PDF)\n# Us"
},
{
"path": "skills/agent-browser/templates/form-automation.sh",
"chars": 1838,
"preview": "#!/bin/bash\n# Template: Form Automation Workflow\n# Purpose: Fill and submit web forms with validation\n# Usage: ./form-au"
}
]
// ... and 8 more files (download for full content)
About this extraction
This page contains the full source code of the vercel-labs/agent-browser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 208 files (3.3 MB), approximately 865.2k tokens, and a symbol index with 1653 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.