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.
← Commit history
Cursor: port HD's AI cursor byte-faithful + add the Adom mark (John: 'we should have a consistent cursor in all of our apps, hd's looks amazing' + 'i can't even read the indicator text ... and i don't see the adom logo'). pup's cursor was a hand-drawn fourth variant with NO intent label at all (reasoning went only to a bottom-of-screen caption) and no brand mark, just a 7px letter A. It is now HD's cursor ported verbatim from the canonical source (adom-browser-extension/extension/src/cdp.js cursorScript, itself an exact match of HD's hd-control AI_CURSOR_JS): teal arrow with glow, PULSING teal ring at the tip, and the readable monospace intent label that flips sides near the screen edge. Added for pup per John: the real Adom mark (icons/pup-overlay-32.png inlined) inside the label chip so the user can tell ADOM is driving and not another AI. browser_click now composes HD-style narration from intent/reason/target so the label never reads a bare Adom.
4 files changed
+90−32
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.195",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.195/adom-bridge-puppeteer-v1.9.195.zip",- "sha256": "ea3353cc79b1c6207405fbe1e62930a5c9b5f0fb3b782b66fba835f967dd9a9b",- "size": 425840,+ "version": "1.9.196",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.196/adom-bridge-puppeteer-v1.9.196.zip",+ "sha256": "15cf19135e3158d96a0674105f06d4359d31dfd7ed1d9d92f3016bf0376717c2",+ "size": 427130, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.195+1.9.196
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.195",+ "version": "1.9.196", "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. browser_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. browser_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, browser_alert_window posts a native toast whose click focuses the window, browser_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.", "homepage": "https://wiki.adom.inc/adom/adom-desktop", "author": "Adom Inc.",
src/server.js+84−26@@ -1728,34 +1728,84 @@ process.on('SIGTERM', () => { process.exit(0); }); // rides on top of whatever page is loaded (pointer-events:none, max z-index). Only visible when the // window is on-screen — which is exactly when the user is watching pup drive. Best-effort; a page // that blocks injection (rare) just doesn't show it, the click still fires.-async function showPupCursor(page, x, y, { click = true } = {}) {+// v1.9.196 (John: "why didn't you make it look like abe's or hd's? we should have a consistent+// cursor in all of our apps. hd's looks amazing" + "i can't even read the indicator text ... and i+// don't see the adom logo in there either").+//+// This is now HD's AI cursor, ported BYTE-FAITHFUL from the canonical source+// (adom-browser-extension/extension/src/cdp.js `cursorScript`, itself an exact match of HD's+// hd-control AI_CURSOR_JS) so the cursor is IDENTICAL across HD, the extension, and pup. My previous+// hand-drawn version was a fourth variant nobody asked for: it had NO intent label at all (the+// reasoning went only to a bottom-of-screen caption) and NO Adom mark, just a 7px letter "A".+//+// What HD's cursor gives us: the teal arrow (#00b8b0) with dark-teal outline + glow, a PULSING teal+// ring around the tip, and a readable monospace LABEL that narrates what the AI is doing right now,+// which flips to the left of the cursor near the screen edge so it never clips.+//+// ADDITION for pup (John's ask): a small ADOM MARK inside the label chip, so the user can tell it is+// ADOM driving the cursor and not some other AI process. It is the real brand asset (icons/+// pup-overlay-32.png) inlined as a data URI, not a drawn letter.+let _pupCursorMarkUri = null;+function pupCursorMarkUri() {+ if (_pupCursorMarkUri !== null) return _pupCursorMarkUri;+ try { _pupCursorMarkUri = 'data:image/png;base64,' + fs.readFileSync(path.join(__dirname, 'icons', 'pup-overlay-32.png')).toString('base64'); }+ catch (e) { _pupCursorMarkUri = ''; }+ return _pupCursorMarkUri;+}++async function showPupCursor(page, x, y, { click = true, label = null } = {}) { try {- await page.evaluate((tx, ty, doClick) => {- const ID = '__pupCursor';- let c = document.getElementById(ID);+ await page.evaluate((tx, ty, doClick, lbl, markUri) => {+ var id = '__adom_cursor__';+ var c = document.getElementById(id); if (!c) {- c = document.createElement('div'); c.id = ID;- c.style.cssText = 'position:fixed;left:0;top:0;width:24px;height:24px;z-index:2147483647;pointer-events:none;transition:transform .40s cubic-bezier(.22,1,.36,1);will-change:transform;filter:drop-shadow(0 2px 3px rgba(0,0,0,.45));';- // v1.9.165 (John: 'stick a tiny Adom logo next to the cursor so the user knows it is ADOM- // doing this, not Claude's browser extension'). Cursor arrow + a small Adom orbital badge.- c.innerHTML = '<svg width="40" height="30" viewBox="0 0 40 30" style="overflow:visible">'- + '<path d="M4 2 L4 19 L8.6 14.6 L11.7 22 L14.6 20.8 L11.5 13.6 L18 13.6 Z" fill="#12e0c4" stroke="#08302c" stroke-width="1.3" stroke-linejoin="round"/>'- + '<g transform="translate(20,15)"><rect x="-2" y="-9" width="26" height="18" rx="9" fill="#0a2b28" stroke="#12e0c4" stroke-width="1"/>'- + '<circle cx="4" cy="0" r="3.4" fill="#12e0c4"/><circle cx="12" cy="0" r="3.4" fill="#12e0c4"/>'- + '<text x="17.5" y="3.5" font-family="Segoe UI,Arial,sans-serif" font-size="7" font-weight="700" fill="#12e0c4">A</text></g></svg>';+ c = document.createElement('div');+ c.id = id;+ c.style.cssText = 'position:fixed;z-index:2147483647;pointer-events:none;transition:left 0.15s,top 0.15s;display:none;';+ var g = document.createElement('div'); // pulsing teal glow ring+ g.id = '__adom_cursor_ring__';+ g.style.cssText = 'position:absolute;top:-16px;left:-16px;width:40px;height:40px;border-radius:50%;background:rgba(0,184,176,0.15);border:2px solid rgba(0,184,176,0.4);animation:adomCurP 1.5s ease-in-out infinite;';+ c.appendChild(g);+ var a = document.createElement('div'); // the teal Adom arrow (HD's exact SVG)+ a.innerHTML = '<svg width="20" height="28" viewBox="0 0 20 28" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L1 22L6.5 16.5L10.5 26L14 24.5L10 15L17 15Z" fill="#00b8b0" stroke="#004d49" stroke-width="1.2" stroke-linejoin="round"/></svg>';+ a.style.cssText = 'position:absolute;top:0;left:0;filter:drop-shadow(0 0 6px rgba(0,184,176,0.7));';+ c.appendChild(a);+ var l = document.createElement('div'); // the intent label (+ Adom mark)+ l.id = '__adom_cursor_label__';+ l.style.cssText = 'position:absolute;top:32px;left:8px;white-space:nowrap;font-size:11px;color:#00b8b0;background:rgba(13,17,23,0.92);padding:2px 10px;border-radius:4px;border:1px solid rgba(0,184,176,0.6);font-family:monospace;display:flex;align-items:center;gap:6px;';+ c.appendChild(l); (document.body || document.documentElement).appendChild(c);- c.style.transform = 'translate(' + Math.round(innerWidth/2) + 'px,' + Math.round(innerHeight/2) + 'px)';- void c.offsetWidth; // force the start position before the glide transition+ if (!document.getElementById('__adom_cur_kf__')) {+ var s = document.createElement('style'); s.id = '__adom_cur_kf__';+ s.textContent = '@keyframes adomCurP{0%{transform:scale(1);opacity:0.7}50%{transform:scale(1.5);opacity:0.2}100%{transform:scale(1);opacity:0.7}}@keyframes adomCurClick{0%{transform:scale(1);opacity:0.85}45%{transform:scale(2.2);opacity:0.05}100%{transform:scale(1);opacity:0.7}}';+ document.head.appendChild(s);+ }+ }+ var lab = c.querySelector('#__adom_cursor_label__');+ if (lab) {+ var txt = (lbl != null ? lbl : 'Adom');+ // Adom mark FIRST (so "who is driving" reads before the narration), then the intent text.+ lab.innerHTML = '';+ if (markUri) {+ var im = document.createElement('img');+ im.src = markUri;+ im.style.cssText = 'width:13px;height:13px;flex:0 0 13px;display:block;';+ lab.appendChild(im);+ }+ var sp = document.createElement('span');+ sp.textContent = txt;+ lab.appendChild(sp);+ var nearRight = (tx != null) && (tx > (window.innerWidth - 250));+ if (nearRight) { lab.style.left = 'auto'; lab.style.right = '0px'; lab.style.textAlign = 'right'; }+ else { lab.style.left = '8px'; lab.style.right = 'auto'; lab.style.textAlign = 'left'; }+ }+ if (tx != null && ty != null) { c.style.left = tx + 'px'; c.style.top = ty + 'px'; }+ c.style.display = 'block';+ if (doClick) {+ var ring = c.querySelector('#__adom_cursor_ring__');+ if (ring) { ring.style.animation = 'none'; void ring.offsetWidth; ring.style.animation = 'adomCurClick 0.45s ease-out, adomCurP 1.5s ease-in-out infinite 0.45s'; } }- c.style.transform = 'translate(' + tx + 'px,' + ty + 'px)';- if (doClick) setTimeout(() => {- const ring = document.createElement('div');- ring.style.cssText = 'position:fixed;left:' + tx + 'px;top:' + ty + 'px;width:10px;height:10px;margin:-5px 0 0 -5px;border:2px solid #12e0c4;border-radius:50%;z-index:2147483646;pointer-events:none;opacity:.95;transition:all .5s ease-out;';- (document.body || document.documentElement).appendChild(ring);- requestAnimationFrame(() => { ring.style.width='44px'; ring.style.height='44px'; ring.style.margin='-22px 0 0 -22px'; ring.style.opacity='0'; });- setTimeout(() => ring.remove(), 540);- }, 400);- }, Math.round(x), Math.round(y), !!click);+ }, x, y, click, label, pupCursorMarkUri()); } catch (e) {} } // v1.9.167 (John's assisted-foreground protocol) — a general OS caption (what/why narration).@@ -9098,13 +9148,21 @@ const server = http.createServer(async (req, res) => { // v1.9.164: show the animated cursor gliding to the target BEFORE the real click, so the // user can see pup is driving. Default on; {showCursor:false} skips it. const _cursor = args.showCursor !== false;+ // v1.9.196: the cursor LABEL must narrate WHAT is being clicked and WHY — that is the whole+ // point of showing a cursor (John: "i can't even read the indicator text you're supposed to+ // put in there explaining your reasoning"). Caller may pass `intent`; otherwise pup composes+ // an HD-style narration from the target itself, so the label is never a bare "Adom".+ const _tgt = args.selector || (args.text ? `"${args.text}"` : null);+ const _intent = (typeof args.intent === 'string' && args.intent.trim())+ ? args.intent.trim()+ : (args.reason && String(args.reason).trim() ? String(args.reason).trim() : (_tgt ? `Clicking ${_tgt}` : 'Clicking')); if (args.selector) { let _box = null; try { _box = await tab.page.$eval(args.selector, el => { const r = el.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }); } catch (e) {}- if (_cursor && _box) { await showPupCursor(tab.page, _box.x, _box.y); await new Promise(r => setTimeout(r, 440)); }+ if (_cursor && _box) { await showPupCursor(tab.page, _box.x, _box.y, { label: _intent }); await new Promise(r => setTimeout(r, 440)); } await withTimeout(tab.page.click(args.selector), 10_000, 'page.click'); } else {- if (_cursor) { await showPupCursor(tab.page, Number(args.x), Number(args.y)); await new Promise(r => setTimeout(r, 440)); }+ if (_cursor) { await showPupCursor(tab.page, Number(args.x), Number(args.y), { label: _intent }); await new Promise(r => setTimeout(r, 440)); } await withTimeout(tab.page.mouse.click(Number(args.x), Number(args.y)), 10_000, 'mouse.click'); } sendJSON(res, { success: true, output: JSON.stringify({ ok: true, clicked: args.selector || { x: Number(args.x), y: Number(args.y) }, sessionId: resolvedId, tabId, _hint: 'Click dispatched (isTrusted, CDP-level). Verify the effect with browser_eval (e.g. document.title or a state probe) — a click that hit nothing still returns ok.' }) });