← Commit history
Update 5 file(s)
5 files changed
+25−11
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.20+0.1.21
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser (Chrome + Edge)",- "version": "0.1.20",+ "version": "0.1.21", "expectedExtensionVersion": "0.1.2", "description": "Drives the user's REAL signed-in Chrome/Edge via an installed extension (native-messaging transport). Mirrors the puppeteer bridge's verb surface as nbrowser_* but runs inside the actual logged-in profile, so cookies/SSO/saved-logins/captcha human-trust all work. The bridge binds an ephemeral loopback port (PORTS.md compliant) and writes a discovery file; the native-messaging host pumps stdio <-> this bridge; the extension never touches a TCP port.", "homepage": "https://wiki.adom.inc/adom/adom-browser-extension",
bridge/package.json+1−1@@ -1,6 +1,6 @@ { "name": "adom-native-browser-bridge",- "version": "0.1.20",+ "version": "0.1.21", "private": true, "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)", "main": "server.js",
bridge/server.js+18−4@@ -1148,7 +1148,7 @@ const VERB_META = { nbrowser_fullscreen: { hint: 'Fullscreen the window (hides tabs/URL bar) - great for clean recordings of a BACKGROUND window. ' + NO_FG, related: ['nbrowser_window_state'], pitfalls: ['F11-via-key does NOT work; use this verb', 'stays in the background (auto-reverted) unless userRequestedForeground:true - it does not raise the window over the user\'s work'] }, nbrowser_login_state: { hint: 'Is the profile signed in to a URL right now (via chrome.cookies)?', related: ['nbrowser_credentials'], pitfalls: ['cookie-based inference, not definitive - a page may still challenge for 2FA even when auth cookies exist', 'never returns the password; Chrome autofills it when you focus the field'] }, nbrowser_credentials: { hint: 'Read-only QUERY of the browser\'s own saved-login store (which vendors the user can auto-login to). Never returns passwords.', related: ['nbrowser_login_state', 'nbrowser_navigate'], pitfalls: ['if a vendor is not listed, the user has no saved login - ask them to save one once', 'never exposes the password; Chrome autofills it when you focus the field'] },- nbrowser_profiles: { hint: 'List every BROWSER x PROFILE you can drive + which is active. Each row carries browser (chrome|edge), email, displayName (the human label the user sees on the browser profile button, e.g. "John Personal" vs "adom.inc"), and profileDir. Pick work-vs-personal from the email DOMAIN + displayName, never from which window happens to be focused. Drive several in parallel with profile:"<key>".', related: ['nbrowser_use_profile', 'nbrowser_profile_block', 'nbrowser_resolve_profiles'], pitfalls: ['active is only the default when no profile is passed', 'an entry with unresolved:true has no email yet - nbrowser_resolve_profiles first, or you may drive the wrong account', 'the same email can live in Chrome AND Edge - match browser too'] },+ nbrowser_profiles: { hint: 'List every BROWSER x PROFILE you can drive + which is active. Each row carries browser (chrome|edge), email, displayName (the human label the user sees on the browser profile button, e.g. "John Personal" vs "adom.inc"), and profileDir. Pick work-vs-personal from the email DOMAIN + displayName, never from which window happens to be focused. Drive several in parallel with profile:"<key>".', related: ['nbrowser_use_profile', 'nbrowser_profile_block', 'nbrowser_resolve_profiles'], pitfalls: ['active is only the default when no profile is passed', 'an entry with unresolved:true has no email yet - nbrowser_resolve_profiles first, or you may drive the wrong account', 'the same email can live in Chrome AND Edge - match browser too', 'asleep:true = the profile exists (Local State) but has no open window so its extension is not running - it cannot be driven until the user opens a window in it; never claim a profile "does not exist" when it is merely asleep'] }, nbrowser_use_profile: { hint: 'Set the default profile that verbs target.', related: ['nbrowser_profiles', 'nbrowser_pin_session'], pitfalls: ['you can still override per-call with profile:"<email>"'] }, nbrowser_pin_session: { hint: 'Pin a sessionId to a profile so EVERY verb carrying that sessionId auto-routes to it (no cross-profile mistakes when driving several accounts). open_window pins automatically to the profile it opened in; use this to re-pin explicitly.', related: ['nbrowser_profiles', 'nbrowser_resolve_profiles', 'nbrowser_open_window'], pitfalls: ['pins are per-session; a verb with an explicit profile: still overrides the pin', 'if the pinned profile goes offline, verbs fall back to the active profile'] }, nbrowser_resolve_profiles: { hint: 'Force every connected extension to re-resolve its Google email and re-announce, so pending-N / empty-email keys eagerly become chrome:<email> / edge:<email> - call it up front so you never drive the wrong (unresolved) profile.', related: ['nbrowser_profiles', 'nbrowser_pin_session', 'nbrowser_whoami'], pitfalls: ['takes ~2.5s (waits for each re-announce)', 'a profile not signed into Google stays email-less - target it by id-key or browser:'] },@@ -1409,7 +1409,17 @@ function dispatch(verb, args) { if (!row.email) row.unresolved = true; return row; });- return resolve({ success: true, output: JSON.stringify({ ok: true, profiles: list, active: activeProfile, blocklist: [...blocklist], _hint: 'Each entry is one BROWSER×PROFILE the AI can drive - `profile` is the addressable key like "edge:[email protected]" (browser:email). displayName = the human label the USER sees on the browser\'s own profile button ("John Personal", "adom.inc") - use it together with the email DOMAIN to pick work vs personal (corporate domain = work; gmail.com etc = personal). NEVER assume "the" browser: the same email can exist in Chrome AND Edge - match `browser` too. An entry with unresolved:true has not announced its email yet - call nbrowser_resolve_profiles before driving so you never hit the wrong account. Target profiles in PARALLEL by passing profile:"<key>" on any verb. ▶active is only the default when no target is passed. Switch with nbrowser_use_profile; exclude with nbrowser_profile_block.' }) });+ // COMPLETENESS: also report every profile the BROWSER knows about (Local State) whose extension is+ // NOT connected right now - a profile with no open window has a SLEEPING service worker, so it+ // never announces, but it absolutely exists and the user may use it daily (real gripe: media@+ // missing from the list read as "the bridge is lazy"). asleep:true = exists, not drivable until a+ // window opens in it.+ const liveEmails = new Set(list.map((r) => `${r.browser}:${(r.email || '').toLowerCase()}`));+ for (const kp of knownProfiles()) {+ if (liveEmails.has(`${kp.browser}:${kp.email.toLowerCase()}`)) continue;+ list.push({ profile: kp.key, label: kp.email, email: kp.email, browser: kp.browser, displayName: kp.displayName, profileDir: kp.profileDir, hasAvatar: kp.hasAvatar, active: false, blocked: isBlocked(kp.key), live: false, asleep: true });+ }+ return resolve({ success: true, output: JSON.stringify({ ok: true, profiles: list, active: activeProfile, blocklist: [...blocklist], _hint: 'Each entry is one BROWSER×PROFILE the AI can drive - `profile` is the addressable key like "edge:[email protected]" (browser:email). displayName = the human label the USER sees on the browser\'s own profile button ("John Personal", "adom.inc") - use it together with the email DOMAIN to pick work vs personal (corporate domain = work; gmail.com etc = personal). NEVER assume "the" browser: the same email can exist in Chrome AND Edge - match `browser` too. unresolved:true = connected but email not announced yet (nbrowser_resolve_profiles first, or you may drive the wrong account). asleep:true = the profile EXISTS on this machine but has NO OPEN WINDOW, so its extension service worker is asleep and it CANNOT be driven yet - ask the user to open a window in that profile (do NOT launch one yourself without the notify+foregroundReason flow); once open it appears live here. Target profiles in PARALLEL by passing profile:"<key>" on any verb. ▶active is only the default when no target is passed. Switch with nbrowser_use_profile; exclude with nbrowser_profile_block.' }) }); } if (verb === 'nbrowser_use_profile') { const want = String((args && (args.profile || args.email)) || '');@@ -1898,9 +1908,13 @@ function knownProfiles() { try { const j = JSON.parse(fs.readFileSync(path.join(root, 'Local State'), 'utf8')); const cache = (j.profile && j.profile.info_cache) || {};- for (const info of Object.values(cache)) {+ for (const [dir, info] of Object.entries(cache)) { const email = String((info && info.user_name) || '');- if (email.includes('@')) out.push({ email, key: `${browser}:${email}` });+ if (!email.includes('@')) continue;+ const names = browser === 'edge' ? ['Edge Profile Picture.png', 'Google Profile Picture.png'] : ['Google Profile Picture.png', 'Edge Profile Picture.png'];+ let hasAvatar = false;+ for (const n of names) if (fs.existsSync(path.join(root, dir, n))) { hasAvatar = true; break; }+ out.push({ email, key: `${browser}:${email}`, browser, displayName: String((info && info.name) || ''), profileDir: dir, hasAvatar }); } } catch {} }
native-browser-bridge-manifest.json+4−4@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.1.20",- "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.20/native-browser-bridge-v0.1.20.bin",- "sha256": "b4783120ea6ccb8e174c210439b23a1ba19cd985e4cb5e96c4a4e76107beeea1",- "size": 73263,+ "version": "0.1.21",+ "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.21/native-browser-bridge-v0.1.21.bin",+ "sha256": "9f45fd1a016126decbf0d319857c1efe53482073be2034d14432be0c71d8eab6",+ "size": 73882, "released_at": "2026-07-18T00:00:00Z" }