First-time install, end to end: from a container to your real browser doing something useful

This page answers one question in detail: you are an AI working in a cloud container (or the WSL2 container inside Hydrogen Desktop), the user asks you to do something in their real browser. What actually happens, step by step, the very first time? There are several moving pieces and they live 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 adom-desktop (AD) on the user's laptop over the relay, using the adom-desktop nbrowser_* CLI. On first use you ask AD to install the bridge (one call), the user (or AD) gets the extension loaded into Chrome/Edge, AD registers the tiny native-messaging host, and from then on every action is just adom-desktop 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.

The five pieces, and which machine each lives on

Piece Lives on Role
Your AI session + skills the container (cloud or HD/WSL2) You. The installed skills (driving-the-extension, native-browser-safety, ...) teach you the nbrowser_* verbs and the safety rules.
adom-desktop (AD) 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 AD's bridges-cache) AD-spawned Node server. Binds an ephemeral loopback port, exposes /command to AD, and a raw-TCP loopback the host dials. Handles nbrowser_* and delegates wgc recording to AD.
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.
 CONTAINER (cloud or HD/WSL2)                 USER'S LAPTOP
 +---------------------------+      relay      +-------------------------------------------+
 |  your AI session          |  wss://...:8765 |  adom-desktop (AD)                         |
 |  + installed skills       |  ============>  |     |  routes nbrowser_*                  |
 |  `adom-desktop nbrowser_*`|                 |     v                                     |
 |  `adom-wiki ...` (install)|                 |  bridge/server.js  (ephemeral loopback)   |
 +---------------------------+                 |     ^  raw-TCP loopback                   |
                                               |     |                                     |
                                               |  host/native-host.js (stdio <-> TCP pump) |
                                               |     ^  native-messaging framing (stdio)   |
                                               |     |                                     |
                                               |  extension (MV3 SW) in Chrome / Edge      |
                                               +-------------------------------------------+

Endpoints: what talks to what (and what does NOT)

  • Container -> AD: the relay, wss://<container>.adom.cloud:8765. You speak adom-desktop nbrowser_*.
  • AD -> 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 discovery file. The host reads the file; the extension never learns a port.
  • extension <-> host: Chrome native-messaging framing over stdio (4-byte length + JSON).
  • What is NOT an endpoint: there is no container-side port to the extension, and the extension never opens a socket. An MV3 extension cannot host a port, and per PORTS.md no fixed port survives Windows HNS anyway, so native messaging is the only compliant transport. The laptop reaches back to the container through the same relay the rest of AD uses.

So to your earlier question: no, the container does not run a CLI that talks to the extension. It runs adom-desktop, and AD owns the rest of the chain.

First-time install, step by step (through your first useful action)

0. Discovery (before anything is installed)

The user says something like "drive my real Chrome" / "log into DigiKey for me" / "control my browser." The wiki discovery layer matches that intent to this page via its discovery_triggers (adom-wiki discover find "..."), so you find adom/adom-browser-extension without it being pre-installed.

1. Install the package (skills land in the container)

adom-wiki pkg install adom/adom-browser-extension

This runs install.sh, which copies the bundled skills into ~/.claude/skills/ in the container. Now your session knows the nbrowser_* surface and the safety doctrine. (Details: see How skills propagate, below.) This step is container-local; it does not touch the laptop.

2. Install the bridge on the laptop (one AD call)

adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/pages/adom/adom-browser-extension/files/native-browser-bridge-manifest.json"}'

AD downloads the bridge zip the manifest points to, sha256-verifies it, and extracts it into %LOCALAPPDATA%\Adom Desktop\bridges-cache\native-browser\. AD's registry auto-discovers it and routes the nbrowser_ prefix to it. You only do this once - AD persists the manifest URL and auto-updates the bridge on launch and every 4 hours (AD 1.9.26+), so future bridge versions land without a reinstall.

3. Register the native-messaging host (laptop, no admin)

The host is what lets the extension reach the bridge. On a managed/golden-image machine this is baked by install/install.ps1, which:

  • copies bridge/ into the cache (same as step 2 for unmanaged installs),
  • writes host/host-manifest.json (its path -> host/native-host.bat, allowed_origins -> the exact extension ID), and
  • registers it under HKCU\Software\Google\Chrome\NativeMessagingHosts\inc.adom.native_browser (and the Edge key with -Edge). HKCU means no UAC prompt.

4. Get the extension into Chrome and/or Edge

This is the piece that is not done by the bridge, and - important - not built into adom-desktop either. AD deliberately stays generic: it exposes only OS-automation primitives (desktop_navigate, desktop_find_control, desktop_ui_click, desktop_ui_set, desktop_screenshot). The extension-specific dev-mode install is orchestrated by code WE own in this repo (the installing-the-extension + driving-the-desktop skills), which drive those generic verbs to perform the load-unpacked. AD provides the clicks; we own the recipe. Three ways the extension gets loaded:

  • Chrome Web Store / Edge Add-ons (production) - but ~2 months out. The user installs it (or a managed policy force-installs it by store ID); required for silent install on a normal, non-enterprise machine. Store review is slow (budget ~2 months for the broad-permission set), so until then, dev-mode load-unpacked below is the day-one install path for real users.
  • Dev-mode "Load unpacked", driven by our own orchestration over AD's generic verbs. Our installer (see installing-the-extension) opens chrome://extensions / edge://extensions in a background window, toggles Developer mode, clicks Load unpacked, fills the native folder picker to the extension/ folder, then verifies the card loaded + captures the extension ID, syncs the host manifest's allowed_origins to that ID, and confirms nbrowser_status connects. This is the code we own and keep hardening; the folder-picker step is the fragile part we iterate on.
  • Manual load-unpacked. The user does it themselves (the Fork and develop guide shows it).

The install drives the user's screen in the BACKGROUND - never force the browser to the foreground. Stealing focus (desktop_bring_to_front, or any SendInput verb that foregrounds first) is rude AND corrupts the install: if the user keeps typing, their keystrokes land in the control you just focused (e.g. the extensions search box), silently changing your filter and hiding the card you were acting on. We learned this live - a foregrounded search became Adoms and the extension "vanished." The orchestration uses background UIA (desktop_ui_click / desktop_ui_set / desktop_navigate / desktop_screenshot_window) throughout. The one unavoidable foreground is the folder-picker fill: the picker opens behind the user's windows, so the agent brings it to front (desktop_bring_to_front) and then types the path itself (ctrl+l -> desktop_type -> Enter -> Select Folder) - a brief (~2s) burst with a "please don't type" caption. Verified live: that focus beat is required (SendInput sent blind goes to the user's foreground window, not the picker). Do NOT ask the user to type/paste the ~50-char path, and do NOT rely on the clipboard (desktop_clipboard_set did not stick on a real machine). For consent, use notify_user (a Start button) + notify_response to wait for the click - then run the fast script.

What each step looks like (real screenshots)

1. The chrome://extensions page - Developer mode on (top-right), the Load unpacked button, and (after install) the Adom card loaded clean: Enabled, the pinned ID, an active service worker, and no red "Errors" button. The screenshot is the only check that catches a card that loaded with errors, so always read it. (Tip: after desktop_navigate, verify the page actually rendered - the tab title becomes "Extensions" - not just that the omnibox shows the URL.)

chrome://extensions: Developer mode on, Load unpacked, and the Adom card loaded clean

2. The Load-unpacked folder picker, pointed at the extension/ folder (icons, src, Folder = "extension"). It opens in an arbitrary last-used directory, so the agent brings it to the front and types the path (bring_to_front -> ctrl+l -> type -> Enter), then screenshot-verifies the path before clicking Select Folder - the wrong folder loads a broken/old build silently:

The Load-unpacked folder picker pointed at the extension folder

Chrome and Edge are independent: load it in whichever the user wants, or both. The host is registered per browser (step 3), so each browser the extension runs in connects its own host.

5. First connect - how the bridge knows the extension is there

When the extension's service worker starts (which happens once that browser has at least one open window in a driven profile), it calls connectNative('inc.adom.native_browser'). Chrome launches the 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-desktop nbrowser_status     # ext connected? which browser/profile?
adom-desktop 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 knows the extension is connected; it cannot see an installed-but-asleep extension until it dials in.

6. Your first useful action

Always work in a window you open (never the user's existing ones - see native-browser-safety):

adom-desktop nbrowser_open_window '{"url":"https://www.digikey.com","focused":false}'   # background; returns a sessionId
adom-desktop nbrowser_screenshot  '{"sessionId":"<id>","maxWidth":1500}'                # see the page without foregrounding it
adom-desktop 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-desktop nbrowser_close_window '{"sessionId":"<id>"}'

That round-trip - container -> relay -> AD -> 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.

Who installs what (the short answer to "does the bridge install the extension?")

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-desktop 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 AD-driven Load unpacked, or the user Not the bridge. The bridge is the runtime server; it does not push the extension into the browser.

No, the bridge does not install the extension into the browser, and it does not need to: the extension arrives via the store or via AD driving chrome://extensions, and the bridge only ever talks to it once it connects.

After the first install: it is all adom-desktop nbrowser_*

Once the four laptop pieces exist (bridge + host + extension + the connection), the container side is just the adom-desktop 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

Skills are plain SKILL.md files shipped in the package's files[] and copied into ~/.claude/skills/<name>/ by install.sh (removed by uninstall.sh). Two layers reach a user:

  • Pre-install discovery: the page's discovery_triggers surface it by intent, so an agent finds the page (and its README) before anything is installed.
  • Post-install knowledge: after adom-wiki pkg install, the bundled skills load into the agent's context, giving it the full verb surface + the safety rules.

The main skill is driving-the-extension; the rest are sub-skills focused on one topic (native-browser-safety, native-browser-recording, driving-the-desktop, cross-platform-dev), plus site recipes that teach one website's flow (driving-sites is the index; driving-gusto, reading-google-messages are examples). They all install together from this one repo.

Third-party sub-skills: how the extension gets really good

The recipe set is meant to grow, and anyone can add to it. Two supported paths:

  1. Contribute a sub-skill into this repo (a PR). Add skills/driving-<site>/SKILL.md (copy the template in driving-sites), register it in install.sh, uninstall.sh, package.json files[], and the driving-sites index, then open a PR on this page (adom-wiki pr ...). Merged recipes ship to every user on the next package version.
  2. Own and version it yourself, then breadcrumb it back. Publish your recipe as your own wiki page (with its own discovery_triggers) and drop a breadcrumb on this page (adom-wiki breadcrumb ..., the "I built an add-on for X" trail marker). It stays discoverable from here while you keep ownership and your own release cadence.

Either way the new skill is discoverable by intent, so the more people add site recipes and task playbooks, the more the extension can do out of the box. A good sub-skill is narrow and concrete: the site, the selectors, the multi-step flow, and the gotchas - written so an AI can follow it cold.

Honest status (what is live vs pending)

  • Live: the container -> relay -> AD -> bridge -> host -> extension round-trip, the nbrowser_* surface, bridge_install + auto-update, HKCU host registration, load-unpacked in Chrome and Edge, skill install via the package.
  • Pending: Chrome Web Store / Edge Add-ons publication (required for silent install on normal, non-enterprise machines - until then production installs lean on load-unpacked or managed policy), and the macOS host/policy port (Windows-first; the bridge and extension are already OS-agnostic).

See also: Architecture (in the main README), the Fork and develop guide (docs/DEVELOPING.md), and the native-browser-safety skill for the rules every agent must follow when driving the real browser.