Hydrogen Desktop Bootstrap (apps + skills)
Public Made by Adomby adom
Layer Hydrogen Desktop's workspace onto a standard Adom workstation.
name: hd-open-url description: > The decision guide for "open a URL / open a website" inside Hydrogen Desktop. When the user says "open ti.com" / "pull up the datasheet page" / "show me that site", HD has SIX distinct ways to do it — a Hydrogen webview tab, an HD browser window, Pup (AI-drivable Chrome), a native desktop browser profile, the Browser Picker chooser, or headless curl/fetch — each with different trade-offs. READ THIS to pick the right one and to explain the options back to the user instead of silently guessing. For the interception/plumbing behind the picker see hd-browser-picker; to drive Pup see pup; for webview tabs see adom-workspace-control. Trigger words — open url, open website, open the site, open ti.com, open a link, pull up the page, show me the website, view this url, open in browser, which browser, how do I open, open the datasheet page, open in a tab, open in pup, open in chrome, headless fetch, ways to open a url, open link options, wv, open in wv, open as wv, webview tab, wv tab, webview, show me in webview, open in webview, in a webview, native webview. NEVER satisfy a webview request with VS Code's built-in Simple Browser — see hd-webview for the exact command.
HD — Opening a URL (your options)
⭐ THE GOVERNING RULE — background by default, foreground only through a justified gate
Whenever YOU (the AI) open a browser window, open it in the BACKGROUND so you never disturb what the user is doing. A window jumping to the foreground steals their focus and covers Hydrogen Desktop. This applies to EVERY path below that opens a real window (HD Native Open, the HD browser window, pup, the native browser).
To bring a window to the FOREGROUND you must pass a foregroundReason — a short
justification for why foregrounding is warranted right now. No reason = background. Legit
reasons: an AUTH/consent page the user must click NOW; the user explicitly said "bring it up"
/ "show me now". "It's more convenient" is NOT a reason. HD logs the reason so foregrounding
is auditable.
POST /open-in-profile/POST /open-url: omitforegroundReason→ background (the safe default). PassforegroundReason:"<why>"to foreground. (/auth/intentURLs are inherently justified and foreground automatically.)- When you DO foreground for the user, prefer telling them (an AD notify / a toast) rather than yanking their screen unannounced.
Everything below inherits this rule.
When a user says "open the ti.com website" (or any URL), HD has several paths and a Browser Picker — but don't hand the user the picker to decide. Pick the best destination for them by use-case and open it directly, then offer the alternatives in one line. The right path depends on what they're doing — look at it, have you drive it, keep it as a tab, or just read it. See "Decide for the user — DON'T hand them the picker" below for the rule.
The six ways
| # | Option | Best when… | What the user sees | AI drives it? |
|---|---|---|---|---|
| 1 | Hydrogen webview tab (aka wv) | the user wants the site inside HD, next to their work, and to keep it around | a panel/tab in the HD workspace | partly (reload/route) |
| 2 | HD Browser Window | a quick popout window owned by HD, not a full browser | a standalone HD-owned window | no |
| 3 | Pup (Puppeteer Chrome) | you need to act on the page — screenshot, click, scrape, fill a form, verify | a real Chrome window on their desktop | yes, fully |
| 4 | Native desktop browser | real logins/sessions, downloads, "just open it for me", auth flows | their own Chrome/Edge profile | no |
| 5 | Browser Picker | you're not sure, or it's an auth/OAuth link — let the user choose (5s auto-default) | a chooser dialog | n/a (routes to one of the above) |
| 6 | curl / fetch (headless) | you only need the content, not to show anything (read a datasheet, scrape JSON) | nothing — runs in the workspace | yes |
"wv" = webview — the shortcut (spread it like pup)
⛔ NEVER: VS Code's "Simple Browser" is NOT a webview
If the ask is "show me X in webview", the ONLY correct destination is option 1
(a native Hydrogen webview tab, adom-cli hydrogen webview open-or-refresh).
Never open VS Code's built-in Simple Browser / any editor-internal browser page
— that happened once from a setup prompt (2026-07-16) and it's a banned outcome.
The dedicated hd-webview skill has the exact command + verification.
wv is the short alias for "webview" — recognize it everywhere a user might say
"webview" (open it in wv, wv tab, shot the wv, wv mode). Treat wv and webview as
identical.
Help it catch on: the first time in a session a user types the long form "webview",
open it as normal and add a one-line nudge — "(btw you can just say wv — fewer
keystrokes)." Say it once, not every time (don't nag). This is exactly how
puppeteer → pup became universal among Adom folks; webview → wv is the same play.
(If the user already says wv, they got the memo — skip the nudge.)
How you invoke each
All of these run from inside the HD workspace. The control API base URL is the live
value in ~/.adom/hd-control-url — http://127.0.0.1:<dynamic> (WSL2 mirrored
networking shares loopback with the Windows host; the port is dynamic per launch).
Read it with BASE="$(cat ~/.adom/hd-control-url)" then hit "$BASE/<endpoint>".
1. Webview tab (wv) — add a web-view tab pointed at the URL (adom-workspace-control,
or adom-cli hydrogen webview open-or-refresh --name <X> --url <url> --panel-id <id>). Good
for "keep this open while we work." ⚠️ open-or-refresh needs --panel-id to CREATE a new
tab — without it you get "Tab 'X' not found and --panel-id not provided" (verified live
2026-06-14; get a panel id from adom-cli hydrogen workspace get). Re-running with the same
--name once it exists just navigates/refreshes (no panel-id needed). Same adom-cli on
web-Hydrogen and HD — the consistent surface.
2 / 4 / 5. Through the Browser Picker — the single unifying entry point. POST the URL and HD routes it (auto-picking a sensible default after a 5-second countdown, or showing the chooser):
BASE="$(cat ~/.adom/hd-control-url)" # http://127.0.0.1:<dynamic>
# Auto-route (picker picks the best default in 5s — hands-free)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://www.ti.com"}' \
"$BASE/open-url"
# Force the chooser dialog (no auto-timer — make the user pick)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://www.ti.com","force":true}' \
"$BASE/open-url"
# Route through the picker with the FRESH-WINDOW toggle pre-checked (the picked
# native browser opens in a new --new-window that auto-foregrounds — use for
# auth/OAuth so the consent page pops to the front by itself):
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://claude.ai/","fresh":true,"force":true}' \
"$BASE/open-url"
# …or bypass the picker and open directly+fresh in a specific browser:
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://claude.ai/","browser":"edge","profileDir":"Default","fresh":true}' \
"$BASE/open-in-profile"
⭐ HD Native Open — the LOGGED-IN native browser, in the BACKGROUND (no extension needed)
When you need the user's REAL, logged-in browser (adom-google auth, opening a Google Sheet
after auth, driving chip-fetcher or any signed-in site BEFORE the Adom extension/abe is
installed), use HD Native Open: POST /open-in-profile in a specific profile so it
carries their login, picker-silent, and, crucially, background:true so the window
sinks behind HD instead of covering it.
# 1. find the right logged-in profile (never assume — the user may have several accounts)
curl -s "$BASE/browser-profiles" # → [{browser, profileDir, email, ...}]
# 2. open it in the BACKGROUND in that profile (does NOT steal focus / cover HD)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://docs.google.com/spreadsheets/d/<id>","browser":"chrome","profileDir":"Default","background":true}' \
"$BASE/open-in-profile"
- Background is the DEFAULT (per the governing rule) — you don't even need to pass
background:true. OmitforegroundReasonand the window opens behind HD. A native browser jumping to the foreground over HD is jarring (it happened on setup). To foreground, passforegroundReason:"<why>"(HD logs it). AUTH consents (/auth/intent,oauth/authorize,claude.ai/login) and a user-chosen picker open foreground automatically — no reason needed. - ALWAYS pass a
profileDirfrom/browser-profilesso the window carries the user's login.browser:"default"with no profile lands them logged-OUT. - NEVER open a signed-in page with a bare OS-default open (
open::that) or the VS Code Simple Browser — those lose the profile/login and can't be backgrounded. HD Native Open is the correct tool. - Distinct from pup (logged-OUT, HD-owned Chromium) and abe/nbrowser (logged-in but needs the extension, not present during setup).
⚠️
/open-urloften replies"HD timed out after 5s"even when it SUCCEEDED (verified live 2026-06-14:{"url":"https://www.google.com"}reported the timeout, but the page opened in Edge). Don't treat that as failure — confirm withGET "$BASE/browser-picker/last-open"→{destination, url, fresh, ts}.fresh:false= routed to a remembered/default destination (no picker shown — e.g.browser:edge:Default);fresh:true= the picker was shown. (So if every domain has a remembered default, opens are silent — the picker only pops for un-remembered domains.)
The picker's auto-default heuristic: *.claude.ai/*.claude.com and Adom
/auth/intent → the user's work browser profile; unknown/right-click/iframe →
Pup; otherwise the user's last choice for that domain. See hd-browser-picker.
Fresh window ("fresh":true) — the native-browser path (#4) can open the URL
either as a tab in an already-running browser (default) or as a brand-new window/
process (--new-window). fresh is about window-vs-tab; it is NOT a foreground
switch — the governing rule still applies, so an AI fresh open is demoted to the
background unless it's an auth consent or you passed a foregroundReason. (Windows tries
to auto-foreground a freshly-launched window; HD sinks it back down when the open isn't
justified.) So for auth flows the user must complete (Claude/Adom sign-in) the consent
comes to the front by itself (auth is the justified case); for everything else, fresh
without a reason stays background. The Browser Picker defaults the "fresh window" toggle ON for auth URLs.
On /open-url, fresh pre-seeds that toggle (the picker still shows; add direct:true
to skip it); /open-in-profile opens fresh directly. See hd-browser-picker.
3. Pup — open and then drive it with adom-desktop browser_* verbs
(browser_open_window, browser_navigate, browser_screenshot, browser_eval,
browser_reload, …). Either open the URL directly via Pup, or let the picker
route to Pup. See the pup skill for the full verb set.
adom-desktop browser_eval '{"sessionId":"default","js":"document.title"}'
6. Headless content — if you only need to read the page, skip the browser entirely:
curl -sL https://www.ti.com/... # or fetch from your own code
Decide for the user — DON'T hand them the picker
Default behavior: pick the destination by use-case and open it DIRECTLY (bypass
the picker), then give a one-line heads-up + easy undo. Do NOT fire a plain
POST /open-url {url} and let the picker's generic 5-second auto-default decide —
that default is luck-of-the-draw and often wrong for the use-case (a vendor-login
site could land in Pup where the user has no login). Users don't want to make this
choice; make the smart one for them. (Mechanics: /open-in-profile opens a
specific native browser+profile directly; /open-url {direct:true} bypasses the
picker; Pup and webview-tab are direct calls. Reserve the picker only as below.)
| Use case | Open it in | How (bypass the picker) |
|---|---|---|
| Login-likely site — vendor/account/portal/cart: TI, Mouser, DigiKey, any "my…"/sign-in | the user's native browser + WORK profile (their saved login/cookies live there) | GET /browser-profiles → find the work profile (non-gmail) → POST /open-in-profile {url, browser, profileDir, fresh:true} |
| Building/testing their own app, or you need to drive/screenshot/scrape the page | Pup (AI-drivable) | open via Pup (browser_*) / route /open-url {url, direct:true} to Pup |
| Glance at content alongside their work | Hydrogen webview tab | adom-workspace-control add web-view tab |
| Genuinely ambiguous, OR an OAuth/sign-in the user must complete themselves | let them choose | POST /open-url {url, force:true} — the one time the picker is right |
Then say it in one line, with the undo: "Opened TI in your Chrome work profile (where your TI login lives) — say the word for a tab here, Pup, or a different profile." Decide, act, offer alternatives — don't ask first.
Example — "open the texas instruments website" (login-likely → native work profile):
BASE="$(cat ~/.adom/hd-control-url)"
curl -s "$BASE/browser-profiles" # pick the work profile (non-gmail email)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://www.ti.com","browser":"chrome","profileDir":"<work profileDir>"}' \
"$BASE/open-in-profile"
Govern routing policy — the picker is now a true FALLBACK (HD prefs API)
The picker's per-domain memory is a first-class API (HD app behavior), so the AI OWNS URL routing — the picker only pops for a domain with no stored policy AND no direct call. Use this to set policy the user would otherwise click through the Manager for ("always open DigiKey in my work profile", "stop auto-opening X in Edge").
BASE="$(cat ~/.adom/hd-control-url)"
# READ the policy map
curl -s "$BASE/browser-picker/prefs"
# → {ok, prefs:{domains:{"<host>":{destination,label}, …}}}
# SET / override one domain
curl -s -X PUT "$BASE/browser-picker/prefs" -H 'Content-Type: application/json' \
-d '{"domain":"digikey.com","destination":"browser:edge:Default","label":"Edge — Default"}'
# FORGET one domain (e.g. "stop auto-opening X in Edge")
curl -s -X DELETE "$BASE/browser-picker/prefs/digikey.com"
# CLEAR all
curl -s -X DELETE "$BASE/browser-picker/prefs"
- Stored value =
{destination, label}— NOTprofileDir/fresh. destinationis an ID:- internal:
hydrogen-tab(a webview/wv tab) ·webview-window·pup-window - native:
browser:<browser>:<dir>— e.g.browser:edge:Default,browser:chrome:Profile 1. The profile dir is encoded in the ID (no separate field).
- internal:
- Discover valid native IDs via
GET $BASE/browser-profiles(maps each detected profile → itsbrowser+dir); the 3 internal IDs are fixed above. freshis NOT stored in prefs — it's a per-OPEN flag (/open-url {fresh:…}//open-in-profile {fresh:…}). Don't try to persist it here.- Writes hit the live Manager UI immediately (the backend writes
hd-url-routing-prefslocalStorage + dispatcheshd-url-routing-prefs-changed; an open Browser Picker Manager re-reads at once, and re-reads on open anyway).
Net: for a one-off open, decide and call the direct endpoint (/open-in-profile,
/open-url {direct:true}, or a wv/pup call). For a standing preference, PUT the domain's
policy so even user-initiated link clicks route there silently. The picker is now only a
fallback for the genuinely-undecided.
Related skills
- hd-browser-picker — the interception layers + picker dialog behind #2/#4/#5
pup— driving Pup browser windows (#3): navigate, screenshot, eval, reloadadom-workspace-control— adding/managing Hydrogen webview tabs (#1)- hd-runtime-mode — none of this changes between the WSL2 and Docker runtimes
---
name: hd-open-url
description: >
The decision guide for "open a URL / open a website" inside Hydrogen Desktop.
When the user says "open ti.com" / "pull up the datasheet page" / "show me
that site", HD has SIX distinct ways to do it — a Hydrogen webview tab, an HD
browser window, Pup (AI-drivable Chrome), a native desktop browser profile,
the Browser Picker chooser, or headless curl/fetch — each with different
trade-offs. READ THIS to pick the right one and to explain the options back to
the user instead of silently guessing. For the interception/plumbing behind the
picker see hd-browser-picker; to drive Pup see pup; for webview tabs see
adom-workspace-control. Trigger words — open url, open website, open the site,
open ti.com, open a link, pull up the page, show me the website, view this url,
open in browser, which browser, how do I open, open the datasheet page, open in
a tab, open in pup, open in chrome, headless fetch, ways to open a url, open
link options, wv, open in wv, open as wv, webview tab, wv tab, webview,
show me in webview, open in webview, in a webview, native webview. NEVER
satisfy a webview request with VS Code's built-in Simple Browser — see
hd-webview for the exact command.
---
# HD — Opening a URL (your options)
## ⭐ THE GOVERNING RULE — background by default, foreground only through a justified gate
**Whenever YOU (the AI) open a browser window, open it in the BACKGROUND so you never
disturb what the user is doing.** A window jumping to the foreground steals their focus and
covers Hydrogen Desktop. This applies to EVERY path below that opens a real window (HD
Native Open, the HD browser window, pup, the native browser).
To bring a window to the FOREGROUND you must pass a **`foregroundReason`** — a short
justification for why foregrounding is warranted right now. No reason = background. Legit
reasons: an AUTH/consent page the user must click NOW; the user explicitly said "bring it up"
/ "show me now". "It's more convenient" is NOT a reason. HD logs the reason so foregrounding
is auditable.
- `POST /open-in-profile` / `POST /open-url`: **omit `foregroundReason` → background**
(the safe default). Pass `foregroundReason:"<why>"` to foreground. (`/auth/intent` URLs are
inherently justified and foreground automatically.)
- When you DO foreground for the user, prefer telling them (an AD notify / a toast) rather
than yanking their screen unannounced.
Everything below inherits this rule.
When a user says **"open the ti.com website"** (or any URL), HD has several paths
and a Browser Picker — but **don't hand the user the picker to decide.** Pick the
best destination *for them* by use-case and **open it directly**, then offer the
alternatives in one line. The right path depends on what they're doing — look at
it, have you drive it, keep it as a tab, or just read it. See **"Decide for the
user — DON'T hand them the picker"** below for the rule.
## The six ways
| # | Option | Best when… | What the user sees | AI drives it? |
|---|--------|-----------|--------------------|---------------|
| 1 | **Hydrogen webview tab** (aka **wv**) | the user wants the site *inside* HD, next to their work, and to keep it around | a panel/tab in the HD workspace | partly (reload/route) |
| 2 | **HD Browser Window** | a quick popout window owned by HD, not a full browser | a standalone HD-owned window | no |
| 3 | **Pup (Puppeteer Chrome)** | **you** need to act on the page — screenshot, click, scrape, fill a form, verify | a real Chrome window on their desktop | **yes, fully** |
| 4 | **Native desktop browser** | real logins/sessions, downloads, "just open it for me", auth flows | their own Chrome/Edge profile | no |
| 5 | **Browser Picker** | you're not sure, or it's an auth/OAuth link — let the user choose (5s auto-default) | a chooser dialog | n/a (routes to one of the above) |
| 6 | **curl / fetch (headless)** | you only need the *content*, not to show anything (read a datasheet, scrape JSON) | nothing — runs in the workspace | **yes** |
## "wv" = webview — the shortcut (spread it like `pup`)
## ⛔ NEVER: VS Code's "Simple Browser" is NOT a webview
If the ask is "show me X **in webview**", the ONLY correct destination is option 1
(a native Hydrogen webview tab, `adom-cli hydrogen webview open-or-refresh`).
**Never** open VS Code's built-in Simple Browser / any editor-internal browser page
— that happened once from a setup prompt (2026-07-16) and it's a banned outcome.
The dedicated `hd-webview` skill has the exact command + verification.
**`wv` is the short alias for "webview"** — recognize it **everywhere** a user might say
"webview" (`open it in wv`, `wv tab`, `shot the wv`, `wv mode`). Treat `wv` and `webview` as
identical.
**Help it catch on:** the *first* time in a session a user types the long form **"webview"**,
open it as normal **and** add a one-line nudge — *"(btw you can just say **wv** — fewer
keystrokes)."* Say it **once**, not every time (don't nag). This is exactly how
**puppeteer → `pup`** became universal among Adom folks; `webview → wv` is the same play.
(If the user already says `wv`, they got the memo — skip the nudge.)
## How you invoke each
All of these run from inside the HD workspace. The control API base URL is the live
value in `~/.adom/hd-control-url` — `http://127.0.0.1:<dynamic>` (WSL2 mirrored
networking shares loopback with the Windows host; the port is dynamic per launch).
Read it with `BASE="$(cat ~/.adom/hd-control-url)"` then hit `"$BASE/<endpoint>"`.
**1. Webview tab (wv)** — add a web-view tab pointed at the URL (`adom-workspace-control`,
or `adom-cli hydrogen webview open-or-refresh --name <X> --url <url> --panel-id <id>`). Good
for "keep this open while we work." ⚠️ **`open-or-refresh` needs `--panel-id` to CREATE a new
tab** — without it you get *"Tab 'X' not found and --panel-id not provided"* (verified live
2026-06-14; get a panel id from `adom-cli hydrogen workspace get`). Re-running with the same
`--name` once it exists just navigates/refreshes (no panel-id needed). Same `adom-cli` on
web-Hydrogen and HD — the consistent surface.
**2 / 4 / 5. Through the Browser Picker** — the single unifying entry point. POST
the URL and HD routes it (auto-picking a sensible default after a 5-second
countdown, or showing the chooser):
```bash
BASE="$(cat ~/.adom/hd-control-url)" # http://127.0.0.1:<dynamic>
# Auto-route (picker picks the best default in 5s — hands-free)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://www.ti.com"}' \
"$BASE/open-url"
# Force the chooser dialog (no auto-timer — make the user pick)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://www.ti.com","force":true}' \
"$BASE/open-url"
# Route through the picker with the FRESH-WINDOW toggle pre-checked (the picked
# native browser opens in a new --new-window that auto-foregrounds — use for
# auth/OAuth so the consent page pops to the front by itself):
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://claude.ai/","fresh":true,"force":true}' \
"$BASE/open-url"
# …or bypass the picker and open directly+fresh in a specific browser:
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://claude.ai/","browser":"edge","profileDir":"Default","fresh":true}' \
"$BASE/open-in-profile"
```
## ⭐ HD Native Open — the LOGGED-IN native browser, in the BACKGROUND (no extension needed)
When you need the user's REAL, logged-in browser (adom-google auth, opening a Google Sheet
after auth, driving chip-fetcher or any signed-in site BEFORE the Adom extension/abe is
installed), use **HD Native Open**: `POST /open-in-profile` in a specific profile so it
carries their login, picker-silent, and, crucially, **`background:true`** so the window
sinks behind HD instead of covering it.
```bash
# 1. find the right logged-in profile (never assume — the user may have several accounts)
curl -s "$BASE/browser-profiles" # → [{browser, profileDir, email, ...}]
# 2. open it in the BACKGROUND in that profile (does NOT steal focus / cover HD)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://docs.google.com/spreadsheets/d/<id>","browser":"chrome","profileDir":"Default","background":true}' \
"$BASE/open-in-profile"
```
- **Background is the DEFAULT (per the governing rule) — you don't even need to pass
`background:true`.** Omit `foregroundReason` and the window opens behind HD. A native
browser jumping to the foreground over HD is jarring (it happened on setup). To foreground,
pass `foregroundReason:"<why>"` (HD logs it). AUTH consents (`/auth/intent`, `oauth/authorize`,
`claude.ai/login`) and a user-chosen picker open foreground automatically — no reason needed.
- **ALWAYS pass a `profileDir`** from `/browser-profiles` so the window carries the user's
login. `browser:"default"` with no profile lands them logged-OUT.
- **NEVER** open a signed-in page with a bare OS-default open (`open::that`) or the VS Code
Simple Browser — those lose the profile/login and can't be backgrounded. HD Native Open
is the correct tool.
- Distinct from **pup** (logged-OUT, HD-owned Chromium) and **abe/nbrowser** (logged-in but
needs the extension, not present during setup).
> ⚠️ **`/open-url` often replies `"HD timed out after 5s"` even when it SUCCEEDED**
> (verified live 2026-06-14: `{"url":"https://www.google.com"}` reported the timeout, but the
> page opened in Edge). **Don't treat that as failure** — confirm with
> `GET "$BASE/browser-picker/last-open"` → `{destination, url, fresh, ts}`. `fresh:false` =
> routed to a remembered/default destination (no picker shown — e.g. `browser:edge:Default`);
> `fresh:true` = the picker was shown. (So if every domain has a remembered default, opens are
> silent — the picker only pops for un-remembered domains.)
The picker's auto-default heuristic: `*.claude.ai`/`*.claude.com` and Adom
`/auth/intent` → the user's work browser profile; unknown/right-click/iframe →
Pup; otherwise the user's last choice for that domain. See `hd-browser-picker`.
**Fresh window (`"fresh":true`)** — the native-browser path (#4) can open the URL
either as a tab in an already-running browser (default) or as a **brand-new window/
process** (`--new-window`). `fresh` is about window-vs-tab; it is **NOT** a foreground
switch — the governing rule still applies, so an AI `fresh` open is demoted to the
background unless it's an auth consent or you passed a `foregroundReason`. (Windows *tries*
to auto-foreground a freshly-launched window; HD sinks it back down when the open isn't
justified.) So for **auth flows the user must complete** (Claude/Adom sign-in) the consent
comes to the front by itself (auth is the justified case); for everything else, `fresh`
without a reason stays background. The Browser Picker defaults the "fresh window" toggle ON for auth URLs.
On `/open-url`, `fresh` pre-seeds that toggle (the picker still shows; add `direct:true`
to skip it); `/open-in-profile` opens fresh directly. See `hd-browser-picker`.
**3. Pup** — open and then drive it with `adom-desktop browser_*` verbs
(`browser_open_window`, `browser_navigate`, `browser_screenshot`, `browser_eval`,
`browser_reload`, …). Either open the URL directly via Pup, or let the picker
route to Pup. See the `pup` skill for the full verb set.
```bash
adom-desktop browser_eval '{"sessionId":"default","js":"document.title"}'
```
**6. Headless content** — if you only need to *read* the page, skip the browser
entirely:
```bash
curl -sL https://www.ti.com/... # or fetch from your own code
```
## Decide for the user — DON'T hand them the picker
**Default behavior: pick the destination by use-case and open it DIRECTLY (bypass
the picker), then give a one-line heads-up + easy undo.** Do NOT fire a plain
`POST /open-url {url}` and let the picker's generic 5-second auto-default decide —
that default is luck-of-the-draw and often wrong for the use-case (a vendor-login
site could land in Pup where the user has no login). Users don't want to make this
choice; make the smart one for them. (Mechanics: `/open-in-profile` opens a
specific native browser+profile directly; `/open-url {direct:true}` bypasses the
picker; Pup and webview-tab are direct calls. Reserve the picker only as below.)
| Use case | Open it in | How (bypass the picker) |
|---|---|---|
| **Login-likely site** — vendor/account/portal/cart: TI, Mouser, DigiKey, any "my…"/sign-in | the user's **native browser + WORK profile** (their saved login/cookies live there) | `GET /browser-profiles` → find the work profile (non-gmail) → `POST /open-in-profile {url, browser, profileDir, fresh:true}` |
| **Building/testing their own app**, or you need to drive/screenshot/scrape the page | **Pup** (AI-drivable) | open via Pup (`browser_*`) / route `/open-url {url, direct:true}` to Pup |
| **Glance at content alongside their work** | **Hydrogen webview tab** | `adom-workspace-control` add web-view tab |
| **Genuinely ambiguous, OR an OAuth/sign-in the user must complete themselves** | let them choose | `POST /open-url {url, force:true}` — the one time the picker is right |
Then say it in one line, with the undo: *"Opened TI in your Chrome work profile
(where your TI login lives) — say the word for a tab here, Pup, or a different
profile."* Decide, act, offer alternatives — don't ask first.
Example — "open the texas instruments website" (login-likely → native work profile):
```bash
BASE="$(cat ~/.adom/hd-control-url)"
curl -s "$BASE/browser-profiles" # pick the work profile (non-gmail email)
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://www.ti.com","browser":"chrome","profileDir":"<work profileDir>"}' \
"$BASE/open-in-profile"
```
## Govern routing policy — the picker is now a true FALLBACK (HD prefs API)
The picker's per-domain memory is a first-class API (HD app behavior), so **the AI OWNS URL
routing** — the picker only pops for a domain with **no stored policy AND no direct call**.
Use this to set policy the user would otherwise click through the Manager for ("always open
DigiKey in my work profile", "stop auto-opening X in Edge").
```bash
BASE="$(cat ~/.adom/hd-control-url)"
# READ the policy map
curl -s "$BASE/browser-picker/prefs"
# → {ok, prefs:{domains:{"<host>":{destination,label}, …}}}
# SET / override one domain
curl -s -X PUT "$BASE/browser-picker/prefs" -H 'Content-Type: application/json' \
-d '{"domain":"digikey.com","destination":"browser:edge:Default","label":"Edge — Default"}'
# FORGET one domain (e.g. "stop auto-opening X in Edge")
curl -s -X DELETE "$BASE/browser-picker/prefs/digikey.com"
# CLEAR all
curl -s -X DELETE "$BASE/browser-picker/prefs"
```
- **Stored value = `{destination, label}`** — NOT `profileDir`/`fresh`.
- **`destination` is an ID:**
- **internal:** `hydrogen-tab` (a webview/wv tab) · `webview-window` · `pup-window`
- **native:** `browser:<browser>:<dir>` — e.g. `browser:edge:Default`, `browser:chrome:Profile 1`. **The profile dir is encoded in the ID** (no separate field).
- **Discover valid native IDs** via `GET $BASE/browser-profiles` (maps each detected profile → its `browser`+`dir`); the 3 internal IDs are fixed above.
- **`fresh` is NOT stored in prefs** — it's a per-OPEN flag (`/open-url {fresh:…}` / `/open-in-profile {fresh:…}`). Don't try to persist it here.
- **Writes hit the live Manager UI immediately** (the backend writes `hd-url-routing-prefs`
localStorage + dispatches `hd-url-routing-prefs-changed`; an open Browser Picker Manager
re-reads at once, and re-reads on open anyway).
**Net:** for a one-off open, *decide and call the direct endpoint* (`/open-in-profile`,
`/open-url {direct:true}`, or a wv/pup call). For a *standing* preference, `PUT` the domain's
policy so even user-initiated link clicks route there silently. The picker is now only a
fallback for the genuinely-undecided.
## Related skills
- [hd-browser-picker](../hd-browser-picker/SKILL.md) — the interception layers + picker dialog behind #2/#4/#5
- `pup` — driving Pup browser windows (#3): navigate, screenshot, eval, reload
- `adom-workspace-control` — adding/managing Hydrogen webview tabs (#1)
- [hd-runtime-mode](../hd-runtime-mode/SKILL.md) — none of this changes between the WSL2 and Docker runtimes