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
Revert 1.9.126 avatar branding — verified it cannot work. Chrome only shows a custom profile photo for a SIGNED-IN Google account (the gaia picture); with no account it PURGES a seeded picture (measured: after one launch Chrome blanked gaia_picture_file_name/gaia_name/gaia_id and DELETED the seeded PNG; the toolbar pill rendered a generic empty circle). pup suppresses sign-in by design (--allow-browser-signin=false), so the pill can't carry a custom image, and a signed-in profile would show the Google photo, not the Adom avatar. Removed brandProfileAvatar, the per-launch reseed, the browser_brand_profile verb, and the branding store. FORCE_PUSH: verified by full diff against the repo that the ONLY change vs 1.9.126 is this avatar removal — browser_login and all 1.9.122-1.9.125 work are preserved intact (an over-broad first revert had clipped browser_login; caught by diffing before push, restored from the repo copy).
4 files changed
+6−105
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.126",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.126/adom-bridge-puppeteer-v1.9.126.zip",- "sha256": "22012725be62dc644b1cf771e36e484f803e7ac613058e28f53bf3c4cdb1f0a5",- "size": 394012,+ "version": "1.9.127",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.127/adom-bridge-puppeteer-v1.9.127.zip",+ "sha256": "a2ed9e290d9cfe698bec1f55ec0ce60f96bdbc76d3441a4e35212d1fa27f2605",+ "size": 392490, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.126+1.9.127
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.126",+ "version": "1.9.127", "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−99@@ -1648,66 +1648,6 @@ async function killBrowserProcess(browser, profileName) { // We only run this when LAUNCHING (not when reconnecting to a running // Chrome via DevToolsActivePort). Reconnect path means Chrome is already // alive and we shouldn't touch its profile files.-// v1.9.126 (John) — brand a pup profile with the user's ADOM AVATAR, so the Chrome-// profile pill in the toolbar shows their real photo instead of a generic circle.-// Chrome normally only shows a custom photo for a SIGNED-IN Google account (the "gaia-// picture"), and pup suppresses sign-in — but the gaia picture is ultimately just a local-// PNG named in the profile's Local State. So we seed that file + the info_cache fields-// ourselves, no account needed. Chrome reads profile branding at LAUNCH, so this must be-// applied while Chrome isn't running the profile; prepareCleanProfile re-applies it on every-// launch (below) so Chrome's own rewrites can't erase it.-const PROFILE_BRANDING_PATH = path.join(ADOM_STATE_DIR, 'pup-profile-branding.json');-function readProfileBranding() {- try { return JSON.parse(fs.readFileSync(PROFILE_BRANDING_PATH, 'utf8')) || {}; } catch (e) { return {}; }-}-function saveProfileBranding(profileName, entry) {- try {- const all = readProfileBranding();- all[profileName] = { ...(all[profileName] || {}), ...entry };- fs.writeFileSync(PROFILE_BRANDING_PATH, JSON.stringify(all, null, 2));- return true;- } catch (e) { console.log(`saveProfileBranding warning: ${e.message}`); return false; }-}-function brandProfileAvatar(userDataDir, avatarSrcPath, displayName) {- try {- if (!userDataDir || !avatarSrcPath || !fs.existsSync(avatarSrcPath)) return false;- const defaultDir = path.join(userDataDir, 'Default');- fs.mkdirSync(defaultDir, { recursive: true });- const picName = 'Google Profile Picture.png'; // the filename Chrome expects for a gaia picture- fs.copyFileSync(avatarSrcPath, path.join(defaultDir, picName));- const name = displayName || 'Adom';- // Default/Preferences- const prefsPath = path.join(defaultDir, 'Preferences');- let prefs = {}; try { prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8')); } catch (e) {}- prefs.profile = prefs.profile || {};- prefs.profile.name = name;- prefs.profile.gaia_picture_file_name = picName;- prefs.profile.using_gaia_picture = true;- fs.writeFileSync(prefsPath, JSON.stringify(prefs));- // Local State (user-data-dir root) info_cache.Default — this drives the toolbar pill.- const lsPath = path.join(userDataDir, 'Local State');- let ls = {}; try { ls = JSON.parse(fs.readFileSync(lsPath, 'utf8')); } catch (e) {}- ls.profile = ls.profile || {};- ls.profile.info_cache = ls.profile.info_cache || {};- const cur = ls.profile.info_cache.Default || {};- ls.profile.info_cache.Default = {- ...cur,- name,- gaia_name: name,- gaia_given_name: name.split(' ')[0],- gaia_id: cur.gaia_id || 'adom-pup',- gaia_picture_file_name: picName,- use_gaia_picture: true,- is_using_default_name: false,- is_using_default_avatar: false,- background_apps: false,- };- ls.profile.last_used = 'Default';- if (!Array.isArray(ls.profile.profiles_order) || !ls.profile.profiles_order.length) ls.profile.profiles_order = ['Default'];- fs.writeFileSync(lsPath, JSON.stringify(ls));- return true;- } catch (e) { console.log(`brandProfileAvatar("${userDataDir}") warning: ${e.message}`); return false; }-} function prepareCleanProfile(userDataDir) { if (!userDataDir) return; try {@@ -1743,13 +1683,6 @@ function prepareCleanProfile(userDataDir) { const p = path.join(defaultDir, name); try { fs.rmSync(p, { recursive: true, force: true }); } catch {} }- // v1.9.126: re-apply avatar branding if this profile has any (Chrome rewrites Local State- // on exit, so a one-time seed would be lost — re-seed at every launch).- try {- const pn = path.basename(userDataDir);- const b = readProfileBranding()[pn];- if (b && b.avatarFile) brandProfileAvatar(userDataDir, b.avatarFile, b.displayName);- } catch (e) {} } catch (e) { // Profile cleanup is defense-in-depth — don't fail the launch if it errors console.log(`prepareCleanProfile("${userDataDir}") warning: ${e.message}`);@@ -9128,38 +9061,6 @@ const server = http.createServer(async (req, res) => { } return; }- // v1.9.126 — brand a pup profile with the user's Adom avatar (or any image/displayName).- case 'browser_brand_profile': {- try {- const profileName = args.profile || DEFAULT_PROFILE;- const displayName = args.displayName || 'Adom';- let avatarSrc = null;- if (args.avatarUrl) {- const resp = await fetch(args.avatarUrl);- if (!resp || !resp.ok) throw new Error(`avatar fetch failed (${resp && resp.status})`);- const buf = Buffer.from(await resp.arrayBuffer());- const dir = path.join(ADOM_STATE_DIR, 'pup-avatars');- fs.mkdirSync(dir, { recursive: true });- avatarSrc = path.join(dir, profileName.replace(/[^a-zA-Z0-9_-]/g, '_') + '.png');- fs.writeFileSync(avatarSrc, buf);- } else if (args.avatarPath && fs.existsSync(args.avatarPath)) {- avatarSrc = args.avatarPath;- }- if (!avatarSrc) { sendJSON(res, { success: false, error: 'avatarUrl (or a local avatarPath) is required' }); return; }- // Persist so every future launch of this profile re-applies the brand.- saveProfileBranding(profileName, { avatarFile: avatarSrc, displayName });- const userDataDir = path.join(PROFILES_DIR, profileName);- const applied = brandProfileAvatar(userDataDir, avatarSrc, displayName);- const live = browsers.has(profileName);- sendJSON(res, { success: true, output: JSON.stringify({ ok: true, profile: profileName, displayName, applied, relaunchNeeded: live }),- _hint: live- ? `Avatar seeded for profile "${profileName}", but it has a LIVE browser — Chrome reads profile branding only at LAUNCH, so close and reopen its window(s) for the new photo to appear in the toolbar pill.`- : `Avatar seeded for profile "${profileName}" — it will show in the Chrome toolbar pill on the next window opened on this profile.` });- } catch (e) {- sendJSON(res, { success: false, error: e.message });- }- return;- } // v1.9.124 — fill (and optionally submit) an HTML login FORM in the BACKGROUND, from the // vault or explicit creds, with NO Chrome autofill dropdown and NO foregrounding. This is // the answer to Q4: pup owns the creds, so it fills the fields over CDP directly instead of