# Fastbrowser

> Control a live browser from the command line: navigate, click, fill forms, and query the accessibility tree with CSS-like selectors. Lighter alternative to Chrome DevTools MCP or Puppeteer. Triggers on: navigate/click/fill actions, page snapshots, or mentions of fastbrowser.

- Skill: `molokoloco/fastbrowser` (Agent Skill)
- Install (CLI): `npx skillmds@latest add molokoloco/fastbrowser`
- Raw SKILL.md: https://api.skillmd.com/api/skills/molokoloco/fastbrowser/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: molokoloco (https://skillmd.com/u/molokoloco)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/molokoloco/fastbrowser

---


# fastbrowser Skill

`fastbrowser-cli` is a command-line client for the FastBrowser HTTP server, which keeps a persistent
MCP connection to a Chrome browser alive so commands incur minimal latency. Each command
maps 1-to-1 to a FastBrowser tool and returns the tool's response on stdout.

## Invocation

Run the CLI directly via `tsx`:

```bash
npx fastbrowser_cli@latest <command> [flags]
```

## Typical Workflow

1. **Query** the accessibility tree for specific nodes with `query_selectors` (first match per selector by default; pass `-a, --all` for every match).
2. **Act** on an element by its accessibility selector: `click`, `fill_form`, `press_keys`. The selector can be a direct uid reference (e.g. `#1_42`, fastest path) or any CSS-like selector (e.g. `button[name="Submit"]`), which is resolved to a uid internally.

Snapshot output looks like:

```
uid=1_0 RootWebArea "Example Domain" url="https://example.com/"
  uid=1_1 heading "Example Domain" level="1"
  uid=1_2 link "More information..." url="https://www.iana.org/..."
```

## Page Management

```bash
# List all open browser pages
npx fastbrowser_cli@latest list_pages

# Open a new page at a URL
npx fastbrowser_cli@latest new_page --url https://example.com

# Close a page by its numeric id
npx fastbrowser_cli@latest close_page --page-id 1

# Navigate the current page to a URL
npx fastbrowser_cli@latest navigate_page --url https://example.com

# Restart the daemon - run this if pages opened by the bridge were closed manually
# and the MCP connection has broken
npx fastbrowser_cli@latest server restart
```


## Configuration

Global flags accepted by every command:

| Flag / env | Purpose | Default |
|---|---|---|
| `--server <url>` / `FASTBROWSER_SERVER` | URL of the `fastbrowser_httpd` daemon | `http://localhost:8787` |
| `--autostart` / `--no-autostart` | Auto-start the daemon if it is not already running | `--autostart` |
| `--mcp-target <target>` / `FASTBROWSER_MCP_TARGET` | Browser backend: `playwright` or `chrome_devtools` | `playwright` |

The daemon binds to one backend at startup. If it is already running with a different backend, the CLI refuses the request and prints the exact restart command to switch.

```bash
# Use chrome-devtools-mcp for one command
npx fastbrowser_cli@latest --mcp-target chrome_devtools list_pages

# Switch the running daemon to a different backend
npx fastbrowser_cli@latest --mcp-target chrome_devtools server restart
```


## Selector Language

The selector syntax is modelled on CSS selectors, adapted for accessibility tree structures.

### Role selector

Matches nodes by their accessibility role.

```
button
link
combobox
searchbox
heading
WebArea
```

### Universal selector

Matches any node.

```
*
```

### UID selector

Matches a node by its exact unique identifier.

```
#4
#1_3
```

### Attribute selectors

Attribute selectors match values inside `node.attributes`. The special virtual attribute `name` maps to `node.name`.

| Syntax | Semantics |
|--------|-----------|
| `[attr]` | attribute is present |
| `[attr="value"]` | exact match |
| `[attr^="prefix"]` | starts with |
| `[attr$="suffix"]` | ends with |
| `[attr*="sub"]` | contains substring |
| `[attr~="word"]` | contains `word` as a whole space-separated word |

```
link[href]
button[disabled="true"]
link[href^="https"]
link[href$=".com"]
link[href*="example"]
button[name~="Submit"]
heading[name="Welcome"]
link[name="Click \"here\""]
```

### Combinators

| Syntax | Semantics |
|--------|-----------|
| `A B` | B is a descendant of A (any depth) |
| `A > B` | B is a direct child of A |
| `A + B` | B is the immediately following sibling of A |
| `A ~ B` | B is any following sibling of A |
| `A, B` | union — matches A or B |

```
WebArea link
main > button
label + textbox
link ~ link
heading, button
RootWebArea > link[href^="https"]
```

### Positional pseudo-classes

Narrow a match by position within the parent's children array. Indexing is 1-based; the root node never matches a positional pseudo-class.

| Syntax | Semantics |
|--------|-----------|
| `:first-child` | node is the first child of its parent |
| `:last-child` | node is the last child of its parent |
| `:nth-child(n)` | node is the nth child (1-based) |

```
link:first-child
button:last-child
menuitem:nth-child(2)
```

### Functional pseudo-classes

Take a comma-separated selector list inside parentheses. The argument list itself supports the full selector language and may be nested.

| Syntax | Semantics |
|--------|-----------|
| `:is(s1, s2, …)` | node matches any selector in the list |
| `:where(s1, s2, …)` | alias of `:is()` (no specificity in this engine) |
| `:not(s1, s2, …)` | node matches none of the selectors |
| `:has(s1, s2, …)` | node has a descendant matching any selector |

`:has()` walks descendants of the candidate node (excluding the node itself). Relative leading combinators (e.g. `:has(> link)`) are not supported.

```
:is(heading, button)
link:not(:first-child)
*:has(button)
*:not(:has(link))
main > *:not(button)
```

### Examples

Sample accessibility tree:

```
uid=1 WebArea "Main Page"
  uid=2 main
    uid=3 heading "Welcome"
    uid=4 link "Click here" href="https://example.com"
    uid=5 button "Submit" disabled="true"
  uid=6 navigation
    uid=7 link "Home" href="/"
    uid=8 link "About" href="/about"
```

Example queries on it:
- `link` matches all the links (uid=4, uid=7, uid=8)
- `navigation > link` matches only the links that are direct children of navigation (uid=7, uid=8)
- `link[href^="https"]` matches links with an external href (uid=4)
- `button[name="Submit"]` matches the submit button by name (uid=5)
- `*[disabled="true"]` matches any disabled element (uid=5)
- `heading, button` matches both headings and buttons in one query (uid=3, uid=5)
- `#7` matches a node by its UID (uid=7)
- `link:first-child` matches uid=4 and uid=7 (first child of `main` and `navigation`)
- `link + button` matches uid=5 (button immediately after a link)
- `:is(heading, button)` is equivalent to `heading, button` (uid=3, uid=5)
- `link:not([href^="https"])` matches the relative-href links (uid=7, uid=8)
- `*:has(button)` matches ancestors of a button (uid=1, uid=2)

## Inspection

- `query_selectors` is the most efficient way to get specific elements or data from the page. Use it instead of `take_snapshot` whenever possible.
- By default, `query_selectors` returns the first match per selector (cheaper, less output). Pass `-a, --all` when you need every match — pair it with `--limit` to cap results per selector.
- Use `--wa, --with-ancestors` to include each match's ancestor chain in the result, and `--wc, --with-children` to include the descendant subtree of each match.

```bash
# Query the accessibility tree returning the FIRST match per selector (--selector is repeatable)
npx fastbrowser_cli@latest query_selectors --selector "button" --selector "link"

# Include ancestor nodes in the result
npx fastbrowser_cli@latest query_selectors --selector 'heading[level="1"]' --with-ancestors

# Include the descendant subtree of each match
npx fastbrowser_cli@latest query_selectors --selector 'main' --with-children

# Per-selector control over withAncestors / withChildren via JSON
npx fastbrowser_cli@latest query_selectors \
  --selectors-json '[{"selector":"button","withAncestors":true},{"selector":"link","withChildren":true}]'

# Pass --all to return every match per selector; --limit caps results per selector (0 = unlimited)
npx fastbrowser_cli@latest query_selectors --all --selector "button" --selector "link" --limit 5

# Include ancestor nodes in the result
npx fastbrowser_cli@latest query_selectors --all --selector 'heading[level="1"]' --with-ancestors

# Per-selector control over limit / withAncestors / withChildren via JSON (with --all)
npx fastbrowser_cli@latest query_selectors --all \
  --selectors-json '[{"selector":"button","limit":3,"withAncestors":true},{"selector":"link","limit":0,"withChildren":true}]'

# Take an accessibility-tree full page snapshot of the current page - very expensive, prefer targeted queries when possible
npx fastbrowser_cli@latest take_snapshot
```

## Interaction

```bash
# Click by a direct uid reference (fast path - no accessibility-tree lookup)
npx fastbrowser_cli@latest click --selector "#1_42"

# Click by any CSS-like selector - resolved to a uid internally
npx fastbrowser_cli@latest click -s 'button[name="Submit"]'

# Fill a single form field - selector can be a uid (#1_7) or any CSS-like selector
npx fastbrowser_cli@latest fill_form -s 'textbox[name="Email"]' -v "hello@example.com"

# Press a comma-separated sequence of keys (literals and named keys both work)
npx fastbrowser_cli@latest press_keys --keys "Tab, Tab, Enter"
npx fastbrowser_cli@latest press_keys --keys "Hello, Tab, Enter"
```

## Batch Execution

Run several commands from one invocation — each line is parsed and executed like a standalone command, reusing the persistent HTTP server session.

Syntax rules:
- One command per line, same syntax as the standalone CLI (e.g. `click -s '...'`)
- Shell-style quoting (single and double quotes)
- Blank lines are ignored
- Lines starting with `#` are comments

By default, the batch stops at the first failing line (shell `set -e` semantics). Pass `--no-stop-on-error` to log failures and continue; the process still exits non-zero at the end if any line failed.

```bash
# From a file
npx fastbrowser_cli@latest batch ./demo.fbs

# Piped on stdin
cat demo.fbs | npx fastbrowser_cli@latest batch

# Inline script
npx fastbrowser_cli@latest batch --script $'press_keys --keys "Enter"\nclick -s 'button[name^="Tout effacer"]''

# Continue through failures
npx fastbrowser_cli@latest batch --no-stop-on-error ./demo.fbs
```

Example `demo.fbs`:

```
# open a page and interact
new_page --url https://example.com
fill_form -s 'textbox[name="Email"]' -v 'hello@example.com'
press_keys --keys "Tab, Enter"
```

## Command Reference

| Command | Purpose | Required flags |
|---------|---------|----------------|
| `list_pages` | List open browser pages | — |
| `new_page` | Open a new page at a URL | `--url` |
| `close_page` | Close a page by id | `--page-id` |
| `navigate_page` | Navigate current page to a URL | `--url` |
| `take_snapshot` | Dump the accessibility tree of the whole page - very expensive, prefer targeted queries (`query_selectors`) when possible | — |
| `query_selectors` | Query a11y tree by CSS-like selector (first match per selector by default; pass `-a, --all` for every match, with optional `--limit`) | `--selector` or `--selectors-json` |
| `click` | Click an element by accessibility selector | `--selector` / `-s` |
| `fill_form` | Fill a form field by accessibility selector | `--selector` / `-s`, `--value` |
| `press_keys` | Press a comma-separated key sequence | `--keys` |
| `batch` | Run multiple commands from a file, piped stdin, or `--script` inline | one of: `<file>`, `--script`, or piped stdin |
| `install [skill-folder]` | Install bundled skills into `<skill-folder>/skills/` (default: `.`) | — |
| `server start` | Start the HTTP server daemon | — |
| `server status` | Report server running/stopped | — |
| `server stop` | Stop the HTTP server | — |
| `server restart` | Restart the HTTP server (re-establishes the MCP connection) | — |

## Output & Errors

Tool output is written to **stdout** — one line per response content part. On failure the
CLI writes `fastbrowser-cli error: <message>` to **stderr** and exits with code 1.

