Skip to main content

Architecture

Components

┌───────────────────────────────────────────────────────────────────┐
│  MonoSurf System (v0.4)                                           │
├───────────────────────────────────────────────────────────────────┤
│                                                                   │
│  CLI Binary (monosurf)                                            │
│  ├── Command parser + flag handling                               │
│  ├── Profile management (start/stop/profiles)                     │
│  ├── Browser discovery (system Chrome, Playwright, Puppeteer)     │
│  ├── Auth gate commands (grant/revoke/status)                     │
│  ├── Site plugin dispatch                                         │
│  ├── Login flow + account detection                               │
│  └── Browse mode (standalone headless)                            │
│                                                                   │
│  Plugin Engine (lib-monosurf/plugin)                              │
│  ├── Load JSON from sites/ directory                              │
│  ├── Domain validation (filename = base_url)                      │
│  ├── Integrity check (checksum before execution)                  │
│  ├── Auth gate check before execution                             │
│  ├── Cookie injection from keychain                               │
│  ├── Navigate → wait → extract pipeline                           │
│  ├── Action executor (click/type/sleep/js)                        │
│  ├── Output formatting (template or generic)                      │
│  ├── Plugin signing (sign/verify/tamper-detect)                   │
│  └── SQLite index for site search                                 │
│                                                                   │
│  Browser Layer (lib-monosurf/browser)                             │
│  ├── chromiumoxide — Rust-native CDP client (direct, no subproc)  │
│  ├── Session struct with block_on boundary (sync API, async CDP)  │
│  ├── Profile system (isolated Chrome per profile)                 │
│  ├── Browser discovery (5 locations, cross-platform)              │
│  ├── Navigate with timeout fallback (goto → JS navigation)        │
│  ├── Cookie management via CDP Network domain (incl. HttpOnly)    │
│  └── Headless + visible mode                                      │
│                                                                   │
│  Auth Gate (lib-auth-gate)                                        │
│  ├── Time-limited scope grants                                    │
│  ├── Gated secret storage (→ lib-secure-storage)                  │
│  └── Reusable by any Monolex tool                                 │
│                                                                   │
│  Secure Storage (lib-secure-storage)                              │
│  ├── macOS Keychain integration                                   │
│  ├── AES-256-GCM encryption                                       │
│  └── Generic key-value store                                      │
│                                                                   │
└───────────────────────────────────────────────────────────────────┘

Library Dependencies

monosurf (binary)
  └── lib-monosurf
      ├── chromiumoxide (Rust crate, CDP client)
      │   └── tokio (async runtime)
      ├── lib-auth-gate
      │   └── lib-secure-storage
      │       └── macOS Keychain
      └── Chrome / Chromium (detected at runtime)
No subprocess chain. chromiumoxide talks CDP directly over WebSocket to Chrome. Each library is independently reusable:
LibraryPurposeCan be used by
lib-secure-storageKeychain + AES encryptionAny Monolex app
lib-auth-gateTime-limited grants + gated secretsAny tool needing human auth
lib-monosurfBrowser plugin engineMonolex apps with browser needs

Browser Layer: chromiumoxide CDP

MonoSurf v0.4 uses chromiumoxide, a Rust-native Chrome DevTools Protocol client (1.5M+ downloads on crates.io).
monosurf command
  → Session::connect_profile("work")
    → TcpStream check: port 9222 alive?
    → Browser::connect_with_config("http://127.0.0.1:9222")
      → WebSocket connection to Chrome CDP
    → page.goto(url) / page.evaluate(js)
      → CDP commands over WebSocket
        → Chrome executes
          → result returns directly

Why chromiumoxide (v0.4) over Playwright MCP (v0.2)

Playwright MCP (v0.2)chromiumoxide (v0.4)
Communication3 subprocess layersDirect WebSocket
RuntimeNode.js requiredRust-native (tokio)
Latency~500ms per command~5ms per command
Dependenciesnpm, niia, PlaywrightZero external
Cookie accessJS only (no HttpOnly)CDP Network domain (full)
Binary size~8MB (no browser code)~13MB (includes CDP client)
Error points5 (monosurf→niia→mcp-run→playwright→chrome)2 (monosurf→chrome)

Profile System

Each profile = independent Chrome instance:
~/Library/Application Support/Monolex/monosurf/
├── profiles.json                    ← {name, port, account} per profile
├── chrome-profile-work/             ← Chrome data dir (login sessions)
├── chrome-profile-personal/         ← Another Chrome data dir
├── chrome-work.pid                  ← PID for process management
└── chrome-personal.pid
Ports auto-assigned: 9222, 9223, 9224…

Browser Discovery

MonoSurf scans 5 locations across 3 platforms:
SourcemacOSWindowsLinux
System Chrome/Applications/Google Chrome.appProgram Files\Google\Chrome/usr/bin/google-chrome-stable
Playwright cache~/Library/Caches/ms-playwright/%LOCALAPPDATA%\ms-playwright\~/.cache/ms-playwright/
Puppeteer cache~/.cache/puppeteer/chrome/%LOCALAPPDATA%\puppeteer\chrome\~/.cache/puppeteer/chrome/
System Chrome works but sites may detect automation. Playwright/Puppeteer Chromium has stealth built in — recommended for headless.

Sync API, Async Internals

Session methods are synchronous externally. Internally, a tokio Runtime::block_on() wraps async CDP calls. This keeps plugin.rs and monosurf.rs simple — no async propagation needed.
pub fn evaluate(&self, js: &str) -> Result<String, String> {
    self.rt.block_on(async {
        let result = self.page.evaluate(js).await?;
        // ...
    })
}

Security Layers

Five layers protect against misuse:
1. Domain validation     filename must match base_url
2. Integrity check       checksum blocks tampered AND unsigned JS
3. Source verification    openclis/partner/dev/local trust levels
4. Auth gate             time-limited grant controls access
5. Registry auth         JWT org tier — server determines source, not client
Execution order: domain check → integrity check → auth gate → cookie injection → navigate → extract. If any layer fails, execution stops. Both tampered and unsigned plugins are blocked.

Data Flow: Read

User: monosurf x.com search "AI" --profile work

CLI
  → parse args: site=x.com, cmd=search, query="AI", profile=work
  → load plugin: sites/x.com.json
  → auth gate: check("read") → grant active? yes

Plugin Engine
  → get cookies from keychain: gate.get_secret("read", "cookies:x.com")

Browser (chromiumoxide CDP)
  → Session::connect_profile("work") → WebSocket to port 9222
  → page.goto("x.com/search?q=AI")
  → auto-dismiss consent banners (JS evaluate)
  → poll for selector: article[data-testid="tweet"]
  → page.evaluate(extraction_js) → JSON array
  → result returns directly (no subprocess unwrapping)

CLI
  → parse JSON → apply display template
  → print to stdout

Data Flow: Write

User: monosurf threads.net post "Hello" --profile work

Browser (chromiumoxide CDP)
  → navigate: threads.net/
  → JS action: click compose area
  → JS action: focus contenteditable, execCommand('insertText', 'Hello')
  → JS action: find Post button, element.click()
  → evaluate extract_js → {status: "posted"}

File Locations

WhatWhere
Binary~/.openclis/bin/monosurf
Site plugins~/Library/Application Support/Monolex/monosurf/sites/*.json
Site index~/Library/Application Support/Monolex/monosurf/sites.db
Profiles~/Library/Application Support/Monolex/monosurf/profiles.json
Chrome profiles~/Library/Application Support/Monolex/monosurf/chrome-profile-{name}/
Auth grants~/Library/Application Support/Monolex/auth-gate/monosurf.json
Encrypted cookies~/.config/Monolex/monosurf/credentials.enc
Encryption keymacOS Keychain → ai.monolex.monosurf

Relationship to MonoFetch

                    Static Content          Dynamic Content
                    (docs, APIs)            (social, SPAs)
                         │                       │
                    ┌────┴────┐             ┌────┴────┐
                    │monofetch│             │monosurf │
                    └────┬────┘             └────┬────┘
                         │                       │
                    HTTP GET              chromiumoxide CDP
                         │                       │
                    HTML → MD              JS eval → JSON
                         │                       │
                    ┌────┴───────────────────────┴────┐
                    │          monomento               │
                    │     (search + index)              │
                    └──────────────────────────────────┘
Both tools produce structured text. Both can feed into monomento for indexing and search. Both can be defined in connector.json. The difference is transport: HTTP vs CDP.

Binary Size

~13 MB. Includes chromiumoxide CDP client, tokio async runtime, SQLite, JSON parser, regex, auth gate + secure storage. Does NOT include: Chrome or Chromium. These are detected at runtime from system installs or Playwright/Puppeteer caches.