Download index.html from Snapkitty/hyperkitty-chromium: direct link, hf CLI and curl.
- Browser
- Download file 24.6 kB
-
https://huggingface.co/Snapkitty/hyperkitty-chromium/resolve/main/index.html
- Command line
-
hf download hf://Snapkitty/hyperkitty-chromium/index.html
-
curl -L -o index.html https://huggingface.co/Snapkitty/hyperkitty-chromium/resolve/main/index.html
24.6 kB
| <!-- SPDX-License-Identifier: LicenseRef-Sovereign-Leviathan-SL-AGPL3-001 | |
| Licensing: see LICENSE in the repository root. --> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>HyperKitty Chromium β Erlang/OTP Browser Agent Platform</title> | |
| <style> | |
| :root { | |
| --bg0: #0a0a0f; | |
| --bg1: #0f0f1a; | |
| --bg2: #141420; | |
| --border: #2a2a45; | |
| --green: #00ff88; | |
| --blue: #00aaff; | |
| --orange: #ffaa00; | |
| --purple: #cc66ff; | |
| --red: #ff4444; | |
| --dim: #666; | |
| --text: #ccc; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| background: var(--bg0); | |
| color: var(--text); | |
| font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; | |
| font-size: 14px; | |
| line-height: 1.6; | |
| } | |
| /* Nav */ | |
| nav { | |
| position: fixed; top: 0; left: 0; right: 0; z-index: 100; | |
| background: rgba(10,10,15,0.95); | |
| border-bottom: 1px solid var(--border); | |
| display: flex; align-items: center; gap: 0; | |
| padding: 0 32px; height: 52px; | |
| backdrop-filter: blur(8px); | |
| } | |
| .nav-logo { color: var(--green); font-weight: bold; font-size: 16px; margin-right: 32px; } | |
| .nav-logo span { color: var(--text); font-weight: normal; } | |
| .nav-links { display: flex; gap: 0; } | |
| .nav-links a { | |
| color: var(--dim); text-decoration: none; font-size: 13px; | |
| padding: 0 16px; height: 52px; display: flex; align-items: center; | |
| border-bottom: 2px solid transparent; transition: all 0.15s; | |
| } | |
| .nav-links a:hover { color: var(--text); } | |
| .nav-links a.active { color: var(--green); border-bottom-color: var(--green); } | |
| .nav-right { margin-left: auto; display: flex; gap: 10px; } | |
| .badge { | |
| padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: bold; | |
| border: 1px solid; | |
| } | |
| .badge.green { color: var(--green); border-color: var(--green); } | |
| .badge.orange { color: var(--orange); border-color: var(--orange); } | |
| /* Hero */ | |
| #hero { | |
| padding: 120px 32px 80px; | |
| max-width: 900px; margin: 0 auto; | |
| } | |
| .hero-title { | |
| font-size: 42px; font-weight: bold; line-height: 1.1; | |
| color: #fff; margin-bottom: 16px; | |
| } | |
| .hero-title .hl { color: var(--green); } | |
| .hero-sub { | |
| font-size: 16px; color: var(--dim); margin-bottom: 32px; | |
| max-width: 600px; line-height: 1.7; | |
| } | |
| .hero-stats { | |
| display: flex; gap: 32px; flex-wrap: wrap; | |
| } | |
| .stat { } | |
| .stat-num { font-size: 28px; font-weight: bold; color: var(--green); } | |
| .stat-label { font-size: 12px; color: var(--dim); } | |
| /* Sections */ | |
| section { | |
| padding: 80px 32px; | |
| max-width: 1100px; margin: 0 auto; | |
| } | |
| .section-label { | |
| font-size: 11px; color: var(--green); text-transform: uppercase; | |
| letter-spacing: 2px; margin-bottom: 8px; | |
| } | |
| .section-title { | |
| font-size: 28px; font-weight: bold; color: #fff; margin-bottom: 16px; | |
| } | |
| .section-sub { font-size: 14px; color: var(--dim); margin-bottom: 40px; max-width: 600px; } | |
| /* Supervision tree */ | |
| #tree-container { | |
| background: var(--bg1); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 32px; | |
| overflow-x: auto; | |
| } | |
| .tree { font-family: inherit; font-size: 12px; line-height: 1.8; } | |
| .tree-root { color: var(--orange); font-weight: bold; } | |
| .tree-sup { color: var(--blue); } | |
| .tree-worker { color: var(--green); } | |
| .tree-line { color: var(--dim); } | |
| .tree-note { color: var(--dim); font-style: italic; } | |
| /* Cards */ | |
| .cards { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: 16px; | |
| } | |
| .card { | |
| background: var(--bg1); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 20px; | |
| transition: border-color 0.15s; | |
| } | |
| .card:hover { border-color: var(--blue); } | |
| .card-icon { font-size: 24px; margin-bottom: 10px; } | |
| .card-title { color: #fff; font-weight: bold; margin-bottom: 6px; font-size: 15px; } | |
| .card-body { color: var(--dim); font-size: 12px; line-height: 1.6; } | |
| .card-tag { | |
| display: inline-block; margin-top: 10px; | |
| padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: bold; | |
| border: 1px solid; | |
| } | |
| .tag-green { color: var(--green); border-color: var(--green); } | |
| .tag-blue { color: var(--blue); border-color: var(--blue); } | |
| .tag-orange { color: var(--orange); border-color: var(--orange); } | |
| .tag-purple { color: var(--purple); border-color: var(--purple); } | |
| /* Test results */ | |
| .test-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); | |
| gap: 10px; | |
| } | |
| .test-item { | |
| background: var(--bg1); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| padding: 12px 16px; | |
| display: flex; align-items: center; gap: 10px; | |
| } | |
| .test-item .check { color: var(--green); font-size: 16px; } | |
| .test-item .test-name { color: var(--text); font-size: 12px; } | |
| .test-item .test-type { color: var(--dim); font-size: 10px; margin-left: auto; } | |
| /* API table */ | |
| .api-table { width: 100%; border-collapse: collapse; font-size: 12px; } | |
| .api-table th { | |
| text-align: left; padding: 8px 12px; | |
| background: var(--bg2); color: var(--dim); | |
| border-bottom: 1px solid var(--border); | |
| font-size: 11px; text-transform: uppercase; letter-spacing: 1px; | |
| } | |
| .api-table td { | |
| padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; | |
| } | |
| .api-table tr:last-child td { border-bottom: none; } | |
| .method { | |
| padding: 2px 8px; border-radius: 4px; font-weight: bold; font-size: 10px; | |
| display: inline-block; | |
| } | |
| .method.GET { background: rgba(0,255,136,0.1); color: var(--green); } | |
| .method.POST { background: rgba(0,170,255,0.1); color: var(--blue); } | |
| .method.DELETE { background: rgba(255,68,68,0.1); color: var(--red); } | |
| .method.WS { background: rgba(204,102,255,0.1);color: var(--purple); } | |
| .endpoint { color: var(--text); font-family: inherit; } | |
| .ep-desc { color: var(--dim); } | |
| /* Code block */ | |
| .code-block { | |
| background: var(--bg0); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| padding: 20px; | |
| overflow-x: auto; | |
| font-size: 12px; | |
| line-height: 1.7; | |
| } | |
| .code-block .kw { color: var(--orange); } | |
| .code-block .fn { color: var(--blue); } | |
| .code-block .str { color: var(--green); } | |
| .code-block .cmt { color: var(--dim); } | |
| .code-block .op { color: var(--purple); } | |
| /* FSM */ | |
| .fsm-states { | |
| display: flex; align-items: center; flex-wrap: wrap; gap: 8px; | |
| } | |
| .fsm-state { | |
| padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: bold; | |
| border: 1px solid; | |
| } | |
| .fsm-state.start { color: var(--green); border-color: var(--green); background: rgba(0,255,136,0.05); } | |
| .fsm-state.middle { color: var(--blue); border-color: var(--blue); background: rgba(0,170,255,0.05); } | |
| .fsm-state.end-ok { color: var(--green); border-color: var(--green); background: rgba(0,255,136,0.1); } | |
| .fsm-state.end-fail{color: var(--red); border-color: var(--red); background: rgba(255,68,68,0.1); } | |
| .fsm-arrow { color: var(--dim); font-size: 18px; } | |
| /* Divider */ | |
| hr { border: none; border-top: 1px solid var(--border); } | |
| /* Footer */ | |
| footer { | |
| padding: 40px 32px; | |
| text-align: center; | |
| color: var(--dim); | |
| font-size: 12px; | |
| border-top: 1px solid var(--border); | |
| } | |
| footer a { color: var(--dim); } | |
| footer a:hover { color: var(--green); } | |
| /* Responsive */ | |
| @media (max-width: 600px) { | |
| .hero-title { font-size: 28px; } | |
| nav { padding: 0 16px; } | |
| section { padding: 60px 16px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Nav --> | |
| <nav> | |
| <div class="nav-logo">HyperKitty <span>Chromium</span></div> | |
| <div class="nav-links"> | |
| <a href="#architecture" class="active">Architecture</a> | |
| <a href="#supervision">Supervision</a> | |
| <a href="#agent">Agent FSM</a> | |
| <a href="#api">API</a> | |
| <a href="#tests">Tests</a> | |
| <a href="#quickstart">Quickstart</a> | |
| </div> | |
| <div class="nav-right"> | |
| <span class="badge green">24 tests β</span> | |
| <span class="badge orange">Erlang/OTP</span> | |
| </div> | |
| </nav> | |
| <!-- Hero --> | |
| <div id="hero"> | |
| <div class="hero-title"> | |
| <span class="hl">HyperKitty</span> Chromium | |
| </div> | |
| <div class="hero-sub"> | |
| An Erlang/OTP-native browser agent platform. Headless Chromium wrapped in an explicit supervision tree β so browser control, agent orchestration, search, and messaging are separate, independently-supervised subsystems rather than library calls inside one monolithic process. | |
| </div> | |
| <div class="hero-stats"> | |
| <div class="stat"><div class="stat-num">24</div><div class="stat-label">tests passing</div></div> | |
| <div class="stat"><div class="stat-num">7</div><div class="stat-label">OTP supervisors</div></div> | |
| <div class="stat"><div class="stat-num">11</div><div class="stat-label">search pipeline stages</div></div> | |
| <div class="stat"><div class="stat-num">14</div><div class="stat-label">browser capability ops</div></div> | |
| <div class="stat"><div class="stat-num">6</div><div class="stat-label">REST + WS API domains</div></div> | |
| </div> | |
| </div> | |
| <hr> | |
| <!-- Architecture --> | |
| <section id="architecture"> | |
| <div class="section-label">Overview</div> | |
| <div class="section-title">The core principle</div> | |
| <div class="section-sub">An LLM-driven agent is a planning function that proposes tool calls. It is never trusted with direct, unchecked access to a browser process. Every tool call crosses an explicit, typed capability boundary β enforced by Erlang code, not by the prompt β before it reaches Chromium.</div> | |
| <div class="cards"> | |
| <div class="card"> | |
| <div class="card-icon">π</div> | |
| <div class="card-title">HK-BROWSER</div> | |
| <div class="card-body">Launches a real headless Chromium OS process per session. Drives it over CDP (via <code>gun</code>) and Chrome's HTTP control endpoint. 14-operation typed capability interface with auth + argument validation.</div> | |
| <span class="card-tag tag-green">gen_server</span> | |
| </div> | |
| <div class="card"> | |
| <div class="card-icon">π€</div> | |
| <div class="card-title">HK-AGENT</div> | |
| <div class="card-body">A <code>gen_statem</code> agent loop enforcing configurable action, time, network, and search-iteration limits before any tool call reaches the browser. 7-state FSM from created β completed/failed/terminated.</div> | |
| <span class="card-tag tag-blue">gen_statem</span> | |
| </div> | |
| <div class="card"> | |
| <div class="card-icon">π</div> | |
| <div class="card-title">HK-SEARCH</div> | |
| <div class="card-body">An 11-stage pipeline where each stage is a literal state. Emits an ordered event per stage with a pluggable provider behaviour and a mock reference implementation.</div> | |
| <span class="card-tag tag-orange">pipeline</span> | |
| </div> | |
| <div class="card"> | |
| <div class="card-icon">π‘</div> | |
| <div class="card-title">HK-MSG</div> | |
| <div class="card-body">Append-only event bus with WebSocket streaming. Every action, state transition, and result is published before it is acted on β making the system fully observable.</div> | |
| <span class="card-tag tag-purple">event bus</span> | |
| </div> | |
| <div class="card"> | |
| <div class="card-icon">π</div> | |
| <div class="card-title">HK-API</div> | |
| <div class="card-body">6-domain REST + WebSocket API built on Cowboy. Agents, browsers, search, messages, events, health β all independently addressable. OpenAPI-compatible route layout.</div> | |
| <span class="card-tag tag-blue">Cowboy + REST</span> | |
| </div> | |
| <div class="card"> | |
| <div class="card-icon">β₯</div> | |
| <div class="card-title">HK-HEALTH</div> | |
| <div class="card-body">Aggregate subsystem health β all 7 subsystems up or down in one call. Used by the observability frontend and external monitoring. No external dependency.</div> | |
| <span class="card-tag tag-green">monitoring</span> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Supervision tree --> | |
| <section id="supervision"> | |
| <div class="section-label">OTP</div> | |
| <div class="section-title">Supervision tree</div> | |
| <div class="section-sub"><code>rest_for_one</code> at the root β if the event bus dies, everything that depends on it restarts too. Each browser and agent is its own isolated process with its own restart budget.</div> | |
| <div id="tree-container"> | |
| <pre class="tree"><span class="tree-root">hyperkitty_sup</span> <span class="tree-note">(rest_for_one, intensity 8/10)</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-sup">hk_event_sup</span> <span class="tree-note">(one_for_one)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_event_bus</span> <span class="tree-note">[gen_server] append-only audit log + pub/sub</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-sup">hk_messaging_sup</span> <span class="tree-note">(one_for_one)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_messaging_portal</span> <span class="tree-note">[gen_server] HK-MSG</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-sup">hk_browser_sup</span> <span class="tree-note">(one_for_one)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_browser_registry</span> <span class="tree-note">[gen_server] session_id β pid</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-sup">hk_browser_session_sup</span> <span class="tree-note">(simple_one_for_one, transient)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_browser_session</span> <span class="tree-note">[gen_server] owns a Chrome OS process per session</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-sup">hk_agent_sup</span> <span class="tree-note">(one_for_one)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_agent_registry</span> <span class="tree-note">[gen_server] agent_id β pid</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-sup">hk_agent_fsm_sup</span> <span class="tree-note">(simple_one_for_one, transient)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_agent_fsm</span> <span class="tree-note">[gen_statem] one per agent, 7-state FSM</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-sup">hk_search_sup</span> <span class="tree-note">(one_for_one)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_search_registry</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-sup">hk_search_job_sup</span> <span class="tree-note">(simple_one_for_one)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_search_job</span> <span class="tree-note">[gen_statem] 11-stage pipeline per job</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-sup">hk_api_sup</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_api_listener</span> <span class="tree-note">[Cowboy] REST + WS (trap_exit for clean teardown)</span> | |
| <span class="tree-line"> β βββ</span> <span class="tree-worker">hk_frontend_*</span> <span class="tree-note">connected client registry</span> | |
| <span class="tree-line"> βββ</span> <span class="tree-worker">hk_health</span> <span class="tree-note">[gen_server] aggregate subsystem health</span></pre> | |
| </div> | |
| </section> | |
| <!-- Agent FSM --> | |
| <section id="agent"> | |
| <div class="section-label">gen_statem</div> | |
| <div class="section-title">Agent state machine</div> | |
| <div class="section-sub">Every agent is a <code>gen_statem</code> process. No tool call reaches the browser without passing through this FSM β configurable action limits, time limits, and network limits are enforced structurally, not by the LLM.</div> | |
| <div style="margin-bottom: 32px;"> | |
| <div class="fsm-states"> | |
| <div class="fsm-state start">created</div> | |
| <div class="fsm-arrow">β</div> | |
| <div class="fsm-state middle">ready</div> | |
| <div class="fsm-arrow">β</div> | |
| <div class="fsm-state middle">planning</div> | |
| <div class="fsm-arrow">β</div> | |
| <div class="fsm-state middle">executing</div> | |
| <div class="fsm-arrow">β</div> | |
| <div class="fsm-state middle">waiting</div> | |
| <div class="fsm-arrow">β</div> | |
| <div class="fsm-state end-ok">completed</div> | |
| </div> | |
| <div style="margin-top: 12px; display: flex; gap: 12px; flex-wrap: wrap;"> | |
| <div class="fsm-state end-fail" style="font-size:11px">failed</div> | |
| <div class="fsm-state end-fail" style="font-size:11px">terminated</div> | |
| <span style="color:var(--dim);font-size:12px;align-self:center">β reachable from any state on limit exceeded</span> | |
| </div> | |
| </div> | |
| <div class="cards"> | |
| <div class="card"> | |
| <div class="card-title">Bugs found and fixed</div> | |
| <div class="card-body" style="margin-top:8px"> | |
| <div style="margin-bottom:8px"><span style="color:var(--red)">Bug 1:</span> <code>ready</code> state's tool-call handler routed to wrong next state β silently dropping the action. Fixed: correct state transition target.</div> | |
| <div><span style="color:var(--red)">Bug 2:</span> OTP race where a mid-tree supervisor killed abruptly outran its children's cleanup. Fixed: <code>hk_sup_util</code> retry-on-death-wait helper + <code>trap_exit</code> on <code>hk_api_listener</code>.</div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <div class="card-title">Chrome PUT fix</div> | |
| <div class="card-body" style="margin-top:8px">Modern Chrome requires <code>PUT</code> for <code>/json/new</code> β newer Chrome dropped support for <code>GET</code> on that endpoint. <code>hk_chrome_http.erl</code> updated accordingly.</div> | |
| <span class="card-tag tag-orange">integration fix</span> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- API --> | |
| <section id="api"> | |
| <div class="section-label">HTTP + WebSocket</div> | |
| <div class="section-title">API reference</div> | |
| <div class="section-sub">All routes served by Cowboy. WebSocket at <code>/api/events/ws</code> streams every event in real time.</div> | |
| <div style="background:var(--bg1);border:1px solid var(--border);border-radius:8px;overflow:hidden"> | |
| <table class="api-table"> | |
| <thead> | |
| <tr><th>Method</th><th>Path</th><th>Description</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr><td><span class="method GET">GET</span></td><td class="endpoint">/api/health</td><td class="ep-desc">All 7 subsystems up/down in one call</td></tr> | |
| <tr><td><span class="method POST">POST</span></td><td class="endpoint">/api/agents</td><td class="ep-desc">Create agent with config (limits, browser_session_id)</td></tr> | |
| <tr><td><span class="method GET">GET</span></td><td class="endpoint">/api/agents/:id</td><td class="ep-desc">Agent state + current FSM state</td></tr> | |
| <tr><td><span class="method DELETE">DELETE</span></td><td class="endpoint">/api/agents/:id</td><td class="ep-desc">Terminate agent (transitions to terminated)</td></tr> | |
| <tr><td><span class="method POST">POST</span></td><td class="endpoint">/api/browsers</td><td class="ep-desc">Spawn headless Chromium session</td></tr> | |
| <tr><td><span class="method POST">POST</span></td><td class="endpoint">/api/browsers/:id/capability</td><td class="ep-desc">Execute typed browser capability (14 ops)</td></tr> | |
| <tr><td><span class="method DELETE">DELETE</span></td><td class="endpoint">/api/browsers/:id</td><td class="ep-desc">Kill Chromium process, clean up session</td></tr> | |
| <tr><td><span class="method POST">POST</span></td><td class="endpoint">/api/search</td><td class="ep-desc">Start 11-stage search pipeline job</td></tr> | |
| <tr><td><span class="method GET">GET</span></td><td class="endpoint">/api/search/:id</td><td class="ep-desc">Search job state + stage progress</td></tr> | |
| <tr><td><span class="method GET">GET</span></td><td class="endpoint">/api/messages</td><td class="ep-desc">Recent messages from event bus</td></tr> | |
| <tr><td><span class="method GET">GET</span></td><td class="endpoint">/api/events/recent</td><td class="ep-desc">Last N events across all subsystems</td></tr> | |
| <tr><td><span class="method WS">WS</span></td><td class="endpoint">/api/events/ws</td><td class="ep-desc">Real-time event stream (all subsystems)</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <!-- Tests --> | |
| <section id="tests"> | |
| <div class="section-label">EUnit</div> | |
| <div class="section-title">24 tests β 0 failures</div> | |
| <div class="section-sub">Includes real headless Chromium integration tests and chaos tests that kill live processes and verify recovery. Run with <code>rebar3 eunit</code>.</div> | |
| <div class="test-grid"> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_agent_fsm_tests</span><span class="test-type">FSM</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_browser_capability_tests</span><span class="test-type">integration</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_browser_session_tests</span><span class="test-type">integration</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_messaging_portal_tests</span><span class="test-type">unit</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_search_job_tests</span><span class="test-type">pipeline</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_search_pipeline_tests</span><span class="test-type">pipeline</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_supervision_tests</span><span class="test-type">chaos</span></div> | |
| <div class="test-item"><span class="check">β</span><span class="test-name">hk_test_helper</span><span class="test-type">harness</span></div> | |
| </div> | |
| <div style="margin-top:24px;background:var(--bg1);border:1px solid var(--green);border-radius:6px;padding:16px;max-width:500px"> | |
| <div style="color:var(--green);font-weight:bold;margin-bottom:8px">Test output</div> | |
| <div style="font-size:12px;color:var(--dim)"> | |
| <div>24/24 tests pass</div> | |
| <div>SUPERVISOR/CRASH/ERROR REPORTs in output are expected β</div> | |
| <div>chaos tests deliberately kill live processes to verify recovery.</div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Quickstart --> | |
| <section id="quickstart"> | |
| <div class="section-label">Setup</div> | |
| <div class="section-title">Quickstart</div> | |
| <div class="section-sub">Requires Erlang/OTP 25+, rebar3, and Chromium. Bootstrap script handles the rest.</div> | |
| <div class="code-block"><span class="cmt"># Bootstrap environment (macOS via Homebrew)</span> | |
| <span class="kw">bash</span> bin/bootstrap.sh | |
| <span class="cmt"># Build</span> | |
| <span class="kw">rebar3</span> compile | |
| <span class="cmt"># Run tests</span> | |
| <span class="kw">rebar3</span> eunit | |
| <span class="cmt"># Start</span> | |
| <span class="kw">rebar3</span> shell | |
| <span class="cmt"># Health check</span> | |
| <span class="kw">curl</span> http://localhost:8080/api/health</div> | |
| <div style="margin-top: 24px;"> | |
| <a href="https://github.com/SNAPKITTYWEST/hyperkitty-chromium" style=" | |
| display: inline-flex; align-items: center; gap: 8px; | |
| padding: 10px 20px; border-radius: 6px; | |
| background: rgba(0,255,136,0.1); border: 1px solid var(--green); | |
| color: var(--green); text-decoration: none; font-size: 13px; font-weight: bold; | |
| transition: background 0.15s; | |
| ">View on GitHub β</a> | |
| </div> | |
| </section> | |
| <footer> | |
| HyperKitty Chromium β Erlang/OTP Browser Agent Platform<br> | |
| <a href="https://github.com/SNAPKITTYWEST/hyperkitty-chromium">SNAPKITTYWEST/hyperkitty-chromium</a> Β· | |
| BSL-1.1 / AGPL-3.0 / MPL-2.0 Β· Patent Pending β Bel Esprit D'Accord Irrevocable Trust | |
| </footer> | |
| <script> | |
| // Smooth scroll + active nav | |
| document.querySelectorAll('a[href^="#"]').forEach(a => { | |
| a.addEventListener('click', e => { | |
| e.preventDefault(); | |
| document.querySelector(a.getAttribute('href'))?.scrollIntoView({ behavior: 'smooth' }); | |
| }); | |
| }); | |
| const sections = document.querySelectorAll('section[id], div[id="hero"]'); | |
| const navLinks = document.querySelectorAll('.nav-links a'); | |
| const obs = new IntersectionObserver(entries => { | |
| entries.forEach(e => { | |
| if (e.isIntersecting) { | |
| navLinks.forEach(l => l.classList.remove('active')); | |
| const id = e.target.id; | |
| const link = document.querySelector(`.nav-links a[href="#${id}"]`); | |
| if (link) link.classList.add('active'); | |
| } | |
| }); | |
| }, { threshold: 0.3 }); | |
| sections.forEach(s => obs.observe(s)); | |
| </script> | |
| </body> | |
| </html> | |