Ctrl K
ComunidadGuías…

public/index.html

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ghost Browser</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap">
<link rel="stylesheet" href="css/console.css">
<link rel="stylesheet" href="css/marketplace.css">
<link rel="stylesheet" href="css/automation.css">
</head>
<body>

<!-- ═══ GATE — sign-in screen ═══════════════════════════════════════════════════════════════════ -->
<div id="gate">
  <div class="gate-split">

    <!-- Left: brand story -->
    <div class="gate-brand">
      <div class="gate-brand-inner">
        <div class="gate-logo">
          <span class="gate-logo-mark">G</span>
          <span class="gate-logo-name">Ghost<b>Browser</b></span>
        </div>
        <p class="gate-tagline">A logged-in browser behind a clean HTTP API. Drop in your AI, your model, your keys.</p>
        <ul class="gate-features">
          <li>Isolated profiles per platform — cookies kept between runs</li>
          <li>Set-of-Mark numbered boxes the agent clicks by index</li>
          <li>Dispatch jobs, poll results, approve what it drafts</li>
          <li>Automation flows that wire roles and profiles together</li>
        </ul>
      </div>
    </div>

    <!-- Right: sign-in form -->
    <div class="gate-form-wrap">
      <div class="gate-form-inner">
        <div class="gate-form-header">
          <h1>Sign in</h1>
          <p id="gateSub">Secure access to your browsing agent</p>
        </div>

        <form id="gateForm">
          <div class="field">
            <label for="u">Username or Email</label>
            <input id="u" type="text" autocomplete="username" required placeholder="you@example.com">
          </div>
          <div class="field">
            <label for="p">Password</label>
            <input id="p" type="password" autocomplete="current-password" required placeholder="••••••••">
          </div>
          <button id="gateGo" type="submit" class="btn primary full">Continue →</button>
        </form>

        <div class="msg" id="gateMsg"></div>

        <div class="gate-recovery">
          <a href="#" id="forgot" class="gate-recovery-link">Forgotten the password?</a>
          <div id="resetBox" class="hide">
            <p class="gate-recovery-info">
              Paste the API key (in the app environment as <code>API_KEYS</code>) to clear the account and start again. Browser profiles are untouched.
            </p>
            <div class="gate-recovery-row">
              <input id="resetKey" placeholder="API key" class="gate-recovery-input">
              <button id="resetGo" class="btn ghost sm">Clear account</button>
            </div>
          </div>
        </div>
      </div>
    </div>

  </div>
</div>


<!-- ═══ DASHBOARD — accounts landing screen ══════════════════════════════════════════════════════
     Shown after login. The sidebar nav switches between views; all views live inside #dashBody.
     Tokens and rules are scoped under #dash so they cannot reach #gate or #app.             -->
<div id="dash" class="hide">
  <div class="dash-layout">

    <!-- Off-canvas wrapper (gets .draw on mobile) + scrim -->
    <div class="dwrap">
      <div id="dashScrim"></div>

      <aside class="dash-sidebar">
        <!-- Brand -->
        <div class="dash-brand">
          <span class="dash-brand-mark">G</span>
          <div class="dash-brand-text">
            <span class="dash-brand-name">GhostBrowser</span>
            <span class="dash-brand-sub">control plane</span>
          </div>
        </div>

        <!-- Navigation -->
        <nav class="dash-nav" role="navigation" aria-label="Main navigation">
          <button class="dash-nav-item on" data-view="accounts">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="1" y="1" width="6" height="6" rx=".75"/><rect x="9" y="1" width="6" height="6" rx=".75"/><rect x="1" y="9" width="6" height="6" rx=".75"/><rect x="9" y="9" width="6" height="6" rx=".75"/></svg>
            <span>Accounts</span>
          </button>
          <button class="dash-nav-item" data-view="activity">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><polyline points="1,8 4,8 5.5,3 7.5,13 9,8 11,8 12,5.5 13,8 15,8"/></svg>
            <span>Activity</span>
            <span class="dash-badge hide" id="dashLiveBadge">0</span>
          </button>
          <button class="dash-nav-item" data-view="roles">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><circle cx="6" cy="5" r="3"/><path d="M1 15c0-2.8 2.2-5 5-5"/><circle cx="12" cy="7" r="2.5"/><path d="M10 15c0-2.2 1-3.8 2-4.5"/></svg>
            <span>Roles</span>
          </button>
          <button class="dash-nav-item" data-view="automation">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="5" y="5" width="6" height="6" rx=".5"/><line x1="8" y1="1" x2="8" y2="5"/><line x1="8" y1="11" x2="8" y2="15"/><line x1="1" y1="8" x2="5" y2="8"/><line x1="11" y1="8" x2="15" y2="8"/><line x1="5" y1="3.5" x2="5" y2="5"/><line x1="11" y1="3.5" x2="11" y2="5"/><line x1="5" y1="11" x2="5" y2="12.5"/><line x1="11" y1="11" x2="11" y2="12.5"/></svg>
            <span>Automation</span>
          </button>

          <a class="dash-nav-item" href="/hub" target="_blank" rel="noopener" title="Device Hub — connected phone/laptop nodes">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="1" y="3" width="10" height="7" rx="1"/><line x1="4" y1="13" x2="8" y2="13"/><rect x="11.5" y="6" width="3.5" height="8" rx=".75"/></svg>
            <span>Device Hub</span>
          </a>

          <div class="dash-nav-sep"></div>

          <button class="dash-nav-item" data-view="platforms">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><circle cx="8" cy="8" r="7"/><path d="M1 8h14"/><path d="M8 1c-1.8 2.2-2.8 4.4-2.8 7S6.2 12.8 8 15"/><path d="M8 1c1.8 2.2 2.8 4.4 2.8 7S9.8 12.8 8 15"/></svg>
            <span>Platforms</span>
          </button>
          <button class="dash-nav-item" data-view="files">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M1 4.5A1 1 0 0 1 2 3.5h4l2 2h6a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4.5z"/></svg>
            <span>Files</span>
          </button>
          <button class="dash-nav-item" data-view="history">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><circle cx="8" cy="8" r="7"/><polyline points="8,4 8,8 11,11"/></svg>
            <span>History</span>
          </button>
          <button class="dash-nav-item" data-view="api">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><polyline points="5,4 1,8 5,12"/><polyline points="11,4 15,8 11,12"/><line x1="10" y1="2" x2="6" y2="14"/></svg>
            <span>API</span>
          </button>
          <button class="dash-nav-item" data-view="settings">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><line x1="2.5" y1="5" x2="13.5" y2="5"/><line x1="2.5" y1="8" x2="13.5" y2="8"/><line x1="2.5" y1="11" x2="13.5" y2="11"/><circle cx="5.5" cy="5" r="1.5" fill="currentColor" stroke="none"/><circle cx="11" cy="8" r="1.5" fill="currentColor" stroke="none"/><circle cx="5.5" cy="11" r="1.5" fill="currentColor" stroke="none"/></svg>
            <span>Settings</span>
          </button>

          <!-- Superadmin — shown only when /api/auth/state confirms superadmin -->
          <button class="dash-nav-item dash-nav-item--super hide" data-view="admin">
            <svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M8 2L14 5v5c0 3.3-2.3 5.8-6 7-3.7-1.2-6-3.7-6-7V5z"/></svg>
            <span>Superadmin</span>
          </button>
        </nav>

        <!-- User footer -->
        <div class="dash-sidebar-foot">
          <div class="dash-user">
            <div class="dash-user-dot" aria-hidden="true"></div>
            <div class="dash-user-label">
              <span class="dash-user-status">Signed in</span>
              <span class="dash-user-device">this browser</span>
            </div>
          </div>
          <button class="dash-signout" id="dashLogout">Sign out</button>
        </div>
      </aside>
    </div><!-- /.dwrap -->

    <!-- Main content area -->
    <main class="dash-main">

      <!-- Top bar -->
      <header class="dash-topbar">
        <button class="dash-ham" id="dashHam" aria-label="Open navigation" title="Navigation">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="2" y1="4" x2="14" y2="4"/><line x1="2" y1="8" x2="14" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>
        </button>

        <div class="dash-topbar-title">
          <h1 class="dash-header-title">Accounts<small>The logins the agent works through — one per platform</small></h1>
        </div>

        <div class="dash-topbar-right">
          <div class="dash-kpis dash-header-stats" id="dashHeaderStats">
            <div class="dash-kpi dash-stat dash-stat--connected">
              <b id="dashKpiOk">0</b>
              <span>Connected</span>
            </div>
            <div class="dash-kpi dash-stat dash-stat--signin">
              <b id="dashKpiOff">0</b>
              <span>Sign in</span>
            </div>
            <div class="dash-kpi dash-stat dash-stat--live">
              <b id="dashKpiLive">0</b>
              <span>Live</span>
            </div>
          </div>
          <button class="dash-theme" id="dashTheme" title="Toggle theme" aria-label="Toggle light/dark theme">
            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="5"/><path d="M8 3V1M8 15v-2M3 8H1M15 8h-2M4.9 4.9l-1.4-1.4M12.5 12.5l-1.4-1.4M4.9 11.1l-1.4 1.4M12.5 3.5l-1.4 1.4"/></svg>
          </button>
        </div>
      </header>

      <!-- View container -->
      <div class="dash-body" id="dashBody">

        <!-- ── Accounts view (default) ──────────────────────────────────────────────── -->
        <div class="dash-view dash-view--accounts" id="dashAccountsView">

          <!-- Status banner (shown when accounts need sign-in) -->
          <div class="dash-banner hide" id="dashBanner">
            <div class="dash-banner-icon">
              <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="7"/><line x1="8" y1="5" x2="8" y2="8"/><circle cx="8" cy="11" r=".5" fill="currentColor" stroke="none"/></svg>
            </div>
            <div class="dash-banner-body">
              <div class="dash-banner-title" id="dashBannerT"></div>
              <div class="dash-banner-sub" id="dashBannerS"></div>
            </div>
            <button class="btn sm" id="dashBannerBtn">Sign in now</button>
          </div>

          <!-- Platforms grid -->
          <section class="dash-section">
            <div class="dash-section-hd">
              <h2>Your platforms</h2>
              <p>Click a platform to open its profile and sign in</p>
            </div>
            <div class="dash-platforms-grid" id="dashPlatformsGrid"></div>
          </section>

          
          <!-- Reply desk: notifications -> drafts -> approval -->
          <section class="dash-section">
            <div id="replyDesk"></div>
          </section>

<!-- Live activity rail -->
          <section class="dash-section">
            <div class="dash-section-hd">
              <h2>Live now</h2>
              <p>What the agent is doing right now — open it to watch or step in</p>
            </div>
            <div class="dash-live-rail" id="dashRail"></div>
          </section>

          <p class="dash-note">Each account is its own isolated profile — its own saved cookies, its own pinned exit route.</p>
        </div>

        <!-- ── Settings view ────────────────────────────────────────────────────────── -->
        <div class="dash-view dash-view--settings hide" id="dashSettingsView">

          <!-- Agent & AI -->
          <div class="scard" id="ep-agent-settings">
            <h3><span class="si" aria-hidden="true">🧠</span>Agent &amp; AI</h3>
            <p class="desc">The model the agent thinks with, and whether it may act on its own. Keys are stored on this browser's own volume and never sent back out — only the last characters are shown.</p>
            <div class="srow">
              <div class="sgrow">
                <label class="slabel">Host</label>
                <input class="sin" id="setHost" placeholder="https://ollama.com">
              </div>
            </div>
            <div class="srow">
              <div class="sgrow">
                <label class="slabel">Model <span class="sstate" id="setModelState"></span></label>
                <div style="display:flex;gap:7px">
                  <select class="sin" id="setModelSel" style="flex:1"></select>
                  <button class="sbtn" id="setModelList" title="Ask the host what it has">List</button>
                </div>
                <input class="sin hide" id="setModel" placeholder="model name" style="margin-top:7px">
              </div>
            </div>
            <div class="srow">
              <div class="sgrow">
                <label class="slabel">API key <span class="sstate" id="setKeyState"></span></label>
                <input class="sin" id="setKey" type="password" placeholder="paste a key to set or replace it">
              </div>
            </div>
            <div class="srow">
              <div class="sgrow">
                <label class="slabel">Backup API key <span class="sstate" id="setKey2State"></span></label>
                <input class="sin" id="setKey2" type="password" placeholder="a second account, used when the first runs out">
              </div>
            </div>
            <label class="stog" style="margin-top:4px">
              <input type="checkbox" id="setAuto">
              <span>
                <span class="tt">Let it act without asking me</span>
                <span class="ts">Off, it drafts every comment, join and follow and waits for you. On, it sends them itself — under your name, with no undo. Leave it off until you trust what it writes.</span>
              </span>
            </label>
            <div class="srow" style="margin-top:14px">
              <div style="flex:0 1 170px">
                <label class="slabel">Stop after this many steps</label>
                <input class="sin" id="setSteps" type="number" min="5" max="300">
              </div>
            </div>
            <div class="srow" style="margin-top:2px">
              <button class="sbtn" id="setTest">Test</button>
              <button class="sbtn primary" id="setSave">Save</button>
            </div>
            <div class="smsg" id="setAgentMsg"></div>
          </div>

          <!-- External MCP clients -->
          <div class="scard" id="mcpSetup">
            <h3><span class="si" aria-hidden="true">🔌</span>MCP for Codex &amp; Claude Code</h3>
            <p class="desc">Connect an external coding agent to this browser's internet tools. It uses this browser's API key, which grants full browser access. Only add it to a client you trust.</p>
            <label class="slabel" for="mcpEndpoint">Server URL</label>
            <div class="srow">
              <input class="sin mcp-url" id="mcpEndpoint" readonly>
              <button class="sbtn" id="mcpCopyUrl" type="button">Copy URL</button>
            </div>
            <p class="mcp-hint">Your key is under Account → Reveal API key below. Keep it out of project files and commits.</p>
            <div class="mcp-client">
              <h4>Codex</h4>
              <p>Set <code>GHOST_BROWSER_KEY</code> in the environment that starts Codex. Add this to <code>~/.codex/config.toml</code>, then start a new session.</p>
              <pre id="mcpCodexConfig" class="mcp-code"></pre>
              <button class="sbtn" id="mcpCopyCodex" type="button">Copy Codex config</button>
            </div>
            <div class="mcp-client">
              <h4>Claude Code</h4>
              <p>Replace <code>YOUR_GHOST_BROWSER_KEY</code> with your key and run this command in a private terminal.</p>
              <pre id="mcpClaudeCommand" class="mcp-code"></pre>
              <button class="sbtn" id="mcpCopyClaude" type="button">Copy Claude command</button>
            </div>
            <div class="smsg" id="mcpSetupMsg" role="status" aria-live="polite"></div>
          </div>

          <!-- Account -->
          <div class="scard">
            <h3><span class="si" aria-hidden="true">🔑</span>Account</h3>
            <p class="desc">This browser's API key grants everything the console can do — it is also the way back in if the password is lost. Keep it somewhere safe.</p>
            <div class="srow">
              <button class="sbtn" id="setKeyReveal">Reveal API key</button>
              <button class="sbtn hide" id="setKeyCopy">Copy</button>
            </div>
            <div class="skey hide" id="setKeyBox"></div>
            <hr class="shr">
            <p class="desc">A password for this Ghost Browser, so you can sign in with your email even when the platform hand-off fails — on any device, in the app or here.</p>
            <div class="srow">
              <input id="setPw" type="password" autocomplete="new-password" placeholder="New password (10+ characters)" class="gate-recovery-input">
              <button class="sbtn" id="setPwGo">Set password</button>
            </div>
            <div class="smsg" id="setPwMsg"></div>
            <hr class="shr">
            <div class="srow"><button class="sbtn bad" id="setLogout">Sign out</button></div>
          </div>

          <!-- Per-login exit route -->
          <div class="scard">
            <h3><span class="si" aria-hidden="true">🗂️</span>Per-login exit route</h3>
            <p class="desc">Override the default for one login. "Always my tailnet" refuses to open while the tailnet is down rather than quietly leaving from this server. Applies on the login's next session.</p>
            <div id="setProfiles"><div class="sempty">Loading logins…</div></div>
            <div class="smsg" id="setProfMsg"></div>
          </div>

        </div><!-- /#dashSettingsView -->

        <!-- ── API reference view ───────────────────────────────────────────────────── -->
        <div class="dash-view dash-view--api apiv hide" id="dashApiView">

          <!-- Getting started -->
          <div class="scard" id="ep-start">
            <h3><span class="si">🚀</span>Getting started</h3>
            <p class="apilead">The Ghost Browser is a real logged-in browser behind an HTTP API. Everything the console does — open a session, drive a page, dispatch an autonomous agent job — is a call you can make yourself. This is the same interface the platform's master agent uses. All requests and responses are <b>JSON</b>.</p>

            <div class="apiquick">
              <a class="apichip" href="#ep-modes">Two ways to use it</a>
              <a class="apichip" href="#ep-model">Your own model</a>
              <a class="apichip" href="#ep-sessions">Sessions</a>
              <a class="apichip" href="#ep-jobs">Agent jobs</a>
              <a class="apichip" href="#ep-roles">Roles</a>
              <a class="apichip" href="#ep-profiles">Profiles</a>
              <a class="apichip" href="#ep-capacity">Capacity</a>
              <a class="apichip" href="#ep-master">How the master uses it</a>
            </div>

            <hr class="shr">
            <p class="apilead"><b>Base URL.</b> Every path is relative to the origin serving this console — for you right now that is <code id="apiBaseUrl">this origin</code>.</p>
            <p class="apilead"><b>Authentication.</b> Programs authenticate with a bearer key. Signed in here, you can read the key straight from the API — then send it on every call as an <code>Authorization: Bearer &lt;key&gt;</code> header.</p>

            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/api/auth/key</code><span class="ep-auth">cookie</span></div>
              <p class="ep-desc">Return this console's API key(s). Requires the signed-in cookie — a key cannot fetch itself. Every returned key grants everything the console can do.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -b cookies.txt https://your-ghost-host/api/auth/key</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{
  "keys": [
    { "key": "gb_live_9f3c2a71b0e4…", "plan": "solo", "maxConcurrent": 1 }
  ]
}</pre>
              </div>
            </div>

            <p class="apilead" style="margin-top:14px">Set these once in your shell and every example below just works:</p>
            <div class="codewrap">
              <div class="codebar"><span class="codelabel">Setup</span><button class="copybtn" type="button">Copy</button></div>
              <pre class="code">BASE="https://your-ghost-host"   # the origin serving this console
KEY="gb_live_9f3c2a71b0e4…"      # from GET /api/auth/key</pre>
            </div>
            <p class="apilead">A refusal comes back as an error object with the HTTP status — for example <code>{ "error": "not your session" }</code> with <code>403</code>.</p>
          </div>

          <!-- Two ways to use it -->
          <div class="scard" id="ep-modes">
            <h3 class="apihd"><span class="si">🧠</span>Two ways to use it</h3>
            <p class="apilead">There are two ways to run the Ghost Browser. Pick either — or start with the built-in agent and move to your own later. Both run on <b>your</b> model and your costs.</p>
            <p class="apilead"><b>1 · Your own LLM drives it (this reference).</b> Your agent runs on your side and is the <b>brain</b>. For every step it calls the API below — open a session, read the page as numbered boxes, click / type / act. The Ghost Browser is purely the <b>hands</b>. Start at <a class="epath" href="#ep-sessions">Sessions</a>, then loop: <code>analyze</code> → decide in your LLM → <code>click</code> / <code>type</code>.</p>
            <p class="apilead"><b>2 · The built-in agent, on your model.</b> Put your own LLM key in <b>Settings</b> and the agent that ships with the Ghost Browser decides <i>and</i> acts by itself. You write no code — you configure <a class="epath" href="#ep-roles">roles</a> and <a class="epath" href="#ep-jobs">agent jobs</a> / workflows as data.</p>
            <hr class="shr">
            <h4 class="apihd" id="ep-model"><span class="si">🔌</span>Bring your own model</h4>
            <p class="apilead">Add your own key so nothing runs on someone else's account. Supported: <b>OpenRouter</b> and your own <b>Ollama</b> endpoint. Set it in Settings → Agent &amp; AI.</p>
            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/agent/settings</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">The current agent configuration (provider + model), secrets redacted.</p>
            </div>
            <hr class="shr">
            <h4 class="apihd"><span class="si">🔑</span>Signing in</h4>
            <p class="apilead">Two ways in. <b>SSO from the platform</b> — sign in on the platform and open the Ghost Browser. And a <b>bearer key</b> for scripts — read it with <a class="epath" href="#ep-start">GET /api/auth/key</a> and send it as <code>Authorization: Bearer &lt;key&gt;</code> on every call.</p>
          </div>

          <!-- Sessions -->
          <div class="scard" id="ep-sessions">
            <h3 class="apihd"><span class="si">🪟</span>Sessions</h3>
            <p class="apilead">A session is one browser tab you hold. Open one, point it at a URL, read the page as numbered boxes (Set-of-Mark), and act on a box by its number.</p>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/sessions</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Open a session. With <code>reuse</code> you get the one you already hold instead of an error; with <code>profile</code> it opens a named, cookie-keeping login.</p>
              <table class="ptable"><thead><tr><th>Field</th><th>Type</th><th>Req</th><th>Description</th></tr></thead>
                <tbody>
                  <tr><td class="nm">profile</td><td class="ty">string</td><td class="opt">no</td><td>Named profile to open — its own saved cookies and pinned exit route.</td></tr>
                  <tr><td class="nm">preset</td><td class="ty">string</td><td class="opt">no</td><td>A known site preset; configures the profile and lands on its login page.</td></tr>
                  <tr><td class="nm">reuse</td><td class="ty">boolean</td><td class="opt">no</td><td>Return an existing session rather than failing at the limit.</td></tr>
                  <tr><td class="nm">takeover</td><td class="ty">boolean</td><td class="opt">no</td><td>Close a conflicting session of yours and open here instead.</td></tr>
                </tbody>
              </table>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -X POST "$BASE/v1/sessions" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "profile": "facebook", "reuse": true }'</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 201</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{
  "sessionId": "a1b2c3d4",
  "profile": "facebook",
  "expiresAt": 1756497200000,
  "plan": "solo",
  "reused": false
}</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/sessions</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">List the sessions you hold, with your concurrency limit and plan.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl "$BASE/v1/sessions" -H "Authorization: Bearer $KEY"</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method delete">DELETE</span><code class="epath">/v1/sessions</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Close every session you hold. Close a single one with <code>DELETE /v1/sessions/:id</code>.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "closed": 1 }</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/sessions/:id/navigate</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Go to a URL. Only public addresses are allowed.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -X POST "$BASE/v1/sessions/a1b2c3d4/navigate" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.facebook.com" }'</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "url": "https://www.facebook.com/", "status": 200, "title": "Facebook" }</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/sessions/:id/analyze</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">The page as numbered, clickable boxes — Set-of-Mark. Each element has an <code>index</code> you then click or type into.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl "$BASE/v1/sessions/a1b2c3d4/analyze?screenshot=false" \
  -H "Authorization: Bearer $KEY"</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{
  "url": "https://www.facebook.com/",
  "title": "Facebook",
  "elementCount": 42,
  "elements": [
    { "index": 1, "tag": "input", "type": "text", "editable": true,
      "placeholder": "Email or phone number", "text": "", "x": 512, "y": 288 },
    { "index": 2, "tag": "button", "role": "button", "text": "Log in", "x": 512, "y": 360 }
  ],
  "summary": "[1] ✎ FIELD[text] — \"Email or phone number\" …\n[2] button — \"Log in\" @ (512,360)",
  "screenshot": null
}</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/sessions/:id/click</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Click a box by its <code>index</code> from the last analyze, or by visible <code>text</code>.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -X POST "$BASE/v1/sessions/a1b2c3d4/click" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "index": 2 }'</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "clicked": { "index": 2, "text": "Log in" }, "url": "https://www.facebook.com/" }</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/sessions/:id/type</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Type into a field box, human-paced. It re-reads the page if it moved (so the number still names the right field), scrolls the field on-screen first, and clears it before typing. For a <b>rich / markdown / React editor</b>, pass <code>paste: true</code>: it inserts the whole block so the editor actually registers it &mdash; plain typing into such a box lands in a hidden backing field and silently does nothing (this is what made a Bugcrowd biography or a GitHub comment look filled but submit empty). <code>submit: true</code> presses Enter after.</p>
              <table class="ptable"><thead><tr><th>Field</th><th>Type</th><th>Req</th><th>Description</th></tr></thead>
                <tbody>
                  <tr><td class="nm">index</td><td class="ty">number</td><td class="req">yes</td><td>The field&rsquo;s number from the last analyze.</td></tr>
                  <tr><td class="nm">text</td><td class="ty">string</td><td class="req">yes</td><td>What to type.</td></tr>
                  <tr><td class="nm">paste</td><td class="ty">boolean</td><td class="req">no</td><td>Insert the whole block at once, firing the input events a rich / markdown / contenteditable editor needs. Use for any editor that ignores plain typing.</td></tr>
                  <tr><td class="nm">clear</td><td class="ty">boolean</td><td class="req">no</td><td>Select and replace the field&rsquo;s existing content first. Default true.</td></tr>
                  <tr><td class="nm">submit</td><td class="ty">boolean</td><td class="req">no</td><td>Press Enter after typing.</td></tr>
                </tbody>
              </table>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request body &mdash; rich editor</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "index": 14, "text": "Security researcher focused on web and API security...", "paste": true }</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "typed": 371, "into": "biography", "mode": "paste", "url": "https://bugcrowd.com/h/onboarding" }</pre>
              </div>
            </div>
          </div>

          <!-- Agent jobs -->
          <div class="scard" id="ep-jobs">
            <h3 class="apihd"><span class="si">🤖</span>Agent jobs</h3>
            <p class="apilead">The important one. A job is "go and do this in the browser I am already logged into". You dispatch it with a <b>goal</b> and a <b>role</b> against an open session, then poll it.</p>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/agent/jobs</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Dispatch a job. It runs in the session you name (open one first). The loop outlives the request — this returns immediately with the job to poll.</p>
              <table class="ptable"><thead><tr><th>Field</th><th>Type</th><th>Req</th><th>Description</th></tr></thead>
                <tbody>
                  <tr><td class="nm">goal</td><td class="ty">string</td><td class="req">yes</td><td>Plain-language description of what to do.</td></tr>
                  <tr><td class="nm">sessionId</td><td class="ty">string</td><td class="req">yes</td><td>The open session to work in.</td></tr>
                  <tr><td class="nm">role</td><td class="ty">string</td><td class="opt">no</td><td>The specialist to run as. Defaults to <code>general</code>.</td></tr>
                  <tr><td class="nm">autoApprove</td><td class="ty">boolean</td><td class="opt">no</td><td>Run this job with acting-without-asking on.</td></tr>
                  <tr><td class="nm">unattended</td><td class="ty">boolean</td><td class="opt">no</td><td>Nobody is watching: conclude and hand the session back.</td></tr>
                </tbody>
              </table>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -X POST "$BASE/v1/agent/jobs" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "sessionId": "a1b2c3d4",
        "role": "reddit.demand",
        "goal": "Find people in r/smallbusiness asking how to invoice EU clients"
      }'</pre>
              </div>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{
  "jobId": "k7f2a9",
  "status": "running",
  "createdAt": 1756490500000,
  "steps": [],
  "leads": [],
  "proposals": []
}</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/agent/jobs/:id</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Poll one job — <code>status</code>, <code>steps</code>, <code>leads</code>, and any <code>proposals</code> waiting on you.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl "$BASE/v1/agent/jobs/k7f2a9" -H "Authorization: Bearer $KEY"</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/agent/jobs/:id/proposals/:pid</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Approve or reject one proposal. <code>edit</code> rewrites the text before it is sent.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -X POST "$BASE/v1/agent/jobs/k7f2a9/proposals/p-1-9f3c" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "approve": true, "edit": "We built exactly this — happy to show you." }'</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/agent/jobs/:id/say</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Say something to a running job — steer it, or nudge an idle one back to work.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request body</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "text": "Only save people who mention VAT specifically." }</pre>
              </div>
            </div>

            <div class="endpoint">
              <div class="ep-head"><span class="method post">POST</span><code class="epath">/v1/agent/jobs/:id/stop</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Stop a job. Any pending proposals are skipped and the session is handed back.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Request · curl</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">curl -X POST "$BASE/v1/agent/jobs/k7f2a9/stop" -H "Authorization: Bearer $KEY"</pre>
              </div>
            </div>
          </div>

          <!-- Roles -->
          <div class="scard" id="ep-roles">
            <h3 class="apihd"><span class="si">🎭</span>Roles</h3>
            <p class="apilead">The specialists a job can run as. A role fixes the tools and the prompt server-side — a scout cannot post, a poster cannot invent leads.</p>
            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/agent/roles</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">Every role, grouped for display. Use <code>name</code> as the <code>role</code> when dispatching a job.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{
  "roles": [
    { "name": "general", "label": "General", "description": "Everything in reach, no site assumed.", "site": null, "group": "Anything" },
    { "name": "facebook.scout", "label": "Facebook · Lead scout", "description": "Reads Facebook groups for people who need what you sell. Cannot post.", "site": "facebook", "group": "Facebook" }
  ]
}</pre>
              </div>
            </div>
          </div>

          <!-- Profiles -->
          <div class="scard" id="ep-profiles">
            <h3 class="apihd"><span class="si">🗂️</span>Profiles</h3>
            <p class="apilead">The saved logins on disk — one isolated profile per platform. Open one by passing its name to <code>POST /v1/sessions</code>.</p>
            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/profiles</code><span class="ep-auth">bearer</span></div>
              <p class="ep-desc">The profile names that exist right now.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{ "profiles": ["facebook", "linkedin", "reddit"] }</pre>
              </div>
            </div>
          </div>

          <!-- Capacity -->
          <div class="scard" id="ep-capacity">
            <h3 class="apihd"><span class="si">📊</span>Capacity &amp; health</h3>
            <p class="apilead">Two open endpoints — no key needed.</p>
            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/v1/capacity</code><span class="ep-auth open">open</span></div>
              <p class="ep-desc">How full the worker is. <code>accepting: false</code> means open a session later.</p>
              <div class="codewrap">
                <div class="codebar"><span class="codelabel">Response 200</span><button class="copybtn" type="button">Copy</button></div>
                <pre class="code">{
  "sessions": 2, "maxSessions": 8, "memoryPct": 41,
  "draining": false, "accepting": true
}</pre>
              </div>
            </div>
            <div class="endpoint">
              <div class="ep-head"><span class="method get">GET</span><code class="epath">/healthz</code><span class="ep-auth open">open</span></div>
              <p class="ep-desc">Liveness. Always <code>{ "ok": true }</code> while the process is up.</p>
            </div>
          </div>

          <!-- How the master uses it -->
          <div class="scard" id="ep-master">
            <h3 class="apihd"><span class="si">🧭</span>How the master uses it</h3>
            <p class="apilead">The whole loop the platform's master agent runs, end to end. Five calls: get a session, dispatch a job, poll it, approve what it drafts, read the structured result.</p>

            <div class="apistep"><div class="num">1</div><div class="tx"><b>Open or reuse a session.</b> The master wants <em>a</em> session, not specifically a new one, so it passes <code>reuse</code>.</div></div>
            <div class="codewrap">
              <div class="codebar"><span class="codelabel">1 · session</span><button class="copybtn" type="button">Copy</button></div>
              <pre class="code">SID=$(curl -s -X POST "$BASE/v1/sessions" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "profile": "linkedin", "reuse": true }' | jq -r .sessionId)</pre>
            </div>

            <div class="apistep"><div class="num">2</div><div class="tx"><b>Dispatch the job</b> with a role and a goal. Returns a <code>jobId</code> at once.</div></div>
            <div class="codewrap">
              <div class="codebar"><span class="codelabel">2 · dispatch</span><button class="copybtn" type="button">Copy</button></div>
              <pre class="code">JOB=$(curl -s -X POST "$BASE/v1/agent/jobs" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d "{ \"sessionId\": \"$SID\", \"role\": \"linkedin.buyers\",
        \"goal\": \"Find heads of finance at 20-100 person agencies\" }" | jq -r .jobId)</pre>
            </div>

            <div class="apistep"><div class="num">3</div><div class="tx"><b>Poll until it settles.</b> Keep going while <code>status</code> is <code>running</code>.</div></div>
            <div class="codewrap">
              <div class="codebar"><span class="codelabel">3 · poll</span><button class="copybtn" type="button">Copy</button></div>
              <pre class="code">while : ; do
  J=$(curl -s "$BASE/v1/agent/jobs/$JOB" -H "Authorization: Bearer $KEY")
  echo "$J" | jq -r .status
  [ "$(echo "$J" | jq -r .status)" = "running" ] || break
  sleep 3
done</pre>
            </div>

            <div class="apistep"><div class="num">4</div><div class="tx"><b>Approve what it drafts.</b> Pending proposals arrive with <code>state: "pending"</code>.</div></div>
            <div class="codewrap">
              <div class="codebar"><span class="codelabel">4 · approve</span><button class="copybtn" type="button">Copy</button></div>
              <pre class="code">curl -s -X POST "$BASE/v1/agent/jobs/$JOB/proposals/$PID" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "approve": true }'</pre>
            </div>

            <div class="apistep"><div class="num">5</div><div class="tx"><b>Read the structured result.</b> When <code>status</code> is <code>done</code>, the deliverable is in <code>leads</code>.</div></div>
            <div class="codewrap">
              <div class="codebar"><span class="codelabel">5 · results</span><button class="copybtn" type="button">Copy</button></div>
              <pre class="code">curl -s "$BASE/v1/agent/jobs/$JOB" -H "Authorization: Bearer $KEY" \
  | jq '.leads[] | { name, why, quote, url }'</pre>
            </div>
          </div>

        </div><!-- /#dashApiView -->

        <!-- ── Roles view — filled by marketplace.js ──────────────────────────────── -->
        <div class="dash-view dash-view--roles rolesv hide" id="dashRolesView"></div>

        <!-- ── Automation view — filled by automation.js ──────────────────────────── -->
        <div class="dash-view dash-view--automation autov hide" id="dashAutoView"></div>

        <!-- ── Files view — filled by files.js ──────────────────────────────────── -->
        <div class="dash-view dash-view--files filesv hide" id="dashFilesView"></div>

        <!-- ── Platforms view — filled by platforms.js ──────────────────────────── -->
        <div class="dash-view dash-view--platforms platformsv hide" id="dashPlatformsView"></div>

        <!-- ── Superadmin view ────────────────────────────────────────────────────── -->
        <div class="dash-view dash-view--admin adminv hide" id="dashAdminView">
          <div class="admin-tabs">
            <button class="admin-tab on" data-atab="users" type="button">Users</button>
            <button class="admin-tab" data-atab="flows" type="button">Flows</button>
            <button class="admin-tab" data-atab="roles" type="button">Roles</button>
            <button class="admin-refresh" id="adminRefresh" type="button">↻ Refresh</button>
          </div>
          <div class="admin-body" id="adminBody"><p class="admin-empty">Loading…</p></div>
        </div>

      </div><!-- /#dashBody -->
    </main>
  </div><!-- /.dash-layout -->
</div><!-- /#dash -->

<!-- Click-a-card detail / sign-in modal -->
<div class="dmodal" id="dashModal">
  <div class="mcard">
    <div class="mhd">
      <div class="logo" id="dmLogo"></div>
      <div><div class="t" id="dmTitle"></div><div class="s" id="dmSub"></div></div>
      <button class="x" id="dmClose">✕</button>
    </div>
    <div class="mbody" id="dmBody"></div>
    <div class="mfoot" id="dmFoot">
      <button class="btn" id="addCreate">Create</button>
    </div>
  </div>
</div>


<!-- ═══ APP — browser console, shown after opening a session ═════════════════════════════════════
     Three zones: header toolbar, URL bar, and main (canvas + side panel). The side panel is a
     bottom sheet on narrow screens, dragged by the grip strip. -->
<div id="app" class="hide">

  <header>
    <button id="dashBack" title="Back to Accounts">
      <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><polyline points="10,3 5,8 10,13"/></svg>
      <span>Accounts</span>
    </button>
    <div class="brand">Ghost<span>Browser</span></div>
    <select id="profile" title="A named profile keeps its cookies between runs"></select>
    <span class="browserlabel" title="One browser, signed into every site">
      <span class="dotb"></span>Browser
    </span>
    <button id="open" class="go">Open</button>
    <button id="close" disabled>Close</button>
    <div class="overflow">
      <button id="moreBtn" class="iconbtn" aria-haspopup="true" aria-label="More actions" title="More actions">⋯</button>
      <div id="hdrMenu">
        <button id="closeAll">Close all mine</button>
        <button id="showKey">API key</button>
        <button id="exitBtn" title="Where this login's traffic leaves from">Exit route…</button>
        <button id="logout">Sign out</button>
      </div>
    </div>
    <div class="cap" id="exitState" style="margin-left:auto;cursor:pointer" title="Where the login actually exits — click to change it">exit: …</div>
    <div class="cap" id="cap" style="margin-left:12px">…</div>
  </header>

  <div class="bar">
    <input class="url" id="url" placeholder="https://…" value="https://www.facebook.com">
    <button id="go" disabled>Go</button>
    <span title="Live stream"><span class="dot" id="wsdot"></span></span>
    <button id="esc" disabled title="Send Escape — closes a dialog or dropdown. It cannot cancel a passkey prompt; use &quot;no passkey&quot; for that.">Esc</button>
    <label style="font-size:12px;color:var(--muted);display:flex;align-items:center;gap:5px"
           title="Refuse passkey prompts so a site offers a code instead">
      <input type="checkbox" id="noPasskey" style="width:auto"> no passkey
    </label>
    <button id="kbdBtn" class="kbdbtn" title="Show or hide the keyboard">⌨</button>
    <button id="marks" disabled title="Show the numbered boxes the agent works from">Agent view</button>
  </div>

  <!-- Exit / Tailscale panel -->
  <div id="exitPanel" class="hide" style="padding:12px 14px;border-bottom:1px solid var(--line);background:var(--panel)">
    <div style="display:flex;align-items:center;gap:9px;flex-wrap:wrap">
      <b style="font-size:12.5px">Exit through your own device</b>
      <span id="tsState" style="font:11px ui-monospace,monospace;color:var(--faint)">checking…</span>
      <span style="margin-left:auto"></span>
      <input id="tsKey" placeholder="Tailscale auth key (optional)" style="width:230px;font-size:12px">
      <button id="tsUp" class="go">Connect</button>
      <button id="tsDown">Disconnect</button>
    </div>
    <label style="display:flex;align-items:center;gap:8px;margin-top:10px;font-size:12.5px;cursor:pointer">
      <input type="checkbox" id="tsAll" style="width:auto;margin:0">
      <span>Send <b>every</b> login through the exit node</span>
    </label>
    <p style="font-size:11px;color:var(--faint);margin:5px 0 0;max-width:80ch">
      On, this is what every login does unless it says otherwise in its own Setup. A login that
      <i>insists</i> on the tailnet refuses to open while the tailnet is down, rather than quietly
      leaving from this server — which is the failure nobody notices until an account is locked.
    </p>
    <div id="tsLogin" class="hide" style="margin-top:9px;font-size:12px;color:var(--muted)"></div>
    <div id="tsNodes" style="margin-top:9px"></div>
    <p style="font-size:11px;color:var(--faint);margin:9px 0 0;max-width:80ch">
      Your phone must have <b>"Use as exit node"</b> switched on in its own Tailscale settings before
      it appears here. Android supports it; iOS does not offer it at all.
    </p>
  </div>

  <main>
    <div class="stage">
      <div class="empty" id="empty">
        Open a session and go somewhere. This is a <b>live browser</b> — click, type, scroll and use
        the page exactly as you would in a normal tab. Pick a named profile first and whatever you
        log into stays logged in for the agent.
      </div>
      <canvas id="view" tabindex="0" width="1280" height="800" hidden></canvas>
      <!-- Keyboard catcher: a real input so phones raise their keyboard -->
      <input id="kbd" value="&#8203;" autocapitalize="off" autocorrect="off"
             autocomplete="off" spellcheck="false" aria-hidden="true">
      <div class="hintbar" id="hint" hidden>tap the page, then type</div>
    </div>

    <aside id="panel">
      <!-- Grip — drag handle on narrow screens -->
      <div class="grip" id="grip">
        <span class="who" id="gripWho">Agent</span>
        <span class="st" id="gripState"></span>
      </div>

      <div class="tabs">
        <button class="tab on" data-tab="agent">Agent<span id="askBadge" class="badge hide">0</span></button>
        <button class="tab" data-tab="els">Elements</button>
        <button class="tab" data-tab="log">Log</button>
      </div>

      <section id="tab-agent">
        <div class="chathead">
          <select id="roleSel" title="What kind of work this conversation is"></select>
          <div class="chathead-actions">
            <button id="newChat" class="chatact" title="Start a fresh conversation">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
              New
            </button>
            <button id="history" class="chatact" title="Earlier conversations">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v5h5"/><path d="M3.05 13A9 9 0 1 0 6 5.3L3 8"/><path d="M12 7v5l3 2"/></svg>
              History
            </button>
            <button id="stopJob" class="chatact danger" disabled title="Stop what it is doing">
              <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
              Stop
            </button>
          </div>
          <button id="agentCfg" class="hide" tabindex="-1" aria-hidden="true" title="Setup"></button>
          <span class="cap hide" id="modelState">checking…</span>
        </div>

        <div class="roleline" id="roleLine"></div>

        <div id="lfBanner" class="ev lead hidden hide"></div>

        <div class="feed" id="feed"></div>

        <div class="leadbar" id="leadRow" hidden>
          <span class="cap" id="leadCount"></span>
          <a id="csv" download>Download leads.csv</a>
        </div>

        <div id="composer">
          <div class="composer-box">
            <textarea id="sayText" rows="1" placeholder="Ask it to do something. e.g. Search Facebook groups for homeowners asking about a leaking roof, read the posts and save the good ones."></textarea>
            <button id="sayBtn" class="go" title="Send">Send</button>
          </div>
          <span class="cap" id="convState"></span>
        </div>
      </section>

      <section id="tab-els" hidden>
        <h2>Elements the agent sees</h2>
        <ol id="els"></ol>
      </section>

      <section id="tab-log" hidden>
        <h2>Log</h2>
        <div class="log" id="log"></div>
        <div id="keyBox" class="kv hide" style="margin-top:10px"></div>
      </section>
    </aside>
  </main>

  <!-- Earlier conversations modal -->
  <div id="histModal" class="modal hide">
    <div>
      <h3>Earlier conversations</h3>
      <p class="cap" style="margin:0 0 12px">Pick one to reopen it in the chat.</p>
      <div id="histList" class="hide"></div>
      <div class="foot">
        <button id="histClose" class="go">Close</button>
      </div>
    </div>
  </div>
</div>


<!-- ═══ CONFIG MODAL — model and profile settings ═══════════════════════════════════════════════ -->
<div id="cfgModal" class="modal hide">
  <div>
    <h3>The model the agent thinks with</h3>
    <p class="cap">Ollama Cloud, or your own Ollama. The key is stored on this browser's own volume and never leaves it.</p>
    <label>Host</label>
    <input id="cfgHost" placeholder="https://ollama.com">
    <label>Model <span id="cfgModels" class="cap"></span></label>
    <div style="display:flex;gap:6px">
      <select id="cfgModelSel" style="flex:1"></select>
      <button id="cfgRefresh" style="flex:0 0 auto" title="Ask the host what it has">List</button>
    </div>
    <input id="cfgModel" class="hide" placeholder="model name" style="margin-top:6px">
    <label>API key <span id="cfgKeyState" class="cap"></span></label>
    <input id="cfgKey" type="password" placeholder="paste a key to set or replace it">
    <label>Backup API key <span id="cfgKey2State" class="cap"></span></label>
    <input id="cfgKey2" type="password" placeholder="a second account, used when the first runs out">
    <div class="cap" style="margin:-2px 0 8px">Only used when the first key reports its allowance is spent.</div>
    <label style="display:flex;align-items:center;gap:7px;margin-top:12px">
      <input type="checkbox" id="cfgAuto" style="width:auto">
      <span>Let it act without asking me</span>
    </label>
    <p class="cap" style="margin:4px 0 0">Off, it drafts every comment and waits for you. On, it sends them itself — under your name, with no undo.</p>
    <label>Stop after this many steps</label>
    <input id="cfgSteps" type="number" min="5" max="300">
    <div class="foot">
      <button id="cfgTest">Test</button>
      <button id="cfgSave" class="go">Save</button>
      <button id="cfgClose">Close</button>
    </div>
    <div class="msg" id="cfgMsg"></div>
    <hr style="border:0;border-top:1px solid var(--line);margin:18px 0 4px">
    <label>How you write</label>
    <p class="cap" style="margin:0 0 8px">Reading your own posts is what makes what it writes sound like you.</p>
    <div class="row">
      <button id="studyMe">Study me</button>
      <button id="showMe">What it knows</button>
    </div>
    <label>This login <span id="siteWho2" class="cap"></span></label>
    <p class="cap" style="margin:0 0 8px">Which site it is signed in to, so the agent picks the right account.</p>
    <div class="row" id="siteRow2">
      <input id="siteVal" placeholder="facebook.com" style="max-width:150px">
      <input id="siteNote" placeholder="personal / business">
      <select id="siteAs" style="max-width:120px">
        <option value="">real (Linux)</option>
        <option value="windows">say Windows</option>
        <option value="mac">say macOS</option>
      </select>
      <select id="siteExit" style="max-width:170px" title="Where this login's traffic leaves from">
        <option value="">exit: follow the default</option>
        <option value="tailscale">always my tailnet</option>
        <option value="direct">always this server</option>
      </select>
      <button id="siteSave">Save</button>
    </div>
    <p class="cap" style="margin:6px 0 0">
      <a href="#" id="siteForget" style="color:var(--bad)">Delete this login</a> — its cookies and everything signed into it. Close the session first.
    </p>
  </div>
</div>


<!-- ═══ ME MODAL — what the agent knows about you ══════════════════════════════════════════════ -->
<div id="meModal" class="modal hide">
  <div>
    <h3>What the agent knows</h3>
    <p class="cap">Everything it has learned, on this browser's own volume, sent only to the model you configured.</p>
    <label>Where leads have come from <span id="pbCount" class="cap"></span></label>
    <div id="pbList" class="pb"></div>
    <p class="cap" style="margin:6px 0 0">Counted from real runs: how many posts each place returned and how many became leads.</p>
    <div class="row" style="margin-top:8px"><button id="pbForget">Forget where leads are</button></div>
    <hr style="border:0;border-top:1px solid var(--line);margin:16px 0 4px">
    <label style="font-size:13px;color:var(--text);font-weight:600">How you write</label>
    <p class="cap">Read from your own posts and replies. This is what makes a reply sound like you.</p>
    <label>Name it writes as</label><input id="meName">
    <label>How you write <span class="cap">- its own summary, edit freely</span></label>
    <textarea id="meStyle" rows="4"></textarea>
    <label>Facts</label><div id="meFacts" class="kv"></div>
    <label>Things you actually wrote <span class="cap" id="meCount"></span></label>
    <div id="meSamples" style="display:flex;flex-direction:column;gap:5px"></div>
    <div class="foot">
      <button id="meSave" class="go">Save</button>
      <button id="meForget">Forget everything</button>
      <button id="meClose">Close</button>
    </div>
    <div class="msg" id="meMsg"></div>
  </div>
</div>


<script src="js/console.js"></script>
<script src="js/dashboard.js"></script>
<script src="js/mcp-setup.js"></script>
<script src="js/marketplace.js"></script>
<script src="js/automation.js"></script>
<script src="js/files.js"></script>
<script src="js/platforms.js"></script>
</body>
</html>