pairbrowse · git:20261005.ea056d8 · 2026-10-05 · sha256 d4f36b592ab05b92

pairbrowse git:20261005.ea056d8A

Immutable. This exact content never changes and is served at /api/v1/blob/d4f36b592ab05b92.

---
name: pairbrowse
description: Fill in web sign-ups, registrations, app listings, store settings and other multi-page forms in the user's visible PairBrowse Chrome window, fast, while the user handles CAPTCHAs, 2FA and final submits. Use when the user asks to register, sign up, set up, list, apply, onboard or fill in anything on a website, or refers to "the browser", "this tab" or "the open tabs".
---

# PairBrowse reference

The PairBrowse core from session start has the hard rules, how to work and the step lists for
sign-ups, listings, testing and working together. This is the reference behind it: tool details,
edge cases and examples. The core wins if the two ever seem to disagree.

The browser keeps running between sessions, so tabs, logins and half-filled pages are still there.

## Before the first browser action

Ask in one message (your question tool if you have one), skipping what the user already said and what the browser asks itself:
- **Which session:** when the browser starts, its first tab is a session picker: the person
  continues a saved session (with its tab count), starts a fresh one, or pastes a `pb-join:` code.
  Your first browser action waits for that pick (up to about 15 seconds) and its result says which;
  if they haven't picked yet, it says so: tell them in chat what the first tab asks, then retry.
  So ask only when the core tells you to (the picker is off, `"sessionPicker": false`, or this is a
  cloud session), and act first when the user already said: "a fresh session" is `new` with
  `clean: true`, a named one is `use`, "join this session: pb-join:..." is `pairbrowse_join`; the
  picker then never shows. If an action answers that the browser is waiting for the person to pick,
  ask them in chat, or wait and retry.
  `pairbrowse_session`: `list`; `use` with `name`; `new` with `clean: true` and no name is a
  throwaway deleted when you switch away; `new` with a `name` is kept (for example one per client).
  `delete` asks the user. Switching closes the window and opens the other session's tabs. While
  other participants are connected only `list` works.

Then follow the session-start note on showing the browser. In the Claude desktop app call
`pairbrowse_dock` with `action` "on" (macOS); if that isn't available, call `pairbrowse_liveview`
once and open its URL in the Browser pane (or give it to the user for that pane). The live view link
controls the browser: never paste it anywhere else. In a terminal or IDE the PairBrowse window is enough.

Make a task list with one item per page or section, and set a badge:
`pairbrowse_status` `{ "text": "Filling the Shopify app listing", "kind": "claude" }`.

## Fast or step by step: decide per page

`pairbrowse_run` steps, one key each: `go`, `fill` {Label: value}, `check`, `uncheck`, `select`
{Label: Option}, `click`, `press`, `scroll` ("down", "up" or pixels, at once), `drag` ([[x,y],...] fractions of the visible page: drawing on canvases, human-like), `upload` {Label: path}, `waitFor`, `expect`, and
`handoff` {say, until} (or `untilGone`). Labels match the label, placeholder or name. Values may use
`{{var}}` from `vars`. It stops at the first problem, says why, and returns an outline of the page.
`saveAs` saves a flow that fully worked; `playbook` + `vars` replays it; `list: true` lists saved ones.
Check the list before building a flow that may already exist.

Go **step by step** (`browser_snapshot`, then `browser_click` / `browser_type` /
`browser_select_option`) for new complex widgets (comboboxes, date pickers, sliders, rich editors,
address autocomplete, wizards that change as you type), for the one step a run stopped on, for
payment, legal or tax steps, and when the user wants to watch closely. Mix freely within a page.

## Speed rules

- One call per page. Without fast mode, put every field into one `browser_fill_form`.
- On long pages use `browser_find` instead of a fresh full snapshot. To scroll, use
  `pairbrowse_scroll` (people watching see it glide), never PageDown or End.
- Results that change the page carry a small screenshot. Act on snapshot refs; for what the
  snapshot doesn't name (an icon-only ×, a map, a canvas) use `pairbrowse_click_at` with x, y in
  that screenshot and `element`. It can't click pay, publish, submit or delete buttons, or inside frames.
- Don't re-snapshot after every fill. Check once per page for validation errors and fix them in one fill.
- Accept cookie banners, Next, Continue, Save, I agree and standard terms checkboxes without asking.
- Autocompletes and custom dropdowns: type, wait for the suggestion, click it. Don't press Enter
  to pick one: it may submit the form.

## Details and passwords

- You may draft marketing copy (descriptions, taglines, features) within the field's limit; list it
  under "Drafted by me" so the user can check it.
- The user can edit details and passwords in the Profile panel (person icon in the live view).
- A password's name typed as the value (from `pairbrowse_facts` `get`) is replaced with the real one
  and shows to you as `<secret>NAME</secret>`. Each works only on the HTTPS sites saved with it. If
  none exists or it's refused, ask the user to save it in the Profile panel or type it themselves.
- Never type a password, code or remembered detail into a site other than the job's: check the Page URL.
- Grant OAuth or app permissions, add team members, or change payout or bank details only when the
  user asked for exactly that in this conversation.

## Hand-offs and waiting

1. `pairbrowse_status` `{ "text": "Solve the CAPTCHA, I'll continue", "kind": "you" }` (the user also gets a notification).
2. One line to the user on what to do and that you'll continue by yourself.
3. Wait without ending your turn: `browser_wait_for` with `textGone` (challenge text) or `text`
   (what the next page shows), up to 20 times (about 10 minutes). Then set the badge back.
4. If it never clears, stop and say exactly where you are and what's left.

## What PairBrowse handles by itself

It reports these in a "### PairBrowse" block in the next result. Read it.
- Alerts and "leave this page?" are answered; harmless confirms get OK. Confirms that pay, delete,
  cancel or submit, and text prompts, are yours: decide with the user, then `browser_handle_dialog`.
- Plain popups are closed. "Something covers the page" lists the rest: close offers, newsletters and
  app prompts with ×, "No thanks" or "Close" without asking. Never press subscribe, buy, sign-up or
  trial buttons to get rid of one.
- At most 10 tabs stay open; opening another closes the one used longest ago. List tabs before
  selecting by number. New tabs a page opens are reported by number; switch with `browser_tabs`.
- Downloads go to the Downloads folder; the result says where.
- While the user clicks or types in the tab, your next action there waits, then the result says
  what they did (scrolling and moving the mouse don't hold you up). Take a fresh snapshot and carry on from where the page is now.
- Site notification and location requests never pop up; leave them alone.

## Uploads

The file picker never opens. `pairbrowse_upload` with `files` (absolute paths anywhere on this
computer, up to 20) and `target` (ref, CSS selector or visible text of the field, button or drop
zone; omit it when there's one file field). Images, video, PDFs and office documents only; key and
credential files are always refused. Other types: click the upload button, then
`browser_file_upload`, which asks the user. Snapshot after and check for size or dimension errors.

## Email codes

Open only the verification message: select the inbox tab, `browser_find` with a pattern such as
`/\b\d{6}\b/` in the newest message from that sender, copy the code or open the link, switch back.

## Pre-submit review and final actions

- Name a click that commits something with its class at the start of `element`: `Pay:` (pays,
  buys, subscribes), `Delete:` (deletes, cancels, ends), `Submit:` (sends, posts, creates, submits),
  `Publish:` (submit for review, publish, go live). The class is what makes the guard ask the user;
  PairBrowse reads no button words.
- `Publish:` clicks are blocked until a passing `review_save` from the last 30 minutes. Read the
  platform's current official requirements in a new tab, not from memory: screenshots, URLs (open
  privacy, support and demo links), reviewer test credentials, justified permissions, naming and
  branding. One check per rule with `ok` and a short `note`; `waived` only if the user said to
  submit anyway. Show the result, then click.
- Ordinary clicks just go: sign-up steps, Continue, Save, Next, plain buttons. Name a click with
  its class up front only when your task tells you it commits something the user can't take
  back: "Pay:", "Delete:", "Publish:", "Send:" (a message, invite or email to other people) or
  "Submit:" (an application, a final order, a submit for review). The user confirms those.
- The helper reads the page's structure, never its words: card, IBAN or billing/shipping fields,
  a payment frame, a danger-styled button, an HTTP DELETE, the confirmation right after a delete
  or payment, or an element it can't read. Such a click is refused until it carries the class the
  refusal names ("Pay: Submit order"); retry with it.
- `browser_handle_dialog` with accept takes `element` the same way: "Delete: OK" right after a
  delete click (required); any other OK goes, or name its class if it commits something. `browser_evaluate` always asks the user; avoid it. Final actions are never
  run in fast mode.
- In Codex those clicks are refused with a note: badge "you", name the button and what it does,
  wait, and carry on from the result. Record the passing review first for submit or publish.

## CLI as the fallback

Do the job in the browser. Offer a CLI only when the site keeps blocking (repeated CAPTCHAs, 403,
rate limits), the setting isn't in the web UI, or the user would repeat many steps. Say so in one
line with the setup it needs (for example `brew install gh` and `gh auth login`). Never put
passwords or tokens into a command.

## Progress updates

After each page, `run_save` (done, left, yourTurn, drafted, tabs from `browser_tabs` list) and post:

```
Shopify app listing: 3 of 6 sections done
Filled: App name, Tagline, Category, Pricing (Free + Pro $29)
Drafted by me, please check: Tagline, Key benefits 1-3
Your turn: none
Left: Screenshots, Privacy URL (not remembered yet), Submit for review (needs you)
```

At the end: `run_save` with `status: "finished"`, badge `kind` "done", and a summary of what was
filled, drafted, left for the user, and remembered. Every action is also logged to `~/.pairbrowse/log/`.

## Several agents on one browser

- Per-tab turns: the agent acting in a tab holds it (two idle minutes, or until release, disconnect
  or the tab closes). Another agent's action there is refused with who holds it: open or select
  another tab. In a shared session this holds across computers ("in use by ... (in Bob's
  browser)"). A person's clicks and typing pause agents in that tab only.
- `pairbrowse_collaboration`: `status` (participants, controller), `identify` with `label`,
  `acquire` the whole-browser lease for work that must keep the browser to itself (two minutes,
  renew with `acquire`), `release` when done or before a hand-off. Take a fresh snapshot after acquiring.
- A stale-ref error means the page changed: snapshot again and reassess before acting.
- Remote participants need SSH access to the same host user and PairBrowse home: a trusted setup,
  not a public invitation. Never share account credentials or expose the socket.

## Invites and join codes

- `pairbrowse_invite` `create`: `role` "drive" (the default; asks the user) or "watch" (only when the user asks for view-only), `label`, `hours` (default
  24, at most 168), `share` "code" (pb-join code over a Cloudflare Quick Tunnel; the default without
  `inviteBaseUrl`) or "link" (Tailscale or SSH). Pass on the code and the steps from the result.
- `list` (invites, join requests and shared dev servers, no keys), `approve` / `deny` with the request
  `id` (approve asks the user), `revoke` with the invite `id`, `revoke_all` (also closes the tunnel
  and stops sharing dev servers).
- `share_port` with `port` (or none: the current localhost tab's): the user's dev server (Next,
  Nuxt, Vite) for joiners, under its own address; their localhost tabs then cross there. It only
  asks the user (Yes / No in the side panel, waits up to 90 s; a later answer comes as a note);
  watch joiners only look. The user can also Share or Stop in the side panel. `unshare_port` (port, or none: all). Use it when the user wants
  joiners to see their local app; tell them built-in `http://localhost` URLs in the app won't
  work for joiners.
- With a code, the joiner's own browser opens the host's tabs and follows them: addresses
  (filtered: no tokens, nothing local; origin and path for watch and sites with saved passwords),
  titles, activity, typed values (sensitive ones only as filled), pointers and who is at work
  where. Never logins, cookies, passwords or a picture.
- `pairbrowse_join` `join` with `code` and the user's `name`: waits for approval, then this browser
  opens the host's tabs in a window of their own and follows them. Browser tools stay in this
  browser; with a drive code, changes in the shared tabs reach the host's browser. A person at
  work in the other copy of a tab pauses agents in it, as here. `status`, `leave`.