app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
master
John Lauer
Close out wiki discussion 557 (startup hang after an ab cycle with orphaned Chrome windows): timeout-cap every hangable…
9b11632
3mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pup — Adom Bridge</title>
<style>
@font-face {
font-family: 'Familjen Grotesk';
src: url('https://adom.inc/fonts/familjen-grotesk/familjen-grotesk-latin-700-normal.woff2') format('woff2');
font-weight: 700; font-display: swap;
}
@font-face {
font-family: 'Familjen Grotesk';
src: url('https://adom.inc/fonts/familjen-grotesk/familjen-grotesk-latin-500-normal.woff2') format('woff2');
font-weight: 500; font-display: swap;
}
@font-face {
font-family: 'Satoshi';
src: url('https://adom.inc/fonts/satoshi/satoshi-400-normal.woff2') format('woff2');
font-weight: 400; font-display: swap;
}
@font-face {
font-family: 'Satoshi';
src: url('https://adom.inc/fonts/satoshi/satoshi-500-normal.woff2') format('woff2');
font-weight: 500; font-display: swap;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2') format('woff2');
font-weight: 400; font-display: swap;
}
:root {
--bg: #0d1117;
--surface: #161b22;
--elevated: #1c2128;
--overlay: #21262d;
--border: #30363d;
--border-muted: #21262d;
--text: #e6edf3;
--text2: #8b949e;
--text3: #484f58;
--accent: #00b8b0;
--accent-bright: #00e6dc;
--accent-muted: rgba(0, 184, 176, 0.12);
--accent-border: rgba(0, 184, 176, 0.25);
--purple: #8C6BF7;
--purple-light: #C5B3FF;
--blue: #0061EF;
--blue-light: #64ABFF;
--teal-light: #8FD3D4;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
* { transition: background 0.15s, color 0.15s, border-color 0.15s, opacity 0.15s; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Satoshi', sans-serif;
font-weight: 400;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
overflow-x: hidden;
}
h1, h2, h3, .title { font-family: 'Familjen Grotesk', sans-serif; font-weight: 700; }
code, pre, .mono { font-family: 'JetBrains Mono', monospace; }
/* ── Gradient accent bar at top ── */
.accent-bar {
width: 100%;
height: 3px;
background: linear-gradient(90deg, var(--blue) 0%, var(--accent) 40%, var(--purple) 100%);
}
/* ── Main content ── */
.container {
max-width: 820px;
width: 100%;
padding: 40px 40px 32px;
flex: 1;
display: flex;
flex-direction: column;
gap: 24px;
}
/* ── Hero ── */
.hero {
display: flex;
align-items: center;
gap: 20px;
}
.hero-icon {
width: 52px;
height: 52px;
flex-shrink: 0;
}
.hero-icon svg { width: 100%; height: 100%; }
.hero-text h1 {
font-size: 30px;
letter-spacing: -0.5px;
line-height: 1.1;
}
.hero-text h1 .pup { color: var(--accent); }
.hero-text h1 .sep { color: var(--text3); font-weight: 500; margin: 0 6px; }
.hero-text h1 .sub { color: var(--text2); font-weight: 500; font-size: 22px; }
.hero-text .tagline {
margin-top: 4px;
color: var(--text2);
font-size: 14px;
line-height: 1.5;
}
/* ── Session identity card ── */
.session-card {
background: var(--accent-muted);
border: 1px solid var(--accent-border);
border-radius: 10px;
padding: 18px 22px;
display: none; /* shown by JS when params present */
}
.session-card.visible { display: block; }
.session-card .session-heading {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--accent);
font-weight: 600;
margin-bottom: 10px;
}
.session-card .session-grid {
display: grid;
grid-template-columns: auto 1fr;
gap: 6px 16px;
align-items: baseline;
}
.session-card .session-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.3px;
color: var(--text3);
font-weight: 600;
white-space: nowrap;
}
.session-card .session-value {
font-size: 13px;
color: var(--text);
font-family: 'JetBrains Mono', monospace;
word-break: break-all;
}
/* ── Pipeline diagram ── */
.pipeline {
display: flex;
align-items: center;
gap: 0;
padding: 18px 22px;
background: rgba(22,27,34,0.92);
backdrop-filter: blur(16px);
border: 1px solid rgba(0,184,177,0.2);
border-radius: 10px;
box-shadow: 0 12px 40px rgba(0,0,0,0.55);
overflow-x: auto;
}
.pipeline-node {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
flex-shrink: 0;
min-width: 90px;
}
.pipeline-node .icon {
width: 34px;
height: 34px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
}
.pipeline-node .label {
font-size: 10px;
font-weight: 500;
color: var(--text2);
text-align: center;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.pipeline-arrow {
color: var(--text3);
font-size: 16px;
margin: 0 6px;
flex-shrink: 0;
margin-bottom: 16px;
}
.node-cloud .icon { background: rgba(0,97,239,0.15); color: var(--blue-light); }
.node-relay .icon { background: rgba(140,107,247,0.15); color: var(--purple-light); }
.node-desktop .icon { background: rgba(0,184,176,0.12); color: var(--accent-bright); }
.node-pup .icon { background: rgba(0,184,176,0.2); color: var(--accent-bright); }
.node-chrome .icon { background: rgba(143,211,212,0.15); color: var(--teal-light); }
/* ── Description card ── */
.desc-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 20px 24px;
}
.desc-card p {
color: var(--text2);
font-size: 13px;
line-height: 1.65;
}
.desc-card p strong { color: var(--text); font-weight: 500; }
.desc-card p code {
background: var(--overlay);
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
color: var(--accent);
}
/* ── Feature chips ── */
.features {
display: flex;
flex-direction: column;
gap: 10px;
}
.features-title {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--accent);
font-weight: 600;
}
.chip-grid {
display: flex;
flex-wrap: wrap;
gap: 7px;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
background: var(--elevated);
border: 1px solid var(--border-muted);
border-radius: 6px;
font-size: 11px;
color: var(--text2);
font-family: 'JetBrains Mono', monospace;
}
.chip:hover {
border-color: rgba(0,184,176,0.3);
color: var(--text);
}
.chip .dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--accent);
flex-shrink: 0;
}
/* ── Footer info bar ── */
.footer {
width: 100%;
background: var(--surface);
border-top: 1px solid var(--border-muted);
padding: 9px 40px;
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.footer-item {
display: flex;
align-items: center;
gap: 6px;
}
.footer-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.3px;
color: var(--text3);
font-weight: 600;
}
.footer-value {
font-size: 11px;
color: var(--text2);
font-family: 'JetBrains Mono', monospace;
}
.footer-value.live {
color: #3fb950;
}
.footer-sep {
width: 1px;
height: 14px;
background: var(--border-muted);
}
</style>
</head>
<body>
<div class="accent-bar"></div>
<div class="container">
<!-- Hero -->
<div class="hero">
<div class="hero-icon">
<svg viewBox="0 0 1061.33 1061.38" xmlns="http://www.w3.org/2000/svg">
<path fill="#00b8b0" d="M957.65,103.68c-116.2-116.2-282.77-138.02-372.05-48.73-36.23,36.23-54.16,85.18-54.92,138.34h0s0,.07,0,.07c0,.17,0,.35,0,.52v329.6c0,3.99,3.24,7.23,7.23,7.23h326.06c54.75.14,105.25-17.79,142.42-54.97,89.28-89.28,67.46-255.85-48.74-372.05Z"/>
<path fill="#00b8b0" d="M103.68,957.7c116.2,116.2,282.77,138.02,372.05,48.73,36.23-36.23,54.16-85.18,54.92-138.34h0s0-.07,0-.07c0-.17,0-.35,0-.52v-329.6c0-3.99-3.24-7.23-7.23-7.23H197.37c-54.75-.14-105.25,17.79-142.42,54.97-89.28,89.28-67.46,255.85,48.74,372.05Z"/>
</svg>
</div>
<div class="hero-text">
<h1><span class="pup">pup</span><span class="sep">—</span><span class="sub">Adom Bridge</span></h1>
<p class="tagline">Browser automation for your desktop, controlled by Adom's cloud AI</p>
</div>
</div>
<!-- Session identity (populated by JS from URL params) -->
<div class="session-card" id="sessionCard">
<div class="session-heading">This Chrome window belongs to</div>
<div class="session-grid">
<span class="session-label">Session</span>
<span class="session-value" id="sessionId">—</span>
<span class="session-label">Profile</span>
<span class="session-value" id="profileName">—</span>
<span class="session-label">User Data</span>
<span class="session-value" id="userDataDir">—</span>
</div>
</div>
<!-- Pipeline -->
<div class="pipeline">
<div class="pipeline-node node-cloud">
<div class="icon">☁️</div>
<div class="label">Adom Cloud</div>
</div>
<div class="pipeline-arrow">→</div>
<div class="pipeline-node node-relay">
<div class="icon">🔗</div>
<div class="label">Relay Server</div>
</div>
<div class="pipeline-arrow">→</div>
<div class="pipeline-node node-desktop">
<div class="icon">🖥️</div>
<div class="label">Desktop App</div>
</div>
<div class="pipeline-arrow">→</div>
<div class="pipeline-node node-pup">
<div class="icon">🐶</div>
<div class="label">Pup Bridge</div>
</div>
<div class="pipeline-arrow">→</div>
<div class="pipeline-node node-chrome">
<div class="icon">🌐</div>
<div class="label">Chrome</div>
</div>
</div>
<!-- Description -->
<div class="desc-card">
<p>
<strong>Pup</strong> is a Node.js bridge on port <code>8851</code> that wraps a persistent
<strong>Chrome for Testing</strong> browser. Your Adom web app sends <code>pup_*</code>
commands through the cloud relay to the Desktop App, which forwards them to Pup.
Chrome launches visibly on your screen — not headless — so you can watch the AI
browse, screenshot, and debug your projects in real time.
</p>
</div>
<!-- Features -->
<div class="features">
<div class="features-title">Capabilities</div>
<div class="chip-grid">
<div class="chip"><span class="dot"></span>pup_launch</div>
<div class="chip"><span class="dot"></span>pup_screenshot</div>
<div class="chip"><span class="dot"></span>pup_navigate</div>
<div class="chip"><span class="dot"></span>pup_eval</div>
<div class="chip"><span class="dot"></span>pup_errors</div>
<div class="chip"><span class="dot"></span>pup_reload</div>
<div class="chip"><span class="dot"></span>pup_wait</div>
<div class="chip"><span class="dot"></span>pup_status</div>
<div class="chip"><span class="dot"></span>pup_close</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="footer">
<div class="footer-item">
<span class="footer-label">Bridge</span>
<span class="footer-value">Puppeteer</span>
</div>
<div class="footer-sep"></div>
<div class="footer-item">
<span class="footer-label">Port</span>
<span class="footer-value">8851</span>
</div>
<div class="footer-sep"></div>
<div class="footer-item">
<span class="footer-label">Mode</span>
<span class="footer-value">headless: false</span>
</div>
<div class="footer-sep"></div>
<div class="footer-item">
<span class="footer-label">Screenshots</span>
<span class="footer-value">lossless PNG ≤ 1568px</span>
</div>
<div class="footer-sep"></div>
<div class="footer-item">
<span class="footer-label">Status</span>
<span class="footer-value live">● live</span>
</div>
</div>
<script>
// Read session info from URL query params
const params = new URLSearchParams(window.location.search);
const sessionId = params.get('sessionId');
const profile = params.get('profile');
const userDataDir = params.get('userDataDir');
if (sessionId) {
// Show the session identity card
document.getElementById('sessionCard').classList.add('visible');
document.getElementById('sessionId').textContent = sessionId;
document.getElementById('profileName').textContent = profile || sessionId;
document.getElementById('userDataDir').textContent = userDataDir || '(default)';
// Set the page title to show session info in the Chrome tab
const titleParts = ['Pup'];
titleParts.push(`session: ${sessionId}`);
if (profile && profile !== sessionId) titleParts.push(`profile: ${profile}`);
document.title = titleParts.join(' — ');
}
</script>
</body>
</html>