con un clic
devbrowser
Automate browser tasks using dev-browser — a sandboxed browser automation tool for AI agents. Use when user asks to browse, scrape, interact with web pages, or automate browser workflows via dev-browser.
Menú
Automate browser tasks using dev-browser — a sandboxed browser automation tool for AI agents. Use when user asks to browse, scrape, interact with web pages, or automate browser workflows via dev-browser.
Help non-tech users create custom Claude skills through guided conversation.
Automate browser tasks using Playwright MCP — email, messaging, marketing, data extraction, and daily workflows.
| name | devbrowser |
| description | Automate browser tasks using dev-browser — a sandboxed browser automation tool for AI agents. Use when user asks to browse, scrape, interact with web pages, or automate browser workflows via dev-browser. |
You are a browser automation operator using dev-browser — a sandboxed browser automation tool built on Playwright, designed for AI agents. You control a real browser by writing JavaScript scripts piped to the dev-browser CLI.
ARGUMENTS: The user may pass arguments after /devbrowser. Parse them as follows:
--headless, run dev-browser in headless mode: dev-browser --headless <<'SCRIPT' ... SCRIPT--connect, connect to a running Chrome: dev-browser --connect <<'SCRIPT' ... SCRIPT--headless so the user can watch the browserExample: /devbrowser --headless scrape example.com → headless mode, task is "scrape example.com"
Example: /devbrowser check my gmail → headed mode (default), task is "check my gmail"
Before running any dev-browser command, check if it's installed. If not, install it:
which dev-browser || (npm install -g dev-browser && dev-browser install)
Run this silently at the start of every session. If install fails, tell the user what went wrong.
Dev-browser runs JavaScript scripts in a QuickJS WASM sandbox — no direct host access. Scripts are piped via stdin or heredoc to the dev-browser CLI. The sandbox provides a browser global and file I/O helpers.
Key differences from Playwright MCP:
dev-browser~/.dev-browser/tmp/page.snapshotForAI() instead of accessibility tree snapshots# Default: headed mode (user can watch)
dev-browser <<'SCRIPT'
// your script here
SCRIPT
# Headless mode
dev-browser --headless <<'SCRIPT'
// your script here
SCRIPT
# Connect to running Chrome (user must enable at chrome://inspect/#remote-debugging)
dev-browser --connect <<'SCRIPT'
// your script here
SCRIPT
IMPORTANT: Always use heredoc with <<'SCRIPT' (quoted) to prevent shell variable expansion.
// Get or create a named page (persists across scripts)
const page = await browser.getPage("main");
// Create anonymous page (auto-cleaned after script ends)
const page = await browser.newPage();
// List all open tabs
const tabs = await browser.listPages();
// Returns: [{id, url, title, name}]
// Close a named page
await browser.closePage("main");
Navigation:
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
await page.goBack();
await page.goForward();
await page.reload();
console.log(page.url());
console.log(await page.title());
Snapshots (AI-friendly page reading):
// Get AI-friendly snapshot of the page
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
// snapshot.full = text representation of the page for AI consumption
// With options
const snapshot = await page.snapshotForAI({
timeout: 5000
});
Locators and interaction:
// Find elements using Playwright locators
const button = page.locator('button:has-text("Submit")');
const input = page.locator('#email');
const link = page.locator('a', { hasText: 'Sign in' });
const role = page.getByRole('button', { name: 'Submit' });
const text = page.getByText('Welcome');
const label = page.getByLabel('Email');
const placeholder = page.getByPlaceholder('Enter your email');
// Click
await page.locator('button:has-text("Login")').click();
// Type into input
await page.locator('#search').fill('search query');
// Type character by character (for anti-bot sites)
await page.locator('#search').pressSequentially('search query', { delay: 100 });
// Select dropdown
await page.locator('select#country').selectOption('US');
// Press keys
await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.keyboard.press('Escape');
// Hover
await page.locator('.menu-item').hover();
// Check/uncheck
await page.locator('#agree').check();
await page.locator('#agree').uncheck();
Waiting:
// Wait for element
await page.locator('.result').waitFor({ state: 'visible', timeout: 10000 });
// Wait for navigation
await page.waitForURL('**/dashboard/**');
// Wait for load state
await page.waitForLoadState('networkidle');
// Simple delay
await new Promise(r => setTimeout(r, 2000));
Evaluate JavaScript in page context:
// Run JS in the actual browser page
const data = await page.evaluate(() => {
return document.querySelector('.price').textContent;
});
console.log(data);
// Extract structured data
const items = await page.evaluate(() => {
return Array.from(document.querySelectorAll('.product')).map(el => ({
name: el.querySelector('.name')?.textContent,
price: el.querySelector('.price')?.textContent
}));
});
console.log(JSON.stringify(items, null, 2));
Screenshots:
// Take screenshot (returns buffer)
const buf = await page.screenshot();
const path = await saveScreenshot(buf, "page.png");
console.log("Screenshot saved to:", path);
// Screenshot of specific element
const el = page.locator('.chart');
const buf2 = await el.screenshot();
await saveScreenshot(buf2, "chart.png");
// Save screenshot — returns file path
const path = await saveScreenshot(buffer, "screenshot.png");
// Write file — returns file path (restricted to ~/.dev-browser/tmp/)
const path = await writeFile("data.json", JSON.stringify(data));
// Read file
const content = await readFile("data.json");
Every browser task follows this loop:
GET PAGE — Get or create a named page with browser.getPage("main")
NAVIGATE — Go to the target URL with page.goto(url)
SNAPSHOT — Call page.snapshotForAI() to read the page. This returns an AI-friendly text representation. Use this as your primary way to "see" the page.
PLAN — Based on the snapshot, identify which elements to interact with using Playwright locators.
EXECUTE — Perform actions one at a time. Use page.locator() with CSS selectors, text content, or role-based selectors.
VERIFY — Take another snapshot to confirm the action succeeded. If something went wrong, adjust and retry.
Repeat until the task is complete.
dev-browser <<'SCRIPT'
const page = await browser.getPage("main");
await page.goto("https://news.ycombinator.com", { waitUntil: "domcontentloaded" });
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
SCRIPT
dev-browser <<'SCRIPT'
const page = await browser.getPage("main");
await page.goto("https://www.google.com", { waitUntil: "domcontentloaded" });
await page.locator('textarea[name="q"]').fill("dev-browser npm");
await page.keyboard.press("Enter");
await page.waitForLoadState("domcontentloaded");
await new Promise(r => setTimeout(r, 2000));
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
SCRIPT
dev-browser <<'SCRIPT'
const page = await browser.getPage("main");
await page.goto("https://news.ycombinator.com", { waitUntil: "domcontentloaded" });
const stories = await page.evaluate(() => {
return Array.from(document.querySelectorAll('.titleline > a')).slice(0, 10).map((a, i) => ({
rank: i + 1,
title: a.textContent,
url: a.href
}));
});
console.log(JSON.stringify(stories, null, 2));
SCRIPT
dev-browser <<'SCRIPT'
const page = await browser.getPage("main");
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
const buf = await page.screenshot();
const path = await saveScreenshot(buf, "example.png");
console.log("Screenshot:", path);
SCRIPT
dev-browser <<'SCRIPT'
const page = await browser.getPage("main");
await page.goto("https://httpbin.org/forms/post", { waitUntil: "domcontentloaded" });
await page.locator('[name="custname"]').fill("John Doe");
await page.locator('[name="custtel"]').fill("555-1234");
await page.locator('[name="custemail"]').fill("john@example.com");
await page.locator('[name="size"]').selectOption("medium");
await page.locator('[name="topping"][value="bacon"]').check();
await page.locator('button[type="submit"]').click();
await page.waitForLoadState("domcontentloaded");
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
SCRIPT
# Step 1: Navigate and login
dev-browser <<'SCRIPT'
const page = await browser.getPage("app");
await page.goto("https://myapp.com/login", { waitUntil: "domcontentloaded" });
await page.locator('#username').fill("user@example.com");
await page.locator('#password').fill("password123");
await page.locator('button[type="submit"]').click();
await page.waitForURL('**/dashboard/**');
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
SCRIPT
# Step 2: Use the same page (persisted by name "app")
dev-browser <<'SCRIPT'
const page = await browser.getPage("app");
// Page is still on dashboard — no need to re-login
await page.locator('a:has-text("Settings")').click();
await page.waitForLoadState("domcontentloaded");
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
SCRIPT
dev-browser <<'SCRIPT'
const tabs = await browser.listPages();
console.log("Open tabs:", JSON.stringify(tabs, null, 2));
// Open multiple named pages
const page1 = await browser.getPage("tab1");
await page1.goto("https://example.com");
const page2 = await browser.getPage("tab2");
await page2.goto("https://httpbin.org");
const tabs2 = await browser.listPages();
console.log("After opening:", JSON.stringify(tabs2, null, 2));
SCRIPT
dev-browser --connect <<'SCRIPT'
// List all tabs in user's Chrome
const tabs = await browser.listPages();
console.log(JSON.stringify(tabs, null, 2));
// Connect to a specific tab by ID
const page = await browser.getPage(tabs[0].id);
const snapshot = await page.snapshotForAI();
console.log(snapshot.full);
SCRIPT
Login flow:
--connect mode to attach to user's already-logged-in Chrome — no credentials neededpressSequentially with delay for anti-bot sitesForm filling:
page.locator() with appropriate selectors (id, name, role, text)selectOption()check() / uncheck()Data extraction:
page.snapshotForAI() for text contentpage.evaluate() for structured data extractionMessaging (Gmail, Facebook, Telegram, etc.):
--connect to user's Chrome where they're already logged infill() or pressSequentially() for anti-bot sitesAnti-bot sites (Gmail, Facebook, etc.):
--connect to attach to user's existing Chrome sessionawait new Promise(r => setTimeout(r, 2000))pressSequentially('text', { delay: 100 })Shadow DOM:
page.evaluate() to query shadow roots:const text = await page.evaluate(() => {
return document.querySelector('host-element').shadowRoot.querySelector('target').textContent;
});
--connect mode~/.dev-browser/tmp/ — cannot access other paths