| disable-model-invocation | true |
| name | dev-browser |
| description | Browser automation with persistent page state. Use when users ask to navigate websites, fill forms, take screenshots, extract web data, test web apps, or automate browser workflows. Trigger phrases include "go to [url]", "click on", "fill out the form", "take a screenshot", "scrape", "automate", "test the website", "log into", or any browser interaction request. |
Dev Browser Skill
Browser automation that maintains page state across interactions. Two interfaces available:
- CLI (
db) — Token-efficient commands for common operations
- Scripts — Full Playwright API access for complex workflows
Quick Start
One-time setup: Install the dev-browser Chrome extension from https://github.com/SawyerHood/dev-browser/releases
Each session:
cd ~/.pi/agent/skills/dev-browser && npm run start-extension
devbrowse go "https://example.com"
devbrowse read
devbrowse click e5
devbrowse snap
Check server status anytime with devbrowse server.
CLI Reference (devbrowse)
The devbrowse CLI provides token-efficient commands for common browser automation tasks.
Setup
export PATH="$PATH:$HOME/.pi/agent/skills/dev-browser"
~/.pi/agent/skills/dev-browser/devbrowse <command>
Navigation
devbrowse go <url>
devbrowse back
devbrowse forward
devbrowse reload
Reading Page State
devbrowse read
devbrowse read --depth 3
devbrowse read --compact
devbrowse read --depth 3 --compact
devbrowse text
devbrowse title
devbrowse url
devbrowse html
Semantic Locators
Find and interact with elements by role, text, or label—no refs needed:
devbrowse locate role button --name "Submit"
devbrowse locate role button --name "Submit" --action click
devbrowse locate role textbox --action fill --value "hello"
devbrowse locate role link --all
devbrowse locate text "Sign In" --action click
devbrowse locate text "Accept" --exact --action click
devbrowse locate label "Email" --action fill --value "test@example.com"
Interaction
devbrowse click <ref>
devbrowse click --selector ".btn"
devbrowse click --x 100 --y 200
devbrowse click e5 --button right
devbrowse click e5 --count 2
devbrowse type <text>
devbrowse type <text> --ref e3
devbrowse type <text> --submit
devbrowse fill <ref> <text>
devbrowse select <ref> <value>
devbrowse hover <ref>
devbrowse focus <ref>
devbrowse clear <ref>
devbrowse press <key>
Scrolling
devbrowse scroll top
devbrowse scroll bottom
devbrowse scroll to <ref>
devbrowse scroll by 500
devbrowse scroll info
Frames (iframes)
devbrowse frame list
devbrowse frame switch 0
devbrowse frame switch "name"
devbrowse frame switch --selector "#iframe"
devbrowse frame main
After switching frames, commands like read, click, text operate within that frame.
Screenshots
devbrowse snap
devbrowse snap /path/to/file.png
devbrowse snap --full
Waiting
devbrowse wait <seconds>
devbrowse wait-for <selector>
devbrowse wait-load
devbrowse wait-url "/dashboard"
devbrowse wait-network
Page Management
devbrowse pages
devbrowse page <name>
devbrowse use <name>
devbrowse close [name]
Data Extraction
devbrowse cookies
devbrowse cookie <name>
devbrowse js <code>
Network Interception
devbrowse intercept-start
devbrowse intercept-stop
Global Options
--page, -p <name>
--json
--help, -h
Examples
devbrowse go "https://news.ycombinator.com"
devbrowse read --depth 3 --compact
devbrowse click e5
devbrowse go "https://example.com/login"
devbrowse locate label "Email" --action fill --value "user@example.com"
devbrowse locate label "Password" --action fill --value "secret"
devbrowse locate role button --name "Sign In" --action click
devbrowse wait-url "/dashboard"
devbrowse frame list
devbrowse frame switch 0
devbrowse read
devbrowse click e3
devbrowse frame main
devbrowse scroll bottom
devbrowse scroll to e15
devbrowse scroll info
devbrowse js "return document.querySelectorAll('.item').length"
devbrowse cookies
devbrowse cookie "session_token"
devbrowse click e10
devbrowse snap /tmp/after-click.png
Script-Based Approach
For complex workflows requiring full Playwright API access (request interception, complex conditionals, loops), write TypeScript scripts.
When to Use Scripts vs CLI
| Use Case | CLI (devbrowse) | Scripts |
|---|
| Navigation | ✅ | |
| Click/type/fill | ✅ | |
| Semantic locators | ✅ | |
| Read page (with depth/compact) | ✅ | |
| Screenshots | ✅ | |
| Scrolling | ✅ | |
| Frame switching | ✅ | |
| Wait for element/URL/network | ✅ | |
| Request interception | ⚠️ Basic logging | ✅ Full (modify/mock) |
| Complex scraping loops | | ✅ |
| Conditional logic | | ✅ |
| API replay with auth | | ✅ |
Writing Scripts
Run scripts from the skills/dev-browser/ directory:
cd ~/.pi/agent/skills/dev-browser && npx tsx <<'EOF'
import { connect, waitForPageLoad } from "@/client.js";
const client = await connect();
const page = await client.page("example");
await page.goto("https://example.com");
await waitForPageLoad(page);
console.log({ title: await page.title(), url: page.url() });
await client.disconnect();
EOF
Request Interception (Scripts Only)
import { connect, waitForPageLoad } from "@/client.js";
import * as fs from "node:fs";
const client = await connect();
const page = await client.page("api-capture");
page.on("response", async (response) => {
if (response.url().includes("/api/")) {
const data = await response.json();
fs.appendFileSync("/tmp/api-log.jsonl", JSON.stringify({
url: response.url(),
status: response.status(),
data
}) + "\n");
}
});
await page.goto("https://example.com");
await waitForPageLoad(page);
await client.disconnect();
Request Modification (Scripts Only)
page.route('**/*.{png,jpg,gif}', route => route.abort());
page.route('**/api/user', route => route.fulfill({
status: 200,
body: JSON.stringify({ name: 'Test User' })
}));
page.route('**/api/**', async route => {
const response = await route.fetch();
const json = await response.json();
json.modified = true;
await route.fulfill({ json });
});
Client API Reference
const client = await connect();
const page = await client.page("name");
const page = await client.page("name", { viewport: { width: 1920, height: 1080 } });
const pages = await client.list();
await client.close("name");
await client.disconnect();
const snapshot = await client.getAISnapshot("name");
const element = await client.selectSnapshotRef("name", "e5");
Server Modes
Extension Mode (Recommended)
Connects to your existing Chrome browser with all your logged-in sessions, cookies, and extensions.
cd ~/.pi/agent/skills/dev-browser
npm run start-extension
Wait for Extension connected message. Requires the dev-browser Chrome extension:
- Download from: https://github.com/SawyerHood/dev-browser/releases
- Unzip and load as unpacked extension in Chrome (
chrome://extensions → Developer mode → Load unpacked)
- Click the extension icon to connect
Standalone Mode
Launches a fresh Chromium browser (no existing sessions/cookies). Downloads Chromium on first run.
cd ~/.pi/agent/skills/dev-browser
./server.sh
./server.sh --headless
ARIA Snapshot Format
The devbrowse read command (and client.getAISnapshot()) returns a YAML accessibility tree:
- banner:
- link "Hacker News" [ref=e1]
- navigation:
- link "new" [ref=e2]
- main:
- list:
- listitem:
- link "Article Title" [ref=e8]
- link "328 comments" [ref=e9]
- contentinfo:
- textbox [ref=e10]
- /placeholder: "Search"
Key elements:
[ref=eN] — Element reference for interaction
[checked], [disabled], [expanded] — Element states
[level=N] — Heading level
/url:, /placeholder: — Element properties
Error Recovery
Page state persists after failures. Debug with:
devbrowse snap /tmp/debug.png
devbrowse url
devbrowse title
devbrowse text | head -50
Or with script:
const page = await client.page("problematic");
await page.screenshot({ path: "tmp/debug.png" });
console.log({ url: page.url(), title: await page.title() });
Environment Variables
DB_SERVER_URL=http://localhost:9222
Scraping Guide
For large datasets, intercept and replay network requests rather than scrolling the DOM. See references/scraping.md for the complete guide.