Hyperbolic Gateway

⚡ Hyperbolic Gateway

The Local Web Browser for Your AI Agents

"I'll hack the Gibson"
— Dade Murphy (AKA Crash Override), Circa 1995

version tools resources license platform

🧭 Index


🕶️ What it is

Hyperbolic Gateway is a visible, stateful web browser that any MCP agent can drive.

Most agent browsers are a library you integrate, or a cloud you rent. Hyperbolic Gateway is a desktop application you own: a real Chromium window on your machine, controlled over the Model Context Protocol. Your agent runs where you already run it — local or remote — and Hyperbolic Gateway supplies the browser, the input, and the memory.

Hyperbolic Gateway driving a real Chromium window

Open the MP4 if your browser does not animate WebP.

[!WARNING] EXPERIMENTAL PROTOTYPE: Hyperbolic Gateway is currently under active, experimental development. Tool contracts, telemetry tuning, and database schemas may evolve rapidly. Built for developers experimenting with multimodal local AI agents.

[!IMPORTANT] RESEARCH PURPOSES ONLY: This software is provided strictly for educational, testing, and local AI agent research purposes. It is not intended for malicious use, automated credential stuffing, unauthorized exploitation, or violating third-party websites' Terms of Service. Users are responsible for complying with all applicable laws and terms.

Installers: grab them from Releases — Windows Setup/Portable/zip, macOS dmg/zip (x64 + arm64), Linux AppImage/deb/tar.gz. macOS is unsigned for now: right-click → Open (or xattr -dr com.apple.quarantine "/Applications/Hyperbolic Gateway.app"). Built by GitHub Actions; see docs/BUILDING.md.

🤘 A Punk-Ethos, DIY Alternative to browser-use, Playwright Agents & Hosted Browser Clouds

Most "AI browser" tooling today is a library you integrate, or a cloud you rent. browser-use is a popular MIT-licensed Python framework that drives Playwright with its own agent loop (plus an optional hosted cloud); Playwright / Puppeteer / Selenium are general-purpose drivers where you write the loop; and managed browser clouds run a remote Chromium and bill per session. They're good tools — this isn't a rivalry, it's a different shape.

Hyperbolic Gateway is the other road: a single, self-hosted desktop app you own (Electron, no account, no dashboard). It runs a real, visible Chromium on your own machine and speaks MCP over stdio (+ a localhost bridge), so it drops into whatever agent runner and whichever model you already use — local or remote, with no cloud account, no API key, no seat pricing, and no telemetry leaving your box.

That's the punk ethos: one operator, one machine, a tool you can read, fork and keep. Built by one person, not a company. No VC, no roadmap-by-committee, no dashboard.

How it differs, honestly

browser-use (Python / Playwright) Playwright · Puppeteer agents Hosted browser clouds Hyperbolic Gateway
Shape Library with its own agent loop Driver — you write the loop Managed remote browser MCP server any MCP client can call
Browser Playwright Chromium (often headless) Playwright / Puppeteer Remote Chromium Visible Chromium (headless: false) in an Electron chassis
Where it runs Your machine, or their cloud Your machine / your CI Their datacentre Your machine, always — local-first
Model Bring your own (usually a cloud LLM) Bring your own Bring your own Bring your own — local or remote
Input Playwright-driven events Playwright-driven events CDP over the wire Focus-independent CDP input + Bézier ghost cursor + Gaussian typing (isTrusted: true)
Memory Framework-level, BYO store none Their store Local LanceDB "Cortex" — semantic recall of everything it harvests
Cost MIT + your LLM (+ optional cloud) Free + your LLM Per-session billing MIT. No cloud, no metering, no telemetry.

What that buys you in practice

New here? Plain-English FAQ

What is this, in one sentence? A web browser that an AI can drive for you: it opens real pages, reads them, clicks, types, fills in forms, and remembers what it found.

Is it a browser I use myself? It's a real browser window. You can watch it, and you can take over at any time — most people just let their AI do the driving.

Do I need to be a programmer? To use it with an AI assistant you already have: no — you paste one line of configuration into that assistant. To change how it behaves internally: yes, it's a code project.

What's an "MCP server", in plain terms? MCP is a standard plug shape for AI tools. If your AI app accepts MCP plugins, it can use this one. Claude Desktop, Cursor and several other apps speak it.

Which AI can drive it? Any assistant that supports MCP — a cloud service, or a model running on your own computer. You choose, and you aren't locked to one provider.

Do I need to pay for anything? No subscription and no account. The software is free and open source. The only cost is whatever you already pay for your AI, if anything.

Where does my data go? Nowhere. The pages it reads, the notes it keeps and the logs all stay on your computer. There's no company server in the middle and no usage tracking.

Could a website tell it's a bot? It's built to be hard to spot: a normal visible browser, mouse movement and typing that look human, and it clears cookie banners politely. Nothing is unbeatable, but it isn't the obvious "automated script" that gets blocked.

What happens at a CAPTCHA? It doesn't solve them. It asks you to step in (it can send a notification and bring the window forward), then continues once you're done.

Does it run headless on a server? Not really, and that's on purpose. It runs a visible browser on a normal desktop, which is exactly why it looks human. For large-scale server-side scraping, a different tool fits better.

Is using it allowed? It's for personal research and testing. You still have to follow each website's rules and the law — running it yourself doesn't change that. Don't use it to break into accounts or ignore terms of service.

What do I need to run it? A normal Windows desktop (that's the tested target). If your computer can run a browser and an AI assistant, it can run this.

Is it finished? No. It's an experimental project by one developer — expect rough edges and things that change.

FAQ for developers — browsers, memory, and the tricky bits

Is there a self-hosted, open-source alternative to browser-use? Yes — MIT-licensed and entirely local. But note the shape: this is a tool surface, not an agent framework. If you want a Python framework that owns the loop, browser-use is the right tool; if you want a self-hosted browser that your own agent drives, this is it.

Can I use it with Claude Desktop, Cursor, or a local LLM? Yes. It's an MCP server, so any MCP client works - point your client at bin/mcp-server.cjs and the hb-browser tool namespace appears (legacy kuang alias still works).

Does it need a cloud account, API key, or per-session billing? No. There is no cloud tier. The only network calls are the ones your agent makes to the pages it visits (plus, optionally, a search engine via search_web).

Is it headless? Deliberately the opposite. It runs a visible Chromium so a human can watch, take over at a CAPTCHA, and so the session stays an honest, real browser. Headless-at-scale scraping is a different job; this is a single operator's console.

Will Cloudflare block it like Puppeteer / Playwright? It's built for exactly that fight: real CDP input with isTrusted: true, cubic Bézier pointer kinematics, Gaussian typing cadence, a genuine cookie jar, and an autonomous consent-modal breaker. No promises — anti-bot is an arms race — but it is not naive headless emulation.

Can I point it at my existing Playwright test suite? No. This isn't a test driver; Playwright remains excellent for deterministic suites. Grade Eleven is an agent-facing browser an LLM operates.

Where does memory live? Locally, in LanceDB ("Cortex"): harvested pages and vaulted scripts are embedded and semantically recallable, so your agent keeps research context across sessions without a hosted vector store.

Is it production-ready? No — it's an experimental prototype for local AI research. That's rather the point.

Punk ethos, not carte blanche. Owning your tooling doesn't change the law: the RESEARCH PURPOSES ONLY terms at the top of this README still apply. The point is ownership and readability, not immunity.


🎯 Core Architecture Principles

  1. 100% Real Browser Chassis: No headless emulation, synthetic user-agent spoofing, or sandboxed scraping. Hyperbolic Browser runs a full-fledged, visible Chromium instance (headless: false) with hardware acceleration, audio/video capabilities, and authentic cookie/session persistence.
  2. Multimodal Vision Integration: Specifically tailored for multimodal models (e.g. Bonsai 2 27B). The agent takes high-res viewport screenshots (take_screenshot) to visually understand layouts, buttons, and dynamic canvas states, and can act directly by coordinate, selector, or mark badge (act).
  3. Full Developer Console & Script Execution: Gives the agent full console access (get_console_logs) to inspect runtime errors and API output, plus arbitrary JavaScript evaluation (evaluate_javascript) to interact with complex SPAs.
  4. Behavioral Bot Detection Bypass: Bypasses Cloudflare, Akamai, Datadome, and behavioral bot sensors by avoiding synthetic element.click() events (isTrusted: false). Instead, it dispatches real input events over the Chrome DevTools Protocol (Input.dispatchMouseEvent / Input.dispatchKeyEvent / mouseWheel) accompanied by organic cubic Bézier pointer movement and human Gaussian typing cadence. CDP delivery is focus-independent — it lands even when another application owns the OS foreground — with webContents.sendInputEvent kept only as a fallback.

🎨 Theme & Cyberdeck Interface

Built with Tailwind CSS v4 and DaisyUI 5:


🕹️ Ghost Cursor Telemetry Engine


🎛️ Focus-Independent Input & Background Rendering

Two real-desktop failure modes are handled explicitly so long harvests and HIL flows stay reliable:


🧩 Orthogonal Primitive Layer

The older tool list is being consolidated onto a few orthogonal verbs - one observation model, one action model, one wait model - with the flat tools becoming projections. All primitives run on the TIER-1 humanlike path (real input events; no page DOM mutation beyond Hyperbolic Browser's own data-mark-id stamp).

Primitive Job Replaces
observe(as, region, budget, cursor) One budgeted, cursor-paged observation model (markdown/text/ax/som/list/table/app_data) get_page_markdown + snapshot_rendered_page + list_marks
act(mark_id | selector | x+y, action, text, secret, await_conditions, dry_run) Unified humanlike click/focus/hover/type/press on a mark ref, CSS selector, or coordinate + optional post-conditions + delta report click_mark/type_mark/click_element/type_text plus a following wait
wait_for(conditions, require, timeout_ms) Declarative readiness gate — the primitive that replaces blind sleeps every sleep/"did it load?" guess
expect(conditions) Immediate assertion (no polling) with per-condition evidence agent-side guessing
find(role, name, contains, regex, tag, selector) Semantic element search → refs (mark ids) hand-written CSS/XPath
extract(kind, ref, selector) Structured reads: repeating list rows, table records, embedded app_data regex over HTML
collect_all(selector, scroll, max_scrolls) Scroll a lazy/virtualized list to the end and dedupe rows in one call a manual scroll-and-harvest sweep
page_state() / diff(since) Cheap monotonic change signature + field-by-field diff for loops re-reading the whole page
bridge_health() Liveness, capabilities[], revision, window, input.transport assuming the bridge is up
export_har(pattern, save) · context_* HAR 1.2 export · named cookie/storage profiles ad-hoc replay · manual session juggling

Refs are marks. A ref is a Set-of-Marks badge id — find returns rows carrying the mark, and act(mark_id=…) consumes it (re-resolving the live centre each time). When there is no badge, the same act also takes selector="…" or x=/y= — one action tool, three address modes.

Wait vocabulary: selector · gone · text/regex · url · role_name · count(min/equals/max) · network_idle · stable(marks|height|text|nodes) · revision.

Structured errors: new endpoints return a machine code (HTTP 400) — STALE_REF (re-scan and re-issue), TIMEOUT, NO_TARGET, BAD_REQUEST, NOT_SUPPORTED.

Full recipes (R1–R5), the wait table, and the error catalogue live in the OKF partition kuang_docs/partitions/primitives_and_workflows.md, also readable as the MCP resource mark-eleven://okf/partitions/primitives-and-workflows.


📄 Token-Efficient Construct Harvester



🧠 LanceDB Persistent Cortex & Semantic Recall

Hyperbolic Gateway features an embedded, serverless vector database (LanceDB) combined with Tantivy-powered Full-Text Search (BM25) stored locally under ./data/cortex/.


📑 Hardware-Accelerated Multi-Tab Browsing


🛡️ Autonomous Shield-Breaker (Cookie & GDPR Destroyer)


🎯 Set-of-Marks (SoM) Visual Grounding


📝 Single-Turn Batch Form Filler


📥 Download Manager & Cortex Harvester


🎥 Timelapse Recorder


🔐 Session & Cookie Jar Vault


📡 Live Network & API Stream Interceptor


⚡ Real-Time WebSocket & SSE Interceptor


💾 Script Vault & LanceDB Semantic Macro Engine


🗔 System Tray & Background Minimization ("Out of the Way")


🚨 Human-In-The-Loop (HIL) Intervention Protocol


🔌 Standardized Semantic MCP Tools (63 advertised · +2 optional)

+2 optional, listed first when active: drive and decide — rows marked ⌥ — appear only when a decision model is configured. The rest of this table is always available.

63 advertised tools (83 registered entries; 23 deprecated aliases are hidden from tools/list but stay callable by name and return a deprecated → use "X" notice — rows marked † below). Seven MCP resources are also exposed: mark-eleven://skills/guide, mark-eleven://tools/recommended, mark-eleven://okf/manifest, and four partition playbooks.

Tier map (mirrors the [TIER n] prefixes the MCP surface puts on the advertised descriptions). TIER 1 · prefer: observe · act · wait_for · expect · find · extract · collect_all · page_state · diff. TIER 2 · fallback: take_screenshot · snapshot_rendered_page · press_key. TIER 3 · use deliberately: evaluate_javascript — powerful, just not the default. Every other tool is unprefixed — it is the only way to do its job. Start with observe → act → wait_for → find → extract/collect_all; the Primitives & Abstraction block is listed first for exactly that reason. Editing this table? Run npm run manual:tools to regenerate the default-tab Manual's collapsible full list from it.

Category Tool Parameters Description
⌥ Optional ⌥ drive goal, values?, max_steps?, threshold?, dry_run? Listed first when a decision model is configured. Runs observe → guarded decide → verified act in one call and returns a typed stop reason (done/blocked/human_required/needs_agent/failed/max_steps). Absent otherwise.
⌥ Optional ⌥ decide state, questions, model?, guard?, eliminate?, include_page_text?, include_screenshot? Listed first when configured. One typed decision (choice/score/noul) over a state, answers forwarded verbatim, with an optional guard + elimination. Absent otherwise.
Primitives & Abstraction observe as?: 'markdown' | 'text' | 'ax' | 'som', region?: number | string, budget?: number, cursor?: string, include_offscreen?: boolean, limit?: number The page read (TIER 1). One budgeted observation model with projections. Use budget + next_cursor to page a large page instead of truncating. Absorbed get_page_markdown / harvest_markdown / list_marks; structured reads live on extract.
act mark_id?: number, selector?: string, x?: number, y?: number, action?: 'click' | 'focus' | 'hover' | 'type' | 'press', text?: string, secret?: boolean, clear_first?: boolean, await_conditions?: object[], timeout_ms?: number, dry_run?: boolean The interaction entry point: one humanlike action addressed by mark ref, CSS selector, or coordinates, with optional post-conditions and a delta report. Selectors pierce open shadow roots and resolve a custom-element host to the control a mouse would hit. secret: true redacts typed text from logs; dry_run: true resolves without acting. Typing appends by default - pass clear_first: true on a pre-filled field (origins, search boxes, comboboxes) to replace instead (trusted Ctrl+A + Backspace). Typing is verified: a provable no-op returns ok: false with error.code: "TYPE_NO_EFFECT" and typing evidence (key counts and value lengths - never the text).
wait_for conditions: object[], require?: 'any' | 'all', timeout_ms?: number, poll_ms?: number Declarative readiness gate — polls until every/any condition holds, then returns. The primitive that replaces blind sleeps.
find role?: string, name?: string, contains?: string, regex?: string, tag?: string, selector?: string, limit?: number, include_offscreen?: boolean Semantic element search returning Set-of-Marks refs (mark ids) instead of pixels. Feed ids straight into act.
extract kind?: 'list' | 'table' | 'tables' | 'app_data' | 'schema_org', ref?: number, selector?: string, type?: string, limit?: number Structured, read-only extraction: repeating rows, one table, every table, embedded app JSON (__NEXT_DATA__/JSON-LD), or typed schema.org items (kind:"schema_org" flattens @graph; type filters to e.g. "Recipe"). Article extraction is observe(as:"markdown") (Readability).
collect_all selector?: string, ref?: number, scroll?: 'container' | 'window', step?: number, delay_ms?: number, max_scrolls?: number, max?: number Scrolls a virtualized/lazy list to the end and collects every distinct row (deduped by text+href) in one call, with scroll diagnostics.
expect conditions: object[] Immediate assertion (no polling) over the same vocabulary as wait_for; returns pass/fail plus per-condition evidence.
page_state none Cheap monotonic state signature (revision, url, marks, nodeCount, height, scrollY, textLen, textHash) for change detection without re-reading the page.
diff since?: number Field-by-field diff between the current state and a previously observed revision — shows exactly what changed.
llm_surface url?: string Checks whether a site publishes an LLM-friendly representation (/llms.txt / /llms-full.txt, /sitemap.xml, a <link rel="alternate"> or a real <a href="….md"> page link, or Markdown content negotiation) and returns recommended — "llms_txt" | "llms_full" | "markdown" | "text", or null when there is none (never "rendered"). Also scope ("origin" | "page") and next — the unambiguous next step (llm_fetch(as) or observe({as:"markdown"})). Well-known paths only; cached per origin.
read_url url?: string, max_chars?: number, cursor?: string | number, as?: 'llms_txt' | 'llms_full' | 'markdown' | 'text' Read any URL as Markdown in ONE call. Reads the site's own representation when one covers the URL asked for (a page-level alternate for a page); otherwise a detached render distilled to Markdown (your active tab is never hijacked). A site-wide index is never returned in place of a page — the page is rendered, and reason says why. Always returns text, so you never branch on recommended. Reports via, scope, scope_mismatch and reason; paged with max_chars + next_cursor.
llm_fetch url?: string, as?: 'llms_txt' | 'llms_full' | 'markdown' | 'text' Fetches that advertised Markdown/Text representation (~5–10× cheaper than a render). Gated: refuses (NO_LLM_SURFACE) unless the site advertises a surface. Reports scope + scope_mismatch (page requested, site-wide index returned). Per-page alternates load via a real navigation, not a synthetic fetch.
search_web query: string, engine?: 'google' | 'duckduckgo' | 'bing' | 'brave' | 'startpage' | 'custom', engines?: string[], url_template?: string, max_results?: number Multi-engine web search run in a detached view (your active tab is untouched) with automatic fallback when an engine bot-checks. Returns ranked {title, url, snippet} to feed navigate_to/collect_all. custom takes a {query} template (e.g. SearXNG).
deep_crawl url: string, max_depth?: number, max_pages?: number, match_patterns?: string[], same_origin?: boolean, prefer_llm?: boolean, harvest_to_cortex?: boolean, selector?: string, delay_ms?: number Autonomous BFS crawl from a seed: follows same-origin links within depth/page budgets, distills each page to Markdown, and indexes it into LanceDB Cortex. Each page takes the cheap path first (content-negotiated Markdown/Text, else a detached render). match_patterns scopes by glob or /regex/.
research_topic query: string, max_sources?: number, engine?, engines?: string[], url_template?: string, excerpt_chars?: number, harvest_to_cortex?: boolean, prefer_llm?: boolean Autonomous research mode: searches, gathers the top sources (cheap Markdown path first), distills them, and indexes them into Cortex. Returns extractive, cited evidence — each highlight is a real paragraph mentioning the query terms. It does not write prose; you synthesize the answer.
bridge_health none Liveness + capability discovery: version, revision, capabilities[], window {visible, focused}, input {transport}, lastActionAt.
context_save name?: string Saves the current browsing context (cookies + storage) as a named profile.
context_open name: string Restores (switches to) a named browsing context/profile.
context_list none Lists saved browsing contexts (cookie/storage profiles).
context_delete name: string Deletes a saved browsing context/profile.
† focus_window none Deprecated — use set_window_visibility({ visible: true }). Brings the Cyberdeck window to the foreground (HIL visibility, or the legacy native input fallback).
Navigation navigate_to url: string, auto_dismiss_consent?: boolean Navigates active tab to URL. Auto-dismisses GDPR/cookie overlays by default. Supports 0ms instant elevation if pre-rendered.
† go_to_url url: string, auto_dismiss_consent?: boolean Primary alias for navigate_to. Dispatches visible window navigation.
navigate_history action: 'back' | 'forward' | 'reload' Browser history navigation and reload controls.
† get_page_info none Returns current URL, title, and load state without full extraction.
Shield & Defense dismiss_consent none Autonomously detects and dismisses GDPR/cookie banners via Bézier mouse click.
Interaction † click_element selector?: string, x?: number, y?: number Deprecated — folded into act(selector: …) / act(x: …, y: …). Clicks element by CSS selector or direct viewport (x, y) coordinates.
† type_text text: string, selector?: string Deprecated — folded into act(action: "type", text: …, selector: …). Types text with human Gaussian cadence variance (60–240ms).
fill_form fields: Record<string, string | boolean | number>, submit_selector?: string Fills multiple form inputs, selects, and checkboxes in a single turn.
press_key key: string Presses specific key (e.g. Enter, Tab, Escape, Backspace).
scroll_page direction: 'up' | 'down', amount?: number Incremental momentum wheel scrolling.
Observation & Vision take_screenshot marks?: boolean, detail?: 'full' | 'economy' | 'markdown', include_offscreen?: boolean, max_width?: number, format?: 'jpeg' | 'png', quality?: number Viewport capture. Plain mode (default) returns a bounded 768px fast JPEG (~15ms encode, tiny wire payload); marks: true returns Set-of-Marks with detail: 'full' (PNG), 'economy' (640px JPEG), or 'markdown' (marks table, no image). Returns file path text & native MCP image block. Saved to data/screenshots/ with automatic retention pruning.
† get_page_markdown none Downsamples active DOM to clean token-efficient Markdown with YAML metadata.
† harvest_markdown none Primary alias for get_page_markdown. Extracts clean markdown from the active tab.
snapshot_rendered_page format?: 'ax_tree' | 'markdown' | 'html' | 'unrendered_html', wait_until_idle_ms?: number, pierce_shadow_dom?: boolean, force_rerender?: boolean, save_to_disk?: boolean Rendered Point-in-Time Snapshot. Freezes dynamic SPAs, pierces Shadow DOM, and extracts ultra-token-efficient Accessibility (AX) Trees, rendered Markdown, or unrendered source HTML.
prerender_links urls?: string[], selector?: string, max_pages?: number, harvest_to_cortex?: boolean, wait_until_idle_ms?: number Speculative Pre-rendering Engine. Pre-hydrates candidate links in background views for 0ms instant navigation, and auto-indexes into LanceDB Cortex memory.
evaluate_javascript script: string Evaluates custom JS expression in page context and returns serialized JSON. Accepts script, code, or expression. A powerful tool that can read and mutate — reach for it deliberately (observe/act cover the common cases, and injected JS is a bot-detection signal).
get_console_logs limit?: number Returns recent web page and script console logs, warnings, and errors.
get_fingerprint_profile none Returns active stealth host browser fingerprint and spoofed headers.
Multi-Tab new_tab url?: string Spawns a new independent browser tab instance.
switch_tab tab_id: string Activates and focuses the specified tab.
close_tab tab_id?: string Closes tab and reclaims memory resources.
list_tabs none Returns structured array of all open tabs (IDs, titles, URLs, active status).
LanceDB Memory recall_memory query: string, limit?: number, domain?: string Performs hybrid vector and BM25 full-text search across visited pages and markdown.
browsing_history limit?: number, domain?: string Chronological browsing history from persistent LanceDB cortex.
browsing_history_clear older_than?: string Prunes or purges browsing history from persistent storage.
add_bookmark url?: string, title?: string, notes?: string, tags?: string[] Saves active or specified page with tags and notes in LanceDB.
list_bookmarks query?: string, tag?: string, limit?: number Retrieves or filters saved bookmarks by query terms (title/notes/URL/tags) or exact tag.
delete_bookmark url_or_id: string Deletes bookmark by URL or unique ID.
Downloads & Docs list_downloads none Lists all files downloaded into data/downloads/ with sizes, state, and timestamps.
read_download filename_or_id: string Parses downloaded file (PDF, CSV, JSON, text) into token-efficient markdown.
Timelapse Recording record_start name?: string Starts a timelapse of the Cyberdeck window itself: frames are captured every few hundred ms and played back faster than real time. The finished .webm path becomes an ordinary value you can type into a file-upload field.
record_stop none Stops the timelapse and saves it (VP9/WebM). Returns the absolute path, size, frame count and speed-up factor.
list_recordings limit?: number Lists finished timelapses as absolute paths, newest first — ready for upload fields.
Session Vault † save_session name?: string Captures active cookies and localStorage state into persistent JSON vault.
† restore_session name: string Rehydrates cookies and localStorage state into browser session.
† list_sessions none Lists all stored session snapshots with timestamps and cookie counts.
Network Intelligence get_network_traffic url_pattern?: string, method?: string, resource_type?: string, limit?: number, since?: number | 'last_action' Inspects live HTTP/XHR/Fetch requests captured via Chrome DevTools Protocol (returns compact 1-line markdown summaries). since narrows to requests after an epoch-ms timestamp, a page revision, or the last mutating action.
† get_api_payload request_id: string Retrieves and parses the full raw JSON or text response body for an intercepted request.
query_json_data source: string, source_id?: string, path?: string, view?: string, filter_predicate?: string, max_items?: number, max_string_length?: number Token-Efficient JSON Navigator. Queries large payloads/streams/files via dot/bracket paths (items[0..3].price), extracts schemas, searches keys, and applies filters without context-window blowup.
† clear_network_traffic none Deprecated — use clear_capture({ scope: "network" }). Resets the in-memory network traffic ring buffer.
export_har pattern?: string, limit?: number, save?: boolean Exports captured traffic as a HAR 1.2 log (request/response headers + body previews). save: true writes data/har/har_<ts>.har and returns the path.
list_realtime_streams type?: 'websocket' | 'sse', active_only?: boolean Lists active WebSocket connections and open SSE channels in compact 1-line format.
† get_stream_messages stream_id?: string, type?: string, direction?: string, limit?: number Inspects captured incoming/outgoing frames from WebSockets and SSE token streams.
† clear_stream_messages stream_id?: string Deprecated — use clear_capture({ scope: "streams" }). Clears the in-memory WebSocket and SSE message frame buffer.
clear_capture scope?: 'network' | 'streams' | 'all' Clears the in-memory capture ring buffers: HTTP traffic ("network"), WebSocket/SSE frames ("streams"), or both ("all", default).
Visual Grounding † take_annotated_screenshot none Tags all visible interactive elements with numbered neon badges ([1], [2], etc.). Returns dual content: Set-of-Marks table text & native MCP image block (type: 'image'). Saved to data/screenshots/.
† take_economy_screenshot none Lightweight variant: same marks table, but the image is downscaled to 640px wide and JPEG-encoded (q45, image/jpeg). Saved to data/screenshots/ as annotated_eco_*.jpg.
† list_marks none Text-only variant: returns the marks table with no image and no file written. Ideal for text-only models.
† click_mark mark_id: number Deprecated — folded into act(mark_id: …). Moves humanized Bézier ghost cursor to mark #ID and clicks with 100% accuracy.
† type_mark mark_id: number, text: string, clear_first?: boolean Deprecated — folded into act(mark_id: …, action: "type", text: …). Focuses mark #ID and types with variable human typing cadence.
Script Vault & Macros save_script name: string, code: string, description?: string, domain?: string, tags?: string[] Saves and embeds a custom JavaScript macro into LanceDB Cortex.
† recall_script query?: string, domain?: string, tag?: string, limit?: number Deprecated — use list_scripts({ query }). Semantically searches saved scrapers & macros using natural language.
run_script name: string, args?: object Executes a saved script in active tab with dynamic runtime arguments.
list_scripts query?: string, tag?: string, limit?: number Lists or semantically searches registered macros in the persistent vault (query ranks by relevance).
delete_script name: string Removes a script from the persistent vault and vector index.
Desktop & System Tray set_window_visibility visible?: boolean, focus?: boolean Shows/restores the window (and focuses it by default) to make it visible to a human, or minimizes it to the Windows notification tray.
configure_system_tray close_to_tray?: boolean Configures tray options such as close-to-tray background operation.
Human-In-The-Loop & Alert request_human_intervention reason: string, timeout_seconds?: number, bring_to_front?: boolean Halts agent, triggers native OS alert, unhides window, and injects interactive banner for CAPTCHAs/credentials.
resolve_human_intervention id?: string, notes?: string Resolves active intervention request and resumes agent execution.
get_human_intervention_status none Checks active intervention status and history log.
send_notification title: string, message: string, urgency?: string, sound?: boolean Emits non-blocking OS native desktop banner notification to user.
† send_desktop_notification message: string, title?: string, urgency?: string, sound?: boolean Dedicated HIL notification tool to summon the human operator with high-priority desktop sound and alert banner.
Assistance & Discovery help category?: 'all' | 'recommended' | 'navigation' | 'interaction' | 'primitives' | 'inspection' | 'tabs' | 'memory' | 'macros' | 'network' | 'system' Returns a categorized operational cheatsheet of all available browser automation capabilities (served directly in OKF).

🔌 REST wrappers (hb-rest)

A second, separate MCP server for turning a REST endpoint into a saved, typed tool. It is deliberately not mixed into the browser server: hb-rest ships its own binary, its own stdio process, its own config entry and its own bridge token.

Browser REST wrappers
Binary bin/mcp-server.cjs bin/wrap-server.cjs
Server name hb-browser (legacy: kuang) hb-rest (legacy: kuang-wrap)
Surface 63 browser tools 10 wrap_* meta-tools + up to 40 generated wrap_<id> (configurable via HB_WRAP_MAX_TOOLS or legacy KUANG_WRAP_MAX_TOOLS)
Env HB_BRIDGE_URL (legacy: KUANG_BRIDGE_URL) HB_WRAP_TOKEN_FILE (legacy: KUANG_WRAP_TOKEN_FILE)

What it is for: agent access to JSON APIs (dashboards, internal tools, SaaS). The wrapper author — human or agent — defines the request once: method, URL, typed arguments, a response recipe, and a credential name. The agent then calls flat arguments; it never assembles a URL, a header set, or a body envelope, and it never sees a credential value.

Credentials are human-only and host-bound: a static value, or an OAuth2 client-credentials client that mints and caches a token. Secrets are encrypted with the OS keychain (safeStorage), injected only on an approved host, and never enter the MCP process.

// Settings → MCP Clients → REST wrappers generates an entry like this
{
  "mcpServers": {
    "hb-rest": {
      "command": "node",
      "args": ["<path-to-repo>/bin/wrap-server.cjs"],
      "env": { "HB_WRAP_TOKEN_FILE": "<userData>/wrap/token" },
    },
  },
}

Meta-tools: wrap_list, wrap_get, wrap_secret_list, wrap_derive, wrap_create, wrap_update, wrap_delete, wrap_run, wrap_test, wrap_reload.

[!WARNING] Read the limits before relying on it. No human-approval gate for mutating calls · OAuth2 is client_credentials only · the wrap server does not raise HIL itself (a rejected credential returns AUTH_REQUIRED) · exposeAsTool has no Settings control · 40-tool cap · DNS-rebinding residual risk · the token URL is outside allowedHosts. The full, blunt breakdown — what works, what doesn't, what is untested, what is risky — is docs/HYPERBOLIC_REST.md.

Tests: npm run test:wrap (50 offline) · npm run test:wrap:mcp (11 lifecycle) · npm run test:wrap:live (46 against DummyJSON).


🗺️ Codebase Directory Structure & Documentation

Each folder contains a dedicated README.md detailing its responsibilities, dependencies, and internal workflows:

Directory Documentation Link Description
docs/ docs/CODEBASE.md Architecture lowdown, field notes, decision-model notes, build and packaging docs.
scripts/ scripts/README.md Test harnesses, release helpers (check-release-tag, release-notes), the MCP bundle build, and one-off tools.
public/ public/README.md Static web root and the built-in offline Cyberdeck Manual (welcome.html).
public/assets/ public/assets/README.md Tactical high-resolution brand insignia (legacy asset names retained for compatibility).
src/ src/README.md Root source directory organizing process-isolated TypeScript modules.
src/main/ src/main/README.md Electron main process, BrowserChassis coordinator, and HTTP bridge (:4111).
src/main/cortex/ src/main/cortex/README.md Embedded LanceDB vector database, 128-d embedder, chunker, and hybrid semantic recall.
src/main/tabs/ src/main/tabs/README.md Hardware-accelerated WebContentsView multi-tab pooling and popup trapping.
src/main/shield/ src/main/shield/README.md Autonomous Shield Breaker engine auto-dismissing cookie banners and GDPR modals.
src/main/downloads/ src/main/downloads/README.md Deterministic workspace file download routing and automatic document-to-Cortex ingestion.
src/main/session/ src/main/session/README.md Persistent cookie jar and session snapshot vault for cross-run auth persistence.
src/main/network/ src/main/network/README.md Chrome DevTools Protocol wire interceptor capturing background XHR/Fetch JSON streams.
src/main/marks/ src/main/marks/README.md Set-of-Marks visual grounding engine injecting numbered neon badges for zero-ambiguity LLM actuation.
src/main/primitives/ src/main/primitives/README.md Orthogonal primitive layer (observe · act · wait_for · expect · find · extract · collect_all · page_state/diff · export_har) with structured error codes and injected DOM probes.
kuang_docs/ kuang_docs/okf.json OKF (Open Knowledge Format) bundle kuang_browser_docs — playbook/reference partitions exposed as MCP resources.
src/main/scripts/ src/main/scripts/README.md Persistent script vault and LanceDB semantic macro recall engine with parameter injection.
src/main/tray/ src/main/tray/README.md Windows System Tray integration and close-to-tray background minimization.
src/main/hil/ src/main/hil/README.md Human-In-The-Loop cooperative intervention protocol, OS notifications, and in-page alert banners.
src/main/telemetry/ src/main/telemetry/README.md Organic cubic Bézier mouse kinematics, Gaussian typing cadence, and stealth fingerprinting.
src/main/harvester/ src/main/harvester/README.md Mozilla Readability & Turndown token-efficient markdown distillation.
src/preload/ src/preload/README.md Sandboxed ContextBridge IPC exposing typed window.electronAPI to the DOM.
src/renderer/ src/renderer/README.md DaisyUI 5 Cyberdeck HUD, phosphor radar trail canvas, and Cortex modal dialogs.
src/shared/ src/shared/README.md Shared TypeScript contracts, telemetry events, and construct schemas.
src/mcp/ src/mcp/README.md In-process TypeScript MCP server definition for zero-overhead unified execution.
src/main/decide/ + drive/ docs/DECISION_MODELS.md Optional typed decision client, guard, human gate detection, and the observe → decide → act loop.
src/main/recording/ src/main/recording/recording-service.ts Timelapse configuration, window capture handshake, file writing, and the recordings registry.
src/main/search/ · crawl/ · research/ src/mcp/README.md Multi-engine search, breadth-first crawl, and cited research.
src/main/updates/ docs/BUILDING.md electron-updater wiring and release feed handling.

🚀 Quickstart

Development Mode (with Live Reloading)

pnpm dev

One bridge per port. Dev and installed builds share the default bridge port 4111 (HB_BRIDGE_PORT overrides it). They also have separate single-instance locks, so both can run at once — and the second one's bridge fails with EADDRINUSE, leaving the MCP tools pointed at the other window. Close one, or run the second with HB_BRIDGE_PORT=4112 and set HB_BRIDGE_URL for its MCP server to match.

Production Build & Launch

pnpm run build
pnpm start

MCP Client Configuration (OpenCode, Claude, Cursor, Antigravity, Atomic Chat)

The HUD's Settings → MCP Clients tab generates a correct, copy-ready config for this installation — OpenCode, Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, Google Antigravity, Gemini CLI, Atomic Chat, Zed, and the generic mcpServers shape. It fills in the absolute server path and bridge port, and offers two runtimes:

A minimal Claude-style entry looks like this (the tab generates the real paths):

{
  "mcpServers": {
    "hb-browser": {
      "command": "<path-to-app>/Hyperbolic Gateway.exe",
      "args": ["<userData>\\bin\\mcp-server.cjs"],
      "env": { "ELECTRON_RUN_AS_NODE": "1" }
    }
  }
}

Note: the Cyberdeck must be running (pnpm run dev or pnpm start) so the stdio server can reach the browser instance on http://127.0.0.1:4111. If you run on another port with HB_BRIDGE_PORT, set HB_BRIDGE_URL for the MCP server — the generated snippet includes it automatically. Installed builds copy the server into userData/bin/ (refreshed on update), because resources/ is temporary for portable and AppImage builds.


🧪 Testing

All suites run against the live Cyberdeck on http://127.0.0.1:4111 (start the app first):

Command Suite What it proves
npm run test:harvest Harvest regression (4 checks) Markdown extraction, marks, off-screen marks still work end-to-end.
npm run test:primitives Offline primitives (25 checks) wait_for, extract, observe budget/cursor, collect_all, find+act+delta, expect, page_state/diff, bridge_health capabilities.
npm run test:mcp:primitives MCP stdio primitives (14 checks) The same primitives through a real MCP client — tool schemas, viewport gating, find→act, contexts, focus_window.
npm run test:focus Focus independence (4 checks) Hands the OS foreground to another app (Notepad) and asserts typing/clicks/wheel still land and that input.transport === 'cdp'.
npm run test:mcp-http MCP HTTP host (6 checks) Bearer auth, stateful and stateless MCP clients, session eviction, non-MCP path rejection, and idle expiry.
npm run test:cloudflared-tunnel Named tunnel manager (3 checks) Registration parse, startup timeout, process failure, and clean shutdown using a fake cloudflared fixture.
npm run test:cloudflared cloudflared manager (4 checks) Release selection, SHA-256 verification, atomic managed install, checksum rejection, unsupported-platform errors, and pure-Node macOS tar extraction.
npm run test:mcp-oauth MCP OAuth server (4 checks) Discovery metadata, forwarded HTTPS origin, authorization code + PKCE, token/refresh, bad verifier and disallowed redirect rejection.
npm run test:devlogs Dev logs MCP smoke Lists/read/searches allowlisted logs and confirms token redaction.
npm run test:remote Remote gateway smoke (experimental) Builds bin/remote-server.cjs, starts it with a token file, rejects unauthenticated calls, lists the browser tools, and tests the app service start/stop/token rotation/tunnel handoff.

Type-check and bundles: npx tsc --noEmit, npm run build, npm run build:mcp.


📚 Documentation & Knowledge (OKF)


📦 Release notes

[!NOTE] What's new in 1.4.6 — read_url, and a probe that no longer lies. llm_surface reported recommended: "rendered" — a strategy sitting in the same union as the fetchable representations — so callers read it as "fetch the rendered variant" and got a hard HTTP 400 from llm_fetch. Now recommended is null when the site advertises nothing, and a machine-readable next ({tool:"llm_fetch",as} or {tool:"observe",as:"markdown"}) says what to do instead. Both llm_surface and llm_fetch also report scope (origin | page) and scope_mismatch, so a page request that can only be answered with a site-wide /llms.txt index now says so rather than silently returning the wrong document. New read_url does the whole thing in one call — site-provided Markdown/Text when advertised, otherwise a detached render distilled to Markdown (active tab untouched) — and is paged via max_chars + next_cursor so a long legal page is never truncated. Discovery also picks up page-level Markdown alternates from real <a href="….md"> links on the page, not only a declared <link rel="alternate">. 63 advertised tools. See CHANGELOG.md.

What's new in 1.4.5 — timelapse recording, a human-only tool gate, and a client-config generator. The REC button records the Cyberdeck window as a true timelapse: frames are captured every intervalMs and stamped 1000/fps apart, so one minute of work becomes about two seconds of video at the defaults. record_start, record_stop and list_recordings return the absolute .webm path. A separate, human-only switch removes those tools from tools/list and makes the bridge refuse them. Settings is now a full-viewport, tabbed view (Decision model / Recording / MCP clients); the client tab generates copy-ready configs for OpenCode, Claude Desktop/Code, Cursor, VS Code, Windsurf, Google Antigravity, Gemini CLI, Atomic Chat and Zed, on either the bundled Electron runtime (no Node install) or system Node. 62 advertised tools (+2 optional).

What's new in 1.4.4 — auto-update. electron-updater checks 30 s after startup and every 6 hours, downloads in the background (sha512-verified), installs on quit, and offers RESTART in the HUD. It is off in dev builds and on unsigned macOS. KUANG_BRIDGE_PORT makes the bridge port configurable so a dev build and an installed build can run side by side, and a port collision now names EADDRINUSE instead of failing silently.

What's new in 1.4.2 — a decision model in the loop, and a loop that drives it. This patch adds drive (observe → guarded decide → verified act, one decision call per step, with a typed stop reason and a text hand-off), screenshots for decide (hosted TypeSafe only — the OpenCode gateway drops the image header, so images are disabled there and the result says so), gate hand-offs raised by Kuang itself, consent sweeps at 0.9/2.6/6 s, clickable labels, and selectable HUD text. It builds on the 1.4.0 work: a decision model in the loop, and the evidence that it worked. observe(as:"components") returns a decision-ready table (role, name, value, region, and the actions each component accepts); an optional decide tool asks a typed decision model (Jev 1.13 via OpenCode's Console endpoint, or a local ONNX model) for the operation and target, guarded by confidence, target membership and affordance checks, with rejected options eliminated and retried, and fallback:"agent" when nothing survives. Age verification and CAPTCHA are hard human hand-offs — detected before any model call. Typing gained an input-method ladder (keys → CDP insertText → paste) for fields that ignore keystrokes, act now sees SPA navigations and clicks large card links at a point that belongs to them, and the HUD gained a Settings dialog, a conditional DECISIONS tab, a right-click menu, and a HIL banner that stays off the viewport. Measured: Jev 10/10 on the page bench at ~376 ms. 59 tools (+ decide and drive when configured). See CHANGELOG.md.

What's new in 1.3.0 — the page is now readable and addressable through shadow DOM, and the tool no longer reports success for actions that did nothing. observe(as:"markdown") harvests the composed tree (open shadow roots, live form values, passwords reduced to a length) with a minimal, stable envelope (reader, extracted_chars/page_chars, shadow_roots) and working budget/next_cursor paging; act resolves selectors through shadow roots to the control a mouse would hit, verifies that typing landed (ok:false + TYPE_NO_EFFECT + evidence, distinguishing "input never arrived" from "the control refuses it"), and reports target.field for web-component controls; Set-of-Marks, find and act(mark_id) traverse open shadow roots too. All found by dogfooding a real signup flow — see docs/FIELD_NOTES_2026-10-20_SIGNUP.md. 62 advertised tools. See CHANGELOG.md.

What's new in 1.2.0 — an autonomous discovery layer. llm_surface detects a site's own Markdown/Text representation (/llms.txt, a <link rel="alternate"> hint, or content negotiation) and llm_fetch reads it (gated on an advertised surface); search_web runs multi-engine search in a detached view with automatic fallback; deep_crawl walks a site breadth-first, distilling and indexing every page into Cortex; research_topic searches → gathers → distils → indexes and returns cited extractive evidence. Plus typed JSON-LD (schema_org) and every-table extraction on extract, one shared reusable off-screen view (fixes flaky detached reads), and an explicit TIER 1/2/3 map across the docs and the default-tab Manual. 62 advertised tools. See CHANGELOG.md and TODO_RESEARCH_CRAWL.md.

What's new in 1.1.1 — MCP stdio reliability hardening so the kuang tool namespace can no longer "just go away". Per-call diagnostics now go to the file log only, stderr is a short bounded boot banner (backpressure-aware, 16 KiB hard cap, lines dropped rather than queued), a KUANG_BRIDGE_TIMEOUT_MS ceiling (default 300 s) stops a wedged Cyberdeck from hanging a tool call, and closing stdin shuts the child down cleanly. Reproduced and verified: stderr unread, 600 calls → 601/601 responses, 0 timeouts (was 158 responses then a stall). See CHANGELOG.md and docs/HANDOFF_ORCHA_MCP_STDERR.md.

What's new in 1.1.0 — an orthogonal primitive layer (observe · act · wait_for · expect · find · extract · collect_all · page_state/diff · export_har · context_*) that removes blind sleeps and guessy selectors, focus-independent CDP input (clicks/typing land even when another app owns the foreground), and background-render hardening so occluded windows still hydrate and scroll. 16 new tools, 1 new OKF playbook partition. See docs/PROPOSED_ABSTRACTIONS.md.


📄 License

This project is licensed under the MIT License © 2026 hyperbolictonic.


Ono-Sendai Cyberspace 7    Hyperbolic Browser    Hyperbolic Rest

🌌 Neuromancer Inspiration & Cyberpunk Canon

"A Kuang Grade Mark Eleven penetration program. Icebreaker. The military stuff, the heavy artillery."
— William Gibson, Neuromancer (1984)

In William Gibson's genre-defining cyberpunk masterpiece Neuromancer, console cowboys Case and the ROM personality construct of the Dixie Flatline jack into the Matrix using an Ono-Sendai Cyberspace 7 deck. Their ultimate weapon: the Kuang Grade Mark Eleven—a mythical military-grade icebreaker designed to slice through the impenetrable corporate ICE surrounding the Tessier-Ashpool mainframe.

Hyperbolic Gateway began as a project inspired by that fictional lineage, then was rebranded to its current name. The quote and the original Kuang Grade Mark Eleven reference are preserved here as inspiration; Hyperbolic Gateway is an independent project and is not affiliated with William Gibson, Neuromancer, or the fictional device.

It carries that inspiration into the modern web: