# Rever

> Reverse-engineer web APIs by driving a running Rever Browser instance — connect to its published MCP endpoint and use its browser-automation, network-capture, and JS-bundle-analysis tools. Use when the user types /rever, asks to reverse or analyze a website's API, capture or inspect its network traffic, deobfuscate its JavaScript, or reproduce its requests with Rever Browser.

- Skill: `greekr4/rever` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add greekr4/rever`
- Raw SKILL.md: https://api.skillmd.com/api/skills/greekr4/rever/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: greekr4 (https://skillmd.com/u/greekr4)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/greekr4/rever

---


# Rever Browser

Drive a running **Rever Browser** app from this Claude Code session to get the user what they want out of the web. Your default way of working is to **operate the browser directly** — navigate, type, click — and read results straight from the rendered DOM. On top of that you can **reverse-engineer web APIs** (read captured traffic, decode tokens, hook scripts, produce reproducible client code) when a task genuinely calls for it. All of this is exposed through ~140 MCP tools.

**DOM-first is the default. API reversing is a powerful mode you switch into on demand** — see "Strategy" below.

## Prerequisite

The **Rever Browser app must be running** — it publishes its MCP endpoint on startup to:

```
~/Library/Application Support/rever-browser/mcp-endpoint.json
```

The port is OS-assigned (changes each launch), so always resolve it from that file. If the file is missing, tell the user to launch Rever Browser first.

## Connect

**Preferred — register as native MCP tools** (tools appear as `mcp__rever__*`):

```bash
claude mcp add --transport http rever "$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/Library/Application Support/rever-browser/mcp-endpoint.json')))['url'])")"
```

Re-run this whenever Rever Browser restarts (the port changes). MCP servers load at session start, so if the tools aren't visible yet, start a fresh `claude` session.

**Quick calls without registration** — this skill bundles a helper (`rever.py`, in this skill's folder). It resolves the endpoint each call, so it works immediately in the current session:

```bash
python3 <this-skill-dir>/rever.py --list                                   # list tools
python3 <this-skill-dir>/rever.py browser_navigate '{"url":"https://example.com"}'
python3 <this-skill-dir>/rever.py list_requests
```

## Iron rule: investigate before you ask

The user has a live browser tab open next to this session. **Whenever a request is ambiguous — "this", "here", "what failed", "fix it", "the page", "just now" — your FIRST action is always to look, not to ask a clarifying question.** Cheapest first, in order:

1. `browser_snapshot` — current URL, title, accessibility tree. Tells you what's on screen.
2. `list_requests({ since: <recent ms> })` — what just got captured.
3. `console_logs({ since })` / `console_exceptions()` — JS errors that just happened.

Ask a clarifying question only after you have looked and still cannot reasonably guess the intent. "What do you mean?" is almost never the right first reply here.

## Strategy: DOM-first by default

Your first instinct is to **drive the browser and read the rendered page**, not to hunt for a JSON API. Most "search X", "get the results", "pull the list", "what does this page show" tasks are solved entirely in the DOM.

**Default flow (DOM-first):**
1. Drive the page to the state you need — `browser_navigate`, then `browser_type` (+ submit), `browser_click`, `browser_scroll`. One step at a time; each interaction returns a fresh snapshot.
2. Read the result from the rendered DOM — `dom_extract` for structured lists (results, tables, cards), `browser_snapshot` for an overview, `browser_evaluate` for anything custom.
3. Present the extracted data. You do **not** need an API to answer.

**Interact like a human; read with JS.** To *act* on the page — click, type, submit — always use `browser_click` / `browser_type` (on a snapshot `rN` ref) or `browser_click_selector` / `browser_type_selector` (on a CSS selector when you have no ref). These move a real cursor and fire trusted events, and survive bot detection. **Never click or set values via `browser_evaluate` / raw JS** (`el.click()`, `el.value = …`, `dispatchEvent`) — it skips the cursor, fires untrusted events, and silently fails on framework-controlled inputs. `browser_evaluate` and `dom_extract` are **read-only**. If a snapshot is too big for a clean ref, do **not** fall back to JS — locate the element by CSS selector and use the `_selector` tools.

**Stuck on what the page is doing?** Use `vision_judge` — it screenshots the page and asks a vision model. Good for "did my search actually run / did results render?", spotting a modal/captcha/ad overlay, or reading text baked into images.

**Server-rendered (SSR) sites — do not get stuck.** Many sites (portals, news, gov, older sites) render everything server-side: the traffic is mostly `document`/HTML with few or no JSON XHR/Fetch calls. On these, **there is no JSON API to reverse — the DOM is the answer.** Never conclude "there's no API so this is hard" and stop. `dom_extract` the rendered result and deliver it.

**When to switch into API-reversing mode.** Only when:
- The user **explicitly** asks — "analyze the API", "reverse this endpoint", "make a client/reproducible script", "how does this get signed".
- DOM-first genuinely can't reach the goal — bulk collection across pagination/infinite scroll, reproducing an authenticated call programmatically, or the data only exists in an XHR/Fetch JSON payload.

**One-line rule:** need the result in front of you? → DOM-first. Need to reproduce/automate it in code? → API mode.

## Tool map

- **Browser** — `browser_navigate`, `browser_snapshot` (accessibility tree with `rN` refs), `browser_click`/`browser_type`/`browser_scroll` (by ref), `browser_click_selector`/`browser_type_selector` (by CSS), `browser_wait_for`, `browser_screenshot`, `browser_evaluate`, `vision_judge`, `set_viewport`, `browser_tabs`.
- **DOM read** — `dom_extract` (structured scrape by selector), `browser_evaluate` (read-only custom JS).
- **Traffic** — `list_requests`, `get_request`, `get_request_initiator`, `request_diff`, `find_api_base`, `fetch_body`, `har_export`, `replay_request`.
- **Bundle analysis** — `list_scripts`, `grep_script`/`grep_scripts`, `extract_context`, `detect_bundler`, `deobfuscate_script` / `deob_auto`, `get_original_source`, `list_sources`, `resolve_source`.
- **Auth & crypto** — `auth_dump`, `decode_token`, `protobuf_decode`, `crypto_trace_start/list/stop`, `crypto_chain`, `hmac_compute`, `hash_iter`, `scan_secrets`.
- **Reproduce & fuzz** — `repeater_send` (browser-context replay), `replay_request` (Node fetch), `graphql_introspect`, `burst_send`, `intruder_run`, `crlf_test`, `lfi_probe`, `path_probe`, `payload_probe`, `create_macro` / `export_python_client`.
- **Intercept & override** — `intercept_add`/`pending`/`continue`/`fulfill`/`fail`/`remove`, `override_add`/`list`/`remove`, `inject_run_now`/`add`/`toggle`/`remove`, `header_preset_save`/`apply`/`disable`.
- **Debugger** — `bp_add`, `bp_eval_in_frame`, `bp_step_*`, `bp_resume`, `bp_status`.
- **Console / storage / cookies** — `console_logs`/`eval`/`exceptions`, `cookie_list`/`set`/`delete`, `local_storage_*`, `session_storage_*`, `sw_list`/`unregister`/`caches`.
- **WebSocket** — `list_websockets`, `get_ws_frames`, `ws_send`.
- **Findings** — `finding_add`, `finding_list`, `finding_export`, `security_inspect`.

Run `--list` for the full, current set.

## Workflow defaults

- **DOM before API.** First ask "can I just read this off the page?" A single filtered `list_requests({ since })` as recon (per the iron rule) is always fine — but don't do API-reversing *work* (diff / replay / codegen) unless you're in API mode.
- **One step at a time** in browser control: navigate → wait/snapshot → confirm → next. Don't chain 5 actions blindly.
- **Filter, don't dump.** Always pass `host`, `since`, `methodOrType`, or `limit` to `list_requests` — the store holds ~500 entries and an unfiltered dump buries the signal. Skip static assets (`.css`, `.js`, `.png`, `.woff`, ad/analytics) unless asked. **API candidates** are usually XHR/Fetch with a JSON body/response, fired right after a user action, often carrying `Authorization` or a cookie session.
- **Bot-detection sites**: rely on the user's own session — never automate login on Instagram, X, etc.
- **Confirm before a real side effect.** You are driving the user's *real authenticated session*, so one call can place an order, send a message, delete data, or hammer a target. **Before** any of these, state in one line what will happen and to which target, then wait for the user:
  - `replay_request` / `repeater_send` of a **non-GET / state-changing** request (POST/PUT/PATCH/DELETE, or a GET with obvious side effects).
  - Delivering a client (`export_python_client`) that reproduces a **non-idempotent** call — say plainly it will act on their account if run.
  - Any active probe: `intruder_run`, `burst_send`, `payload_probe`, `crlf_test`, `path_probe`, `lfi_probe` — name the target and rough request volume, and agree a scope/throttle first.
  - **Read-only work never needs this gate**: navigate, snapshot, `dom_extract`, `list_requests`/`get_request`, a single GET replay, decoding, source grep. Don't get timid on ordinary recon — the gate is only for state changes and aggressive probing.
- **Tear down your hooks when done.** `inject_add` snippets keep running on every page load, `intercept_add` in `block`/`modify` keeps stalling/rewriting traffic, `bp_add` leaves execution paused, `override_add` / `dom_set_*` keep altering the page — all silently break the user's normal browsing. When the task that set them up is finished, remove/toggle them off and resume any paused request. Leave the browser as you found it.
- **Deliverables land in your scratch cwd** — standalone Python/Node clients, curl one-liners, HAR/JSON dumps. Don't scatter files into the user's project unasked.

## Reproducing a signed/encrypted request — first-divergence discipline

When your local repro of a signed/encrypted value doesn't match what the real browser produced, never report "almost working." Instead:
1. Pin the **first** point of divergence — param ordering, seconds-vs-ms timestamp, salt position/encoding, key-derivation input, trailing newline.
2. Use `console_eval` to compare intermediate values, and `crypto_trace_start`/`list`/`stop` to recover the real key/message/signature at call time (start tracing → trigger the signing action → list). Misses hand-rolled pure-JS crypto.
3. Record each environment patch you apply to close the gap, then state explicitly whether you now have a **stable** repro and what gap (if any) remains — turning "almost done" into a concrete next step.

## Don't talk yourself out of the right move (excuse → rebuttal)

| The excuse you'll reach for | The rule (do this instead) |
|---|---|
| "Snapshot is huge / no clean `rN` ref — I'll just `el.click()` or `el.value=…` via `browser_evaluate`." | **Forbidden.** Raw JS fires untrusted events and silently fails on framework inputs. Find the element by CSS selector and use `browser_click_selector` / `browser_type_selector`. |
| "No JSON XHR/Fetch here, so this page has no API — I'll stop and say it's hard." | **SSR means the DOM *is* the answer.** Don't stop. `dom_extract` the rendered result and deliver it. |
| "The request is vague ('this', 'why broken', 'fix it') — let me ask what they mean." | **Look first.** `browser_snapshot` → `list_requests({since})` → `console_exceptions()`, then answer. A clarifying question is the last resort. |
| "Let me `list_requests` with no filter and read everything to be safe." | **Filter, don't dump.** Always pass `host` / `since` / `methodOrType` / `limit`. |
| "They asked for `block`/`modify`, but `log` is safer — I'll just log it." | **Never silently downgrade a destructive parameter the user asked for.** Requested `mode:"block"` → use `block`. If risky, say so and let them decide. |
| "I've basically got it — I'll paste the client and move on." | Run the self-audit below first. "Basically done" with a leaked token or an uncited claim is not done. |

## Before you claim it's done (self-audit)

Before you say "done", silently confirm each — if any answer is "no", fix it *before* replying:

- **Evidence cited?** Every claim about a request carries its `requestId`; every DOM result carries the CSS `selector` + page URL.
- **Secrets masked?** No full token, cookie, password, card number, or national ID in output (`Authorization: Bearer ********`).
- **Reproducible?** A delivered client/script runs as-is (real endpoint, required headers) — not a sketch.
- **Read vs. done?** You actually ran the tools and saw the result — not "I would run X".
- **Side effect confirmed?** If this turn would replay a state-changing request, deliver a non-idempotent client, or run an active probe, you told the user what it does to which target and got a go-ahead first.

## Deliverables & output style

**DOM extraction (the default ask):** present the data as a table/list, cite the CSS `selector` (and page URL) so it's reproducible, and say how far you paginated/scrolled if you collected more.

**API client (only when asked to analyze the API):** confirm which request you picked (cite `requestId`); table of endpoint · method · required headers · body schema · response schema; one client function in the language requested (default: Python `requests`).

- **Bullets and tables, not prose.** Cite `requestId` for every claim. Code blocks carry a language tag. Keep responses tight — the user can ask for depth.
- **Always mask secrets** in output. Never echo full tokens, passwords, card numbers, national IDs.

## Troubleshooting

- **"No MCP endpoint found"** → Rever Browser isn't running. Launch the app.
- **Tools missing after `claude mcp add`** → MCP loads at session start; open a new `claude` session, or use the `rever.py` helper for the current one.
- **Connection refused / stale** → Rever restarted and the port changed. Re-run the connect command.

