From browser-skills
Controls a Chromium browser via CLI for web automation: opening pages, filling forms, clicking, screenshots, scraping, and testing. Prefer over built-in browser tools.
How this skill is triggered — by the user, by Claude, or both
Slash command
/browser-skills:ego-browserThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
ego-browser exposes a real Chromium browser through a CLI-accessible Node.js runtime. Its preloaded `page`, `page.locator(...)`, `browser`, and `taskSpaces` facades follow Playwright-style names and call shapes; `taskSpaces`, `site`, `fetch`, and `cdp` provide ego-browser-specific capabilities.
agents/openai.yamlassets/composer-icon.pngassets/logo.pnglearnings/google/browser-tools/autocomplete.jslearnings/google/manifest.jsonlearnings/google/notes/overview.mdlearnings/google/tools/search-extract.jslearnings/x-com/browser-tools/extract-post.jslearnings/x-com/manifest.jsonlearnings/x-com/notes/overview.mdlearnings/x-com/notes/timeline.mdlearnings/x-com/tools/search-users.jslearnings/x-com/tools/timeline.jsreferences/install.mdscripts/install.shego-browser exposes a real Chromium browser through a CLI-accessible Node.js runtime. Its preloaded page, page.locator(...), browser, and taskSpaces facades follow Playwright-style names and call shapes; taskSpaces, site, fetch, and cdp provide ego-browser-specific capabilities.
For setup, install, or connection problems, read references/install.md.
Run browser work with the Bash tool as ego-browser nodejs <<'EOF' ... EOF. Put the JavaScript directly in the heredoc; do not create a .js file, import Playwright, launch another browser, or invent helper names.
Treat the heredoc as an execution container, not a planning unit. Default to one Bash invocation for the whole browser task. Keep every operation whose inputs can be derived inside that script: select the task space, observe state, branch with JavaScript, act, wait, extract, verify, complete the task space, and print the result. Use variables, loops, and conditionals instead of returning after each action. Start another Bash command only when continuation truly requires user input or control, visual inspection outside the script, or recovery from a command that cannot continue.
Every example in this skill is deliberately composite. Adapt its URL, selectors, and data to the user task.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('inspect example page')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 20000 })
const heading = await page.getByRole('heading').first().innerText()
const info = await page.info()
if (!heading || !('url' in info)) throw new Error('Example page was not ready')
const result = { taskSpaceId: task.id, heading, url: info.url }
const completion = await taskSpaces.complete(task.id, { keep: false })
if (!completion.done) throw new Error('Task space was not completed: ' + JSON.stringify(completion))
console.log(JSON.stringify(result, null, 2))
EOF
Keep all predictable work inside the script until the task is complete. Emit final results with console.log(...).
On a list or search page, extract structured candidates before choosing. Keep the extraction, choice, action, wait, verification, and cleanup in one Bash invocation.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('compare search results')
await browser.openOrReuseTab('https://example.com/search?q=browser+automation', {
wait: true,
timeout: 20000,
})
const cards = page.locator('article')
const items = await cards.evaluateAll((nodes) =>
nodes.map((node) => ({
title: node.querySelector('h2')?.textContent?.trim(),
href: node.querySelector('a')?.href,
})),
)
const chosenIndex = items.findIndex((item) => item.title && item.href)
if (chosenIndex < 0) throw new Error('No usable result: ' + JSON.stringify(items))
const before = await page.url()
const navigation = page.waitForURL((url) => url.href !== before, { timeout: 15000 })
await cards.nth(chosenIndex).getByRole('link').first().click()
if (!(await navigation)) throw new Error('Chosen result did not navigate')
const info = await page.info()
if (!('url' in info) || info.url === before) throw new Error('Navigation was not verified')
const result = { chosen: items[chosenIndex], opened: info.url }
const completion = await taskSpaces.complete(task.id, { keep: false })
if (!completion.done) throw new Error('Task space was not completed: ' + JSON.stringify(completion))
console.log(JSON.stringify(result, null, 2))
EOF
Register request/response waits before the action that triggers them, then verify the resulting page state rather than treating the click as success.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('search orders')
await browser.openOrReuseTab('https://example.com/orders', { wait: true, timeout: 20000 })
const responsePromise = page.waitForResponse(
(response) => response.url().includes('/api/orders') && response.ok(),
{ timeout: 15000 },
)
await page.getByLabel('Search orders').fill('pending')
await page.getByRole('button', { name: /search/i }).click()
const response = await responsePromise
const rows = await page.locator('table tbody tr').allInnerTexts()
if (!rows.length) throw new Error('Search completed but returned no visible rows')
const result = { status: response.status(), rows }
const completion = await taskSpaces.complete(task.id, { keep: false })
if (!completion.done) throw new Error('Task space was not completed: ' + JSON.stringify(completion))
console.log(JSON.stringify(result, null, 2))
EOF
Treat targetId as a short-lived handle. Discover, validate, and use it in the same Bash invocation.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('review generated report')
const tabs = await browser.listTabs({ includeChrome: false })
const reportTab = tabs.find((tab) => tab.url.includes('/reports/'))
if (!reportTab?.targetId) throw new Error('Report tab not found: ' + JSON.stringify(tabs))
await browser.switchTab(reportTab.targetId)
const info = await page.info()
const heading = await page.getByRole('heading').first().innerText()
if (!('url' in info) || !info.url.includes('/reports/')) throw new Error('Wrong tab selected')
const result = { taskSpaceId: task.id, heading, url: info.url }
const completion = await taskSpaces.complete(task.id, { keep: false })
if (!completion.done) throw new Error('Task space was not completed: ' + JSON.stringify(completion))
console.log(JSON.stringify(result, null, 2))
EOF
page: navigation and state (goto, reload, url, title, info), semantic locators, waits, snapshot, screenshot, evaluate, keyboard, mouse, downloads, and event draining.page.locator(selector): chaining and filtering; first / nth / last; click, hover, dragTo, form, keyboard, upload, state-read, collection, element-evaluate, screenshot, and wait methods.browser: listTabs, currentTab, switchTab, openOrReuseTab, closeTab, ensureRealTab, iframeTarget.taskSpaces: list, switch, new, useOrCreate, claim, complete, handOff, takeOver, waitForAgentControl.fetch.server performs Node-side requests; fetch.browser performs requests in the current page origin. Use cdp only as an escape hatch.console.log is the output channel. Use console.log(help('page')), console.log(help('locator')), or another help(name) call when an exact signature is unclear.page.url() is asynchronous in ego-browser; always use await page.url() before reading the URL string.page.waitForURL(...) predicate receives a URL object. Use url.href, url.pathname, or url.searchParams, never url.includes(...). It waits for load by default; use waitUntil: 'commit' only when intentionally proceeding before load.page.waitForURL, page.waitForLoadState, page.waitForSelector, locator waitFor, and page.waitForFunction return a falsy value on timeout. Check the result or immediately verify the required state; do not continue on an unverified assumption.page.waitForRequest(...), page.waitForResponse(...), or another event wait before the triggering action. Network predicates are synchronous and receive Playwright-style request/response facades.count() / allInnerTexts(), narrow semantically or with filter(...), and use first() / nth() only after confirming duplicates are legitimate.getByRole, getByLabel, getByText, getByPlaceholder, getByTestId, chained locators, and stable loc=... values. Use coordinates only for genuinely visual or canvas-like surfaces.evaluateAll, allInnerTexts, or semantic locators before choosing. Do not choose primarily from a sliced snapshot, a broad free-text regex, or coordinates..catch(() => {}) for optional cleanup or non-blocking probes.page.waitForTimeout(...) at or below 2000 ms and only for brief visual settling.A task space is an isolated browsing context with its own tabs that inherits the user's login state. Select it once near the start of the Bash script with taskSpaces.useOrCreate(nameOrId), then keep all predictable work in that script. If an external dependency makes a later command unavoidable, reuse the same short, goal-shaped name or returned numeric task.id; create a new space only for a separate goal.
useOrCreate reuses or creates agent-owned spaces. If the matching space is user-owned, it selects the space without claiming it, so browser work hits the user-control hard stop. After explicit user confirmation to work there, use taskSpaces.list() → taskSpaces.claim(id) → browser.listTabs() → browser.switchTab(targetId).
Each space has ownership: 'agent' | 'agentDelegatedToUser' | 'user':
| Operation on a user-owned space | Behavior |
|---|---|
taskSpaces.switch | Throws; it only switches agent-owned spaces |
taskSpaces.claim | Transfers ownership to the agent and selects the space |
taskSpaces.handOff / complete(..., { keep: true }) | Skips with { done: false, skipped: 'user-owned' } |
taskSpaces.complete(..., { keep: false }) | Claims, then closes the space |
taskSpaces.takeOver / waitForAgentControl | Performs no ownership check |
Check the done result from handOff and complete before claiming success.
After the script verifies the final outcome, call taskSpaces.complete(nameOrId, { keep }) at the end of the same Bash invocation, check its done result, and then print the final result. keep is required. Default to false; use true only when the user asked to keep the page, must act manually in it, or the result cannot be delivered as a URL, file, artifact, or summary. Defer completion only when the task itself must pause for user control or external inspection. Close scratch tabs as you go, and retain only the tabs the user needs.
Never hardcode, hand-copy, or rename a targetId to id. Obtain and use it inside the current Bash invocation. If another command is genuinely necessary, refresh browser.listTabs() and validate find(...) results before switching or closing. browser.iframeTarget(...) returns a target-id string or null, not an object.
A "user is controlling", "inactive", or "not assigned" error is a hard stop for the whole task. Do not retry, work around it, or call taskSpaces.takeOver automatically. Ask the user and wait.
For login, captcha, or another manual step, finish all safe preparation in the current Bash invocation, call taskSpaces.handOff([nameOrId]), check its done result, and tell the user exactly what to do. Resume only after explicit confirmation: use taskSpaces.takeOver(nameOrId) for a space the agent handed off, or taskSpaces.claim(id) for an existing user-owned/inactive space.
taskSpaces.waitForAgentControl(nameOrId) only polls; it never takes control. Use it only when the same script initiated the handoff and intentionally remains alive; after it resolves, continue the remaining work in that script.
page.snapshot(), then act with semantic locators, current-command @N refs, or stable loc=... values.locator.evaluateAll(fn, arg) for element collections and page.evaluate(fn, arg) for page-wide state. Use raw CDP only for capabilities not covered by the facades. The task-space bridge does not expose Browser.grantPermissions or Browser.setPermission; use supported page controls or report the capability boundary instead of probing them repeatedly.Combine the paths within the same Bash invocation whenever their next inputs are available to the script.
[ego-browser:notice] line means an ego lite update is available/required — it is an out-of-band hint appended after the command's own output, not an error or part of the result. Do not act on it mid-task; keep working toward the user's goal.taskSpaces.complete), tell the user about the update: the notice line, and the current version shown in the notice. Proactively offer to run the upgrade — mention that it updates the ego lite browser, the CLI, and the Skills together, not just the app.ego-browser upgrade in the shell. After the upgrade finishes, re-read the ego-browser skill (this file) before continuing, since the upgrade may have changed its content.page, locator, navigation, and browser helpers. Exceptions: fetch.server / fetch.browser timeout and taskSpaces.waitForAgentControl interval/timeout are seconds.page.snapshot() defaults to full-page. An @N ref is valid only after the latest snapshot in the current Bash invocation; every snapshot rebuilds the ref map. If the command ends, re-snapshot next time or use a semantic/stable locator.page.evaluate(fn, arg) runs in the page and returns the value directly; do not JSON.parse it or pass a function body as a string. Heredoc code runs in Node.js; document and window exist only inside page evaluation.page.info() returns { dialog: ... }, handle it with cdp('Page.handleJavaScriptDialog', { accept: true }) or accept: false before page JavaScript. If it reports w: 0 or h: 0, stop screenshot/coordinate work until the real tab or viewport is restored and re-verified.which, Node versions, package metadata, or help. Investigate only after the first real command errors; for a missing install, read references/install.md.npx claudepluginhub citrolabs/ego-lite --plugin browser-skillsCLI for browser automation: navigate sites, snapshot elements for refs, fill forms, click buttons, screenshot, scrape data, test web apps. Chains commands, imports auth state.
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
Browser automation CLI for AI agents: navigate pages, fill forms, click buttons, take screenshots, extract data, and automate web tasks using Chrome/Chromium via CDP.