> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monolex.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Site Plugins

> Add any website with a JSON file. No code, no build, instant.

# Site Plugins

Every website MonoSurf can access is defined by a single JSON file. Adding a new site means creating one file — no Rust code, no rebuilding, no deploying. Standalone `monosurf` and `kernel-cli websurf` enter through the same `lib-monosurf` facade and load the same plugins through `lib-monosurf-core`.

*Verified against `monosurf 0.11.3`, OpenCLIs build
`2026-07-19-222928`.*

## Plugin Location

```
~/Library/Application Support/Monolex/monosurf/sites/
├── x.com.json
├── threads.net.json
├── news.ycombinator.com.json
├── github.com.json
├── reddit.com.json
└── producthunt.com.json
```

## Plugin Structure

```json theme={null}
{
  "description": "Human-readable site description",
  "base_url": "https://example.com",
  "tags": ["category", "tags", "for", "search"],

  "commands": {
    "command_name": {
      "url": "/path?q={query}",
      "wait": "css-selector",
      "wait_timeout": 10,
      "extract_js": "(() => { /* JS that returns JSON array */ })()",
      "intent": "read"
    }
  },

  "display": {
    "command_name": {
      "format": "list",
      "template": "{field1} — {field2}\n   {field3}"
    }
  }
}
```

## Fields

### Top Level

| Field         | Required | Description                 |
| ------------- | -------- | --------------------------- |
| `description` | Yes      | What this site is           |
| `base_url`    | Yes      | Must match filename domain  |
| `tags`        | No       | For `monosurf sites search` |
| `commands`    | Yes      | Available commands          |
| `display`     | No       | Output formatting           |

### Command Fields

| Field          | Required | Description                                                      |
| -------------- | -------- | ---------------------------------------------------------------- |
| `url`          | Yes      | URL to navigate. `{query}` for variable substitution             |
| `wait`         | No       | CSS selector to wait for before extraction. Empty = timeout only |
| `wait_timeout` | No       | Seconds to wait (default: 10)                                    |
| `extract_js`   | No       | JavaScript that returns `JSON.stringify([...])`                  |
| `intent`       | No       | `"read"` (default) or `"write"`                                  |
| `actions`      | No       | Sequential actions for write operations                          |

### Actions (Write Operations)

```json theme={null}
"actions": [
  {"click": "css-selector"},
  {"type": "{text}"},
  {"wait": "css-selector"},
  {"sleep": 500},
  {"js": "(() => { document.querySelector('[role=button]').click(); return 'clicked'; })()"}
]
```

| Action  | Description                                                                                                                                    |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `click` | Click an element by CSS selector (JS `element.click()`)                                                                                        |
| `type`  | Type text into focused element. Supports multiline (line breaks preserved)                                                                     |
| `wait`  | Wait for a CSS selector between actions, bounded by `wait_timeout`                                                                             |
| `sleep` | Wait milliseconds                                                                                                                              |
| `js`    | Execute JavaScript in browser context via CDP `Runtime.evaluate`                                                                               |
| `pw`    | Legacy compatibility shape. It can still parse and appears in installed help, but CDP mode refuses execution; convert it to supported actions. |

### Accessing Accessibility Tree in JS Actions

For sites that strip CSS selectors (like Meta's Threads), use ARIA roles in JS:

```json theme={null}
{"js": "(() => { const btns = document.querySelectorAll('[role=button]'); for (const b of btns) { if (b.textContent.trim() === 'Reply') { b.click(); return 'clicked'; } } return 'not found'; })()"}
```

Use `js`, `click`, `type`, and `wait` for current CDP workflows. Legacy plugin
JSON can still contain a `pw` action and `monosurf sites help` currently lists
that compatibility shape, but execution fails closed with conversion guidance.

### Display Templates

Templates use `{field}` to reference fields from extracted JSON objects:

```json theme={null}
"display": {
  "top": {
    "format": "list",
    "template": "{rank}. {title} ({site})\n   {score} | {author} | {comments}"
  }
}
```

## Domain Security

The filename IS the identity. MonoSurf enforces:

1. **Filename = domain**: `x.com.json` can only define `base_url` for `x.com`
2. **URL validation**: Relative URLs are prepended with `base_url`
3. **Mismatch = rejection**: If `base_url` domain doesn't match filename, plugin is refused

This prevents a malicious plugin from masquerading as another domain.

## Variable Substitution

URLs and type actions support `{variable}` syntax:

```
monosurf x.com search "rust programming"
                       ↓
url: "/search?q={query}" → "/search?q=rust%20programming"
```

Variables come from CLI positional arguments:

* First arg after command → `{query}`, `{id}`, `{text}`
* Unresolved variables are silently removed

## Shared Extraction via \$ref

Commands can reuse extraction logic from other commands:

```json theme={null}
"new": {
  "url": "/newest",
  "wait": ".athing",
  "extract_js": "$ref:top"
}
```

`$ref:top` copies the `extract_js` from the `top` command. Avoids duplicating long JavaScript strings.

## Plugin Integrity

Site plugins contain JavaScript that runs in the browser. MonoSurf protects against tampering with a checksum system.

### Signing

```bash theme={null}
monosurf sites sign              # Sign all plugins
monosurf sites sign x.com        # Sign one plugin
```

Signing computes a checksum of all `extract_js` and `actions` in the plugin and writes it to the JSON:

```json theme={null}
"integrity": {
  "verified": "2026-04-07",
  "checksum": "c707dc804a7b0065"
}
```

### Verification

```bash theme={null}
monosurf sites verify
```

```
[OK]       x.com: OK (verified: 2026-04-07)
[OK]       github.com: OK (verified: 2026-04-07)
[TAMPERED] reddit.com: Checksum mismatch. Plugin may have been modified.
[UNSIGNED] custom.com: No checksum. Run 'monosurf sites sign' to seal.
```

### Runtime Enforcement

Every plugin execution checks integrity before running:

* **OK** — executes normally
* **UNSIGNED** — **blocked**. Must be signed before first use.
* **TAMPERED** — **blocked**. Execution refused until reviewed and re-signed.

Execution also uses the 0.11.3 exact-port ownership guard. New profiles skip
active unregistered ports; after selecting a profile, MonoSurf verifies that
the exact chosen CDP listener carries that profile's `--user-data-dir`. A known
foreign listener blocks immediately, and temporarily unknown ownership gets
only a bounded settle interval before failure. The command stops before the plugin can navigate, read cookies, or run actions. This protects unattended
site commands and batch work as well as direct profile browsing.

If the live domain jar is empty and a gated bridge/login snapshot exists,
hydration happens before plugin navigation. Every cookie scope is validated,
an explicit HTTP(S) origin is supplied, attributes are preserved, and any CDP
error fails the command. A non-empty live jar always wins.

### Workflow

1. Create or modify a plugin JSON
2. `monosurf sites sign` — seal it
3. Plugin runs normally
4. If JSON is modified externally → checksum breaks → execution blocked
5. Review the change → `monosurf sites sign` to re-seal

## Plugin Registry

Official plugins are distributed through `api.openclis.com`. Users can pull plugins from the server, and authorized developers can push updates.

### Source Levels

| Source     | Who                    | Trust                    |
| ---------- | ---------------------- | ------------------------ |
| `openclis` | Monolex team           | Official — highest trust |
| `partner`  | Registered partner org | Trusted — org verified   |
| `dev`      | Registered developer   | Review recommended       |
| `local`    | User's machine         | User's responsibility    |

Source is determined **server-side** from the developer's JWT org membership. It cannot be self-asserted by the client.

### Publishing

```bash theme={null}
openclis-dev login                      # authenticate as developer
monosurf sites push x.com              # upload to registry
```

### Installing

```bash theme={null}
monosurf sites pull x.com              # download from registry (Ed25519 signed)
```

### Reset to Official

```bash theme={null}
monosurf sites reset x.com             # restore from official backup
```

If AI modifies a plugin locally (source changes to `local`), `reset` restores the official version from backup.

### Server Endpoints

| Endpoint            | Method | Auth             | Purpose                        |
| ------------------- | ------ | ---------------- | ------------------------------ |
| `/plugins`          | GET    | Public           | List all plugins               |
| `/plugins/<domain>` | GET    | Public           | Get plugin + Ed25519 signature |
| `/admin/plugins`    | POST   | JWT or Admin key | Upload/update plugin           |

## Indexing and Search

```bash theme={null}
monosurf sites index              # Scan all JSON files → SQLite index
monosurf sites search "tech"      # Search by tags, description, domain
monosurf sites                    # List all installed plugins
```

The index enables AI to discover which sites are available:

```
AI: "What tech news sites can I read?"
  → monosurf sites search "tech news"
  → news.ycombinator.com, reddit.com, producthunt.com
  → monosurf news.ycombinator.com top --limit 5
```

## Existing Plugins

| Domain                 | Read                              | Write               | Description    |
| ---------------------- | --------------------------------- | ------------------- | -------------- |
| `x.com`                | search, timeline, read, trending  | post, reply, delete | X (Twitter)    |
| `threads.net`          | feed, search, profile, read       | post, reply, delete | Threads (Meta) |
| `news.ycombinator.com` | top, new, ask, show, search, read | —                   | Hacker News    |
| `github.com`           | trending, trending-weekly, search | —                   | GitHub         |
| `reddit.com`           | hot, new, search, read, front     | —                   | Reddit         |
| `producthunt.com`      | today                             | —                   | Product Hunt   |
