# gate-browser — skill (for AI agents)

**Instructions:** This file is returned by **GET /** as `text/markdown` on the service base URL. Fetch **GET /** on the live deployment for the authoritative contract. Production base URL: **`https://browser.ctrl1.com`**. The same process serves **captcha recognition** at **`POST /captcha`** (HTTP JSON) and a **WebSocket gateway** that proxies to Chromium’s **native Chrome DevTools Protocol (CDP)** (`--remote-debugging-port`). Prefer the sections below for the task at hand.

---

## Endpoints (summary)

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/` | This skill document (`skill.md`) |
| GET | `/health` | `{"ok":true}` liveness |
| POST | `/captcha` | Recognize captcha (JSON body) |
| WebSocket | `GET /<username>` (path optional) | Connect to a browser; optional `?profile=<name>` |

Reserved **`<username>`** values (cannot be used as the first path segment): `health`, `browser`, `captcha`, `favicon.ico`. Use only `[a-zA-Z0-9_-]{1,64}` for `username` and for `profile` names.

---

## Browser gateway (WebSocket)

### URL shape

Production WebSocket base: **`wss://browser.ctrl1.com`** (TLS, default **443** — no path port suffix).

**Default username:** If the path has **no** first segment (e.g. **`wss://browser.ctrl1.com`**, **`wss://browser.ctrl1.com/`**, or **`wss://browser.ctrl1.com?…`** only), the server uses **`guest`** as the username (same as explicitly connecting to **`wss://browser.ctrl1.com/guest`**).

- **Named profile (persistent user-data directory):**  
  `wss://browser.ctrl1.com/<username>?profile=<profileName>`  
  Example: **`wss://browser.ctrl1.com/alice?profile=work`**  
  Same URL reconnects to the **same** persisted Chromium profile on the server (created automatically). Multiple `profile` values per user are supported (e.g. `work`, `home`).

- **Ephemeral (throwaway browser, no `profile` query):**  
  `wss://browser.ctrl1.com/<username>`  
  Example: **`wss://browser.ctrl1.com/alice`**  
  Each new WebSocket upgrade starts a **new** Chromium instance with a fresh temp directory on the server. When the client disconnects, the server **closes the browser** and **deletes** that directory (after optional delays).

On **`https://browser.ctrl1.com`**, captcha recognition uses **`POST /captcha`** (not `POST /`).

### Client libraries

The gateway **WebSocket-upgrades** (path `/<username>` or empty path → **`guest`**) are proxied to Chromium’s **root CDP** socket (same as `webSocketDebuggerUrl` from `/json/version`). Clients should use lightweight packages only — **`puppeteer-core`** (`connect({ browserWSEndpoint })`) or **`playwright-core`** (`connectOverCDP`) — the browser runs on **https://browser.ctrl1.com**; do not bundle or download Chromium in the client for this flow.

**puppeteer-core:**

```javascript
const puppeteer = require("puppeteer-core");

// Ephemeral as guest (omit path): browserWSEndpoint: "wss://browser.ctrl1.com"
const browser = await puppeteer.connect({
  browserWSEndpoint: "wss://browser.ctrl1.com/alice?profile=work",
});
```

**playwright-core:**

```javascript
import { chromium } from "playwright-core";

const wsUrl = "wss://browser.ctrl1.com/alice?profile=work";
// Default guest (omit path): "wss://browser.ctrl1.com"
const browser = await chromium.connectOverCDP(wsUrl);
// Persistent profile on the server already has a default context:
const context = browser.contexts()[0];
const page = context.pages()[0] ?? (await context.newPage());
```

**`chromium.connect(wsUrl)`** (Playwright `launchServer` wire protocol) must **not** be used against this gateway — use **`connectOverCDP`** with **`playwright-core`** instead.

The **browser.ctrl1.com** backend uses the full **`playwright`** package to launch Chromium on the server; that is independent of your client dependencies (**`puppeteer-core`** / **`playwright-core`** only).

---

## Captcha — what it does

**POST /captcha** accepts JSON with **`input`** only: an image as an `http(s)` URL, a `data:image/...;base64,...` string, or raw base64. The service returns JSON containing the recognized captcha text. How the image is processed on the server is not part of this contract.

### Captcha endpoint

| Method | Path | Purpose |
|--------|------|---------|
| POST | `/captcha` | Recognize captcha (JSON body) |

### POST /captcha request body

Only **`input`** is defined for clients. Other JSON fields may be ignored.

```json
{
  "input": "<required: url | data URL | raw base64>"
}
```

- **`input`** (required): trimmed string — `http(s)://...` image URL, `data:image/png;base64,...`, or base64-only payload.

### POST /captcha success response

```json
{
  "ok": true,
  "text": "<recognized string>",
  "raw": "<auxiliary string; treat as opaque unless you need it>",
  "meta": {
    "inputKind": "url | dataUrl | base64",
    "requestId": "<opaque id; include in support tickets if asked>"
  }
}
```

Response fields do not describe how recognition is implemented on the server.

### POST /captcha error response

```json
{
  "ok": false,
  "error": "<message>",
  "meta": { "requestId": "...", "inputKind": "..." }
}
```

Typical status codes: **400** (bad or unsupported `input`), **502** (recognition could not be completed), **500** (unexpected server error).

### curl (HTTP on production)

Examples use **`https://browser.ctrl1.com`**.

```bash
# Health
curl -sS "https://browser.ctrl1.com/health"

# Load this skill from the running server
curl -sS "https://browser.ctrl1.com/"

# Recognize — raw base64 (short PNG example)
curl -sS -X POST "https://browser.ctrl1.com/captcha" \
  -H "Content-Type: application/json" \
  -d "{\"input\":\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"}"

# Recognize — data URL
curl -sS -X POST "https://browser.ctrl1.com/captcha" \
  -H "Content-Type: application/json" \
  -d "{\"input\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"}"

# Recognize — image URL (must be reachable from the service host)
curl -sS -X POST "https://browser.ctrl1.com/captcha" \
  -H "Content-Type: application/json" \
  -d "{\"input\":\"https://example.com/captcha.png\"}"
```

### Node.js (captcha, global fetch)

```javascript
const BASE = process.env.BROWSER_BASE || "https://browser.ctrl1.com";

async function recognizeCaptcha(input) {
  const res = await fetch(BASE + "/captcha", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ input }),
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) {
    const err = new Error(data.error || res.statusText);
    err.status = res.status;
    err.body = data;
    throw err;
  }
  return data;
}

// Example
recognizeCaptcha("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")
  .then((r) => console.log(r.text))
  .catch((e) => console.error(e.status, e.message));
```

---

## Agent checklist

1. **Discover:** Call **`GET https://browser.ctrl1.com/`** for the full contract (captcha + browser).
2. **Captcha:** `POST https://browser.ctrl1.com/captcha` with JSON `{ "input": "<...>" }` only.
3. **Browser:** **`wss://browser.ctrl1.com/<username>?profile=<name>`** (named) or **`wss://browser.ctrl1.com/<username>`** (ephemeral), or **`wss://browser.ctrl1.com`** / **`wss://browser.ctrl1.com/`** (no segment → **`guest`** ephemeral); use the URL as the **CDP** endpoint (**`puppeteer-core`** **`browserWSEndpoint`**, or **`playwright-core`** **`chromium.connectOverCDP(wsUrl)`**).
4. **Do not** call **`POST /browser/sessions`**, **`DELETE /browser/...`**, or any other **`/browser/**` HTTP routes** — they are **not** part of this contract and are not for agent automation. Browser access here is **WebSocket only** (`wss://…`).
5. **Do not** send captcha JSON to the WebSocket endpoint, or treat **`POST /captcha`** as a browser API.
6. **Privacy:** Avoid pasting full `input` (especially long base64) into public chats or logs.
7. **Limits:** Keep JSON bodies within normal size limits; very large bodies may be rejected.
