← Commit history
0.2.4: nbrowser_uninstall cascade hook (ab 2.0.35) + adom-bridge CLI name sweep
24 files changed
+106−70
README.md+4−4bridge/BRIDGE_VERSION+1−1bridge/bridge.json+4−2bridge/package.json+1−1bridge/server.js+42−8dev-skills/developing-the-bridge/SKILL.md+1−1docs/INSTALL-FLOW.md+15−15docs/UNINSTALL-FLOW.md+1−1extension/src/cdp.js+1−1extension/src/native.js+1−1install/README.md+1−1install/install.ps1+1−1native-browser-bridge-manifest.json+5−5package.json+2−2page.json+1−1skills/adom-native-browser/SKILL.md+4−4skills/driving-the-desktop/SKILL.md+1−1skills/driving-the-extension-demo/SKILL.md+1−1skills/driving-the-extension/SKILL.md+14−14skills/installing-the-extension/SKILL.md+1−1skills/native-browser-recording/SKILL.md+1−1skills/native-browser-safety/SKILL.md+1−1skills/uninstalling-the-extension/SKILL.md+1−1tools/verbs-policy.json+1−1README.md+4−4@@ -73,7 +73,7 @@ that rule is enforced in code, not policy. Every layer-1 response tells the AI honestly what it could not do and what layer 2 would add, so the upgrade suggestion arrives at a natural moment instead of as a wall. -Cloud containers, Hydrogen, and the `adom-bridge-cli` CLI all reach this the same way they reach pup:+Cloud containers, Hydrogen, and the `adom-bridge` CLI all reach this the same way they reach pup: `relay -> Bridge -> bridge`. ## Who we are, and why this extension needs broad browser capabilities@@ -311,7 +311,7 @@ about a minute, you click one **Start** button, that's it. How the AI drives it (the flow, proven end-to-end on a fresh machine): 1. **Detect** - a plain request like *"log into DigiKey for me"* surfaces this tool via wiki discovery;- the AI checks `adom-bridge-cli nbrowser_readiness`.+ the AI checks `adom-bridge nbrowser_readiness`. 2. **Install the bridge if missing** - `bridge_install {manifestUrl}`. Bridge provisions Node itself (system, or a portable no-UAC copy) and spawns the bridge - **you are never asked to install Node or anything else.**@@ -330,7 +330,7 @@ Cardinal rule throughout: **the AI does everything it can itself - it never make | Dependency | Required? | Why | |---|---|---|-| **[Bridge](https://wiki.adom.inc/adom/adom-bridge)** (`>= 2.0.3`) | **Yes** | This is a **bridge that rides Bridge**. The container drives it entirely through the `adom-bridge-cli` CLI (`nbrowser_*` verbs relayed to the bridge). Bridge also spawns the bridge, provisions its Node runtime, and auto-updates it. The one hard dependency. |+| **[Bridge](https://wiki.adom.inc/adom/adom-bridge)** (`>= 2.0.3`) | **Yes** | This is a **bridge that rides Bridge**. The container drives it entirely through the `adom-bridge` CLI (`nbrowser_*` verbs relayed to the bridge). Bridge also spawns the bridge, provisions its Node runtime, and auto-updates it. The one hard dependency. | | **Node** (in Bridge) | Yes, **auto-provided** | The bridge + native host are Node. **You never install it** - Bridge provisions Node 22 (system if present, else a portable no-UAC copy) and the bridge publishes that path so the Chrome-launched host reuses it. (SDK runtime contract.) | | **Chrome and/or Edge** | Yes | The extension loads into your real browser. Either works (one codebase); the extension ID is pinned so the native-host registration is deterministic. | | The **MV3 extension + native-messaging host** | Yes, **installed by us** | Shipped in this repo (`extension/`, `host/`); the AI deploys + registers them during auto-install. |@@ -423,7 +423,7 @@ node bridge/server.js # (Edge: ...\Microsoft\Edge\NativeMessagingHosts\...) # 4. The extension popup should show "connected". Then through ab:-# adom-bridge-cli nbrowser_status -> {ok:true, ext:"nbe", browser:"chrome"|"edge"}+# adom-bridge nbrowser_status -> {ok:true, ext:"nbe", browser:"chrome"|"edge"} ``` ## Recording - full-framerate, in the background (the doctrine)
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.2.3+0.2.4
bridge/bridge.json+4−2@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser",- "version": "0.2.3",+ "version": "0.2.4", "expectedExtensionVersion": "0.2.0", "description": "Drives the user's REAL signed-in Chrome/Edge, in two layers. Layer 1 (nb) needs NOTHING installed and works on any machine: nbrowser_open_os_window (open a URL in the user's real profile, background+flashing, the OAuth/consent path), nbrowser_os_windows, nbrowser_profiles, nbrowser_wake_profile, nbrowser_force_close. Layer 2 (nbe, the Adom Native Browser Extension) unlocks the rest: CDP driving (navigate/click/read/screenshot) inside their logged-in profile. The bridge binds an ephemeral loopback port and the native-messaging host pumps stdio <-> bridge.", "homepage": "https://wiki.adom.inc/adom/nb-bridge",@@ -89,6 +89,7 @@ "nbrowser_taskbar", "nbrowser_describe", "nbrowser_handle_dialog",+ "nbrowser_uninstall", "nbrowser_dev_reload", "nbrowser_clear_cookies", "nbrowser_caption",@@ -149,5 +150,6 @@ "seconds": 120 } ]- }+ },+ "uninstall": "nbrowser_uninstall" }
bridge/package.json+1−1@@ -1,6 +1,6 @@ { "name": "adom-native-browser-bridge",- "version": "0.2.3",+ "version": "0.2.4", "private": true, "description": "Native-browser bridge for Bridge (ab): layer 1 (nb) plus layer 2 via nbe, the Adom Native Browser Extension (Chrome + Edge).", "main": "server.js",
bridge/server.js+42−8@@ -462,11 +462,11 @@ async function wgcRecord(verb, args) { return { success: false, error: 'ab is too old for native window recording',- _hint: 'desktop_record_window_* shipped in Bridge - upgrade ab (adom-bridge-cli --version must be >= 1.8.148; older builds mis-route to the puppeteer bridge). Meanwhile use method:"getdisplaymedia" or the "Record this tab" toolbar button.',+ _hint: 'desktop_record_window_* shipped in Bridge - upgrade ab (adom-bridge --version must be >= 1.8.148; older builds mis-route to the puppeteer bridge). Meanwhile use method:"getdisplaymedia" or the "Record this tab" toolbar button.', }; } if (r.status >= 200 && r.status < 300 && out && out.success !== false && !out.error) {- const stopHint = 'mp4 is on the laptop at out.path (under %TEMP%\\adom-bridge-recordings) - fetch it with: adom-bridge-cli pull_file {"filePaths":["<path>"]}';+ const stopHint = 'mp4 is on the laptop at out.path (under %TEMP%\\adom-bridge-recordings) - fetch it with: adom-bridge pull_file {"filePaths":["<path>"]}'; const startHint = 'recording started in the BACKGROUND - the user keeps working; do NOT foreground the window (foregrounding is the user\'s choice, never the AI\'s). ⚠️ If frames come out BLANK, the recorded Chrome was NOT launched with the occlusion-disable flags (--disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-background-timer-throttling --disable-features=CalculateNativeWinOcclusion) - Chrome stops painting an occluded window without them. pup\'s Chrome already has these (so it background-records fine); a default user Chrome must be relaunched with them. Poll nbrowser_record_status {method:"wgc",recordingId}; stop with nbrowser_record_stop {method:"wgc",recordingId}.'; return { success: true, output: JSON.stringify({ ok: true, method: 'wgc',@@ -1248,7 +1248,7 @@ function _hostManifestPaths() { const OWN_ONLY = 'ONLY operate on a window/tab YOU opened via nbrowser_open_window. NEVER navigate/reload/close a window or tab the user already had open - the bridge drives their REAL browser, so you would destroy their live page, forms, sessions, or other AI threads.'; // The politeness mandate, repeated verbatim on every focus-relevant verb. The bridge ENFORCES it in code // (background-by-default + focus-steal auto-revert + focus_violation events); this hint tells the AI why.-const NO_FG = 'Open in the BACKGROUND — users automating the browser almost always have OTHER things in the foreground, and a raised window STEALS their keyboard focus (it has typed a user\'s own keystrokes into a username field and dismissed Chrome\'s password dropdown mid-task). Background is where automation SUCCEEDS. NEVER fall back to OS-level launches (Start-Process/launch/msedge/chrome --new-window) or bring_to_front/maximize to surface a window. To genuinely foreground (some auth pages / browser features need it) you must do real work: (1) FIRST warn the user with a Bridge notify (adom-bridge-cli notify_user {message:"Adom is about to bring a browser window forward for <reason>"}), then (2) pass userRequestedForeground:true AND foregroundReason:"<why>". A bare userRequestedForeground with no foregroundReason STAYS background; any driven window that lands topmost is auto-reverted + logged (focus_violation / foreground_raised).';+const NO_FG = 'Open in the BACKGROUND — users automating the browser almost always have OTHER things in the foreground, and a raised window STEALS their keyboard focus (it has typed a user\'s own keystrokes into a username field and dismissed Chrome\'s password dropdown mid-task). Background is where automation SUCCEEDS. NEVER fall back to OS-level launches (Start-Process/launch/msedge/chrome --new-window) or bring_to_front/maximize to surface a window. To genuinely foreground (some auth pages / browser features need it) you must do real work: (1) FIRST warn the user with a Bridge notify (adom-bridge notify_user {message:"Adom is about to bring a browser window forward for <reason>"}), then (2) pass userRequestedForeground:true AND foregroundReason:"<why>". A bare userRequestedForeground with no foregroundReason STAYS background; any driven window that lands topmost is auto-reverted + logged (focus_violation / foreground_raised).'; const VERB_META = { nbrowser_status: { hint: 'Connectivity check: connected profiles + active browser. Call this first if any verb errors "not connected".', related: ['nbrowser_profiles', 'nbrowser_ping', 'nbrowser_open_window'], pitfalls: ['a profile\'s extension SLEEPS when that profile has no open windows - it then shows disconnected; open a window to wake it'] }, nbrowser_readiness: { hint: 'READ-ONLY cold-start probe: is the native-browser extension connected + ready to drive? NEVER spawns work or installs anything. state:"ready" = drive now; state:"needs-extension" = offer to install it (Level-2, see installing-the-extension) or it is asleep (open a window in that profile). Also reports chromeInstalled: if false, the user has no native Chrome and you can offer nbrowser_install_chrome.', related: ['nbrowser_status', 'nbrowser_open_window', 'nbrowser_install_chrome'], pitfalls: ['"needs-extension" can mean not-installed OR installed-but-asleep - nbrowser_status\'s tooltip distinguishes', 'this verb never installs; to install, run the installing-the-extension flow', 'for a machine-wide "what do I have" answer prefer ab\'s bridge_readiness (aggregates all bridges, spawns none)'] },@@ -1311,13 +1311,14 @@ const VERB_META = { nbrowser_record_start: { hint: RECORD_WGC_HINT, related: ['nbrowser_record_stop', 'nbrowser_record_status'], next: 'nbrowser_record_status', pitfalls: ['⭐ prefer method:"wgc" (delegates to core desktop_record_window_start)', 'getdisplaymedia/tabcapture are REFUSED without fallbackConfirmed:true - a browser extension cannot silently screen-record'] }, nbrowser_record_stop: { hint: 'Stop a recording and finalize the mp4 (pull it with pull_file).', related: ['nbrowser_record_status'], pitfalls: ['needs the recordingId from record_start, not a sessionId'] }, nbrowser_record_status: { hint: 'Poll a recording.', related: ['nbrowser_record_start'], pitfalls: ['pass the SAME method (wgc|getdisplaymedia|tabcapture) + recordingId you started with', 'blank frames on a WGC recording = the recorded Chrome lacks the occlusion-disable launch flags'] },- nbrowser_record_list: { hint: 'List recent recordings.', related: ['nbrowser_record_status'], pitfalls: ['paths are on the LAPTOP - pull them with adom-bridge-cli pull_file, do not expect them in the container'] },+ nbrowser_record_list: { hint: 'List recent recordings.', related: ['nbrowser_record_status'], pitfalls: ['paths are on the LAPTOP - pull them with adom-bridge pull_file, do not expect them in the container'] }, nbrowser_flash: { hint: 'Flash the driven window\'s taskbar button - the "come look" cue (e.g. at a captcha/2FA/login human-gate).', related: ['nbrowser_flash_stop', 'nbrowser_flash_mode'], pitfalls: ['auto-clears on focus; nbrowser_flash_stop to clear early'] }, nbrowser_flash_stop: { hint: 'Stop the taskbar flash.', related: ['nbrowser_flash'], pitfalls: ['the flash already auto-clears when the user focuses the window - only stop it early if you no longer need their attention'] }, nbrowser_flash_mode: { hint: 'Set auto-flash verbosity: quiet (come-look moments: page load, screenshot, error/refusal, human-gate, spawned window) | chatty (every action) | off (no auto-flash at all). Flash manually with nbrowser_flash at task-done.', related: ['nbrowser_flash'], pitfalls: ['off is the user opt-out - if the user says the orange flashing is annoying, set flash_mode:"off" and it stops entirely', 'quiet is the default; the flash is a "come look" cue, so keep it for human-gates even if you dial the rest down'] }, nbrowser_taskbar: { hint: 'Paint progress/overlay/flash on the window taskbar button (e.g. paused=yellow for a human gate). The "Adom is driving" badge is AUTOMATIC: the bridge composites the profile\'s circle avatar + the Adom favicon at open/drive, and idle-expires it (~3 min) back to the plain native avatar - you rarely need to touch overlay yourself.', related: ['nbrowser_flash'], pitfalls: ['clear progress+flash at task end: {progress:{state:"none"},flash:"stop"} - the badge clears itself (idle-expiry / close)', 'overlay.badge:"none" RESTORES the native avatar look (the bridge never blanks the overlay slot - blanking would erase the user\'s profile avatar)'] }, nbrowser_describe: { hint: 'Returns per-verb schemas + hint/related/pitfalls for ab\'s Verbs tab.', related: ['nbrowser_help'], pitfalls: ['this is the catalog - a few extension-only verbs (e.g. nbrowser_login_form) are real but resolved in the extension, not listed here'] }, nbrowser_handle_dialog: { hint: 'Press OK/Cancel (or answer a prompt) on a native JS dialog (alert/confirm/prompt/beforeunload) BLOCKING the page. accept:true=OK/Yes/Leave · accept:false=Cancel/Stay · text:"…" answers a prompt.', related: ['nbrowser_screenshot', 'nbrowser_click'], next: 'nbrowser_screenshot', pitfalls: ['these dialogs are NOT in a screenshot and have NO hwnd (Chrome renders them) - you learn of them via a modal_open error or a screenshot _modal note', 'the page is FROZEN until you handle it: clicks/typing/eval return modal_open until you do', 'if the AI "keeps failing to fill a form", suspect an open dialog - handle it, then re-screenshot'] },+ nbrowser_uninstall: { hint: 'CASCADE HOOK, not for normal use: ab calls this itself during bridge_uninstall (ab 2.0.35+) with {cascade:true} BEFORE deleting the bridge cache, so nb can remove artifacts that live outside it - above all the HKCU native-messaging host registration, which would otherwise leave every profile dialing a deleted binary forever ("Native host has exited"). Also clears ~/.adom/bridges/native-browser, the activity trail, and the icon cache.', related: ['nbrowser_readiness'], pitfalls: ['you almost never call this directly - run ab\'s bridge_uninstall and let it cascade', 'it deliberately does NOT remove the nbe extension from the user\'s browser (their profile, their choice) - it only stops working; use uninstalling-the-extension to remove it properly', 'best-effort by contract: a failure here still lets ab delete the cache, so check removed[]/failed[] rather than assuming'] }, nbrowser_dev_reload: { hint: 'MAINTAINER-only: reload the extension to pick up on-disk code changes (2-pass, reconnects in ~10s).', related: ['nbrowser_status'], pitfalls: ['throttled to 1/90s (a reload restarts the SW + native host)', 'not for normal driving - only after editing the extension source', 'reloads ONE profile; other profiles pick up the new code on their own next reload'] }, nbrowser_clear_cookies: { hint: 'Background logout: drop a site\'s cookies (incl. httpOnly) for ONE registrable domain, leaving other sites (e.g. Google SSO) intact.', related: ['nbrowser_login_state'], pitfalls: ['scoped to one domain so it never nukes unrelated sessions', '⚠️ do NOT use this to log OUT of a site that ties device-trust to cookies (e.g. Gusto) - it forces a fresh 2FA; use the site\'s own Sign out'] }, nbrowser_caption: { hint: 'Paint an on-page caption (subtitle or red alert banner) for demos/narration.', related: ['nbrowser_cursor'], pitfalls: ['only on a window you opened', 'pass action:"hide" or empty text to dismiss'] },@@ -1556,6 +1557,7 @@ function dispatch(verb, args) { d('nbrowser_clear_cookies', 'Background logout: drop one registrable domain\'s cookies (incl. httpOnly), leaving other sites (e.g. Google SSO) intact.', { host: 'required str (e.g. "gusto.com"; url/domain also accepted)' }, { ok: 'bool', removed: 'int', hosts: '[str]' }, 30, { example: { host: 'gusto.com' } }), d('nbrowser_caption', 'Paint an on-page caption (subtitle, or red alert banner) for demos and narration.', { sessionId: S, text: 'required str (or action:"hide")', style: 'optional "subtitle"(default)|"alert"', position: 'optional str', fontSize: 'optional int', pulse: 'optional bool', durationMs: 'optional int (default 5000)', action: 'optional "hide"' }, { ok: 'bool', tabId: 'str', style: 'str' }, 20, { example: { sessionId: 's1', text: 'Signing in to DigiKey' } }), d('nbrowser_cursor', 'Show or move the gliding Adom cursor with an intent label (demo narration). Visual only, it does not click.', { sessionId: S, action: 'optional "move"(default)|"hide"', x: 'optional int', y: 'optional int', label: 'optional str (or intent)' }, { ok: 'bool', tabId: 'str', action: 'str', label: 'str' }, 20, { example: { sessionId: 's1', x: 400, y: 300, label: 'Clicking Sign in' } }),+ d('nbrowser_uninstall', 'CASCADE HOOK (ab 2.0.35+): ab calls this during bridge_uninstall, before deleting the cache, so nb removes its own out-of-cache artifacts (HKCU native-messaging host for Chrome + Edge, ~/.adom/bridges/native-browser, activity trail, icon cache). Never removes the extension from the user\'s browser.', { cascade: 'bool (set by ab)', name: 'str (set by ab)' }, { ok: 'bool', removed: '[str]', failed: '[str]', extensionStillInstalled: '[str]' }, 30, { example: { cascade: true, name: 'native-browser' } }), d('nbrowser_dev_reload', 'MAINTAINER-only: reload the extension to pick up on-disk code changes (2-pass, reconnects in ~10s). Throttled to 1 per 90s.', { profile: P }, { ok: 'bool', reloaded: 'bool', throttled: 'bool?' }, 30, { example: { profile: 'chrome:[email protected]' } }), d('nbrowser_describe', 'This verb: returns per-verb schemas for the ab Verbs tab.', {}, { success: 'bool', verbs: '[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example}]' }, 15, { example: {} }), ];@@ -1817,6 +1819,38 @@ function dispatch(verb, args) { })(); return; }+ // nbrowser_uninstall - the CASCADE hook (ab 2.0.35+). ab calls this BEFORE deleting our cache dir,+ // with {cascade:true,name}, so we can remove artifacts that live OUTSIDE that dir while our files+ // still exist. THE ONE THAT MATTERS: the HKCU native-messaging host registrations. Chrome/Edge keep+ // pointing at native-host.bat forever; once the cache is gone that path is dead and every profile+ // with the extension gets "Native host has exited" with no way to diagnose it. We own that key, so+ // we clean it. We deliberately do NOT remove the extension from the user's browser: that lives in+ // THEIR profile and is THEIR choice (uninstalling-the-extension walks them through it if they want).+ if (verb === 'nbrowser_uninstall') {+ (async () => {+ const removed = [], failed = [];+ const HOST = 'inc.adom.native_browser';+ for (const vendor of ['Google\\Chrome', 'Microsoft\\Edge']) {+ const key = `HKCU\\Software\\${vendor}\\NativeMessagingHosts\\${HOST}`;+ try {+ const r = await abCommand(null, 'registry_delete', { path: key, reason: 'nb bridge uninstall cascade: removing our own native-messaging host registration so the browser stops dialing a deleted binary' });+ (r && r.success !== false ? removed : failed).push(key);+ } catch { failed.push(key); }+ }+ for (const p of [path.join(os.homedir(), '.adom', 'bridges', BRIDGE), ACT_FILE, ICON_DIR]) {+ try { fs.rmSync(p, { recursive: true, force: true }); removed.push(p); } catch { failed.push(p); }+ }+ const stillInstalled = knownProfiles().filter((kp) => (adomExtStateForProfile(kp.browser, kp.profileDir) || {}).installed).map((kp) => kp.key);+ resolve({ success: true, output: JSON.stringify({+ ok: true, cascade: !!(a && a.cascade), removed, failed,+ extensionStillInstalled: stillInstalled,+ _hint: `Cleaned nb's OWN artifacts: the HKCU native-messaging host registration (Chrome + Edge), ~/.adom/bridges/${BRIDGE}, the activity trail, and the icon cache. ab removes the bridge cache dir next.` ++ (stillInstalled.length+ ? ` NOT REMOVED (deliberately): the nbe extension is still loaded in ${stillInstalled.join(', ')} - it lives in the USER'S browser profile and is their choice. It will simply stop working (its native host is gone). To remove it properly walk them through the uninstalling-the-extension skill; never rip an extension out from under a user.`+ : ` No profile currently has the nbe extension installed, so nothing is left in the user's browser.`) }) });+ })();+ return;+ } if (verb === 'nbrowser_os_windows') { (async () => { let ws = []; let abOk = true;@@ -2072,7 +2106,7 @@ function dispatch(verb, args) { } if (Array.isArray(args.filePaths) && args.filePaths.length && args.chooserStatus !== true) { // both real modes read the files at serve time; a status probe needs no files const missing = (args.filePaths || []).filter((fp) => { try { return !fs.existsSync(fp); } catch { return true; } });- if (missing.length) return resolve({ success: false, errorCode: 'file_missing', error: `file_missing: ${missing.join(', ')} not found on the laptop`, _hint: 'Paths must be ABSOLUTE paths on the machine running the browser. Push files there first with adom-bridge-cli send_files, or pass files:[{name, contentBase64}] and the bridge stages them for you.' });+ if (missing.length) return resolve({ success: false, errorCode: 'file_missing', error: `file_missing: ${missing.join(', ')} not found on the laptop`, _hint: 'Paths must be ABSOLUTE paths on the machine running the browser. Push files there first with adom-bridge send_files, or pass files:[{name, contentBase64}] and the bridge stages them for you.' }); } } catch (e) { return resolve({ success: false, error: 'upload staging failed: ' + ((e && e.message) || e) }); } }@@ -2154,7 +2188,7 @@ async function onHostFrameInner(frame) { let payload = frame.payload || {}; // Screenshot file-save: huge base64 over the relay times out, so when the caller passes // toFile:true we write the PNG to a laptop temp file here and return its path instead of the- // base64. Pull it with `adom-bridge-cli pull_file` (binary chunked) - fast + no relay timeout.+ // base64. Pull it with `adom-bridge pull_file` (binary chunked) - fast + no relay timeout. const isShot = /screenshot/.test(p.verb); const isRec = p.verb === 'nbrowser_record_stop'; if (payload.base64 && ((isShot && p.toFile) || isRec)) {@@ -2165,7 +2199,7 @@ async function onHostFrameInner(frame) { const file = path.join(dir, `${isRec ? 'rec' : 'shot'}-${Date.now()}.${isRec ? 'webm' : 'png'}`); fs.writeFileSync(file, Buffer.from(payload.base64, 'base64')); const sizeKB = Math.round(Buffer.byteLength(payload.base64, 'base64') / 1024);- payload = { ...payload, path: file, savedToFile: true, sizeKB, _hint: 'Pull this with `adom-bridge-cli pull_file {filePaths:["' + file.replace(/\\/g, '\\\\') + '"]}` (binary, chunked, no relay timeout).' };+ payload = { ...payload, path: file, savedToFile: true, sizeKB, _hint: 'Pull this with `adom-bridge pull_file {filePaths:["' + file.replace(/\\/g, '\\\\') + '"]}` (binary, chunked, no relay timeout).' }; delete payload.base64; } catch (e) { payload._fileError = String((e && e.message) || e); } }@@ -2186,7 +2220,7 @@ async function onHostFrameInner(frame) { const f = payload.finder; if (f.annotatedShot) spill(f.annotatedShot, 'annotated'); if (Array.isArray(f.shots)) f.shots.forEach((s, i) => spill(s, (s.label || 'shot') + '-' + i));- f._hint = 'Pull annotatedShot.path / shots[].path with `adom-bridge-cli pull_file {filePaths:[...]}` (binary, no relay timeout). In the annotated overlay each candidate is a numbered box - ★=chosen, ⛔=occluded - and the blue crosshair is the click point.';+ f._hint = 'Pull annotatedShot.path / shots[].path with `adom-bridge pull_file {filePaths:[...]}` (binary, no relay timeout). In the annotated overlay each candidate is a numbered box - ★=chosen, ⛔=occluded - and the blue crosshair is the click point.'; } catch (e) { payload._finderFileError = String((e && e.message) || e); } } // taskbar: capture the driven window's hwnd at control-start, auto-flash per mode, and TEACH the
dev-skills/developing-the-bridge/SKILL.md+1−1@@ -388,7 +388,7 @@ Locked, and it answers the extension display-name question: - **ab** = Bridge, **ah** = Hydrogen. Single-letter shorthands are gone; every shorthand is 2+ chars, and a would-be single letter takes the Adom `a` prefix (w→aw, h→ah, b→ab). OS variants: `ab win`, `ah mac`, etc.-- CLI is **`adom-bridge-cli`** in the container; `adom-bridge` is the host-OS app launcher. The+- CLI is **`adom-bridge`** in the container; `adom-bridge` is the host-OS app launcher. The `adom-desktop` binary is GONE, not aliased. - pup's verbs are **`pup_*`**, renamed from `browser_*`, no aliases.
docs/INSTALL-FLOW.md+15−15@@ -6,10 +6,10 @@ actually happens, step by step, the very first time?** There are several moving on two different machines, so it is worth laying out precisely. > One-paragraph version: the **container never talks to the browser extension directly**. It talks to-> **Bridge (ab)** on the user's laptop over the relay, using the `adom-bridge-cli nbrowser_*` CLI.+> **Bridge (ab)** on the user's laptop over the relay, using the `adom-bridge nbrowser_*` CLI. > On first use you ask ab to install the **bridge** (one call), the user (or ab) gets the **extension** > loaded into Chrome/Edge, ab registers the tiny **native-messaging host**, and from then on every-> action is just `adom-bridge-cli nbrowser_*`. The container's only local artifact is the **skills** that+> action is just `adom-bridge nbrowser_*`. The container's only local artifact is the **skills** that > teach you those verbs. Nothing in the container listens for the browser; the laptop reaches back > through the relay. @@ -18,7 +18,7 @@ on two different machines, so it is worth laying out precisely. | Piece | Lives on | Role | |---|---|---| | **Your AI session + skills** | the **container** (cloud or ah/WSL2) | You. The installed skills (`driving-the-extension`, `native-browser-safety`, ...) teach you the `nbrowser_*` verbs and the safety rules. |-| **`adom-bridge-cli` (ab)** | the user's **laptop** | The hub. Receives your verbs over the relay, routes `nbrowser_*` to the bridge, ships results back. Always present. |+| **`adom-bridge` (ab)** | the user's **laptop** | The hub. Receives your verbs over the relay, routes `nbrowser_*` to the bridge, ships results back. Always present. | | **The bridge** (`bridge/server.js`) | the **laptop** (in ab's `bridges-cache`) | ab-spawned Node server. Binds an ephemeral loopback port, exposes `/command` to ab, and a raw-TCP loopback the host dials. Handles `nbrowser_*` and delegates `wgc` recording to ab. | | **The native-messaging host** (`host/native-host.js`) | the **laptop** | A tiny stdio<->TCP pump. Chrome/Edge launch it when the extension calls `connectNative`; it reads the bridge's discovery file and connects the extension to the bridge. | | **The extension** (`extension/`) | the user's **Chrome/Edge** | The hands. MV3 service worker that runs CDP + native `chrome.*` calls inside the user's real, signed-in profile. |@@ -28,7 +28,7 @@ on two different machines, so it is worth laying out precisely. +---------------------------+ relay +-------------------------------------------+ | your AI session | wss://...:8765 | Bridge (ab) | | + installed skills | ============> | | routes nbrowser_* |- | `adom-bridge-cli nbrowser_*`| | v |+ | `adom-bridge nbrowser_*`| | v | | `adom-wiki ...` (install)| | bridge/server.js (ephemeral loopback) | +---------------------------+ | ^ raw-TCP loopback | | | |@@ -41,7 +41,7 @@ on two different machines, so it is worth laying out precisely. ## Endpoints: what talks to what (and what does NOT) -- **Container -> ab:** the relay, `wss://<container>.adom.cloud:8765`. You speak `adom-bridge-cli nbrowser_*`.+- **Container -> ab:** the relay, `wss://<container>.adom.cloud:8765`. You speak `adom-bridge nbrowser_*`. - **ab -> bridge:** HTTP `POST /command` on the bridge's ephemeral `127.0.0.1` port (written to a discovery file; PORTS.md-compliant, never a fixed port). - **host -> bridge:** raw TCP on a second ephemeral loopback port the bridge writes to the same@@ -73,7 +73,7 @@ propagate*, below.) This step is container-local; it does not touch the laptop. ### 2. Install the bridge on the laptop (one ab call) ```-adom-bridge-cli bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/pages/adom/nb-bridge/files/native-browser-bridge-manifest.json"}'+adom-bridge bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/pages/adom/nb-bridge/files/native-browser-bridge-manifest.json"}' ``` ab downloads the bridge zip the manifest points to, sha256-verifies it, and extracts it into `%LOCALAPPDATA%\Adom Bridge\bridges-cache\native-browser\`. ab's registry auto-discovers it and@@ -152,8 +152,8 @@ window in a driven profile), it calls `connectNative('inc.adom.native_browser')` host, the host reads the bridge discovery file and dials the bridge's loopback, and sends a `hello` with the profile id. **That is the moment the bridge knows the extension is live.** Check it: ```-adom-bridge-cli nbrowser_status # ext connected? which browser/profile?-adom-bridge-cli nbrowser_profiles # every BROWSER x PROFILE you can drive+adom-bridge nbrowser_status # ext connected? which browser/profile?+adom-bridge nbrowser_profiles # every BROWSER x PROFILE you can drive ``` If `nbrowser_status` says not connected, the usual cause is that the profile has **no open window** (an MV3 service worker sleeps when its profile has no windows). Opening a window wakes it. The bridge@@ -162,11 +162,11 @@ knows the extension is **connected**; it cannot see an installed-but-asleep exte ### 6. Your first useful action Always work in a window **you** open (never the user's existing ones - see `native-browser-safety`): ```-adom-bridge-cli nbrowser_open_window '{"url":"https://www.digikey.com","focused":false}' # background; returns a sessionId-adom-bridge-cli nbrowser_screenshot '{"sessionId":"<id>","maxWidth":1500}' # see the page without foregrounding it-adom-bridge-cli nbrowser_credentials '{"match":"digikey"}' # is there a saved login?+adom-bridge nbrowser_open_window '{"url":"https://www.digikey.com","focused":false}' # background; returns a sessionId+adom-bridge nbrowser_screenshot '{"sessionId":"<id>","maxWidth":1500}' # see the page without foregrounding it+adom-bridge nbrowser_credentials '{"match":"digikey"}' # is there a saved login? # ...click the sign-in field (Chrome autofills), submit, then fetch the CAD file behind the login.-adom-bridge-cli nbrowser_close_window '{"sessionId":"<id>"}'+adom-bridge nbrowser_close_window '{"sessionId":"<id>"}' ``` That round-trip - container -> relay -> ab -> bridge -> host -> extension -> back - is the whole point: it runs in the user's real, signed-in profile, so cookies/SSO/saved-logins/captcha all just work.@@ -176,7 +176,7 @@ it runs in the user's real, signed-in profile, so cookies/SSO/saved-logins/captc | Component | Installed by | Notes | |---|---|---| | Skills (in the container) | `adom-wiki pkg install` -> `install.sh` | Container-local; this is all the container keeps. |-| Bridge (laptop cache) | `adom-bridge-cli bridge_install` (once) | Auto-updates after that. |+| Bridge (laptop cache) | `adom-bridge bridge_install` (once) | Auto-updates after that. | | Native-messaging host (HKCU) | `install/install.ps1` (or the golden-image bake) | Per-browser; no UAC. | | Extension (in Chrome/Edge) | Web Store / managed policy, **or** ab-driven `Load unpacked`, **or** the user | **Not the bridge.** The bridge is the runtime server; it does not push the extension into the browser. | @@ -184,10 +184,10 @@ it runs in the user's real, signed-in profile, so cookies/SSO/saved-logins/captc extension arrives via the store or via ab driving `chrome://extensions`, and the bridge only ever *talks to* it once it connects. -## After the first install: it is all `adom-bridge-cli nbrowser_*`+## After the first install: it is all `adom-bridge nbrowser_*` Once the four laptop pieces exist (bridge + host + extension + the connection), the container side is-just the `adom-bridge-cli nbrowser_*` CLI plus the skills that teach it. No re-install per task. The+just the `adom-bridge nbrowser_*` CLI plus the skills that teach it. No re-install per task. The bridge auto-updates; the extension auto-updates from the store (or you re-`Load unpacked` in dev). ## How skills propagate
docs/UNINSTALL-FLOW.md+1−1@@ -52,7 +52,7 @@ array (it is an owned popup) - then click **Cancel** instead of Remove. - **Edge:** identical via `edge://extensions`; verify the `edge:` key drops. - **Just unload vs full decommission:** removing the card unloads the extension. To fully remove it, also delete the HKCU native-messaging host (`...\NativeMessagingHosts\inc.adom.native_browser`, plus the Edge- key) and, if wanted, `adom-bridge-cli bridge_uninstall {name:"native-browser"}`. For "take it out of my+ key) and, if wanted, `adom-bridge bridge_uninstall {name:"native-browser"}`. For "take it out of my browser," Steps 1-4 are enough. ## Reinstall
extension/src/cdp.js+1−1@@ -933,7 +933,7 @@ export async function setFileInput(tabId, args) { const s = _chooserServed.get(Number(tabId)) || null; return { ok: true, armed: _chooserArm.has(Number(tabId)), served: !!s, ...(s ? { files: s.files, servedAgoMs: Date.now() - s.ts } : {}), _hint: s ? 'The intercepted file chooser was satisfied with the staged files.' : (_chooserArm.has(Number(tabId)) ? 'Still armed - click the Browse/Upload control now (nbrowser_click), then re-check.' : 'Not armed and nothing served. Arm with {armForNextChooser:true, filePaths} first.') }; }- if (!files.length) return { ok: false, errorCode: 'no_files', error: 'filePaths is empty', _hint: 'Pass filePaths:["C:/abs/path/on/the/laptop", ...]. Push files to the laptop first with adom-bridge-cli send_files (or use files:[{name,contentBase64}] - the bridge stages them for you).' };+ if (!files.length) return { ok: false, errorCode: 'no_files', error: 'filePaths is empty', _hint: 'Pass filePaths:["C:/abs/path/on/the/laptop", ...]. Push files to the laptop first with adom-bridge send_files (or use files:[{name,contentBase64}] - the bridge stages them for you).' }; if (args.armForNextChooser === true) { await send(tabId, 'Page.setInterceptFileChooserDialog', { enabled: true }); const prev = _chooserArm.get(Number(tabId)); if (prev && prev.timer) clearTimeout(prev.timer);
extension/src/native.js+1−1@@ -338,7 +338,7 @@ const FOREGROUND_JUSTIFY_HINT = 'password dropdown). Background is where automation SUCCEEDS (the user keeps working; screenshots, eval, clicks, ' + 'and typing all work on a background window). To genuinely foreground you must: (1) pass foregroundReason:"<why ' + 'the user must see this, or which browser/auth feature requires a foreground window>", and (2) FIRST warn the ' +- 'user with a Bridge notify (adom-bridge-cli notify_user {message:"Adom is about to bring a browser window forward for <reason>"}). ' ++ 'user with a Bridge notify (adom-bridge notify_user {message:"Adom is about to bring a browser window forward for <reason>"}). ' + 'Only then set userRequestedForeground:true + foregroundReason.'; // Note on the response when a raise IS granted — remind the AI it should have notified the user. const FOREGROUND_GRANTED_HINT = 'Raised to the FOREGROUND (you justified it). This interrupts the user — make sure you sent a Bridge notify first, do the ONE thing you needed the foreground for, then return to the background so you stop capturing their focus.';
install/README.md+1−1@@ -21,7 +21,7 @@ So **from a Claude session, do the bridges-cache copy via WSL2 `cp`** (bypasses cp -r bridge/* "/c/Users/<user>/AppData/Local/Adom Bridge/bridges-cache/native-browser/" ``` -…or via `adom-bridge-cli shell_execute`. The HKCU/HKLM registry writes and the `.bat`/manifest writes+…or via `adom-bridge shell_execute`. The HKCU/HKLM registry writes and the `.bat`/manifest writes (under this repo) are NOT in the trapped paths and are fine from PowerShell run outside Claude. ## Better long-term: wiki-streamed bridge
install/install.ps1+1−1@@ -3,7 +3,7 @@ -------------------------------------------------------------------------------- Registers the three pieces. Run OUTSIDE Claude Code (the %LOCALAPPDATA%\Adom Bridge path is AppContainer/wcifs-virtualized for Claude-spawned tools — see Bridge/CLAUDE.md). From a- Claude session, do the bridges-cache copy via WSL2 `cp` or `adom-bridge-cli shell_execute` instead.+ Claude session, do the bridges-cache copy via WSL2 `cp` or `adom-bridge shell_execute` instead. Usage: powershell -ExecutionPolicy Bypass -File install.ps1 -ExtensionId <id> [-Edge]
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.2.3",- "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.2.3/native-browser-bridge-v0.2.3.zip",- "sha256": "593e0ec16dfc47e8f568f3d8e76a5448d998f1b22f4ce12e9ac35a6076770000",- "size": 101449,- "released_at": "2026-08-13T13:38:42Z"+ "version": "0.2.4",+ "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.2.4/native-browser-bridge-v0.2.4.zip",+ "sha256": "a29d759c0d4127da8ce60a45248d8860dafbe58ad30ab0b5957094e11834347c",+ "size": 102936,+ "released_at": "2026-08-14T09:50:34Z" }
package.json+2−2@@ -1,6 +1,6 @@ { "slug": "nb-bridge",- "version": "0.6.2",+ "version": "0.6.3", "type": "app", "title": "NB - Native Browser Bridge", "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",@@ -93,7 +93,7 @@ "open it in my real browser", "native browser" ],- "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge-cli bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge-cli nbrowser_readiness' (state:needs-extension means offer the install).",+ "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge nbrowser_readiness' (state:needs-extension means offer the install).", "sample_prompts": [ { "label": "Open in my browser",
page.json+1−1@@ -70,5 +70,5 @@ }, "visibility": "public", "brief": "Your real browser, driven by AI.",- "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge-cli bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge-cli nbrowser_readiness' (state:needs-extension means offer the install)."+ "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge nbrowser_readiness' (state:needs-extension means offer the install)." }
skills/adom-native-browser/SKILL.md+4−4@@ -42,7 +42,7 @@ The extension is a **Level-2, proactively-offered** install: it is often NOT pre asks for it. Before you try to drive anything, probe (READ-ONLY, never triggers an install): ```-adom-bridge-cli nbrowser_readiness # THIS bridge: {ready,state,extensionConnected,profiles}+adom-bridge nbrowser_readiness # THIS bridge: {ready,state,extensionConnected,profiles} ``` - `state:"ready"` (extensionConnected:true, one or more profiles) -> go drive it (**driving-the-extension**).@@ -51,7 +51,7 @@ adom-bridge-cli nbrowser_readiness # THIS bridge: {ready,state,extensionCon user do it by hand. **For a broad "what tools do I have / is it ready?" question, call the ab-level aggregate-`adom-bridge-cli bridge_readiness` instead** - it is side-effect-free (never spawns a bridge or installs)+`adom-bridge bridge_readiness` instead** - it is side-effect-free (never spawns a bridge or installs) and reports every bridge's `state` at once. Use `nbrowser_readiness` when you specifically care about THIS bridge; use `bridge_readiness` for the survey. Neither ever triggers work. @@ -78,9 +78,9 @@ user to go from "never heard of it" to "logged in for me": container / WSL2 / Lima (`~/.claude/skills/`). **They surface BEFORE the extension exists** - that is deliberate. Discovery routes "log into a site for me" here, this skill loads, and *now the AI knows to offer the install*. `adom-wiki pkg install adom/nb-bridge` (or- `adom-bridge-cli sync_skills`) puts them there.+ `adom-bridge sync_skills`) puts them there. 2. **The nb bridge -> Bridge (ab).** The `nbrowser_*` verbs come from the `native-browser`- bridge. `adom-bridge-cli bridge_list` shows if it's installed; if not,+ bridge. `adom-bridge bridge_list` shows if it's installed; if not, `bridge_install {manifestUrl:"https://wiki.adom.inc/api/pages/adom/nb-bridge/files/native-browser-bridge-manifest.json"}`. It auto-updates from there (per-bridge `updateManifestUrl`). 3. **The MV3 extension -> the user's real Chrome/Edge.** The actual browser extension, loaded via
skills/driving-the-desktop/SKILL.md+1−1@@ -23,7 +23,7 @@ previous AI spent an hour re-discovering - follow it and you won't have to. tabs, extension-action **popup bubbles**) live *outside* the page - CDP never sees them. All verbs below are ab `desktop_*` verbs. **Pass `--target <laptop>`** (e.g. `AdomLapper`); need ab-ab (Bridge) on the box and a matching CLI (`adom-bridge-cli --version`; upgrade with+ab (Bridge) on the box and a matching CLI (`adom-bridge --version`; upgrade with `adom-wiki pkg install adom/adom-bridge`). ## Capture - and `coordMap` is your map
skills/driving-the-extension-demo/SKILL.md+1−1@@ -8,7 +8,7 @@ Parent skill: **driving-the-extension** # Native-browser DEMO playbook (`nbrowser_*`) This is the copy-paste recipe set for **driving and demoing** the user's real Chrome/Edge. Each-recipe is a sequence of `adom-bridge-cli nbrowser_*` / `desktop_*` calls proven live. For the full+recipe is a sequence of `adom-bridge nbrowser_*` / `desktop_*` calls proven live. For the full capability reference (CDP-vs-native, the debug bar, xy-finding, verbs), read the parent skill **driving-the-extension** first.
skills/driving-the-extension/SKILL.md+14−14@@ -72,7 +72,7 @@ posture**; foreground is **opt-in, only when the user explicitly asks to watch.* focus - if they are typing, your window captures their keystrokes (it has typed a user's message into a username field and killed Chrome's password dropdown). As of v0.1.0 a bare `userRequestedForeground:true` is **not enough**: without a `foregroundReason` the window stays background. To genuinely foreground-(some auth pages need it): **first** `adom-bridge-cli notify_user` to warn the user, **then** pass+(some auth pages need it): **first** `adom-bridge notify_user` to warn the user, **then** pass `userRequestedForeground:true` + `foregroundReason:"<why>"`, do the one thing, and return to background. The verbs are built for this: `nbrowser_open_window` opens **unfocused**, `open_tab` opens `active:false`, `switch_window` does **not** raise - leave those defaults alone. Capture without@@ -101,13 +101,13 @@ as a disruption. Observed live: a `desktop_navigate` to `chrome://extensions` le with no `bring_to_front` called. **Path:** `cloud → relay → Bridge (laptop) → native-browser bridge → native-messaging host →-extension service worker → back`. You call `adom-bridge-cli nbrowser_* '<json>'`.+extension service worker → back`. You call `adom-bridge nbrowser_* '<json>'`. ## Targeting (READ FIRST) If more than one Bridge is on the relay (laptop **AdomLapper** + the **winvm**), every call must name one or it errors `ambiguous_target`: ```bash-adom-bridge-cli --target AdomLapper nbrowser_status+adom-bridge --target AdomLapper nbrowser_status ``` A "session" = a real Chrome window (`sessionId` you choose, or auto `s1`…). `tabId` = the real chrome tab id. The session→window map persists to `chrome.storage`, so sessions survive reloads.@@ -291,13 +291,13 @@ This is **not** screenshot→eyeball-a-pixel→click automation, so it does **no `matchedText`/`changed` in the response after a text click. ```bash # click by VISIBLE TEXT (finder computes xy) - returns {x,y,matchedText}-adom-bridge-cli -t AdomLapper nbrowser_click '{"sessionId":"s1","text":"Pay","cursor":true,"intent":"Clicking the Pay button"}'-adom-bridge-cli -t AdomLapper nbrowser_click '{"sessionId":"s1","selector":"#download"}' # or by selector-adom-bridge-cli -t AdomLapper nbrowser_type '{"sessionId":"s1","selector":"#search","text":"STM32F103C8T6"}'-adom-bridge-cli -t AdomLapper nbrowser_press_key '{"sessionId":"s1","key":"Enter"}' # Enter/Tab/ArrowDown/…-adom-bridge-cli -t AdomLapper nbrowser_hover '{"sessionId":"s1","text":"Hover me"}' # fires :hover + tooltips-adom-bridge-cli -t AdomLapper nbrowser_hover '{"sessionId":"s1","selector":".info-icon"}' # or by selector (surest for hover)-adom-bridge-cli -t AdomLapper nbrowser_right_click '{"sessionId":"s1","selector":".ctx"}' # real context menu+adom-bridge -t AdomLapper nbrowser_click '{"sessionId":"s1","text":"Pay","cursor":true,"intent":"Clicking the Pay button"}'+adom-bridge -t AdomLapper nbrowser_click '{"sessionId":"s1","selector":"#download"}' # or by selector+adom-bridge -t AdomLapper nbrowser_type '{"sessionId":"s1","selector":"#search","text":"STM32F103C8T6"}'+adom-bridge -t AdomLapper nbrowser_press_key '{"sessionId":"s1","key":"Enter"}' # Enter/Tab/ArrowDown/…+adom-bridge -t AdomLapper nbrowser_hover '{"sessionId":"s1","text":"Hover me"}' # fires :hover + tooltips+adom-bridge -t AdomLapper nbrowser_hover '{"sessionId":"s1","selector":".info-icon"}' # or by selector (surest for hover)+adom-bridge -t AdomLapper nbrowser_right_click '{"sessionId":"s1","selector":".ctx"}' # real context menu ``` **Hover targets are often NON-interactive** (info icons, cards, tooltip triggers, plain spans). The finder first scans clickable elements (button/a/input/…); if `text` matches none, it **falls back to the@@ -334,9 +334,9 @@ Three ways to "see", and the choice matters: other work, is imprecise, and usually implies foregrounding. There is always a targeted option above. (`nbrowser_screenshot_full_res` is full-**PAGE**, not full-monitor - it still only sees the page.) ```bash-adom-bridge-cli -t AdomLapper nbrowser_screenshot '{"sessionId":"s1","toFile":true}' # PAGE bitmap → laptop PNG path-adom-bridge-cli -t AdomLapper desktop_screenshot_window '{"hwnd":<from open_window>}' # WINDOW + chrome + child popups-# then: adom-bridge-cli -t AdomLapper pull_file '{"filePaths":["<path>"],"saveTo":"/tmp"}'+adom-bridge -t AdomLapper nbrowser_screenshot '{"sessionId":"s1","toFile":true}' # PAGE bitmap → laptop PNG path+adom-bridge -t AdomLapper desktop_screenshot_window '{"hwnd":<from open_window>}' # WINDOW + chrome + child popups+# then: adom-bridge -t AdomLapper pull_file '{"filePaths":["<path>"],"saveTo":"/tmp"}' ``` Screenshots are large base64 - over the relay they can time out, so **prefer `toFile:true` + `pull_file`** (binary, chunked). `screenshot_full_res` is unresized; default resizes ≤1568 for the model.@@ -493,7 +493,7 @@ windows in the background. So: To say "come look" without forcing the window forward, use the taskbar **flash / overlay** above. - **Only** raise a window when the user EXPLICITLY asks to see it live ("show me", "let me watch"): ```bash-adom-bridge-cli -t AdomLapper desktop_bring_to_front '{"titleContains":"<page title>","state":"restore"}' # or {hwnd}+adom-bridge -t AdomLapper desktop_bring_to_front '{"titleContains":"<page title>","state":"restore"}' # or {hwnd} ``` ## 🎥 Recording doctrine - full-framerate background recording of the NATIVE browser
skills/installing-the-extension/SKILL.md+1−1@@ -43,7 +43,7 @@ This is handled automatically, and you usually do nothing: **If you ever need to write it explicitly** (e.g. a repair, or the host somehow launched before the bridge ever ran): ask ab for the managed Node path and drop it in yourself - ```-adom-bridge-cli runtimes # -> runtimes.node.exe (source: system|cache), e.g. C:/Program Files/nodejs/node.exe+adom-bridge runtimes # -> runtimes.node.exe (source: system|cache), e.g. C:/Program Files/nodejs/node.exe # then write that absolute path (one line, no newline games) to: # %USERPROFILE%\.adom\bridges\native-browser\node-path.txt ```
skills/native-browser-recording/SKILL.md+1−1@@ -57,7 +57,7 @@ rendering). So enabling them is a **temporary, opt-in mode** we enter for record ## The flow - record a logged-in demo in the background 1. **Confirm you actually need the real session.** If not, go use pup/Hydrogen. 2. **Check whether native Chrome already runs with the flags** - read its launch command line- (`adom-bridge-cli process_list {nameContains:"chrome"}` → the main `chrome.exe` with no `--type=`). pup's+ (`adom-bridge process_list {nameContains:"chrome"}` → the main `chrome.exe` with no `--type=`). pup's "Chrome for Testing" already has them; the user's real Chrome usually does not. 3. **If the flags are missing → ASK + WARN, then fully restart Chrome into background-recording mode:** - Tell the user plainly: *"To record at full framerate while you keep working, I need to fully restart
skills/native-browser-safety/SKILL.md+1−1@@ -119,7 +119,7 @@ their keystrokes (real incident: it typed a user's chat message into a username Chrome's password dropdown, twice). So a bare `userRequestedForeground:true` is **no longer enough** - without a `foregroundReason` the window **stays background**. To genuinely foreground (some auth pages / browser features truly need it):-1. **Warn the user first** with a Bridge notify: `adom-bridge-cli notify_user {message:"Adom is about to bring a browser window forward for <reason>"}`.+1. **Warn the user first** with a Bridge notify: `adom-bridge notify_user {message:"Adom is about to bring a browser window forward for <reason>"}`. 2. Then pass **`userRequestedForeground:true` + `foregroundReason:"<why>"`**. 3. Do the ONE thing that needed the foreground, then return to the background so you stop capturing focus. Every justified raise is logged as a `foreground_raised` event. And **never** fall back to an OS-level
skills/uninstalling-the-extension/SKILL.md+1−1@@ -75,7 +75,7 @@ desktop_caption {text:""} # clear - **Just unloading vs full removal:** removing the card unloads the extension and drops the SW. To fully decommission (not just unload), also: remove the HKCU native-messaging host (`HKCU\Software\Google\Chrome\NativeMessagingHosts\inc.adom.native_browser`, and the Edge key), and if- the user wants the bridge gone too, `adom-bridge-cli bridge_uninstall {name:"native-browser"}`. For a plain+ the user wants the bridge gone too, `adom-bridge bridge_uninstall {name:"native-browser"}`. For a plain "take it out of my browser," Steps 1-4 are enough. ## Recovery
tools/verbs-policy.json+1−1@@ -57,4 +57,4 @@ "eval": "mutating", "evaluate": "alias", "set_file_input": "mutating"-}+}