Pair a remote AI agent with your browser. One command generates a setup key and prints instructions the other agent can follow to connect. Works with OpenClaw, Hermes, Codex, Cursor, or any agent that can make HTTP requests. The remote agent gets its own tab. Default access is read+write+admin+meta — the trust boundary is the pairing ceremony, not the scope; `--control` adds stop/restart/disconnect and `--restrict` narrows it.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add timurgaleev/vibestack --skill pair-agent --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Pair Agent?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/timurgaleev-pair-agent)More formats (shields.io, HTML) on the badges page.
---
name: pair-agent
description: |
Pair a remote AI agent with your browser. One command generates a setup key and prints instructions the other agent can follow to connect. Works with OpenClaw, Hermes, Codex, Cursor, or any agent that can make HTTP requests. The remote agent gets its own tab. Default access is read+write+admin+meta — the trust boundary is the pairing ceremony, not the scope; `--control` adds stop/restart/disconnect and `--restrict` narrows it.
triggers:
- pair with agent
- connect remote agent
- share my browser
allowed-tools:
- Bash
- Read
- AskUserQuestion
---
## When to invoke
Use when asked to "pair agent", "connect agent", "share browser", "remote browser", "let another agent use my browser", or "give browser access".
## Preamble
```bash
eval "$(~/.vibestack/bin/vibe-slug 2>/dev/null)" 2>/dev/null || SLUG="unknown"
_LEARN_FILE="${VIBESTACK_HOME:-$HOME/.vibestack}/projects/${SLUG:-unknown}/learnings.jsonl"
if [ -f "$_LEARN_FILE" ]; then
_LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ')
echo "LEARNINGS: $_LEARN_COUNT entries loaded"
if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then
~/.vibestack/bin/vibe-learnings-search --limit 5 2>/dev/null || true
fi
else
echo "LEARNINGS: none yet"
fi
```
{{include lib/snippets/session-host.md}}
{{include lib/snippets/decision-brief.md}}
{{include lib/snippets/working-protocols.md}}
{{include lib/snippets/state-protocols.md}}
# /pair-agent — Share Your Browser With Another AI Agent
You're sitting in Claude Code with a browser running. You also have another AI agent
open (OpenClaw, Hermes, Codex, Cursor, whatever). You want that other agent to be
able to browse the web using YOUR browser. This skill makes that happen.
## How it works
Your vibestack browser runs a local HTTP server. This skill creates a one-time setup
key, prints a block of instructions, and you paste those instructions into the other
agent. The other agent exchanges the key for a session token, creates its own tab,
and starts browsing. Each agent gets its own tab. They can't mess with each other's
tabs.
The setup key expires in 5 minutes and can only be used once. If it leaks, it's dead
before anyone can abuse it. The session token lasts 24 hours.
**Same machine:** If the other agent is on the same machine (like OpenClaw running
locally), you can skip the copy-paste ceremony and write the credentials directly to
the agent's config directory.
**Remote:** If the other agent is on a different machine, you need an ngrok tunnel.
The skill will tell you if one is needed and how to set it up.
## SETUP (run this check BEFORE any browse command)
```bash
B="${CLAUDE_SKILL_DIR:-$HOME/.claude/skills/pair-agent}/../browse/bin/vibe-browse"
[ -x "$B" ] || B="$(command -v vibe-browse || true)"
if [ -n "$B" ] && [ -x "$B" ] && [ "$("$B" status 2>/dev/null)" != "BROWSE_NOT_AVAILABLE" ]; then
echo "READY: $B"
else
echo "NEEDS_SETUP"
fi
```
If `NEEDS_SETUP`, stop and tell the user the browse binary could not be found or
could not start — node is missing, or first-run setup was declined.
Pairing needs the full browse daemon: the stateless fallback shim has no token
registry to mint against and answers `NOT_SUPPORTED:pair-agent`. If you see
that, say the daemon is not running in this checkout and stop — never describe a
pairing that did not happen.
## Step 1: Check prerequisites
```bash
$B status 2>/dev/null
```
If the browse server is not running, start it:
```bash
$B goto about:blank
```
This ensures the server is up and healthy before pairing.
## Step 2: Ask what they want
Use AskUserQuestion:
> Which agent do you want to pair with your browser? This determines the
> instructions format and where credentials get written.
Options:
- A) OpenClaw (local or remote)
- B) Codex / OpenAI Agents (local)
- C) Cursor (local)
- D) Another Claude Code session (local or remote)
- E) Something else (generic HTTP instructions — use this for Hermes)
Based on the answer, set `TARGET_HOST`:
- A → `openclaw`
- B → `codex`
- C → `cursor`
- D → `claude`
- E → generic (no host-specific config)
## Step 3: Local or remote?
Use AskUserQuestion:
> Is the other agent running on this same machine, or on a different machine/server?
>
> **Same machine** skips the copy-paste ceremony. Credentials are written directly to
> the agent's config directory. No tunnel needed.
>
> **Different machine** generates a setup key and instruction block. If ngrok is
> installed, the tunnel starts automatically. If not, I'll walk you through setup.
>
> RECOMMENDATION: Choose A if the agent is local. It's instant, no copy-paste needed.
Options:
- A) Same machine (write credentials directly)
- B) Different machine (generate instruction block for copy-paste)
## Step 4: Execute pairing
### If same machine (option A):
Run pair-agent with --local flag:
```bash
$B pair-agent --local TARGET_HOST
```
Replace `TARGET_HOST` with the value from Step 2 (openclaw, codex, cursor, etc.).
If it succeeds, tell the user:
"Done. TARGET_HOST can now use your browser. It will read credentials from the
config file that was written. Try asking it to navigate to a URL."
If it fails (host not found, write permission error), show the error and suggest
using the generic remote flow instead.
### If different machine (option B):
**Consent gate (once per machine).** The tunnel exposes this browser beyond the
machine, so it stays off until the user opts in. Check the standing consent:
```bash
~/.vibestack/bin/vibe-config get pair_agent 2>/dev/null || echo "unset"
```
If the value is not `on`, ask via AskUserQuestion. This is a one-way door — it
opens a path from the internet to a browser that is already logged into the
user's accounts — so ask it every time consent is absent, and never let a
question preference suppress it:
> "Remote pairing runs an ngrok tunnel from the internet to this machine's
> browser. The remote agent gets a scoped token and a command allowlist, but the
> browser it reaches is the one holding your sessions. Enable pair-agent on this
> machine?"
Options: A) Enable — run `~/.vibestack/bin/vibe-config set pair_agent on`, confirm
it reads back `on`, and continue. B) No — stop here; local pairing (option A
above) still works and exposes nothing.
If the value is already `on`, say nothing and continue — consent stands until
`vibe-config set pair_agent off`.
Then detect ngrok status:
```bash
which ngrok 2>/dev/null && echo "NGROK_INSTALLED" || echo "NGROK_NOT_INSTALLED"
ngrok config check 2>/dev/null && echo "NGROK_AUTHED" || echo "NGROK_NOT_AUTHED"
```
**If ngrok is installed and authed:** Just run the command. The CLI will auto-detect
ngrok, start the tunnel, and print the instruction block with the tunnel URL:
```bash
$B pair-agent --client TARGET_HOST
```
**What a paired agent gets by default.** The daemon grants read + write +
admin + meta — the trust boundary is the pairing ceremony, not the scope, so a
default agent CAN execute JavaScript and read cookies and storage. `--control`
(and its `--admin` alias) adds the browser-wide destructive commands: stop,
restart, disconnect. `--restrict` is what narrows access. Say this plainly when
you present the pairing: the person approving it is granting more than the flag
names suggest.
If the user also needs the control commands (stop, restart, disconnect):
```bash
$B pair-agent --control --client TARGET_HOST
```
**CRITICAL: You MUST output the full instruction block to the user.** The command
prints everything between ═══ lines. Copy the ENTIRE block verbatim into your
response so the user can copy-paste it into their other agent. Do NOT summarize it,
do NOT skip it, do NOT just say "here's the output." The user needs to SEE the block
to copy it. Output it inside a markdown code block so it's easy to select and copy.
Then tell the user:
"Copy the block above and paste it into your other agent's chat. The setup key
expires in 5 minutes."
**If ngrok is installed but NOT authed:** Walk the user through authentication.
SECURITY: the ngrok authtoken must NEVER pass through this chat, a Bash tool
call, or shell history — a token pasted here lands in the transcript, and in
anything the transcript is synced or shared to. The user runs the auth command in
their OWN terminal; you only verify the result.
Tell the user:
"ngrok is installed but not logged in. Let's fix that — in your own terminal
(not here; the token should never enter this chat):
1. Go to https://dashboard.ngrok.com/get-started/your-authtoken
2. Copy your auth token
3. In YOUR terminal, run: ngrok config add-authtoken <paste your token>
4. Tell me 'done' when finished."
STOP here and wait for the user to say they have run it. Do NOT ask for the
token and do NOT accept a pasted one. If the user pastes it anyway, tell them to
rotate it at https://dashboard.ngrok.com — it is in the transcript now — and to
re-auth in their terminal with the new one.
When they say done, verify without ever touching the token:
```bash
ngrok config check 2>/dev/null && echo "NGROK_AUTHED" || echo "NGROK_NOT_AUTHED"
```
If `NGROK_AUTHED`: retry `$B pair-agent --client TARGET_HOST`.
**If ngrok is NOT installed:** Walk the user through installation:
Tell the user:
"To connect a remote agent, we need ngrok (a tunnel that exposes your local
browser to the internet securely).
1. Go to https://ngrok.com and sign up (free tier works)
2. Install ngrok:
- macOS: `brew install ngrok`
- Linux: `snap install ngrok` or download from ngrok.com/download
3. Auth it: `ngrok config add-authtoken YOUR_TOKEN`
(get your token from https://dashboard.ngrok.com/get-started/your-authtoken)
4. Come back here and run `/pair-agent` again."
STOP here. Wait for the user to install ngrok and re-invoke.
## Step 5: Verify connection
After the user pastes the instructions into the other agent, wait a moment then check:
```bash
$B status
```
Look for the connected agent in the status output. If it appears, tell the user:
"The remote agent is connected and has its own tab. You'll see its activity in the
side panel if you have vibestack Browser open."
## What the remote agent can do
With default access (read + write + admin + meta):
- Navigate to URLs, click elements, fill forms, take screenshots
- Read page content (text, HTML, snapshot)
- Create new tabs (each agent gets its own)
`--control` (and its `--admin` alias) adds the browser-wide destructive
commands: stop, restart, disconnect. `--restrict` is what narrows access.
**Scope is not the only gate.** A remote agent reaches the daemon through the
tunnel surface, which serves a fixed command allowlist on top of whatever the
token grants. `eval` is on it; `js`, `cookies` and `storage` are not, and no
scope makes them dispatchable remotely. `eval --out` is also refused there —
`--out` is a disk write, and the tunnel surface never grants disk-write
capability. An agent paired with `--local` talks to the daemon directly and gets
all four. Explain the asymmetry when a remote agent reports a command "missing":
it is a boundary, not a bug.
**Never pass `root` as a client name.** `root` is the daemon's own identity, and
the scope, domain and rate-limit checks all short-circuit for it — an agent
paired under that name would inherit unrestricted access no `--restrict` could
claw back.
## Troubleshooting
**"Tab not owned by your agent"** — The remote agent tried to interact with a tab
it didn't create. Tell it to run `newtab` first to get its own tab.
**"Domain not allowed"** — The token has domain restrictions. Re-pair with broader
domain access or no domain restrictions.
**Re-pairing replaces, but only under the same name.** Minting a session token
for a client name revokes that name's previous session token. So tightening an
agent that is already live means re-pairing it with the **same `--client` name**
and the narrower `--restrict`. Give it a new name and you have not tightened
anything — you have two live agents, the over-scoped one still holding its
token until it expires.
**"Rate limit exceeded"** — The agent is sending > 10 requests/second. It should
wait for the Retry-After header and slow down.
**"Token expired"** — The 24-hour session expired. Run `/pair-agent` again to
generate a new setup key.
**Agent can't reach the server** — If remote, check the ngrok tunnel is running
(`$B status`). If local, check the browse server is running.
## Platform-specific notes
### OpenClaw / AlphaClaw
OpenClaw agents use the `exec` tool instead of `Bash`. The instruction block uses
`exec curl` syntax which OpenClaw understands natively. When using `--local openclaw`,
credentials are written to `~/.openclaw/skills/vibestack/browse-remote.json`.
### Codex
Codex agents can execute shell commands via `codex exec`. The instruction block's
curl commands work directly. When using `--local codex`, credentials are written
to `~/.codex/skills/vibestack/browse-remote.json`.
### Cursor
Cursor's AI can run terminal commands. The instruction block works as-is.
When using `--local cursor`, credentials are written to
`~/.cursor/skills/vibestack/browse-remote.json`.
## Revoking access
**See who holds access first.** The daemon serves a root-only listing of live
agents — client name, scopes, domain restrictions, expiry, command count:
```bash
curl -H "Authorization: Bearer $ROOT_TOKEN" "http://127.0.0.1:<port>/agents"
```
It lists exchanged session tokens only. A setup key nobody has redeemed yet does
not appear, so an empty list is not proof that no pairing is in flight — a key
minted in the last five minutes may still be exchangeable. There is no CLI
wrapper for this listing; `$B tunnel agents` does not exist.
**Stopping the daemon revokes everything.** Scoped tokens live in daemon memory
and are never written to the state file, so the next command boots a fresh daemon
with a new root token and every previously issued token — session tokens and
unexchanged setup keys alike — is dead:
```bash
$B stop
```
That also drops the tunnel: the daemon holds the ngrok listener in-process, so
it goes down with the daemon and the public URL stops resolving. Do NOT reach
for `pkill -f ngrok` — it would kill every unrelated ngrok the user is running
and still not be the thing serving this tunnel.
**Per-agent revoke exists, but only over HTTP.** The daemon serves a root-only
`DELETE /token/<clientId>`, which invalidates that agent's tokens and leaves the
others alone:
```bash
curl -X DELETE -H "Authorization: Bearer $ROOT_TOKEN" \
"http://127.0.0.1:<port>/token/<agent-name>"
```
There is no CLI wrapper for it — `$B tunnel revoke` and `$B tunnel rotate` do
NOT exist, so never tell a user to run those; they would believe a shared
browser session had been cut off when it had not. If the root token is not to
hand, stopping the daemon is the blunt equivalent.
Tokens expire on their own (24h by default), and a setup key expires in 5
minutes if never exchanged.
## Orchestrator injection prompts
When a remote/orchestrating agent spawns a vibestack coding session (rather than
just sharing the browser), it can inject a ready-made discipline prompt from
`lib/orchestrator/` into the target repo's `CLAUDE.md`:
- `lite.md` — planning discipline for any spawned coding task
- `full.md` — `/autoplan` → implement → `/ship`, report the PR URL
- `plan.md` — `/office-hours` → `/autoplan`, save the reviewed plan, don't build
These are plain prompts (nothing installs them); the orchestrator reads the file
and appends its body. See `lib/orchestrator/README.md`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!