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
New pup_import_browser_logins verb: seed pup's vault from the user's own saved Chrome/Edge/Brave passwords so pup can sign in as them on sites they already use. The import machinery existed only behind the dashboard button; now an AI thread can run it. Scans profiles, calls AD's SYSTEM-elevated desktop_decrypt_pup_credentials LOCALLY (the user approves one consent + one UAC on the desktop; relayed callers are refused, which is why pup as a local bridge must be the one to ask), merges identity-tagged rows into the keychain vault, and drops the plaintext, returning counts only. Optional hostFilter for a surgical import (e.g. just the vendor sites).
4 files changed
+67−6
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.142",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.142/adom-bridge-puppeteer-v2.0.142.zip",- "sha256": "b735968c96b93d3e54f43f3a1cd66e672bb5d998825b9ea516c3bdd49f5adf59",- "size": 894635,+ "version": "2.0.143",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.143/adom-bridge-puppeteer-v2.0.143.zip",+ "sha256": "7510be90937793f0a2bc2b4fb58f9c5dc52fbc9478d6f425927a3fd97b1b604c",+ "size": 896754, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.142+2.0.143
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.142",+ "version": "2.0.143", "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+61@@ -11964,6 +11964,7 @@ const server = http.createServer(async (req, res) => { V('pup_raise_os_window', 'Bring a pup window to the FOREGROUND (user\'s screen). GATED (v1.8.70): REFUSED with foreground_reason_required unless you pass foregroundReason — a quote/paraphrase of what the user said that justifies it (min 10 chars; logged, recorded in lastAgentUpdate, auditable). Allowed ONLY when the user EXPLICITLY must SEE it (a demo, recording, "show me"). It STEALS focus — to just signal "I updated this window", use pup_alert_window (or the auto-flash), NOT raise. After the show-me: pup_lower_os_window to return it to the background. Alias: pup_focus_window. IMPORTANT: if you are foregrounding in order to CLICK a native dialog or a cross-origin FedCM/Google account chooser, do NOT raise-then-click yourself — use pup_assisted_click, which waits for the user to actually be on the window (alt-tab aware) and narrates via captions, so you never act while they are not ready.', { sessionId: 'optional str', foregroundReason: 'REQUIRED — why the user asked to see this window' }, 15, { foregroundReason: 'user said "show me the wiki page"' }), V('pup_login', 'Fill (and optionally submit) an HTML login form in the CURRENT tab, in the BACKGROUND — no Chrome autofill dropdown, no foregrounding, React/Vue-safe. Creds from the keychain vault (matched on the tab URL; captured automatically as you log in, or stored via credential_set / save:true) or passed explicitly. Passwords are NEVER echoed back. Vault-matched pages AUTO-FILL on load (_autoLogin on the response). SUBMIT IS REASON-GATED: submit:true requires submitReason AND makes you confirm the live domain (the refusal regurgitates the exact host so a lookalike/phishing site is caught before the secret is sent). pup_configure {autoLogin:"off"} disables auto-fill; {captureLogins:"off"} stops auto-capture.', { sessionId: 'optional str — omit to target the window your own thread owns', username: 'optional str', password: 'optional str — with username, fills these instead of the vault', submit: 'optional bool — submit after filling; REQUIRES submitReason', submitReason: 'required WITH submit:true — why you are confident the CURRENT domain is the correct, intended site to send these credentials to (min 10 chars; the refusal shows you the live host to confirm; logged, auditable)', save: 'optional bool — with explicit creds, store them (OS keychain) so next time is automatic', host: 'optional str — override the vault host key when saving', realKeystrokes: 'optional bool — type with REAL key events instead of a background .value-set. Needed for React-controlled MASKED fields (Gusto/Keycloak SSO) that ignore .value and would submit an empty password. It briefly FOREGROUNDS the window (real keystrokes only register on-screen). Use when a normal fill returned filledPassword:false / reactMaskedFieldSuspected, or when the login-recipe for the site says so.' }, 20, { sessionId: 'work', submit: true, submitReason: 'confirmed the tab is on the real vendor domain I intended to sign into' }), V('pup_credentials', 'List saved logins (host + username only; passwords are in the OS keychain and NEVER returned). pup captures these automatically as you sign into sites and auto-fills them in the background on return visits.', {}, 10, {}),+ V('pup_import_browser_logins', 'Seed pup\'s vault from the user\'s OWN saved Chrome/Edge/Brave passwords, so pup can sign in as them on sites they already use (vendor CAD portals, etc). Runs the SYSTEM-elevated decrypt LOCALLY — the user approves ONE consent dialog + ONE Windows UAC prompt on the desktop (a cloud/relay caller cannot do this; only pup, a local bridge, can ask). Merges the identity-tagged rows into the keychain vault and DROPS the plaintext. Returns COUNTS ONLY. hostFilter narrows the import to matching hosts (surgical, e.g. ["snapeda","digikey"]); omit to import everything saved. Pair with pup_import_scan (read-only preview of how many logins exist).', { browsers: 'optional str[] subset of ["chrome","edge","brave"] (default: all found)', hostFilter: 'optional str or str[] — only import logins whose host contains one of these (surgical import)' }, 190, { hostFilter: ['snapeda', 'ultralibrarian', 'digikey'] }), V('pup_reap_profiles', 'Delete LEAKED isolated profile directories — the iso-* throwaway jars minted by isolated:true opens (and by wikiView:public before 2.0.132, when the public view moved to the server-side ?view=public param) whose window is long gone (they used to pile up, 100s per machine). Only removes an iso-* dir that no live session or session file owns, that no Chrome is holding, and that is >15 min old; durable jars (adom-you, named profiles) are NEVER touched. Runs automatically every 15 min too. dryRun:true previews the list without deleting.', { dryRun: 'optional bool — preview which iso-* profiles would be removed, delete nothing' }, 20, { dryRun: true }), V('pup_move_to_workspace', 'MOVE this pup window\'s page into the user\'s Hydrogen workspace: the active tab\'s URL is handed to Hydrogen (ah) on this machine via its Browser Picker (webview by default, honoring the user\'s per-domain choice), then the pup window closes — its owner initiated the move. keepWindow:true keeps the pup window open too. Needs Hydrogen running locally (hydrogen_unreachable otherwise; the window is left untouched on failure). Cloud Hydrogen (hc) is not yet supported.', { sessionId: 'optional str', keepWindow: 'optional bool — hand the URL over but keep the pup window open' }, 25, {}), V('pup_uninstall', 'DESTRUCTIVE — cleanly remove pup\'s WHOLE footprint from this machine: the HKCU AppUserModelId Adom.Pup registry keys, all staged extensions, ALL pup profiles + sessions, the OS-keychain credential vault, pup-settings.json, the badge-debug temp dir, and the jump-list launcher. ALWAYS preview first with dryRun:true (removes nothing, returns the plan). To actually remove, pass confirm:"uninstall". keepCredentials:true preserves the saved-login vault; keepProfiles:true preserves profiles/sessions. Chrome-for-Testing + firewall rules are SHARED and left alone unless removeChrome:true / removeFirewallRules:true. Closes every window + quits every browser first to release profile locks. (The bridge itself is managed by Adom Bridge — remove pup from Bridge to stop it re-seeding.)', { dryRun: 'optional bool — preview the removal plan without removing anything (do this first)', confirm: 'required str "uninstall" to actually remove', keepCredentials: 'optional bool — keep the saved-login vault', keepProfiles: 'optional bool — keep pup profiles + sessions', removeChrome: 'optional bool — also delete the Chrome-for-Testing install (shared; default keep)', removeFirewallRules: 'optional bool — also remove the CfT firewall rules (needs admin)' }, 60, { dryRun: true }),@@ -15933,6 +15934,66 @@ const server = http.createServer(async (req, res) => { }); return; }+ // v2.0.143 (John: "import from my native browsers via ab's credential verb — that i don't+ // think ever worked — and get it into your stored credential system"). It DID work, but only+ // from the dashboard's "Import logins" button; there was no AI-callable verb, so a thread+ // couldn't seed pup's vault from the user's real Chrome/Edge. This verb runs the whole flow:+ // scan → call AD's SYSTEM-elevated decrypt LOCALLY (the user approves one consent + one UAC+ // on the desktop; a relayed call is refused, which is why pup — a LOCAL bridge — must be the+ // one to ask) → merge the identity-tagged rows into the keychain vault → DROP the plaintext.+ // Returns COUNTS ONLY, never a password. hostFilter narrows the merge to matching hosts (a+ // surgical import, e.g. just the vendor sites); omit to import everything the user has saved.+ case 'pup_import_browser_logins': {+ if (process.platform !== 'win32') { sendJSON(res, { success: false, error: 'Browser-login import is Windows-only (Chromium ABE/DPAPI).' }); return; }+ // 1) scan for profiles (reuse the discovery verb over loopback).+ let scan = {};+ try {+ const sr = await fetch(`http://127.0.0.1:${PORT}/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'pup_import_scan', args: {} }) });+ const sj = await sr.json().catch(() => ({}));+ try { scan = JSON.parse(sj.output); } catch (e) { scan = sj; }+ } catch (e) {}+ const allProfiles = Array.isArray(scan.profiles) ? scan.profiles : [];+ const wantBrowsers = Array.isArray(args.browsers) && args.browsers.length ? args.browsers : [...new Set(allProfiles.map(p => p.browser))];+ const browsers = wantBrowsers.filter(Boolean);+ if (!browsers.length) { sendJSON(res, { success: false, error: 'No Chromium browser profiles with saved passwords were found to import from.' }); return; }+ // 2) the SYSTEM-elevated decrypt, called LOCALLY (consent + UAC prompt on the desktop).+ console.log(`[import-verb] decrypt requested for browsers=[${browsers.join(', ')}] — AD helper, consent + UAC will prompt on the desktop`);+ let resp;+ try { resp = await chrome.abCommand('desktop_decrypt_pup_credentials', { browsers, include: ['passwords'] }, { timeoutMs: 180000 }); }+ catch (e) { sendJSON(res, { success: false, errorCode: 'decrypt_call_failed', error: 'decrypt helper call failed: ' + (e && e.message || e) }); return; }+ const payload = abPayload(resp) || {};+ const ec = payload.errorCode || (resp && resp.errorCode);+ if (ec) {+ const emap = {+ relay_forbidden: 'AD refused the decrypt as a relayed call — this should not happen from pup (a local bridge). Please report it.',+ consent_declined: 'You declined the Adom Bridge consent dialog on the desktop. Nothing was read.',+ elevation_declined: 'You cancelled the Windows admin (UAC) prompt. Nothing was decrypted.',+ helper_missing: 'The signed decrypt helper is not installed — update/repair Adom Bridge.',+ };+ sendJSON(res, { success: false, errorCode: ec, error: emap[ec] || ('Decrypt failed: ' + ec) });+ return;+ }+ let pwRows = Array.isArray(payload.passwords) ? payload.passwords : [];+ if (!payload.ok && !pwRows.length) { sendJSON(res, { success: false, error: 'decrypt helper returned no data' + (payload.error ? ': ' + payload.error : '') }); return; }+ // 3) optional surgical filter by host substring(s), then merge into the vault.+ const filters = (typeof args.hostFilter === 'string' ? [args.hostFilter] : (Array.isArray(args.hostFilter) ? args.hostFilter : [])).map(s => String(s).toLowerCase()).filter(Boolean);+ let rows = pwRows;+ if (filters.length) rows = pwRows.filter(r => { const h = String((r && (r.host || r.url || r.origin)) || '').toLowerCase(); return filters.some(f => h.includes(f)); });+ let merge = { added: 0, updated: 0, skipped: 0 };+ try { merge = await credentialVault.mergeImportedCredentials(rows); }+ catch (e) { sendJSON(res, { success: false, error: 'vault merge failed: ' + (e && e.message || e) }); return; }+ // 4) DROP all plaintext immediately.+ try { for (const r of pwRows) { if (r) r.password = undefined; } rows.length = 0; pwRows = []; } catch (e) {}+ const st = payload.stats || {};+ console.log(`[import-verb] merge done: +${merge.added} new, ${merge.updated} updated, ${merge.skipped} skipped${filters.length ? ` (filtered to: ${filters.join(', ')})` : ''}`);+ sendJSON(res, { success: true, output: JSON.stringify({+ ok: true, added: merge.added, updated: merge.updated, skipped: merge.skipped,+ hostFilter: filters.length ? filters : null,+ stats: { profilesRead: st.profilesRead, passwordsOk: st.passwordsOk, passwordsFailed: st.passwordsFailed },+ _hint: `Imported ${merge.added + merge.updated} login(s) into the adom-you vault (${merge.added} new, ${merge.updated} updated${merge.skipped ? ', ' + merge.skipped + ' skipped' : ''})${filters.length ? `, filtered to host(s) matching [${filters.join(', ')}]` : ''}. pup can now auto-sign-in as you on those sites: navigate a pup window to one and it fills + (with submitReason) submits. Passwords were dropped from memory; pup_credentials lists host+username only.`,+ }) });+ return;+ } case 'pup_prewarm': { // Install Chrome for Testing WITHOUT opening a window — the warm-before- // first-use path. Default non-blocking so AD/HD can fire-and-forget on