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
The toolbar extension now simply auto-exists in pup (John: this is pup, you control the browser, there has to be a better way than a file dialog). The UIA dialog-driving experiment is deleted. Before a profile's Chrome ever launches, pup does an invisible two-second pipe-launch of the same profile and installs the extension via CDP Extensions.loadUnpacked (puppeteer enableExtensions plus installExtension, the sanctioned replacement for the load-extension flag branded Chrome removed in 137), so the extension persists in the profile and every window is born with the toolbar: no dialogs, no chrome-extensions driving, no user action, ever. Marker-gated per extension version so upgrades re-install once. pup_install_toolbar is now a thin status/trigger verb
4 files changed
+62−76
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.51",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.51/adom-bridge-puppeteer-v2.0.51.zip",- "sha256": "ad76e57a1c9295defd3db771f80bb7ed60abdf5b1a1203f1e78d4c42121d5485",- "size": 742113,+ "version": "2.0.52",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.52/adom-bridge-puppeteer-v2.0.52.zip",+ "sha256": "c6f12701732ebb1005571e879ebd288c158cc98a6c96f31036a54b8d8afffc0c",+ "size": 742025, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.51+2.0.52
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.51",+ "version": "2.0.52", "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. pup_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. pup_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, pup_alert_window posts a native toast whose click focuses the window, pup_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-bridge", "author": "Adom Inc.",
src/server.js+56−70@@ -5518,6 +5518,37 @@ async function showCaptureToast(pg, previewDataUrl, line1) { } catch (e) {} } +async function ensureToolbarExtensionInstalled(userDataDir, executablePath) {+ if (process.platform !== 'win32' || !userDataDir) return false;+ if (pupSettings().toolbarExtension === 'off') return false;+ const srcDir = path.join(__dirname, 'extension');+ let extVer = '0';+ try { extVer = JSON.parse(fs.readFileSync(path.join(srcDir, 'manifest.json'), 'utf8')).version; } catch (e) { return false; }+ const marker = path.join(userDataDir, '.pup-toolbar-installed');+ try { if (fs.readFileSync(marker, 'utf8').trim() === extVer) return true; } catch (e) {}+ // stage at a stable absolute path (the profile references it)+ const dstDir = path.join(os.homedir(), '.adom', 'pup-extension');+ try {+ fs.mkdirSync(dstDir, { recursive: true });+ for (const f of fs.readdirSync(srcDir)) fs.copyFileSync(path.join(srcDir, f), path.join(dstDir, f));+ } catch (e) { return false; }+ let tb = null;+ try {+ tb = await puppeteer.launch({+ pipe: true, enableExtensions: true, userDataDir,+ ...(executablePath ? { executablePath } : {}),+ args: ['--no-first-run', '--no-default-browser-check', '--window-position=-32000,-32000', '--window-size=1,1'],+ handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false,+ });+ const id = await tb.installExtension(dstDir);+ fs.writeFileSync(marker, extVer);+ console.log(`[toolbar] extension v${extVer} auto-installed into profile "${path.basename(userDataDir)}" (pipe launch, id ${id}) — every window on this profile is born with the toolbar`);+ return true;+ } catch (e) {+ console.log(`[toolbar] pre-launch extension install failed: ${e.message} — continuing without the toolbar`);+ return false;+ } finally { try { if (tb) await tb.close(); } catch (e) {} }+} async function startAnnotation(session, sessionId, callerThread, opts) { const tab = (session.activeTabId && (session.tabs || []).find(t => t.tabId === session.activeTabId)) || (session.tabs || [])[0]; const pg = tab && tab.page;@@ -7929,6 +7960,15 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer return b; }; + // v2.0.52 (John: "why can't your extension just auto-exist in pup? you control the+ // browser"): IT DOES NOW. Before a profile's Chrome ever launches, pup does an invisible+ // ~2s pipe-launch of the SAME profile and installs the toolbar extension via CDP+ // Extensions.loadUnpacked (puppeteer enableExtensions/installExtension — the sanctioned+ // replacement for --load-extension, which branded Chrome >=137 removed). The extension+ // persists in the profile, so every window is simply BORN with the toolbar. No dialogs,+ // no chrome://extensions driving, no user action, ever. Marker-gated per extension version.+ try { await ensureToolbarExtensionInstalled(userDataDir, (candidates[0] || {}).executablePath); } catch (e) {}+ // ─── Spawn-verified launch: try each candidate browser in order, up to 3 ─── // attempts each (with orphan/lock cleanup between attempts). If a browser can't // actually spawn (corrupt install, wrong arch, locked), fall THROUGH to the next@@ -10840,7 +10880,7 @@ const server = http.createServer(async (req, res) => { V('pup_press_key', 'Press a key, optionally with modifier chords held (Ctrl+A, Ctrl+V, Shift+Tab).', { sessionId: 'required str', key: 'required str', modifiers: 'optional array — any of ["Control","Shift","Alt","Meta"] (aliases ctrl/cmd/command/option accepted); held down around the key press' }, 15, { sessionId: 'work', key: 'a', modifiers: ['Control'] }), V('pup_NATIVE_INPUT_WARNING', '⛔ READ BEFORE USING AD\'s desktop_click / desktop_type / desktop_press_key ON A PUP WINDOW. Native OS input does NOT target a window — it goes to WHATEVER WINDOW HAS OS FOCUS at that instant. This user runs many AI threads and alt-tabs away from a foregrounded window almost immediately. If you fire native input without warning them, your keystrokes land in THEIR document (this happened: a username was typed into the user\'s own window). REQUIRED: notify_user toast + desktop_caption FIRST, tell them explicitly "don\'t type or click until I say done", wait until the window is genuinely foreground, re-verify immediately before EACH event, and caption when done. pup_assisted_click already does all of this — use it. And ALWAYS prefer CDP input (pup_click/pup_type/pup_hover): it needs no foreground and disturbs nobody. A native verb returning ok:true means the event was dispatched to the OS, NOT that it reached your window.', {}, 0, {}), V('pup_set_viewport', 'Pin the page viewport (CDP device-metrics) to an exact size for deterministic screenshots — OR clear that pin. ⚠ A pin PERSISTS: it survives navigation and hard reload, and while set the window stops tracking OS resizing (a human resizing it sees no reflow, which looks exactly like an app layout bug). ALWAYS clear it when done. Reported as emulatedViewport in pup_status / pup_list_windows.', { sessionId: 'required str', width: 'int — required unless reset', height: 'int — required unless reset', deviceScaleFactor: 'optional number (default 1)', reset: 'optional bool — CLEAR the override and track the OS window again (width/height not needed)' }, 15, { sessionId: 'work', reset: true }),- V('pup_install_toolbar', 'ONE-TIME automated install of the Adom Pup toolbar extension into the pup Chrome profile (pup drives chrome://extensions + ab drives the native folder dialog; branded Chrome killed --load-extension so this is the zero-click path). Idempotent: returns alreadyInstalled:true when present.', {}, 60, {}),+ V('pup_install_toolbar', 'Status/trigger for the toolbar extension, which AUTO-INSTALLS invisibly at every profile launch (transient pipe-launch + CDP Extensions.loadUnpacked; persists in the profile). Running profiles pick it up on their next launch; cold profiles install immediately.', { profile: 'optional, default adom-you' }, 30, {}), V('pup_annotate', 'Start the HUMAN annotation overlay on a window\'s active tab (pen/arrow/box/text toolbar injected in-page; also on every window\'s taskbar jump list as "Annotate this page"). Done captures a PNG AND leaves the drawing in the DOM, so the owning thread\'s next pup_screenshot shows exactly what the user marked — check session.lastAnnotation via pup_list_windows. action:"cancel" removes the overlay.', { sessionId: 'required window', action: 'optional "cancel"' }, 15, {}), V('pup_dashboard', 'Open the LIVE pup management dashboard in its OWN window (its own profile, so its own taskbar icon). Shows every window pup is driving as taskbar button -> window(s) -> tabs with each tab favicon, owning AI thread, foreground/background + the reason given to foreground it, highFps lease, uptime, and WHY each overlay icon was chosen. Live over SSE. Reuses the one window on repeat calls; backgrounded by default like any pup window.', {}, 30, {}), V('pup_deps', 'Report pup dependency health (wiki issue #15): depsPresent, selfInstalledThisBoot, lastError. Use this to answer "is pup broken?" without reading spawn logs.', { }, 10, {}),@@ -13863,78 +13903,24 @@ const server = http.createServer(async (req, res) => { return; } case 'pup_install_toolbar': {- // v2.0.51 (John: "can't you just do that automatically behind the scenes since you own- // pup?"): YES. Branded Chrome >=137 killed --load-extension, but pup OWNS this Chrome:- // it stages the extension dir, drives chrome://extensions itself (dev-mode toggle +- // Load unpacked through the shadow DOM), and ab drives the one NATIVE folder dialog- // (desktop_ui_set/click). One-time per profile; the durable profile keeps it forever.+ // v2.0.52: thin now — the extension AUTO-INSTALLS at profile launch (see+ // ensureToolbarExtensionInstalled). This verb covers the one already-running-profile+ // case: it reports what to do, or installs immediately when the profile is cold. if (process.platform !== 'win32') { sendJSON(res, { success: false, error: 'windows only' }); return; } try {- // 1) stage the extension shipped inside the bridge onto disk- const srcDir = path.join(__dirname, 'extension');- const dstDir = path.join(os.homedir(), '.adom', 'pup-extension');- fs.mkdirSync(dstDir, { recursive: true });- for (const f of fs.readdirSync(srcDir)) fs.copyFileSync(path.join(srcDir, f), path.join(dstDir, f));- // 2) open chrome://extensions in the shared profile- const be = browsers.get('adom-you') || [...browsers.values()][0];- if (!be || !be.browser) { sendJSON(res, { success: false, error: 'no pup browser running - open any pup window first', errorCode: 'no_browser' }); return; }- const epg = await be.browser.newPage();- await epg.goto('chrome://extensions', { waitUntil: 'domcontentloaded', timeout: 15000 });- const already = await epg.evaluate(() => {- try {- const items = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-item-list').shadowRoot.querySelectorAll('extensions-item');- return [...items].some(i => /Adom Pup/i.test(i.shadowRoot.querySelector('#name').textContent));- } catch (e) { return false; }- });- if (already) { await epg.close().catch(() => {}); sendJSON(res, { success: true, output: JSON.stringify({ ok: true, alreadyInstalled: true }) }); return; }- // 3) dev mode + Load unpacked (opens the native folder dialog)- await epg.evaluate(() => {- const tb = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-toolbar').shadowRoot;- const dev = tb.querySelector('#devMode');- if (dev && dev.getAttribute('aria-pressed') !== 'true' && !dev.checked) dev.click();- });- await new Promise(r => setTimeout(r, 700));- await epg.evaluate(() => {- const tb = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-toolbar').shadowRoot;- tb.querySelector('#loadUnpacked').click();- });- // 4) drive the native dialog via ab: type the path, confirm- let dlg = null;- for (let i = 0; i < 12 && !dlg; i++) {- await new Promise(r => setTimeout(r, 500));- try {- const fw = abPayload(await chrome.abCommand('desktop_find_window', { titleContains: 'Select the extension' }, { timeoutMs: 4000 }));- const b2 = fw && (fw.best || (fw.data && fw.data.best));- if (b2 && b2.hwnd) dlg = b2.hwnd;- } catch (e) {}- }- if (!dlg) { await epg.close().catch(() => {}); sendJSON(res, { success: false, error: 'folder dialog never appeared', errorCode: 'dialog_missing' }); return; }- let setOk = false;- try {- const r1 = abPayload(await chrome.abCommand('desktop_ui_set', { hwnd: dlg, contains: 'Folder', text: dstDir }, { timeoutMs: 6000 }));- setOk = !!(r1 && (r1.ok || r1.set));- } catch (e) {}- if (!setOk) {- try { await chrome.abCommand('desktop_ui_set', { hwnd: dlg, contains: 'File name', text: dstDir }, { timeoutMs: 6000 }); setOk = true; } catch (e) {}- }- let clicked = false;- try { const r2 = abPayload(await chrome.abCommand('desktop_ui_click', { hwnd: dlg, contains: 'Select Folder' }, { timeoutMs: 6000 })); clicked = !!(r2 && (r2.ok || r2.clicked)); } catch (e) {}- if (!clicked) { try { await chrome.abCommand('desktop_type', { hwnd: dlg, text: dstDir + '\n' }, { timeoutMs: 6000 }); } catch (e) {} }- // 5) verify it landed- let installed = false;- for (let i = 0; i < 10 && !installed; i++) {- await new Promise(r => setTimeout(r, 800));- installed = await epg.evaluate(() => {- try {- const items = document.querySelector('extensions-manager').shadowRoot.querySelector('extensions-item-list').shadowRoot.querySelectorAll('extensions-item');- return [...items].some(i2 => /Adom Pup/i.test(i2.shadowRoot.querySelector('#name').textContent));- } catch (e) { return false; }- }).catch(() => false);+ const profileName = args.profile || 'adom-you';+ const userDataDir = path.join(PROFILES_DIR, profileName);+ const be = browsers.get(profileName);+ const running = !!(be && be.browser && be.browser.isConnected());+ if (running) {+ const marker = path.join(userDataDir, '.pup-toolbar-installed');+ let installed = false; try { installed = !!fs.readFileSync(marker, 'utf8').trim(); } catch (e) {}+ sendJSON(res, { success: true, output: JSON.stringify({ ok: true, installed, running: true, _hint: installed ? 'Toolbar already installed in this profile.' : 'Profile Chrome is running; the toolbar auto-installs on the profile\'s NEXT launch (close its windows or wait for the natural restart — no action needed).' }) });+ return; }- await epg.close().catch(() => {});- console.log(`[toolbar] extension install ${installed ? 'SUCCEEDED' : 'FAILED'} (staged at ${dstDir})`);- sendJSON(res, { success: installed, output: JSON.stringify({ ok: installed, stagedAt: dstDir }), ...(installed ? {} : { error: 'extension did not appear after Load unpacked', errorCode: 'install_unverified' }) });- } catch (e) { sendJSON(res, { success: false, error: `install failed: ${e.message}` }); }+ const okInst = await ensureToolbarExtensionInstalled(userDataDir, null);+ sendJSON(res, { success: okInst, output: JSON.stringify({ ok: okInst, installedNow: okInst }) });+ } catch (e) { sendJSON(res, { success: false, error: e.message }); } return; } case 'pup_annotate': {