Skip to main content

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.

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

{
  "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

FieldRequiredDescription
descriptionYesWhat this site is
base_urlYesMust match filename domain
tagsNoFor monosurf sites search
commandsYesAvailable commands
displayNoOutput formatting

Command Fields

FieldRequiredDescription
urlYesURL to navigate. {query} for variable substitution
waitNoCSS selector to wait for before extraction. Empty = timeout only
wait_timeoutNoSeconds to wait (default: 10)
extract_jsNoJavaScript that returns JSON.stringify([...])
intentNo"read" (default) or "write"
actionsNoSequential actions for write operations

Actions (Write Operations)

"actions": [
  {"click": "css-selector"},
  {"type": "{text}"},
  {"sleep": 500},
  {"js": "(() => { document.querySelector('[role=button]').click(); return 'clicked'; })()"}
]
ActionDescription
clickClick an element by CSS selector (JS element.click())
typeType text into focused element. Supports multiline (line breaks preserved)
sleepWait milliseconds
jsExecute JavaScript in browser context via CDP Runtime.evaluate

Accessing Accessibility Tree in JS Actions

For sites that strip CSS selectors (like Meta’s Threads), use ARIA roles in JS:
{"js": "(() => { const btns = document.querySelectorAll('[role=button]'); for (const b of btns) { if (b.textContent.trim() === 'Reply') { b.click(); return 'clicked'; } } return 'not found'; })()"}
This replaces the v0.2 pw (Playwright script) action type, which is no longer supported since MonoSurf v0.4 communicates with Chrome directly via CDP without Playwright.

Display Templates

Templates use {field} to reference fields from extracted JSON objects:
"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:
"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

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:
"integrity": {
  "verified": "2026-04-07",
  "checksum": "c707dc804a7b0065"
}

Verification

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
  • UNSIGNEDblocked. Must be signed before first use.
  • TAMPEREDblocked. Execution refused until reviewed and re-signed.

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

SourceWhoTrust
openclisMonolex teamOfficial — highest trust
partnerRegistered partner orgTrusted — org verified
devRegistered developerReview recommended
localUser’s machineUser’s responsibility
Source is determined server-side from the developer’s JWT org membership. It cannot be self-asserted by the client.

Publishing

openclis-dev login                      # authenticate as developer
monosurf sites push x.com              # upload to registry

Installing

monosurf sites pull x.com              # download from registry (Ed25519 signed)

Reset to Official

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

EndpointMethodAuthPurpose
/pluginsGETPublicList all plugins
/plugins/<domain>GETPublicGet plugin + Ed25519 signature
/admin/pluginsPOSTJWT or Admin keyUpload/update plugin
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

DomainReadWriteDescription
x.comsearch, timeline, read, trendingpost, replyX (Twitter)
threads.netfeed, search, profile, readpost, reply, deleteThreads (Meta)
news.ycombinator.comtop, new, ask, show, search, readHacker News
github.comtrending, trending-weekly, searchGitHub
reddit.comhot, new, search, read, frontReddit
producthunt.comtodayProduct Hunt