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
Wiki auto-sign: pup now signs its wiki window in AUTOMATICALLY from the user's identity that AD already stores at ~/.adom/session.json on the same desktop (mints a single-use browser-session magic link server-side from that bearer token and navigates the vault window to it), instead of ever making the user hand-type a login into pup's no-memory browser. Applies to BOTH the jump-list logged-in-view toggle and the AI wikiView:authed open path; manual /login is now only the fallback when AD itself is signed out or the session expired.
4 files changed
+99−15
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.87",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.87/adom-bridge-puppeteer-v1.9.87.zip",- "sha256": "ff2f07358ce80cd7b7308bc428f2ee08e30aebfef6f9fee507ea784ce8db2b71",- "size": 358865,+ "version": "1.9.88",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.88/adom-bridge-puppeteer-v1.9.88.zip",+ "sha256": "61bd6d99d0dae80d5a80ab9b82946dc18a0bd3642b6ca6d52ebc80431a399096",+ "size": 360597, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.87+1.9.88
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.87",+ "version": "1.9.88", "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+93−9@@ -2675,6 +2675,63 @@ async function checkWikiAuth(page) { }); } catch (e) { return { authed: false }; } }+// v1.9.88 — AUTO-SIGN the wiki from AD's stored identity, so pup NEVER makes the user hand-type a+// login into its no-memory browser. John, verbatim: "the ai calling you knows exactly who i am and+// ad knows who i am. so that's 2 potential places to get the info from and then you're asking me to+// login from scratch." Right. AD writes the user's wiki session token to ~/.adom/session.json on+// THIS desktop (the same box the bridge runs on), so pup mints a single-use browser-session magic+// link SERVER-SIDE from that token — the same endpoint an already-authed page uses, but authorized+// by the bearer token instead of a page cookie (verified: POST returns {url, expires_in} for a+// bearer with no browser) — and navigates the vault window straight to it. The human is asked for+// nothing; the shared vault profile then persists the session ~30 days. Manual /login is the+// fallback ONLY when AD itself is signed out (no token) or the session expired.+function readDesktopWikiToken() {+ try {+ const j = JSON.parse(fs.readFileSync(path.join(ADOM_STATE_DIR, 'session.json'), 'utf8'));+ if (!j || !j.session_token) return null;+ const exp = j.expires_at ? Date.parse(j.expires_at) : NaN;+ if (Number.isFinite(exp) && exp < Date.now()) return null; // expired — force the manual path+ return { token: j.session_token, user: j.user_name || j.user_email || null };+ } catch (e) { return null; }+}+async function mintWikiMagicUrl(nextPath) {+ const tok = readDesktopWikiToken();+ if (!tok) return null;+ try {+ const r = await fetch('https://wiki.adom.inc/api/v1/auth/browser-session', {+ method: 'POST',+ headers: { 'content-type': 'application/json', 'authorization': 'Bearer ' + tok.token, 'user-agent': 'adom-pup-bridge' },+ body: JSON.stringify({ next: nextPath || '/' }),+ });+ if (!r.ok) return null;+ const j = await r.json();+ if (!j || !j.url) return null;+ // The endpoint returns ?ticket=...; the deep-link is appended client-side (same as the in-page+ // mint above + the adom-wiki CLI), else the redeem lands on the wiki ROOT and the user loses the+ // page they were on.+ const sep = j.url.includes('?') ? '&' : '?';+ return j.url + sep + 'next=' + encodeURIComponent(nextPath || '/');+ } catch (e) { return null; }+}+// Sign the given pup page into the wiki vault IN PLACE from the desktop token. Returns the user+// label on success (mint + navigate succeeded), or null to fall back to a manual login. A successful+// mint means the token was accepted (a bad token yields no url), so navigation is the real signal;+// the auth re-check is only to surface the live display name and never gates the result.+async function autoSignWikiFromDesktop(page, nextPath) {+ const magic = await mintWikiMagicUrl(nextPath);+ if (!magic) return null;+ try {+ await page.goto(magic, { waitUntil: 'domcontentloaded', timeout: 25000 });+ } catch (e) { return null; }+ let who = null;+ for (let i = 0; i < 6; i++) {+ let a = null; try { a = await checkWikiAuth(page); } catch (e) {}+ if (a && a.authed) { who = a.user; break; }+ await new Promise(r => setTimeout(r, 1000));+ }+ const tk = readDesktopWikiToken();+ return who || (tk && tk.user) || 'authed';+} // v1.8.97: gentle IN-TAB mode indicator (John: "indicate which mode that pup window is in ... // in the chrome ui in a gentle way"). Can't touch the native tab-strip empty space (browser UI, // not reachable), but the TAB TITLE is: a single leading monochrome glyph shows on the tab.@@ -5888,6 +5945,16 @@ const server = http.createServer(async (req, res) => { if (session && isAdomUrl(args.url)) { if (args.wikiView === 'authed') { try { _wikiAuth = await Promise.race([checkWikiAuth(session.page), new Promise(r => setTimeout(() => r({ authed: false }), 3500))]); } catch (e) {} } }+ // v1.9.88 — authed view requested but the vault isn't signed in yet: AUTO-SIGN from AD's+ // stored desktop token BEFORE reporting wikiLoginNeeded, so the AI-driven open never makes+ // the user log in by hand either (same mechanism as the jump-list toggle).+ if (session && isAdomUrl(args.url) && args.wikiView === 'authed' && (!_wikiAuth || !_wikiAuth.authed)) {+ try {+ const _np = (() => { try { const u2 = new URL(args.url); return u2.pathname + u2.search; } catch (e) { return '/adom'; } })();+ const _who = await autoSignWikiFromDesktop(session.page, _np);+ if (_who) { _wikiAuth = { authed: true, user: _who !== 'authed' ? _who : undefined, autoSigned: true }; console.log(`[wikiview] "${sessionId}" open wikiView:authed AUTO-SIGNED from AD desktop token (${_who})`); }+ } catch (e) {}+ } const wikiHint = wikiViewHint(args.url, args.wikiView === 'authed' ? 'authed' : 'public', _wikiAuth); // v1.8.97: gentle in-tab glyph — 🔓 authed (logged in), 📖 public. if (session && isAdomUrl(args.url)) {@@ -6259,16 +6326,33 @@ const server = http.createServer(async (req, res) => { }, nextPath); } if (!targetUrl) {- // v1.9.82 — FIRST-EVER LOGIN: no authenticated pup page exists to mint a magic- // link from. The old behaviour fell through to a relaunch under the empty shared- // authed profile, saw still-not-authed, and popped a "sign in in this window"- // toast — but LEFT THE WINDOW ON THE PUBLIC PAGE. The user (John) clicked the task,- // got told to sign in, and had no sign-in page to sign in on. Fix: navigate THIS- // window to the actual wiki sign-in page and raise it so they SEE it.- // /login?redirect=<path> bounces through the hydrogen SSO and returns here signed- // in; once signed in this window IS the logged-in view, and every other thread's- // toggle can then mint the magic link from it. No relaunch, tabs preserved.+ // v1.9.88 — AUTO-SIGN from AD's stored identity BEFORE ever asking the human. No+ // already-authed pup page exists to mint from, but AD wrote the user's wiki session+ // token to ~/.adom/session.json on THIS desktop, so pup mints the magic link from+ // that token server-side and navigates this window straight to it. The user is asked+ // for nothing; the vault profile then persists it ~30 days. Manual /login is only the+ // fallback when AD itself is signed out (no token) or the ~30-day session expired. const nextPath = (() => { try { const u2 = new URL(url); return u2.pathname + u2.search; } catch (e) { return "/adom"; } })();+ const _autoWho = await autoSignWikiFromDesktop(session.page, nextPath);+ if (_autoWho) {+ session._wikiView = "authed";+ try { await setWikiModeGlyph(session.page, "● Logged in"); } catch (e) {}+ try { await setWikiAuthedState(session, sessionId, true); } catch (e) {}+ session._wikiJumplistKey = undefined;+ updateWikiJumplist(session, sessionId).catch(() => {});+ updateThumbnailTooltip(session, sessionId).catch(() => {});+ session._foreground = true;+ try { await session.page.bringToFront(); } catch (e) {}+ try { await osRaiseSessionWindow(session, sessionId); } catch (e) {}+ wikiCaption(`Adom wiki: signed you in automatically${_autoWho && _autoWho !== 'authed' ? ` as ${_autoWho}` : ''} — no login needed`, 6000).catch(() => {});+ console.log(`[wikiview] "${sessionId}" AUTO-SIGNED from AD desktop token (${_autoWho}) — no manual login`);+ sendJSON(res, { success: true, output: JSON.stringify({+ ok: true, view: "authed", autoSigned: true, url: session.page.url(),+ _hint: "pup signed this window into the Adom wiki AUTOMATICALLY using the identity AD already holds on this desktop (~/.adom/session.json) — no manual login. The shared vault profile now carries the session, so every thread's authed view works and it persists ~30 days.",+ }) });+ return;+ }+ // Fall back to the manual sign-in page (AD is signed out, or the session expired). const loginUrl = "https://wiki.adom.inc/login?redirect=" + encodeURIComponent(nextPath); console.log(`[wikiview] "${sessionId}" no authed session yet — navigating THIS window to the sign-in page: ${loginUrl}`); await session.page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 25000 });