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.
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:
| Library | Purpose | Can be used by |
|---|
lib-secure-storage | Keychain + AES encryption | Any Monolex app |
lib-auth-gate | Time-limited grants + gated secrets | Any tool needing human auth |
lib-monosurf | Browser plugin engine | Monolex 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) |
|---|
| Communication | 3 subprocess layers | Direct WebSocket |
| Runtime | Node.js required | Rust-native (tokio) |
| Latency | ~500ms per command | ~5ms per command |
| Dependencies | npm, niia, Playwright | Zero external |
| Cookie access | JS only (no HttpOnly) | CDP Network domain (full) |
| Binary size | ~8MB (no browser code) | ~13MB (includes CDP client) |
| Error points | 5 (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:
| Source | macOS | Windows | Linux |
|---|
| System Chrome | /Applications/Google Chrome.app | Program 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
| What | Where |
|---|
| 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 key | macOS 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.