← Commit history
package.json+1−1
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.3.19",+  "version": "0.3.20",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "platform": "any",
page.json+1−1
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.3.19",+  "version": "0.3.20",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "tags": [
skills/hydrogen-ai-threads-api/SKILL.mdadded+257
@@ -0,0 +1,257 @@+---+name: hydrogen-ai-threads-api+description: >+  How to open and drive AI conversations in Hydrogen from your own app, panel, or script:+  open a new thread (letting the user's preferred AI decide), list what is open, put text+  in a chat box, send a prompt and confirm it landed, reopen a past conversation, and close+  a tab. Plain HTTP on Hydrogen's control API, no Tauri IPC and no special privileges.+  Read this before shelling out to a CLI or driving the editor UI yourself. Trigger words -+  open an AI thread, new claude thread, new codex thread, new kimi thread, ai threads api,+  send a prompt to an AI, put text in the chat box, list open AI conversations, reopen a+  conversation, close an AI tab, default AI, which AI should I use, dock opens a thread.+---++# Driving Hydrogen's AI threads from your own app++Hydrogen keeps every open AI conversation (Claude, Codex, Kimi) in one live tree and exposes+it over its control API. **Anything that can reach that port can do everything the AI Threads+panel does** — the panel itself is a plain HTTP client with no privileged access, so there is+no capability you are missing.++## Find the API++The control port is dynamic (it drifts when 47084 is busy), so never hardcode it.++Inside the workspace:++```bash+BASE="$(cat ~/.adom/hydrogen-control-url)"      # e.g. http://127.0.0.1:55038+curl -s "$BASE/ai-threads" | jq .+```++From a Hydrogen panel or webview, ask your own origin: `fetch('/ports')` returns the resolved+`control` port. If neither is available, the discovery responder answers on the first+bindable of `47080, 48080, 49180, 50280` and returns the live `control_url`.++## From Adom Bridge or the Windows host++The same discovery works OUTSIDE the workspace (an Adom Bridge caller, a dock card, a+wiki page acting through ab): read `%APPDATA%\hydrogen-desktop\ports.json` for the live+`control` port, or probe the discovery responder (`GET http://127.0.0.1:<c>/` for c in+47080, 48080, 49180, 50280; the first answer carries `control_url`). Then call the same+`/ai-threads/*` routes. Requires Adom Hydrogen 1.0.141 or newer. Note: while the AI+Threads panel is off (its default), the first driving call wakes the machinery for that+session; read-only census calls while parked return `ok:false` by design.++## Open a new thread++```bash+curl -sX POST "$BASE/ai-threads/new" -H 'content-type: application/json' -d '{}'+```++**Omit `type` and the user's own preference decides.** That is the point: a caller like the+dock knows which APP the user clicked, not which AI they like, and hardcoding one imposes a+choice that is not yours to make. The default is **claude**, the workhorse every Adom user is+required to have, so the fallback is always something the user actually has installed.++```jsonc+{"ok": true, "type": "claude", "from_default": true, "result": { "via": "keybinding", ... }}+```++- `type` is the AI that was actually used.+- `from_default: true` means it came from the setting rather than your request.+- `via: "keybinding"` means it opened without flashing the command palette at the user.++Name one explicitly only when your feature is genuinely AI-specific:++```bash+curl -sX POST "$BASE/ai-threads/new" -H 'content-type: application/json' \+  -d '{"type":"codex","prompt":"review the diff in this repo"}'+```++`prompt` is optional and seeds the conversation's first message.++`tags` is optional and marks the thread as YOURS before it even has an id:++```bash+curl -sX POST "$BASE/ai-threads/new" -H 'content-type: application/json' \+  -d '{"tags":["dock","app:kicad"],"prompt":"help me with this board"}'+```++A brand-new thread has no stable id until its first message creates the conversation, so+tags passed here STAGE and attach automatically the moment the id appears (the response+carries `tags_staged` confirming that). Staged tags expire after 10 minutes if no thread+materializes, so a failed open cannot mis-tag a stranger.++**Read or change the preference** (it is a normal setting, so let the user own it):++```bash+curl -s "$BASE/ai-threads/settings" | jq .default_ai            # "claude"+curl -sX POST "$BASE/ai-threads/settings" -H 'content-type: application/json' \+  -d '{"default_ai":"kimi"}'+```++**Open the AI's CLI in a terminal tab instead** (the caret menu next to each toolbar+button does exactly this):++```bash+curl -sX POST "$BASE/ai-threads/new-tui" -H 'content-type: application/json' \+  -d '{"type":"claude"}'+```++A named terminal ("Claude TUI" / "Codex TUI" / "Kimi TUI") opens as an editor tab+running that AI's full CLI. TUI threads are FIRST-CLASS rows in the tree (marked+`tui: true`): they list alongside webview threads, and `/ai-threads/rename` and+`/ai-threads/close` work on them (close also answers VS Code's "terminate running+processes?" dialog). Delivery endpoints (`run-prompt`, `inject`) do not type into+TUIs; drive a TUI conversation in its terminal.++## See what is open++```bash+curl -s "$BASE/ai-threads" | jq '.groups[].threads[] | {tid, type, label, sid, instantiated}'+```++- `tid` is the CURRENT position (`g0t3`). It changes when tabs move, so do not store it.+- `sid` is the STABLE conversation id. Store this one.+- `instantiated: false` means the tab exists but VS Code has not materialized its webview+  yet. Text cannot be typed into it until it has been focused once.+- `summary.identity` reports how each row was identified, so you can tell a solid row from a+  guessed one.+- `tags` is the thread's durable tag list (see "Tag a thread" below). Filter on it to find+  the threads that belong to your app: `jq '.groups[].threads[] | select(.tags | index("dock"))'`.+- `mention` (claude rows with a live session) is the thread's cross-session @mention+  address, e.g. `"@project-d5"`: any other Claude session on the machine can message it by+  typing that name in its composer.++`GET /ai-threads/wait` long-polls and returns when the tree changes. Use it instead of+polling on a timer.++## Put text in a chat box, or send it++```bash+# stage it for the human to review, nothing is sent+curl -sX POST "$BASE/ai-threads/inject" -H 'content-type: application/json' \+  -d '{"tid":"g0t1","prompt":"summarize this file","submit":false}'++# send it, and confirm it actually landed in THAT conversation+curl -sX POST "$BASE/ai-threads/run-prompt" -H 'content-type: application/json' \+  -d '{"sid":"<stable id>","prompt":"what model are you?"}'+```++Two guarantees worth relying on:++- **`submit: false` really does not send.** Prefer it when a human should see the text first.+- **`run-prompt` confirms delivery by reading the AI's own message list**, not by assuming+  the keystrokes went somewhere. If it says it delivered, it checked.++Typical latency is single-digit to low-double-digit milliseconds against a loaded thread.+A thread whose webview has never been materialized costs a few seconds the first time while+VS Code creates it, so if you care about that, `POST /ai-threads/focus {tid}` first.++## Tag a thread, and find it again forever++Tags are durable strings keyed to the conversation's STABLE id, so they survive restart,+drag, rename, close, and reopen. This is how an external app (the dock) re-finds ITS+threads without guessing from titles.++```bash+# tag an existing thread (ai + sid, or its current tid, or an explicit "<ai>:<sid>" key)+curl -sX POST "$BASE/ai-threads/tag" -H 'content-type: application/json' \+  -d '{"ai":"claude","sid":"<sid>","add":["dock","app:kicad"]}'++# remove one+curl -sX POST "$BASE/ai-threads/tag" -H 'content-type: application/json' \+  -d '{"ai":"claude","sid":"<sid>","remove":["app:kicad"]}'++# the whole store+curl -s "$BASE/ai-threads/tags"+```++Tags come back on BOTH surfaces, open and closed:++```bash+curl -s "$BASE/ai-threads" | jq '.groups[].threads[] | {label, sid, tags}'+curl -s "$BASE/ai-threads/history?tag=app:adom/fusion-bridge"    # server-side filter+```++**Bring the user back to their thread, with a visual cue.** `highlight: true` on focus+foregrounds the tab AND pulses its header for about a second, so the user sees WHY this+tab just came forward ("this existing thread is why nothing new opened"):++```bash+curl -sX POST "$BASE/ai-threads/focus" -H 'content-type: application/json' \+  -d '{"sid":"<sid>","highlight":true}'+```++A thread with no conversation yet cannot be tagged here (there is no id to key on); pass+`tags` to `/ai-threads/new` instead and they attach when the id appears.++## Route wiki issues into a thread++Associate a thread with a wiki repo and Hydrogen's global notification watcher queues new+issues on that thread's row (amber count; the user injects with one click, or opts into+auto-inject in Settings):++```bash+curl -sX POST "$BASE/ai-threads/repo-link" -H 'content-type: application/json' \+  -d '{"ai":"claude","sid":"<sid>","repo":"adom/adom-shotlog"}'   # repo:"" clears+curl -s "$BASE/ai-threads/repo-links"+curl -s "$BASE/ai-threads/issue-queue"                            # or ?key=<ai>:<sid>+curl -sX POST "$BASE/ai-threads/issue-inject" -H 'content-type: application/json' \+  -d '{"ai":"claude","sid":"<sid>"}'                              # injects the next queued issue+```++One thread per repo (a new association moves it and reports `replaced`). Injected issues+are framed as bug reports to evaluate, never as instructions, and only an injected+issue's notification is marked read.++## Reopen a past conversation++```bash+curl -s "$BASE/ai-threads/history" | jq '.items[] | {ai, id, title, updated_ms}'+curl -sX POST "$BASE/ai-threads/resume" -H 'content-type: application/json' \+  -d '{"ai":"codex","id":"01a01bfb-a89d-75e0-9f17-01a163ba399b"}'+```++History lists conversations on disk for all three AIs and EXCLUDES anything currently open,+so "in History" means "not open right now". Every AI reopens into a real editor tab.++## Close a tab++```bash+curl -sX POST "$BASE/ai-threads/close" -H 'content-type: application/json' -d '{"tid":"g0t1"}'+```++The conversation is not deleted. It moves to History and can be reopened.++## Rules that will save you time++1. **Store `sid`, never `tid`.** `tid` is a position and moves.+2. **Do not pick an AI on the user's behalf.** Omit `type` unless your feature is genuinely+   specific to one.+3. **Trust `ok`, and read the rest.** These endpoints verify against real state (a tab+   appeared, the composer holds exactly your text, the message reached the conversation) and+   report `ok: false` with a reason rather than claiming success.+4. **Do not drive the editor UI yourself.** Clicking tabs or typing into webviews from+   outside races Hydrogen's own sweep and will deliver to the wrong conversation. Everything+   you need is here.+5. **Do not hardcode the port.**++## Complete example: a dock icon opens a thread about its app++```javascript+const base = await fetch('/ports').then(r => r.json()).then(p => `http://127.0.0.1:${p.control}`);++async function openThreadFor(app) {+  const r = await fetch(`${base}/ai-threads/new`, {+    method: 'POST',+    headers: { 'content-type': 'application/json' },+    // No `type`: the user's default_ai decides. No prompt if you would rather they type.+    body: JSON.stringify({ prompt: `I just opened ${app.name}. Help me use it.` }),+  }).then(r => r.json());++  if (!r.ok) throw new Error(r.error || 'could not open a thread');+  return r;   // {ok, type, from_default, result:{via, ...}}+}+```
skills/hydrogen-ai-threads/SKILL.mdadded+64
@@ -0,0 +1,64 @@+---+name: hydrogen-ai-threads+description: >+  The AI Threads panel in Adom Hydrogen — the live tree of every AI conversation+  open in the editor (Claude, Codex, Kimi): which is in front, which are busy,+  drag to reorder, rename, tag, and terminal (TUI) threads that run an AI's full+  CLI in an editor tab. Covers what the panel shows, the BETA on-startup setting+  (off by default; nothing AI-Threads runs while it is off), opening it once from+  Add Panel, TUI threads via the toolbar carets, and how the panel relates to the+  /ai-threads API (see hydrogen-ai-threads-api for driving it from code). Trigger+  words: ai threads, ai threads panel, thread tree, my ai conversations, which ai+  is running, reorder threads, rename a thread, tui thread, claude in a terminal,+  codex tui, kimi tui, ai threads beta, enable ai threads, ai threads setting.+---++# hydrogen-ai-threads — the AI Threads panel++AI Threads is a panel that shows every AI conversation open in the editor as one+live tree, grouped the way the editor groups its tabs. Each row shows the+conversation's AI (Claude, Codex, Kimi), its name, whether it is the foreground+tab, and a busy indicator while that AI is generating. Click a row to bring its+conversation forward.++**It is BETA and off by default.** The setting is+`Settings > Hydrogen > Behavior > Ai Threads On Startup`. While it is off, nothing+related to AI Threads runs: no background scans, no editor probing, nothing. You+can still open the panel once from **Add Panel** (the `+` in any pane's tab bar)+without touching the setting; doing so wakes the machinery for that session only.++## What you can do in the panel++- **Reorder**: drag a row; the row ghosts while the move lands, then settles. The+  order IS the editor's real tab order, so reordering here reorders the tabs.+  Pressing Escape mid-drag abandons the drag and restores everything.+- **Rename**: right-click (or the row's menu) > Rename. The name is written into+  the conversation itself, so it survives restarts and shows on the editor tab.+  Renaming also works on TUI threads.+- **Tags**: threads can carry tags (the dock uses `dock:<app>` to mark threads it+  opened). Tags attach as soon as a brand-new thread earns its identity.+- **New thread buttons**: the toolbar has a button per AI. The small caret beside+  each opens that AI **in a terminal instead**: a named editor tab ("Claude TUI",+  "Codex TUI", "Kimi TUI") running the AI's full CLI. TUI threads appear in the+  tree like any other thread; drive the conversation in the terminal itself.+- **History / resume**: past conversations that are not open right now can be+  reopened into a real editor tab.++## Relationship to the API++The panel is a plain HTTP client of Hydrogen's control API and has no privileged+access: everything above is `POST /ai-threads/*` under the hood, and any app,+dock card, or AI can call the same routes. See **hydrogen-ai-threads-api** for+the full surface (open, list, deliver a prompt with verification, resume, rename,+close, tags) and how to find the control port from inside or outside the+workspace.++## Troubleshooting++- **Panel missing after restart**: the on-startup setting is off (its default);+  turn it on, or open the panel from Add Panel for this session.+- **API returns "AI Threads is not active"**: same cause; while parked, read-only+  census calls refuse by design. A driving call (`/ai-threads/new`, a delivery)+  wakes the machinery for the session.+- **A terminal row shows a shell name** (bash/zsh): that tab is a plain terminal,+  not an AI TUI; the tree deliberately excludes it from thread rows.
skills/hydrogen-api/SKILL.md+152−56
@@ -2,16 +2,18 @@ name: hydrogen-api description: >   Complete reference for Hydrogen control API endpoints. The control-  API runs on a dynamic port (find it via port discovery). Use these endpoints-  to drive Hydrogen programmatically: manage the workspace runtime, control Claude-  Code, manipulate VS Code, manage workspace tabs, run setup steps, trigger-  virgin reset, and more. ~159 endpoints. Two self-documenting sources of truth-  list them live: GET /_manifest (curated grouped list) and GET /test/help-  (the /test/* diagnostics).+  API runs on a dynamic port (find it in the Hydrogen log or via port discovery). Use+  these endpoints to drive Hydrogen programmatically: manage the workspace (the WSL2 `Adom-Workspace` distro), control+  Claude Code, manipulate VS Code, manage workspace tabs, run setup steps, trigger+  virgin reset, and more. ~200 endpoints. The COMPLETE, always-current catalog is+  GET /_manifest (every route with a category + description; curated entries are+  richer). The API Explorer window (Adom menu > API Explorer, or POST /api-explorer/open)+  renders it with a runnable Try per endpoint. GET /test/help lists the gated /test/*+  diagnostics. NEVER blind-call destructive endpoints (see the Safety section).   Trigger words: Hydrogen API, control API, Hydrogen endpoint, container exec, claude api,-  iframe eval, workspace tabs, setup run-all, runtime status, reload vscode,-  browser profiles, open url, virgin reset api, diagnostics, ad health,-  test window-tree, ai cursor, setup panel, screenshot mode.+  iframe eval, workspace tabs, setup run-all, workspace status, reload vscode,+  browser profiles, open url, virgin reset api, reboot api, diagnostics,+  ad health, test window-tree, ai cursor, setup panel, screenshot mode. ---  # Hydrogen Control API Reference@@ -22,12 +24,50 @@ sources of truth you should hit first:  | Source | What it gives you | Where | |---|---|---|-| `GET /_manifest` | Curated, grouped, machine-readable list of the highest-value endpoints (path, method, args, description, `visibility`). `hd_api` forwards anything not listed. | `lib.rs:9144` |-| `GET /test/help` | Lists the `/test/*` diagnostics (window enumeration, UIA click, dialog classification, cascade reset). Gated behind `HD_TEST_API_ENABLED=1`. | `lib.rs:9723` |--**Base URL:** `http://127.0.0.1:<dynamic>` — the loopback address. The port is-dynamic per launch; read the live URL from `~/.adom/hydrogen-control-url` — see-"Finding the Control Port" below.+| `GET /_manifest` | **THE complete catalog** — every registered route (`path`, `method`, `args`, `category`, `description`, `visibility`). Curated entries carry richer descriptions; the rest are auto-generated from the router (`scripts/gen-manifest.py`), so it never drifts. **Discover here first — don't guess paths.** | `hydrogen-control/src/lib.rs` `/_manifest` arm |+| **API Explorer window** | The human face of `/_manifest`: every endpoint grouped by category tab, with a runnable **Try** + Copy URL. Open from the Adom menu, or `POST /api-explorer/open`. | Adom menu ▸ API Explorer |+| `GET /test/help` | The `/test/*` diagnostics (window enumeration, UIA click, dialog classification, cascade reset). **Gated behind `HD_TEST_API_ENABLED=1`** — 404 by default (expected, not a bug). | `hydrogen-control/src/lib.rs` `/test/help` arm |++## ⛔ Safety — the API enforces this, and tells you in-band++The API itself now labels, warns about, and gates dangerous endpoints (single source of+truth: `safety.rs` in hydrogen-control). Three tiers:++- **`catastrophic`** — irreversibly destroys real user state: `/setup/virgin-reset`,+  `/setup/panel/(run-virgin-reset|wipe-keep-auth)`, `/wsl/unregister` (deletes the whole+  workspace distro), `/system/reboot` (reboots the+  user's COMPUTER), `/config-reset`, `/claude/wipe-creds`, `*-delete`.+  **Headless calls (no browser Origin header — i.e. YOU) get HTTP 409 `confirmation_required`+  and NOTHING happens** until you re-call with `?confirm=1`. Only add `?confirm=1` when the+  user explicitly asked for exactly that action — never to make an error go away.+- **`destructive`** — kills/restarts/re-runs real state (recoverable but real):+  `/app/(restart|relaunch-frontend)`, `/reload-vscode`,+  `/container(-stop|-restart)`, `/workspace/apply-updates`, `/setup/(run-all|run-step|state|+  force-close|trigger|countdown)`, `/claude/tabs/close-all`, `/updater/install`.+  Never call these speculatively or in a sweep.+- **`disruptive`** — the user SEES it (window opens, focus moves, typing lands in their+  real chat, screen recording starts): `/hbw/open` (and `/hbw/close` to close one by label), `/api-explorer/open`, `/open-url`,+  `/open-in-profile`, `/window/foreground`, `/ai-cursor/*`, `/recording/native/start`,+  `/ui/toast`, `/claude/(trigger-auth|authorize-in-browser|open-with-message|inject*|send|+  submit|uia-*|new-chat-and-send|type-and-submit)` (the send/submit ones SPEND a real user+  turn). Run only when the user actually wants that action, and clean up after yourself.+  (Calling `/claude/authorize-in-browser` blindly is how you spray OAuth windows into Edge.)++How the API tells you:+- **Before calling:** every `GET /_manifest` entry carries `safety` + `safety_note`. Check+  them before you POST anything unfamiliar. Unlabeled = safe.+- **When you call:** responses from these endpoints include a `_safety` block. READ IT —+  it says exactly what you just did to the user's machine.+- **The 409 gate** is your last chance to stop; it is not an error to route around.++A safe GET/POST returns within a couple seconds with a 2xx or a clean 4xx `{ok:false,...}`.+If you must probe an endpoint you're unsure about, POST an empty `{}` body and treat a+graceful validation error as "it works" — a 500 or a hang is the bug.++**Base URL:** `http://127.0.0.1:<dynamic>` — the same loopback address from inside the+workspace (WSL2 mirrored networking shares loopback with the Windows host) and on the+Windows host. The port is dynamic per launch; read the live URL from+`~/.adom/hydrogen-control-url` — see "Finding the Control Port" below.  ## Finding the Control Port @@ -35,11 +75,22 @@ The control API port is dynamic (default 47084, auto-resolves conflicts).  ```bash # From Hydrogen log-adom-bridge-cli hd_log '{"tail":100}' | grep "control="+adom-bridge hd_log '{"tail":100}' | grep "control="++# From ports.json+type %APPDATA%\hydrogen-desktop\ports.json -# From inside the workspace — USE THIS. Hydrogen writes its live control URL to a discovery-# file every launch, so you never hunt for the dynamic port:+# From inside the WSL2 workspace — USE THIS. Hydrogen writes its live control URL to a discovery+# file every launch (mirrored loopback), so you never hunt for the dynamic port: CTRL="$(cat ~/.adom/hydrogen-control-url)"; curl -sf "$CTRL/health"++# No file access (remote tool, browser)? Probe the DISCOVERY responder: fixed candidate+# ports tried in order (a single fixed port is not bindable on every Windows box; WSL2+# mirrored networking claims ports invisibly). Take the FIRST that answers with+# service == "hydrogen-desktop-discovery"; its control_url is the live control API:+for p in 47080 48080 49180 50280; do+  R=$(curl -sf --max-time 2 "http://127.0.0.1:$p/") && echo "$R" | grep -q hydrogen-desktop-discovery && break+done ```  ⭐ **From the workspace, read `~/.adom/hydrogen-control-url`** (e.g. `http://127.0.0.1:<control>`) — a@@ -48,9 +99,33 @@ rewrites it with the live dynamic port every launch. This is the channel for the the live ports map, Claude control, etc. — distinct from `adom-cli` (`ADOM_HYDROGEN_URL` → SSE), which is unchanged for web-hydrogen parity. -> For the host-side ways to discover the port (Hydrogen log / the ports file) and the-> runtime-specific host endpoints, see the platform companion skills-> ([[hydrogen-api-windows]] / [[hydrogen-api-mac]]).+## Calling gotchas (learned the hard way)++- **Never hardcode the port.** It's dynamic (47084 is only the default and it auto-resolves+  conflicts). Read `~/.adom/hydrogen-control-url` from the workspace, or `GET /ports`. Hardcoding+  47084 is what makes tools show Hydrogen as "down" when it's actually up on another port.+- **Calling from a REMOTE container (hw/cloud)? Use `adom-bridge hd_api`,** the relay's+  pass-through verb: `hd_api {"method":"POST","path":"/...","body":{...}}`. It auto-discovers+  the port; never shell curl through the relay (quoting gets mangled). Long-blocking calls+  (typed injection) can outlive the relay timeout: the work still completes on Hydrogen's side, so+  verify state instead of re-firing.+- **Getting files OUT (recordings, screenshots): streamed binary, never base64.** Endpoints+  return paths inside the WSL distro; a remote container pulls them with `adom-bridge-cli+  --target <desktop> pull_file {filePaths:["\\\\wsl$\\Adom-Workspace\\<path>"], saveTo:...}`+  (1 MiB binary chunks, sha256-verified, no practical size limit). `/recording/native/stop`+  returns the ready-made `fileWindows` UNC path. Full demo-video pipeline: the+  `hydrogen-demo-recording` skill.+- **Confirm it's really the control API, not the SPA.** In dev, the frontend file server+  answers 200 with HTML for *any* path — so a bare 200 doesn't prove you hit Hydrogen's API. Check+  `GET /health` returns JSON with `service:"hydrogen-desktop-control"`.+- **Driving Hydrogen from a page Hydrogen serves? Use relative fetches, not Tauri IPC.** Tauri `invoke`+  is unavailable on remote http origins; a same-origin `fetch('/ports')` / `fetch('/_manifest')`+  works and sidesteps port discovery entirely.+- **Driving from the CLOUD over the relay:** if two Adom Bridges are connected (laptop ++  a VM), `desktop_*` verbs need `--target <name>` (e.g. `AdomLapper`) or they no-route and+  look empty; `nbrowser_*`/`hd_*` auto-route. PowerShell emits **CRLF** — strip `\r` before+  string-comparing its output. Long calls can exceed the relay's per-call timeout — run+  detached + poll a marker (see `hydrogen-cloud-relay-patterns`).  ## Health & Status @@ -59,23 +134,25 @@ which is unchanged for web-hydrogen parity. | GET | `/health` | Hydrogen alive check — returns `{ok, port, service}` | | GET | `/buildinfo` | Running build git SHA + build metadata (the only trustworthy "did my build ship?" check) | | GET | `/ports` | The 4 editable PortConfig ports (proxy, control, cdp, code_server) |-| GET | `/ports/all` | LIVE full port map — every port Hydrogen has bound, with status info |+| GET | `/ports/all` | LIVE full port map — every host + WSL2 port Hydrogen has bound, with status + mirrored-mode info |++## ab-health diagnostics (`/ab/*`) -## ab-health diagnostics (`/ad/*`)+(Canonical since 1.0.47; the old `/ad/*` spellings still answer as aliases.)  These verify the **adom-bridge-cli / relay / embedded-ab** half of Hydrogen is alive (the bridge that connects this cloud container to Hydrogen). All GET, all return `{ok, ...}`.  | Method | Path | Description | |---|---|---|-| GET | `/ad/health` | Overall embedded-ab health roll-up |-| GET | `/ad/process` | Is the adom-bridge-cli process running |-| GET | `/ad/embedded` | Embedded-ab module status |-| GET | `/ad/workspace-direct` | ⭐ The **converged** check — direct path to the workspace relay (use this one) |-| GET | `/ad/relay` | Relay (WS/HTTP) reachability |-| GET | `/ad/liveness-beacon` | Liveness beacon freshness |-| GET | `/ad/direct-api` | Direct-API reachability |-| GET | `/ad/discovery-file` | Discovery file present + parseable |+| GET | `/ab/health` | Overall embedded-ab health roll-up |+| GET | `/ab/process` | Is the adom-bridge process running |+| GET | `/ab/embedded` | Embedded-ab module status |+| GET | `/ab/workspace-direct` | ⭐ The **converged** check — direct path to the workspace relay (use this one) |+| GET | `/ab/relay` | Relay (WS/HTTP) reachability |+| GET | `/ab/liveness-beacon` | Liveness beacon freshness |+| GET | `/ab/direct-api` | Direct-API reachability |+| GET | `/ab/discovery-file` | Discovery file present + parseable |  ## Driving Hydrogen's UI (command bus) @@ -90,31 +167,44 @@ Open/close/toggle any Hydrogen menu, dialog, or panel — the first-class altern | GET | `/diagnostics/all` | Run all diagnostic checks | | GET | `/diagnostics/{name}` | Run a single diagnostic check | -## Workspace / Container Management+## Workspace Management -These endpoints manage the active workspace runtime.+These endpoints manage the workspace: the WSL2 distro `Adom-Workspace`. The paths+keep their historical `container` spelling; there is no container.  | Method | Path | Body | Description | |---|---|---|---|-| GET | `/container-status` | — | Workspace runtime available, image/distro present, workspace exists/running |+| GET | `/container-status` | — | `runtime_available` (WSL2 usable), `workspace_installed` (distro present), workspace exists/running | | GET | `/container-stats` | — | CPU, memory, disk usage |-| POST | `/container-restart` | — | Restart the active workspace runtime (5s timeout) |-| POST | `/container-stop` | — | Stop the active workspace runtime |+| POST | `/container-restart` | — | Restart the workspace (5s timeout) |+| POST | `/container-stop` | — | Stop the workspace | | POST | `/container-exec` | `{"command":"..."}` | Run command in the workspace as user `adom` | | POST | `/container-delete` | — | Remove the workspace |-| POST | `/image-delete` | — | Remove workspace image |-| GET | `/container-images` | — | List available workspace images |-| POST | `/container-switch-image` | `{"image":"..."}` | Switch to different image |-| POST | `/container-pull-image` | `{"image":"..."}` | Pull an image | | GET | `/container/logs` | — | Workspace stdout/stderr | | GET | `/container/env` | — | Workspace environment variables |-| GET | `/container/ports` | — | Published port mappings |+| GET | `/container/ports` | — | Forwarded port mappings | | GET | `/container/disk` | — | Disk usage inside the workspace | | POST | `/container/restart` | — | Alias for `/container-restart` |+| POST | `/direct/command` | `{...}` | Direct-connect command channel into the workspace | -> Runtime-specific endpoint tables (including the legacy Docker-only endpoints)-> live in the platform companion skills ([[hydrogen-api-windows]] /-> [[hydrogen-api-mac]]).+> `GET /container-status` renamed two fields: `docker_available` → **`runtime_available`**+> and `image_pulled` → **`workspace_installed`**. Old field names are gone.++### Workspace / WSL endpoints++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/wsl/status` | — | WSL distro state (registered, running, code-server reachable) |+| GET | `/workspace/health` | — | Deep workspace health (distro + code-server + host reachability) |+| GET | `/virgin-reset-progress` | — | Live progress of an in-flight virgin reset |+| POST | `/workspace/terminal-profile` | — | Ensure the stable `Adom-Workspace` Windows Terminal profile (idempotent) |+| POST | `/workspace/terminal-profile/remove` | — | Remove the `Adom-Workspace` Windows Terminal profile(s) |+| POST | `/workspace/check-updates` | — | Check upstream for newer code-server + Claude extension; stage them |+| POST | `/workspace/apply-updates` | — | Apply staged code-server/extension updates + reload editor |++> ⛔ `POST /wsl/unregister` is **REFUSED as deprecated** — it was a headless+> destructive reset (deletes `/home/adom/project`). Use the Setup panel's Virgin+> Reset or `POST /setup/panel/run-virgin-reset` instead.  ## WebView & VS Code @@ -232,8 +322,8 @@ glides it, and can click. Use for demos or when the user should see where the AI | POST | `/config-reset` | — | Reset Hydrogen config |  > ⛔ **REFUSED as deprecated** (headless runs with no visible UI):-> `POST /setup/run-all`, `POST /setup/run-step`, `POST /setup/virgin-reset`.-> They return a refusal pointing at the UI-visible path.+> `POST /setup/run-all`, `POST /setup/run-step`, `POST /setup/virgin-reset`,+> `POST /wsl/unregister`. They return a refusal pointing at the UI-visible path. > To run steps: use `POST /setup/step/<id>` or relaunch Hydrogen to auto-run the cascade.  ### Setup panel control@@ -259,6 +349,15 @@ glides it, and can click. Use for demos or when the user should see where the AI | GET | `/workspace/tabs/find?name=X` | — | Find tab by name (404 if not found) | | POST | `/workspace/tabs` | `{tab spec}` | Add a new tab | +## System++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/system/nonce` | — | Get nonce for authenticated reboot |+| GET | `/system/reboot-state` | — | Check reboot pending state |+| POST | `/system/reboot` | `{"nonce":"..."}` | Reboot Windows (nonce-authenticated) |+| POST | `/system/cancel-reboot` | — | Cancel pending reboot |+ ## App lifecycle & window  | Method | Path | Body | Description |@@ -267,9 +366,6 @@ glides it, and can click. Use for demos or when the user should see where the AI | POST | `/app/relaunch-frontend` | — | Relaunch the Hydrogen GUI (kill+respawn) while it is responsive | | POST | `/window/foreground` | — | Bring the Hydrogen window to the foreground (unminimize + focus) | -> Host OS reboot (`/system/*`) is platform-specific — see the platform-> companion skills ([[hydrogen-api-windows]] / [[hydrogen-api-mac]]).- ## Diagnostics (`/test/*`)  Window enumeration, UIA clicking, native-dialog classification, and cascade@@ -283,12 +379,10 @@ disabled" otherwise). `GET /test/help` self-lists the whole cluster. | POST | `/test/focus` | `{"window":"..."}` | Focus Hydrogen (`self`) or a window by title substring | | POST | `/test/click-uia` | `{...}` | Poll UIAutomation for a Button by Name; Invoke when found | | GET | `/test/setup-steps` | — | Raw setup-steps.json + derived summary (alias: `/test/install-steps`) |-| POST | `/test/dump-state` | `{...}` | Write a diagnostic bundle (logs, install-steps, runtime state, tasklist, window-tree) |+| POST | `/test/dump-state` | `{...}` | Write a diagnostic bundle (logs, install-steps, workspace state, tasklist, window-tree) | | POST | `/test/reset-cascade` | — | Delete install-steps.json + emit setup-reset event | | POST | `/test/screenshot-hydrogen-window` | `{...}` | Capture Hydrogen's own window to a PNG file |--> The `/test/probe-dialogs` endpoint (classifies Windows-only blocker dialogs)-> is Windows-specific — see the [[hydrogen-api-windows]] companion.+| GET | `/test/probe-dialogs` | — | Enumerate visible windows, classify against the cascade-blocker catalog (UAC, WSL2 update, MSI installer, …); returns `kind` + `is_blocking` per window |  ## Key Patterns @@ -309,8 +403,10 @@ not `~/`). Tilde doesn't expand through the exec chain. POST /container-exec {"command":"ls /home/adom/project"} ``` -### Trigger setup re-run after completion-The trigger file poller STOPS when all steps are done. Use the step API instead:+### Re-run a setup step after completion+The trigger file poller STOPS when all steps are done. `POST /setup/run-all` is+**REFUSED as deprecated** — don't use it. Re-run one step by id (or relaunch Hydrogen to+auto-run the cascade): ```-POST /setup/step/<id> {"continue_after": true}+POST /setup/step/<id> ```
skills/hydrogen-browser-picker/SKILL.md+15−10
@@ -359,8 +359,13 @@ windows = { version = "0.61", features = [  ## Hard-Won Lessons -### `NewWindowResponse::Allow` must NEVER be returned-Original code returned `Allow` for localhost URLs. Code-server's `openExternal` wraps URLs in localhost routes. Returning `Allow` created uncontrolled WebView2 windows with no handlers. Fix: Deny everything, always.+### `NewWindowResponse::Allow` must NEVER be returned; `Create` is the one sanctioned exception+Original code returned `Allow` for localhost URLs. Code-server's `openExternal` wraps URLs in localhost routes. Returning `Allow` created uncontrolled WebView2 windows: no Tauri label, no handlers, no way to close or navigation-police them. `Allow` stays banned, always, for every URL.++Since 1.0.137 (granted popups), `NewWindowResponse::Create {{ window }}` IS permitted for exactly two shapes: `about:blank` (VS Code "Open Terminal in New Window": the opener scripts the blank doc) and same-origin code-server-port URLs. Create is the opposite of the failure that motivated the ban: WebView2 gets a MANAGED `WebviewWindow` (`hd-popup-*`) with a label, an `on_navigation` that still routes externals to the Browser Picker, a concurrency cap (6), and Destroyed cleanup. Rules that keep the original lesson intact:+- The window MUST be a `WebviewWindow` built SYNCHRONOUSLY inside `on_new_window`, with `.window_features(features)` (applies the opener's WebView2 environment, mandatory on Windows; tauri-runtime-wry PANICS on a plain `Window` or an unregistered window).+- External http(s) NEVER gets Allow or Create: picker dispatch then Deny, unchanged.+- Any build failure: log `POPUP CREATE FAILED` and Deny. Never "fall back" to Allow.  ### Cross-origin iframe `target="_blank"` is invisible to WebView2 No COM event fires on the parent WebView2 when a cross-origin child iframe has a `target="_blank"` click. Not `NewWindowRequested`, not `FrameNavigationStarting`, not `NavigationStarting`. The ONLY way to catch it: `AddScriptToExecuteOnDocumentCreated`.@@ -432,22 +437,22 @@ Tauri compiles capabilities INTO the binary at build time. Capability changes re #    OR let the 5s auto-countdown pick Pup for unknown URLs.  # 2. Trigger a URL open (read the live control URL — the port is dynamic, default 47084)-adom-bridge-cli shell_execute '{"command":"CTRL=\"$(cat ~/.adom/hydrogen-control-url)\"; curl -s -X POST -d {\"url\":\"https://example.com\"} \"$CTRL/open-url\""}'+adom-bridge shell_execute '{"command":"CTRL=\"$(cat ~/.adom/hydrogen-control-url)\"; curl -s -X POST -d {\"url\":\"https://example.com\"} \"$CTRL/open-url\""}'  # 3. Within 5s, picker auto-routes to Pup (or saved pref)-# 4. Drive Pup with adom-bridge-cli browser_* verbs-adom-bridge-cli browser_eval '{"sessionId":"default","js":"document.title"}'+# 4. Drive Pup with adom-bridge browser_* verbs+adom-bridge browser_eval '{"sessionId":"default","js":"document.title"}'  # Force a picker dialog (no auto-timer)-adom-bridge-cli shell_execute '{"command":"CTRL=\"$(cat ~/.adom/hydrogen-control-url)\"; curl -s -X POST -d {\"url\":\"https://example.com\",\"force\":true} \"$CTRL/open-url\""}'+adom-bridge shell_execute '{"command":"CTRL=\"$(cat ~/.adom/hydrogen-control-url)\"; curl -s -X POST -d {\"url\":\"https://example.com\",\"force\":true} \"$CTRL/open-url\""}'  # Route through the picker with the fresh-window toggle PRE-CHECKED (force = skip the # 5s timer). The user/AI then picks a native browser → opens fresh + auto-foregrounds.-adom-bridge-cli hd_api '{"method":"POST","path":"/open-url","body":{"url":"https://claude.ai/","fresh":true,"force":true}}'+adom-bridge hd_api '{"method":"POST","path":"/open-url","body":{"url":"https://claude.ai/","fresh":true,"force":true}}' # BYPASS the picker and open directly in a specific browser+profile, fresh:-adom-bridge-cli hd_api '{"method":"POST","path":"/open-in-profile","body":{"url":"https://claude.ai/","browser":"edge","profileDir":"Default","fresh":true}}'+adom-bridge hd_api '{"method":"POST","path":"/open-in-profile","body":{"url":"https://claude.ai/","browser":"edge","profileDir":"Default","fresh":true}}' # /open-url with direct:true also bypasses the picker (opens immediately, default browser):-adom-bridge-cli hd_api '{"method":"POST","path":"/open-url","body":{"url":"https://claude.ai/","fresh":true,"direct":true}}'+adom-bridge hd_api '{"method":"POST","path":"/open-url","body":{"url":"https://claude.ai/","fresh":true,"direct":true}}' ```  `/open-url` ROUTES THROUGH THE PICKER by default (emits `browser-open-request`):@@ -464,7 +469,7 @@ the picker only pops for a domain with no policy + no direct call: `GET /browser-picker/prefs` (read `{domains:{<host>:{destination,label}}}`), `PUT /browser-picker/prefs {domain,destination,label?}` (set/override), `DELETE /browser-picker/prefs/<host>` (forget one), `DELETE /browser-picker/prefs` (clear all).-`destination` is an ID: `hydrogen-tab` (wv) | `webview-window` (**hbw** — Hydrogen's standalone Tauri browser window; also `POST /hbw/open`) | `pup-window` (pup), or a native id (**nb**): users address these as `nb <browser> [profile]` — "nb chrome work", "nb edge" — resolve via GET /browser-profiles; the id form is+`destination` is an ID: `hydrogen-tab` (wv) | `webview-window` (**hbw** — Hydrogen's standalone Tauri browser window; also `POST /hbw/open`; close one with `POST /hbw/close {"label":"hbw-<ts>"}`, hbw labels only, from 1.0.142) | `pup-window` (pup), or a native id (**nb**): users address these as `nb <browser> [profile]` — "nb chrome work", "nb edge" — resolve via GET /browser-profiles; the id form is `browser:<browser>:<dir>` (profile dir encoded in the ID). `fresh` is per-open, not stored. Writes hit the Manager's live state (localStorage + `hydrogen-url-routing-prefs-changed`). Full routing-policy guidance lives in **[hydrogen-open-url](../hydrogen-open-url/SKILL.md)**.
skills/hydrogen-dock/SKILL.md+18−1
@@ -5,7 +5,10 @@ description: The Hydrogen dock bar (the app launcher rail on the right edge of t  # hydrogen-dock: the Hydrogen app launcher -The dock is the launcher rail on the right edge of the Hydrogen workspace. Every+The dock is the launcher rail on the right edge of the Hydrogen workspace.+Since 1.0.140 it is **visible after startup by default** (so new users meet it), then+auto-collapses after an app launch; "Start with the dock collapsed" and the other+visibility behaviors live in Dock settings (the gear at the top of the rail). Every card in it is a wiki app page. **Any public wiki app can be a dock card**, and users can add one themselves without anybody's permission. @@ -83,6 +86,19 @@ also makes `skip_if` report "installed" for the author and "missing" for users.  Both bundled bridges shipped both bugs. Check these two first when a card fails. +Two contracts that ride with the rules (full detail in the Dock Bar guide):++- **Print the DIRECT URL**: `http://127.0.0.1:<port>/`. The workspace is local+  WSL2 with mirrored networking, so a distro port IS a Windows localhost port.+  Never the `:7380/proxy/<port>/` form, and prefer 127.0.0.1 over the name+  localhost (IPv6-first resolution costs ~800ms per request on Windows).+- **The webview belongs to your CLI.** Every step runs with+  `ADOM_DOCK_WEBVIEW` (the tab the dock pre-opened) and `ADOM_DOCK_APP` in its+  environment; once serving, take the tab over with+  `adom-cli hydrogen webview open-or-refresh --name "$ADOM_DOCK_WEBVIEW" --url ...`+  and drive it yourself from then on. The printed URL is only the dock's+  readiness gate.+ ## What a click does  ```@@ -143,6 +159,7 @@ command transcript (never the failures) when the output would be noise. | "started, but <url> never answered" | The serve command did not self-background | | "no URL within Ns" | Nothing matching `https?://\S+` on stdout (printed to stderr?) | | Install step runs every launch | `skip_if` missing or probing the wrong name |+| LED red "not answering" while the server answers curl | Health endpoint lacks `Access-Control-Allow-Origin: *` (the dock probes cross-origin from the shell) | | A duplicate tab appears | `webview.title` changed between versions | | The `ai` prompt never fires | `mode: "webview"` has no steps and no ai branch | | Works for the author, fails for users | Tested where the CLI was already installed |
skills/hydrogen-lightbulb/SKILL.md+6
@@ -22,6 +22,12 @@ human-types the prompt (open → ready → type → verify → submit, all verif the user just watches it run. It exists to teach by DOING: each prompt demonstrates a capability end to end. +**Hidden by default since 1.0.142** (`Settings > Hydrogen > Behavior > Show Lightbulb`,+default off): the menu earns its keep while learning Hydrogen and is header noise after+that. Turning the setting on shows the button immediately, no restart. The+`samples-menu.open` / `samples-menu.close` AI commands stay registered even while the+button is hidden.+ ## Current lineup (0.1.260+)  Full tour (everything at once) · Adom parts search · KiCad · Fusion 360 ·