---
name: browser-relay
description: Control the Chrome the user already has open and logged in through the Browser Relay CLI, without launching a separate automation browser or taking over the foreground tab. Use when an agent needs to work with existing sessions, cookies, extensions, SSO or intranet pages, or a browser on another machine. Prefer the CLI; use HTTP only for code, tests, or integrations. Skip static pages and pure REST APIs.
---

# Browser Relay Skill

Work alongside the user in the Chrome they already use. Browser Relay lets the
agent operate existing tabs through a Skill + CLI, including background tabs,
without launching a blank browser profile or repeatedly pulling Chrome to the
foreground. Remote Relay can reach the user's explicitly enabled browser on
another machine.

## Use Browser Relay For

Use Browser Relay when you need to:

- **Share the user's current Chrome** — keep their existing tabs, cookies, extensions, and login state
- **Interact with login-protected sites** — including SSO, intranet pages, dashboards, Gmail, Notion, and similar apps
- **Work without interrupting the foreground tab** — select a specific background tab and keep the user's current tab in place
- **Reach another machine** — use Remote Relay only after the user explicitly enables it on that browser
- **Handle dynamic pages** — JavaScript-rendered content and SPAs
- **Perform actions** — click buttons, fill forms, scroll, take screenshots
- **Get structured page text** — annotated DOM snapshot with `[link]`, `[button]`, `[input]` markers
- **Evaluate arbitrary JavaScript** in the page context

**When NOT to use:**
- Simple static pages without login — use WebFetch or Jina Reader (faster, cheaper)
- Pure API calls — use the site's REST API directly

## Prerequisites

Check setup before the first browser action:

```bash
browser-relay doctor
```

If `browser-relay` is not installed, tell the user to install the open-source
CLI, then rerun the check:

```bash
npm install -g @linsoai/browser-relay
browser-relay path
```

The user must load the directory printed by `browser-relay path` from
`chrome://extensions` and explicitly enable the extension. Do not install it,
enable Remote Relay, or invent a Remote Device ID without the user. A ready
setup has the relay server running, the extension connected, and at least one
regular Chrome tab attached.

## Connection Info

```
Relay URL:  http://127.0.0.1:18795
WebSocket:  ws://127.0.0.1:18795/extension
```

No authentication needed — the relay only accepts connections from localhost.

Remote mode ("Remote Relay") lets an agent drive a browser on **another machine**.
The user opens the extension Options, turns on Remote Relay, and copies the
generated `remote-device-id` — a secret capability like `br-xxx`. Only then use
remote flags (the hosted hub `relay.linso.ai` is the default host):

```bash
browser-relay tabs --remote-device-id br-xxx
browser-relay remote add mymac br-xxx    # or save an alias once…
browser-relay tabs --remote mymac        # …and use the short name (remote ls / rm to manage)
```

Never invent or guess a `remote-device-id`; it is a secret generated by the
extension. The local relay stays bound to localhost — remote mode connects the
browser out to the hub and does not need it.

## Preferred CLI Workflow

When shell access is available, use the `browser-relay` CLI for browser
interaction. Do not hand-write `curl` for normal agent browsing tasks. The CLI
avoids JSON escaping, keeps commands short, and prints compact output by
default.

Use the HTTP API directly only when you are writing code, tests, scripts, or an
integration against Browser Relay, or when the CLI is unavailable. Use `--json`
only when you need the full API response.

```bash
browser-relay tabs
browser-relay console --tab <tabId> --limit 50
browser-relay network --tab <tabId> --type response --status 500 --limit 20
browser-relay snapshot --tab <tabId> --max-length 20000
browser-relay wait 'button[type=submit]' --state visible --timeout 10000 --tab <tabId>
browser-relay click 'button[type=submit]' --tab <tabId>
browser-relay type 'hello world' --selector 'input[name=q]' --clear --submit --tab <tabId>
browser-relay key Control+L --tab <tabId>
browser-relay scroll down --amount 1000 --tab <tabId>
browser-relay download-start https://example.com/file.pdf --filename files/file.pdf
browser-relay downloads --limit 20
browser-relay screenshot /tmp/page.png --full-page --tab <tabId>
browser-relay eval 'document.title' --tab <tabId>
```

For maintenance, upgrade Browser Relay with:

```bash
browser-relay update
```

Then refresh this Skill for the active agent (for example, `codex` or
`claude-code`) and let Browser Relay verify the installed copy:

```bash
browser-relay skill install --agent codex
```

For long text or JavaScript, avoid shell escaping with stdin:

```bash
printf '%s' "$TEXT" | browser-relay type --selector textarea --stdin --tab <tabId>
browser-relay eval --stdin --tab <tabId> < script.js
```

## HTTP API Reference

The HTTP API below is for code, tests, custom tools, and low-level debugging.
For interactive agent work, prefer the CLI workflow above.

Errors are structured across HTTP, CLI `--json`, and MCP tool errors:

```
{ "ok": false, "code": "invalid_request", "error": "url is required", "message": "url is required", "status": 400, "retryable": false }
```

Agents should branch on `code` rather than matching localized/free-form error
text. `retryable: true` means a reconnect/retry is reasonable.

### 1. browser_tabs
List all attached browser tabs.
```
GET http://127.0.0.1:18795/api/tabs
```
Returns: `{ ok: true, tabs: [{ id, title, url }] }`

### 2. browser_navigate
Navigate a tab to a URL.
```
POST http://127.0.0.1:18795/api/navigate
Header: Content-Type: application/json
Body: { "url": "https://example.com", "tabId?": "optional-tab-id" }
```

### 2b. browser_console
Read captured console, page error, and browser log entries.
```
GET http://127.0.0.1:18795/api/console?tabId=<id>&limit=100&level=error&clear=false
POST http://127.0.0.1:18795/api/console/clear
Body: { "tabId?": "...", "level?": "error" }
```
Use this after actions that may trigger frontend errors or warnings.

### 2c. browser_network
Read captured request/response/finished/failed network events. Sensitive
headers such as `Authorization`, `Cookie`, `Proxy-Authorization`, and
`Set-Cookie` are redacted; request bodies are not captured.
```
GET http://127.0.0.1:18795/api/network?tabId=<id>&type=response&status=500&limit=100&clear=false
POST http://127.0.0.1:18795/api/network/clear
Body: { "tabId?": "...", "type?": "request|response|finished|failed", "method?": "GET", "status?": 500, "requestId?": "...", "url?": "substring" }
```
Use this after actions that fail silently, after form submits, or when console
errors imply an API request failed.

### 3. browser_snapshot
Get a text representation of the current page (interactive elements annotated).
```
GET http://127.0.0.1:18795/api/snapshot?tabId=<id>&format=text&maxLength=100000
```
Format can be `"text"` (annotated DOM) or `"html"` (raw HTML).

### 3b. browser_wait
Wait for a CSS selector to be attached to the DOM or become visible. Prefer
this over fixed sleeps after navigation or actions.
```
POST http://127.0.0.1:18795/api/wait
Body: {
  "selector": "button.submit",
  "state?": "attached|visible",
  "timeoutMs?": 5000,
  "pollMs?": 100,
  "tabId?": "..."
}
```
`state` defaults to `visible`. `timeoutMs` accepts 1–20000 and `pollMs`
accepts 50–1000. A timeout returns `code: "wait_timeout"` with
`retryable: true`; a tab closing or the extension disconnecting fails
immediately instead of waiting for the timeout.

### 4. browser_click
Click an element by CSS selector. Scrolls into view first, uses real mouse events.
```
POST http://127.0.0.1:18795/api/click
Body: { "selector": "button.submit", "tabId?": "...", "doubleClick?": false }
```

### 5. browser_type
Type text into an input field. Optionally clear and submit.
```
POST http://127.0.0.1:18795/api/type
Body: {
  "text": "hello world",
  "selector?": "input[name='q']",
  "clear?": true,
  "submit?": true,
  "tabId?": "..."
}
```

### 6. browser_scroll
Scroll the page.
```
POST http://127.0.0.1:18795/api/scroll
Body: { "direction": "down|up|top|bottom", "amount?": 800, "tabId?": "..." }
```

### 7. browser_key
Press a key or keyboard shortcut using real keyboard events.
```
POST http://127.0.0.1:18795/api/key
Body: { "key?": "Enter", "combo?": "Control+L", "tabId?": "..." }
```

Use `combo` for shortcuts (`Control+L`, `Meta+K`, `Shift+Tab`) and `key`
for single keys (`Enter`, `Escape`, `ArrowDown`, `a`).

### 8. browser_screenshot
Capture a PNG screenshot (base64).
```
POST/GET http://127.0.0.1:18795/api/screenshot?tabId=<id>&fullPage=true
```
Full-page captures use layout metrics plus a clipped screenshot when possible,
then fall back to Chrome's `captureBeyondViewport` path. Returns:
`{ ok: true, data: "base64...", format: "png", fullPage, strategy, width?, height?, bytes }`

### 9. browser_eval
Evaluate arbitrary JavaScript in the page. The escape hatch.
```
POST http://127.0.0.1:18795/api/eval
Body: { "expression": "document.querySelector('h1').innerText", "tabId?": "..." }
```

### 10. browser_download
Get the URL of an image/media/link element.
```
POST http://127.0.0.1:18795/api/download
Body: { "selector": "img.profile-pic", "tabId?": "..." }
```

### 10. browser_download_start
Start a real Chrome download from a URL using the user's browser profile.
```
POST http://127.0.0.1:18795/api/download/start
Body: {
  "url": "https://example.com/file.pdf",
  "filename?": "files/file.pdf",
  "saveAs?": false,
  "conflictAction?": "uniquify|overwrite|prompt"
}
```
Returns: `{ ok: true, downloadId, id, options }`

### 11. browser_downloads
List Chrome downloads plus recent Browser Relay download events.
```
GET http://127.0.0.1:18795/api/downloads?limit=20&state=complete
POST http://127.0.0.1:18795/api/downloads/clear
```
Use this after `browser_download_start` to verify completion or diagnose interruptions.

Real downloads require the extension's `downloads` permission. If Browser Relay
was already loaded in Chrome before this capability was installed, reload the
unpacked extension in `chrome://extensions`.

## Agent Decision Workflow

When asked to do something with a web page:

1. **`browser-relay tabs` first** — discover available tabs and their URLs
2. **`browser-relay navigate`** if needed — go to the target page
3. **`browser-relay snapshot`** — understand the page structure
4. **Plan actions** based on snapshot (click what, type where)
5. **Execute** (`browser-relay click`, `browser-relay type`, `browser-relay key`, `browser-relay scroll`) one at a time
6. **`browser-relay wait`** for the next expected element after navigation or an action; do not guess with fixed sleeps
7. **`browser-relay console`** if the page behaves unexpectedly or after risky actions
8. **`browser-relay network`** if a request fails, hangs, or the UI changes without visible errors
9. **Re-snapshot** after each action to verify state
10. **Use `browser-relay download-start` and `browser-relay downloads`** for real file downloads
11. **Screenshot** if visual confirmation is needed

## Example Session

```bash
# 1. List tabs
browser-relay tabs
# t_A7k2Pm9QxL    Google    https://google.com

# 2. Take snapshot to see the page
browser-relay snapshot --tab t_A7k2Pm9QxL
# [input type=text name=q placeholder="Search Google"]
# [button "Google Search"]

# 3. Type into the search box
browser-relay type 'browser relay' --selector 'input[name=q]' --submit --tab t_A7k2Pm9QxL

# 4. Wait for results instead of sleeping
browser-relay wait 'a[href*="github.com"]' --state visible --timeout 10000 --tab t_A7k2Pm9QxL

# 5. New snapshot after navigation
browser-relay snapshot --tab t_A7k2Pm9QxL

# 6. Click a result
browser-relay click 'a[href*="github.com"]' --tab t_A7k2Pm9QxL
```

## MCP Registration (Claude Desktop / Cursor / Windsurf)

Add to your MCP config (`~/.claude/mcp.json` or equivalent):

```json
{
  "mcpServers": {
    "browser": {
      "command": "browser-relay-mcp",
      "env": {
        "BROWSER_RELAY_URL": "http://127.0.0.1:18795"
      }
    }
  }
}
```

## Error Patterns

| Error | Meaning | Fix |
|-------|---------|-----|
| Extension not connected | Relay server is running but no browser extension connected | Check that Chrome is running with the Browser Relay extension installed |
| No attached tabs | Extension connected but no tab is attached | The extension auto-attaches all regular tabs. Make sure at least one non-chrome:// tab is open |
| Element not found: selector | The CSS selector did not match anything on the page | Try a different selector, or take a snapshot first to inspect the DOM |
| wait_timeout | The selector did not reach the requested attached/visible state before timeout | Re-snapshot the page, check the selector, or retry when the page is expected to load more slowly |
| Session with given id not found (-32001) | The relay holds stale session state (e.g. after an extension reload) | Run `browser-relay fix` — restarts the relay and clears stale sessions; the extension reconnects automatically |

## Health Check

```bash
curl http://127.0.0.1:18795/
# → OK

curl http://127.0.0.1:18795/api/debug
# → { "version": "<package-version>", "connected": true/false, "tabCount": N }
```
