← Commit history

Rename verbs browser_* to pup_* and rebrand Adom Desktop to Bridge in the pup skill + README (clean break, v2.0.0)

John Lauer ·498a8cf2b1 ·2mo ago ·parent 6ed3d66
2 files changed +80−76
README.md+21−21
@@ -1,4 +1,4 @@-# Adom Desktop — Puppeteer (pup) Bridge+# pup — the Puppeteer bridge  Drive a browser on the user's desktop from the cloud: open/close windows + tabs, navigate, screenshot, eval JS, record windows. pup drives the browser **already on the machine** (installed Chrome, else@@ -24,14 +24,14 @@ The page header shows **two** things, for two different places. Most people need | On the page | What it is | Who runs it | You usually… | |---|---|---|---| | **Install** — `adom-wiki pkg install …` | The **AI skill pack** — the `pup` skills that teach a cloud/container AI how to drive the bridge. Docs only, no runtime; drops into `~/.claude/skills` + `~/.codex/skills`. | your **container / cloud AI** | **run this** (or it arrives via `sync_skills`) |-| **Download for your machine** — `adom-bridge-puppeteer-v<ver>.zip` | The **bridge runtime** — the actual Node program (browser detection + driving) that runs on the desktop. | **Adom Desktop**, on the user's PC | **do nothing** — Adom Desktop **auto-installs & auto-updates** it; the manual download is only for offline/manual installs |+| **Download for your machine** — `adom-bridge-puppeteer-v<ver>.zip` | The **bridge runtime** — the actual Node program (browser detection + driving) that runs on the desktop. | **Bridge**, on the user's PC | **do nothing** — Bridge **auto-installs & auto-updates** it; the manual download is only for offline/manual installs |  **Why two different version numbers?** They're independent artifacts. The **Install (pkg)** version bumps whenever the *skills/docs* change (often). The **Download (runtime)** version bumps only when the *bridge code* changes (rarely). So it's normal to see e.g. Install `v1.8.27` and Download `v1.8.20` — nothing is out of sync; they just version on their own clocks. -*(There's also a third copy — the runtime is **bundled inside the Adom Desktop installer** as a first-run+*(There's also a third copy — the runtime is **bundled inside the Bridge installer** as a first-run seed, superseded by a newer cache copy via `updateManifestUrl`. You never touch it directly.)*  ## Use it@@ -39,13 +39,13 @@ seed, superseded by a newer cache copy via `updateManifestUrl`. You never touch ```bash # everyday: open + screenshot. Opens in the BACKGROUND automatically — no need to lower it, # and it's fully drivable/screenshottable while hidden, so the user is never interrupted.-adom-desktop browser_open_window '{"sessionId":"myapp-web","owner":"myapp","url":"http://localhost:3000"}'-adom-desktop browser_screenshot  '{"sessionId":"myapp-web"}'+adom-desktop pup_open_window '{"sessionId":"myapp-web","owner":"myapp","url":"http://localhost:3000"}'+adom-desktop pup_screenshot  '{"sessionId":"myapp-web"}' -# readiness (use browser_readiness, NOT browser_status). On a box with Chrome or Edge this is+# readiness (use pup_readiness, NOT pup_status). On a box with Chrome or Edge this is # instantly {ready:true} with no download. Only a box with no Chromium at all fetches Chrome for # Testing in the background — poll until ready, then open.-adom-desktop browser_readiness '{}'      # → {ready:true, browserKind:"chrome"|"edge"|...}+adom-desktop pup_readiness '{}'      # → {ready:true, browserKind:"chrome"|"edge"|...} ```  - **Task-prefix your `sessionId` + pass `owner`** — sessions are shared across every AI thread on the@@ -83,7 +83,7 @@ no gradients, no emoji):   AppUserModelID that **carries the category**: the only way Win11 allows a taskbar tile to change   live. Grouped mode instead keeps one stable, pinnable `Adom.Pup` identity. -### Using pup's icons in your own tool (Hydrogen browser picker, launchers, menus)+### Using pup's icons in your own tool (HD browser picker, launchers, menus)  If you are building a surface that offers "open a pup window" (a browser picker, a launcher tile, a menu item), **use pup's real icon, not a stand-in.** The canonical, self-describing asset set lives@@ -159,7 +159,7 @@ pup can show any Adom wiki page from **two viewpoints**, and both the AI and the  - **Public (default)**: a fresh logged-out profile: exactly what the world sees. Use it to verify   what a publish actually exposed.-- **Signed in**: `browser_open_window {..., wikiView:"authed"}` routes to a reserved persistent+- **Signed in**: `pup_open_window {..., wikiView:"authed"}` routes to a reserved persistent   profile whose cookie jar holds the user's ~30-day SSO session. One login serves **every** AI   thread on the machine. First use returns `wikiLoginNeeded:true`; the user signs in once, in that   window.@@ -169,7 +169,7 @@ carries **"Adom wiki: switch to logged-in view"** (or "...switch to public view" relaunches that same window under the other cookie jar, brings it to the front, and shows an on-screen caption while it works ("Switching to the logged-in view of the Adom wiki", then "Adom wiki: now the LOGGED-IN view (Their Name)"). The AI-side equivalent is the-`browser_wiki_set_view` verb, which exists for that jump-list click: AI threads should pass+`pup_wiki_set_view` verb, which exists for that jump-list click: AI threads should pass `wikiView` on open instead.  ## One session, one OS window@@ -187,7 +187,7 @@ cloud, with no HUD and no need to bring the window forward.  ![Example pup recording — driving the Adom wiki, captured in the background](https://wiki.adom.inc/api/v1/pages/adom-desktop-puppeteer-bridge/files/pup-recording-demo.gif) -*Above: a GIF preview of a real `browser_record` capture (≈8.6 s, the tab navigating the Adom wiki, recorded in the background on a VM). The native capture is the WebM linked below — this GIF is just an inline preview.*+*Above: a GIF preview of a real `pup_record` capture (≈8.6 s, the tab navigating the Adom wiki, recorded in the background on a VM). The native capture is the WebM linked below — this GIF is just an inline preview.*  ### High-FPS windows: recording something the user is not looking at @@ -200,11 +200,11 @@ So it is **opt-in and leased**:  - **Default** is Chrome's own power behaviour. Normal windows throttle when occluded and cost   nothing in the background. Right for driving, scraping and screenshots.-- **`browser_open_window {highFps:true}`** renders at full rate while occluded, for background+- **`pup_open_window {highFps:true}`** renders at full rate while occluded, for background   recording. It holds a lease (default 20 min), and **an active recording holds that lease open   indefinitely, so a take is never cut off**. On lapse the window is *downgraded* back to normal   power by relaunching with its tabs intact, never closed.-- **`browser_highfps_extend {sessionId, minutes}`** negotiates more time up front.+- **`pup_highfps_extend {sessionId, minutes}`** negotiates more time up front.  **Full details, defaults, activity signals, and rules of thumb: [RECORDING.md](RECORDING.md).** @@ -212,19 +212,19 @@ So it is **opt-in and leased**:  | Verb pair | Captures | How it works under the hood | |---|---|---|-| **`browser_record_start` / `browser_record_stop`** | ONE pup **tab/window** — the page content | **CDP `Page.startScreencast`** (JPEG frames, up to ~50 fps) piped to **ffmpeg**, encoded to **VP9** in real time → one `.webm`. Tab-scoped at the protocol level, so it captures the exact tab even in the **background** — no `getDisplayMedia`, no picker, no foreground. |+| **`pup_record_start` / `pup_record_stop`** | ONE pup **tab/window** — the page content | **CDP `Page.startScreencast`** (JPEG frames, up to ~50 fps) piped to **ffmpeg**, encoded to **VP9** in real time → one `.webm`. Tab-scoped at the protocol level, so it captures the exact tab even in the **background** — no `getDisplayMedia`, no picker, no foreground. | | **`desktop_record_start` / `desktop_record_stop`** | the WHOLE **desktop** (multiple apps, dialogs) | Screen capture via **MediaRecorder** → one `.webm`; ffmpeg remuxes it to stamp a proper Duration. |  ```bash-adom-desktop browser_record_start '{"sessionId":"demo-web"}'          # → { recordingId }+adom-desktop pup_record_start '{"sessionId":"demo-web"}'          # → { recordingId } # ...drive the scene: navigate, click, type, scroll...-adom-desktop browser_record_stop  '{"sessionId":"demo-web","recordingId":"<id>"}'   # → { path: "...webm" }+adom-desktop pup_record_stop  '{"sessionId":"demo-web","recordingId":"<id>"}'   # → { path: "...webm" } ```  **ffmpeg** is required for recording. The bridge auto-detects it (a WinGet `Gyan.FFmpeg` install, PATH, or common dirs) and re-checks **lazily on `record_start`** — so if it's missing you can-`winget install Gyan.FFmpeg` and just retry, no bridge restart. `browser_record_status` /-`browser_record_list` (and the `desktop_record_*` equivalents) report in-flight and finished captures.+`winget install Gyan.FFmpeg` and just retry, no bridge restart. `pup_record_status` /+`pup_record_list` (and the `desktop_record_*` equivalents) report in-flight and finished captures.  ### Formats — one native format, easy conversion @@ -252,7 +252,7 @@ PowerPoint/phone needs it; make a short **GIF** only for an inline auto-play pre ## Source & development  This bridge is cloud-owned here (extracted from `adom-desktop/plugins/puppeteer`, mirroring the-kicad/fusion bridges). Adom Desktop ships a bundled seed and auto-pulls newer versions from this page.+kicad/fusion bridges). Bridge ships a bundled seed and auto-pulls newer versions from this page.  - Clone the source: `adom-wiki repo clone adom/adom-desktop-puppeteer-bridge` - Runtime lives in `src/` (`server.js`, `chrome.js`, `bridge.json`, …); the release zip is `src/` at zip-root.@@ -264,7 +264,7 @@ kicad/fusion bridges). Adom Desktop ships a bundled seed and auto-pulls newer ve | `pup` (root SKILL.md) | ✅ user | Start here — mental model (CDP-launched fresh process of the installed browser, rendered-but-backgrounded), when-vs-extension, quick start | | `pup-windows-sessions-tabs` | ✅ user | sessionIds, window ownership (don't steal another thread's window), background-vs-foreground, many tabs in one window | | `pup-screenshots-recording` | ✅ user | Screenshots (full-page/full-res) + recording; driving/verifying tricky pages (shadow DOM, nested scroll) |-| `pup-browsers-and-chrome` | ✅ user | Browser detection/launch, `browser_use`, installing Chrome/CfT (incl. UAC-notify), readiness, cold-start errors |+| `pup-browsers-and-chrome` | ✅ user | Browser detection/launch, `pup_use`, installing Chrome/CfT (incl. UAC-notify), readiness, cold-start errors | | `pup-adom-wiki` | ✅ user | Driving + verifying `wiki.adom.inc` pages in pup (view/screenshot/verify; the login situation) | | `pup-bridge-dev` / `pup-bridge-publish` | ❌ source-only | MAINTAINERS: bridge internals + the publish/release recipe (in `dev-skills/`/`publish-skills/`, not shipped) | - Ownership boundary: `CLAUDE.md`. Publish recipe: `PUBLISHING.md`. History: `CHANGELOG.md`.@@ -294,6 +294,6 @@ Keep breadcrumbs on-topic (they genuinely extend pup / browser-driving); off-top  - [Bridge SDK guide](https://wiki.adom.inc/adom/adom-desktop-bridges) — `bridge.json` schema, packaging, lifecycle - [KiCad bridge](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge) · [Fusion 360 bridge](https://wiki.adom.inc/adom/adom-desktop-fusion-bridge)-- [Adom Desktop (parent app)](https://wiki.adom.inc/adom/adom-desktop)+- [Bridge (parent app)](https://wiki.adom.inc/adom/adom-desktop)  MIT licensed.
SKILL.md+59−55
@@ -1,7 +1,7 @@ --- name: pup user-invocable: true-description: "Drive a browser on the user's desktop from the cloud — the Puppeteer 'pup' bridge (browser_* verbs via the adom-desktop CLI). pup drives the browser ALREADY on the machine in a pup-managed DURABLE profile (logins/localStorage persist and are shared across pup windows; {isolated:true} for a throwaway) (installed Chrome, else Microsoft Edge — on Windows every PC has Edge, on macOS neither is guaranteed so a download may be needed), driven over CDP, in the BACKGROUND so it never disturbs the user; NOT the user's real signed-in profile (that's the Adom extension's nbrowser_* verbs), NOT headless (a real rendered window, so screenshots/recording are true-to-life). Chrome for Testing only as a last resort. Open/close windows + tabs, navigate, screenshot, eval JS, record. This is the START-HERE skill: the mental model + when-to-use + the skill map; deeper topics route to the sub-skills. For 'is it ready' use browser_readiness (not browser_status). Trigger words: pup, puppeteer, browser window, open in pup, open my app in pup, browser screenshot, browser eval, browser reload, visual debug, headful chrome, browser_open_window, browser_screenshot, browser_readiness, browser_prewarm, chrome for testing, open in browser, browser_login, log into a site in pup, autofill password, saved login, durable profile, isolated window."+description: "Drive a browser on the user's desktop from the cloud — the Puppeteer 'pup' bridge (pup_* verbs via the adom-desktop CLI). pup drives the browser ALREADY on the machine in a pup-managed DURABLE profile (logins/localStorage persist and are shared across pup windows; {isolated:true} for a throwaway) (installed Chrome, else Microsoft Edge — on Windows every PC has Edge, on macOS neither is guaranteed so a download may be needed), driven over CDP, in the BACKGROUND so it never disturbs the user; NOT the user's real signed-in profile (that's the Adom extension's nbrowser_* verbs), NOT headless (a real rendered window, so screenshots/recording are true-to-life). Chrome for Testing only as a last resort. Open/close windows + tabs, navigate, screenshot, eval JS, record. This is the START-HERE skill: the mental model + when-to-use + the skill map; deeper topics route to the sub-skills. For 'is it ready' use pup_readiness (not pup_status). Trigger words: pup, puppeteer, browser window, open in pup, open my app in pup, browser screenshot, browser eval, browser reload, visual debug, headful chrome, pup_open_window, pup_screenshot, pup_readiness, pup_prewarm, chrome for testing, open in browser, pup_login, log into a site in pup, autofill password, saved login, durable profile, isolated window." ---  # Pup — browser control on the user's desktop (start here)@@ -10,8 +10,8 @@ Drive a browser on the user's desktop from a cloud container via the `adom-deskt **Bash command** (NOT an MCP tool):  ```bash-adom-desktop --ai-thread "<your thread>" browser_open_window '{"sessionId":"mytask-web","owner":"mytask","url":"https://example.com"}'-adom-desktop --ai-thread "<your thread>" browser_screenshot  '{"sessionId":"mytask-web"}'+adom-desktop --ai-thread "<your thread>" pup_open_window '{"sessionId":"mytask-web","url":"https://example.com"}'+adom-desktop --ai-thread "<your thread>" pup_screenshot  '{"sessionId":"mytask-web"}' ```  ## THE CORE GOAL (John's words, so everyone truly understands it)@@ -40,7 +40,7 @@ USER explicitly told you not to save that particular thing. SAVE the credentials to the adom-you profile: accept every "Save password?" and "Update password?" bubble, so the shared username/password store grows into the best adom-you profile it can be. A signed-in session whose password you did NOT save is a missed brick in that database. If you filled-a login via `browser_login`, pass `save:true` (or accept the resulting Chrome save bubble) so the+a login via `pup_login`, pass `save:true` (or accept the resulting Chrome save bubble) so the credential is persisted, not just used once.  - These are Chrome's own bubbles (browser UI, not page DOM), so click them by accessible name in the@@ -53,14 +53,14 @@ credential is persisted, not just used once.  ## The mental model — what pup actually is (read this once) -pup is a small **Node bridge** that Adom Desktop spawns on the user's machine. When you open a window it+pup is a small **Node bridge** that Bridge spawns on the user's machine. When you open a window it does NOT attach to the user's running browser and does NOT run headless. It:  1. **Launches a brand-new process of the browser ALREADY installed** on the machine — **installed Chrome    first, else Microsoft Edge**. On **Windows** every PC ships Edge, so there's almost always a candidate    and **no download**. On **macOS** nothing is guaranteed: Safari is not CDP-drivable, and Edge is not    preinstalled — so on a Mac with no Chrome, pup falls through to downloading Chrome (or cached Chrome-   for Testing). Don't promise the user "no download" off Windows; check `browser_readiness` instead. It+   for Testing). Don't promise the user "no download" off Windows; check `pup_readiness` instead. It    uses their *browser binary*, never their *profile*. 2. **…with a pup-managed `--user-data-dir` + a CDP debugging port** (`--remote-debugging-port`), driven over    the Chrome DevTools Protocol. This is a profile **pup owns and persists**, never the user's own signed-in@@ -72,7 +72,7 @@ does NOT attach to the user's running browser and does NOT run headless. It:    explicit `{profile:"<name>"}` for a separate durable jar. (Need the user's REAL signed-in profile/SSO?    That's the **Adom extension** `nbrowser_*` — see below.) 3. **Renders a REAL window, but keeps it in the BACKGROUND.** It is *not* headless — a real GPU-composited-   window, so `browser_screenshot` / recording are true-to-life and canvas/WebGL/media render correctly.+   window, so `pup_screenshot` / recording are true-to-life and canvas/WebGL/media render correctly.    But pup immediately z-orders it to the **bottom** (Windows: Win32 `SetWindowPos` with    `SWP_NOACTIVATE`; macOS: the browser process is **hidden**, the Cmd+H state — same contract), so it    renders **without stealing focus and without covering the user's active window**. You drive and@@ -103,10 +103,10 @@ throttled, not broken.)  **When you genuinely need a background window to keep rendering** — recording it, or a live view the user watches WITHOUT focusing it (a dashboard, a running demo) — upgrade that ONE window:-`browser_open_window {highFps:true}` (or the recorder path sets it). This applies the anti-throttle+`pup_open_window {highFps:true}` (or the recorder path sets it). This applies the anti-throttle flags so the occluded window renders full-rate. It is **LEASED**: 20 min by default, 120 max, and it **auto-downgrades when idle / when the lease lapses**, so it can NEVER drain the battery the way-issue #14 did. Renew a still-needed lease with `browser_highfps_extend {sessionId, minutes}`.+issue #14 did. Renew a still-needed lease with `pup_highfps_extend {sessionId, minutes}`.  **The balance, in one line:** throttled by default (battery), `highFps` only for a window that must keep rendering while unfocused, and the lease makes that self-limiting. Do NOT reach for `highFps` to@@ -117,7 +117,7 @@ keep rendering while unfocused, and the lease makes that self-limiting. Do NOT r pup is built to stay usable when the shared Chrome is under heavy load, and to recover on its own if Chrome ever crashes. Read these signals correctly instead of assuming the worst: -- **`_degraded` on `browser_list_windows` / `browser_status`.** These verbs ALWAYS return the full+- **`_degraded` on `pup_list_windows` / `pup_status`.** These verbs ALWAYS return the full   window list, fast, even when a window is too busy (Chrome under heavy load on a heavy SPA) to answer   a live read. A busy window comes back marked `degraded` with its LAST-KNOWN url/title. `_degraded`   means "that window is busy, NOT crashed — retry shortly for live values." It does NOT mean pup is@@ -131,7 +131,7 @@ Chrome ever crashes. Read these signals correctly instead of assuming the worst:   does pup stop retrying it (after 2 attempts) and tell the user the page itself looks unstable. - **One window times out but others work = a per-window jam, not a dead bridge.** Verbs are   concurrent; a slow/jammed window does not block operations on OTHER windows, and never blocks-  list/status. If `browser_eval` on window A times out, window B still works and the bridge is fine.+  list/status. If `pup_eval` on window A times out, window B still works and the bridge is fine. - **Genuinely frozen vs throttled vs busy:** a background window that looks static is almost always   just THROTTLED (see Power & rendering above) — focus it and it wakes. A window that is BUSY shows up   as `degraded`. A window that CRASHED gets auto-relaunched. None of these is "pup is dead."@@ -159,11 +159,11 @@ the OS for what was really "the human took their focus back, because nobody aske    change between two events, and a half-typed password in the wrong window is a real harm. 6. **Caption each step** as you go, and caption when you are DONE so they know they can resume. -**`browser_assisted_click` already implements all six.** Use it instead of hand-rolling+**`pup_assisted_click` already implements all six.** Use it instead of hand-rolling raise-then-click. If you need native typing, follow the same protocol around it.  **Corollaries that cost this project real time:**-- CDP input (`browser_click`, `browser_type`) needs NO foreground and disturbs nobody — **always+- CDP input (`pup_click`, `pup_type`) needs NO foreground and disturbs nobody — **always   prefer it**. Reach for native input ONLY when the target genuinely cannot be driven any other way   (a cross-origin FedCM chooser, native OS chrome, a site that rejects synthetic events). - Native input returning `ok:true` means "the event was dispatched to the OS", **NOT** "it reached@@ -178,11 +178,11 @@ NEVER put a window on the user's screen silently. Any time you FOREGROUND a pup pup's auto-toast do it, v1.9.166+) so the user's attention is drawn to what just appeared. A window that pops up with no heads-up reads as the agent hijacking the screen. pup now auto-fires `notify_user` on every granted foreground, but you should still narrate WHY in the reason. For a-background nudge (not a full foreground), use `browser_alert_window` (taskbar flash) instead.+background nudge (not a full foreground), use `pup_alert_window` (taskbar flash) instead.  **If you foreground in order to CLICK something pup's CDP click can't drive** (a native OS dialog, or a cross-origin FedCM "Sign in with Google" account chooser that rejects in-page clicks), use-**`browser_assisted_click`** — never raise-then-click by hand. It captions the reason + toasts first,+**`pup_assisted_click`** — never raise-then-click by hand. It captions the reason + toasts first, foregrounds, then WAITS and detects if the user alt-tabbed away (holding, captioning "Adom is waiting" until they're back — it clicks NOTHING while they're not ready), then does the tight real-cursor hover→click and narrates each step. This is the protocol so pup never foregrounds-and-acts while the@@ -204,12 +204,12 @@ counterpart when you need the user's REAL profile/SSO directly.  - Sign into a site ONCE in any pup window and the durable profile keeps it — every later window   (any thread) is already signed in. Google/wiki proven; works for any cookie-session site.-- `browser_login {sessionId, username?, password?, submit?, save?}` fills an HTML login form in+- `pup_login {sessionId, username?, password?, submit?, save?}` fills an HTML login form in   the BACKGROUND (no Chrome dropdown, no foregrounding, React/Vue-safe). With `save:true` the   cred is stored (OS keychain via credential_set) and next time a bare-  `browser_login {sessionId, submit:true}` signs in from the vault.+  `pup_login {sessionId, submit:true}` signs in from the vault. - Vault-matched login pages AUTO-FILL on page load (fill only, never auto-submit) — response-  carries `_autoLogin`. Off switch: `browser_configure {autoLogin:"off"}`.+  carries `_autoLogin`. Off switch: `pup_configure {autoLogin:"off"}`. - Chrome's own "Save password?" bubble works in pup windows (the durable profile enables the   password manager), so manual logins accumulate too. - Need a window that is NOT signed in (fresh-login test, act-as-another-user)? `{isolated:true}`@@ -223,7 +223,7 @@ counterpart when you need the user's REAL profile/SSO directly. | **`pup`** (this file) | The mental model, when-to-use, quick start, the essentials | | **`pup-windows-sessions-tabs`** | sessionIds + naming, **window ownership** (don't steal another AI thread's window), **background vs foreground** rules, **many tabs in one window**, list/switch/close, verifying what you opened | | **`pup-screenshots-recording`** | Screenshots (full-page, full-res), reading the shot, **recording** a window vs the whole desktop, framing a window for a clean shot without foregrounding |-| **`pup-browsers-and-chrome`** | The browser detection/CDP-launch details, `browser_use` to pin a browser, **installing Chrome / Chrome-for-Testing** (incl. the UAC-notify flow), **readiness**, the cold-start error table, Node provisioning, the shell-approval gate |+| **`pup-browsers-and-chrome`** | The browser detection/CDP-launch details, `pup_use` to pin a browser, **installing Chrome / Chrome-for-Testing** (incl. the UAC-notify flow), **readiness**, the cold-start error table, Node provisioning, the shell-approval gate | | **`pup-adom-wiki`** | Driving + verifying **`wiki.adom.inc`** pages in pup (show/screenshot/verify a published page; the login situation — pup views the wiki logged out for now) |  *(Maintaining the bridge itself? The `pup-bridge-dev` + `pup-bridge-publish` skills are source-only in the@@ -233,29 +233,32 @@ repo — not shipped here.)*  ```bash # Open in the BACKGROUND (automatic — never disturbs the user), then drive it invisibly.-adom-desktop --ai-thread "<your thread>" browser_open_window '{"sessionId":"mytask-web","owner":"mytask","url":"http://localhost:3000"}'-adom-desktop --ai-thread "<your thread>" browser_eval        '{"sessionId":"mytask-web","expr":"document.title"}'   # verify it really loaded-adom-desktop --ai-thread "<your thread>" browser_screenshot  '{"sessionId":"mytask-web"}'                            # true-to-life PNG+adom-desktop --ai-thread "<your thread>" pup_open_window '{"sessionId":"mytask-web","url":"http://localhost:3000"}'+adom-desktop --ai-thread "<your thread>" pup_eval        '{"sessionId":"mytask-web","expr":"document.title"}'   # verify it really loaded+adom-desktop --ai-thread "<your thread>" pup_screenshot  '{"sessionId":"mytask-web"}'                            # true-to-life PNG ``` -- **Task-prefix your `sessionId` and pass `owner`** — sessions are shared across every AI thread on the-  desktop; this stops another thread stealing your window (and you stealing theirs). See+- **Your `--ai-thread` name IS the owner** — pup owns every window by the ai-thread that opened it, so+  just always pass `--ai-thread "<your conversation name>"` (you already must — see below) and the window+  is yours; no separate `owner` arg needed (it's a legacy alias). A `pup_open_window` with no+  ai-thread (and no `owner`) is **refused** with `caller_identity_required`. Task-prefix your `sessionId`+  (it's the window's KEY, never its identity) so two of your own windows don't collide. See   `pup-windows-sessions-tabs`.-- **`ok:true` means navigation *started*, not that content rendered** — sanity-check with `browser_eval`+- **`ok:true` means navigation *started*, not that content rendered** — sanity-check with `pup_eval`   on `document.title` (404s / login walls change it) or a screenshot. - **On a fresh PC it's usually a non-event** — pup drives the installed Chrome/Edge with no download. The   rare "no browser at all" / Node-provisioning / disk cases are all in `pup-browsers-and-chrome`.  ## macOS differences (v1.9.74+) — same verbs, mac-native mechanics -Every `browser_*` verb works on macOS; only the OS-presentation layer differs:+Every `pup_*` verb works on macOS; only the OS-presentation layer differs:  - **Background = hidden app.** A background open launches the session's browser process hidden   (the Cmd+H state) — truly invisible, still fully drivable/screenshottable/recordable.-  `browser_lower_os_window` re-hides; `browser_raise_os_window` (with `foregroundReason`)+  `pup_lower_os_window` re-hides; `pup_raise_os_window` (with `foregroundReason`)   unhides + activates. One session = one browser process, so a shared-profile window group   hides/shows together.-- **`browser_alert_window` = un-hide (not focused) + a native toast** whose click focuses the+- **`pup_alert_window` = un-hide (not focused) + a native toast** whose click focuses the   window — macOS has no taskbar to flash. `{stop:true}` dismisses the toast. - **No taskbar identity on mac** — the per-window icons/badges/jump-lists below are   Windows-only; on mac the window title's `(session: <id>)` suffix is the identity.@@ -264,13 +267,13 @@ Every `browser_*` verb works on macOS; only the OS-presentation layer differs:  ## Taskbar preference — the USER can ask for "combined mode" -> **WINDOWS ONLY.** macOS has no taskbar; `browser_configure` stores the preference and returns+> **WINDOWS ONLY.** macOS has no taskbar; `pup_configure` stores the preference and returns > `taskbarSupported:false`, but nothing changes on screen. Don't propose it to a Mac user.  pup windows each get their own taskbar button by default ("split": one button per window, badged with its tab count). If the user says anything like "combine my pup windows", "group the pup icons", "one taskbar button for pup", "my taskbar is cluttered with pup windows" — that's the-GROUPED preference: `browser_configure {"taskbarGrouping":"grouped"}` stacks every pup window+GROUPED preference: `pup_configure {"taskbarGrouping":"grouped"}` stacks every pup window under one "Adom Pup" button whose badge shows windows + total tabs. "Split them back" / "separate icons" → `{"taskbarGrouping":"split"}`. The setting persists across updates. Change it ONLY when the user asks — it's their taskbar. Details: `pup-windows-sessions-tabs`.@@ -280,8 +283,8 @@ the user asks — it's their taskbar. Details: `pup-windows-sessions-tabs`. > **WINDOWS ONLY.** Everything in this section is a Windows taskbar behaviour. **macOS has no > taskbar**, no per-window tile, and no jump list, so on a Mac none of these icons, overlays or > right-click affordances exist. The category signal that DOES survive cross-platform is the **tab-> title** (solid dot = signed in, hollow dot = public). On macOS, use `browser_list_windows` /-> `browser_list_tabs` to tell windows apart — do not refer the user to an icon they cannot see,+> title** (solid dot = signed in, hollow dot = public). On macOS, use `pup_list_windows` /+> `pup_list_tabs` to tell windows apart — do not refer the user to an icon they cannot see, > and do not skip a check on the assumption an icon already answered it.  In split mode every pup window's taskbar icon states its CONTENT KIND. All five share the teal@@ -302,42 +305,43 @@ any of this; pup does it automatically. It is worth KNOWING because **on Windows login state and window kind at a glance — there, do not open a second window to "check" what a taskbar icon already answers, and when the user says "the window with the book/globe icon" you know which one they mean. **On macOS neither shortcut applies**: there is no icon to read, so check with-`browser_list_tabs` rather than assuming, and expect the user to describe a window by its title or+`pup_list_tabs` rather than assuming, and expect the user to describe a window by its title or URL rather than by an icon.  A WIKI window's taskbar right-click jump list also carries a view toggle ("Adom wiki: switch to logged-in view" / "...to public view") — a USER affordance that relaunches that window under the-other cookie jar with on-screen captions. Its callback verb `browser_wiki_set_view` is not for AI-threads; pass `wikiView:"authed"|"public"` on browser_open_window instead. Details: `pup-adom-wiki`.+other cookie jar with on-screen captions. Its callback verb `pup_wiki_set_view` is not for AI+threads; pass `wikiView:"authed"|"public"` on pup_open_window instead. Details: `pup-adom-wiki`.  ## Key rules (the essentials — details in the sub-skills)  - **Always pass `sessionId`** on session-scoped verbs — `'{}'` targets the *active* session, which may not-  be the one you mean. There is no top-level `pup` subcommand; it's `adom-desktop browser_<cmd> <json>`.-  **ONE DANGEROUS EXCEPTION: `browser_close`.** There `'{}'` does NOT mean "the active session" — it means+  be the one you mean. There is no top-level `pup` subcommand; it's `adom-desktop pup_<cmd> <json>`.+  **ONE DANGEROUS EXCEPTION: `pup_close`.** There `'{}'` does NOT mean "the active session" — it means   **ALL sessions, every window, across every AI thread on the machine**, immediately and without-  confirmation. If you want to close one window it is `browser_close_window {"sessionId":"…"}`, never-  `browser_close '{}'`. The rule above would otherwise lead you to expect the opposite, which is exactly+  confirmation. If you want to close one window it is `pup_close_window {"sessionId":"…"}`, never+  `pup_close '{}'`. The rule above would otherwise lead you to expect the opposite, which is exactly   how a tidy-up call becomes a data-loss event on someone's long-running dashboards. - **Background is guaranteed; `foreground:true` is the only way to show a window** — not sizing, not   navigating, not screenshotting.-- **Own your window** (`owner:"<task>"`, task-prefixed `sessionId`); never navigate/close a window you-  don't own.-- **One window, many tabs** — use `browser_open_tab`, not a second `browser_open_window`, for multi-URL work.-- **`browser_readiness`** (not `browser_status`) answers "is it ready / which browser?".-- **No semicolons in `browser_eval`** — use the comma operator or an IIFE.+- **Own your window** — always pass `--ai-thread "<your conversation name>"`; that name is the window's+  owner (the `owner:` arg is a legacy alias). Task-prefix your `sessionId` (the window key, not an+  identity). Never navigate/close a window you don't own.+- **One window, many tabs** — use `pup_open_tab`, not a second `pup_open_window`, for multi-URL work.+- **`pup_readiness`** (not `pup_status`) answers "is it ready / which browser?".+- **No semicolons in `pup_eval`** — use the comma operator or an IIFE. - **Complex page fighting you?** (a `querySelectorAll` comes back empty, or a screenshot overshoots the   target) — it's usually shadow DOM or a nested scroll container. Verify by DOM, scroll the *element* not   the window, count before you eyeball. See `pup-screenshots-recording` → "Driving & verifying tricky pages."-- **Never broadly kill Chrome** — `browser_close_window` for a specific session.+- **Never broadly kill Chrome** — `pup_close_window` for a specific session. - **Surface a verb's `_hint` verbatim** whenever it reports not-ready or failure. - **If pup's verbs TIME OUT (or the AD LED is dim / `browser.bridgeRunning:false`) the bridge PROCESS is   down** — usually right after a `restart_bridge` or a crash. To wake it, fire **one**-  `browser_open_window` (a status probe like `browser_readiness` does NOT respawn a down bridge — only a-  window/tab verb does), then poll `browser_list_windows` with backoff until it answers. **Do NOT spam+  `pup_open_window` (a status probe like `pup_readiness` does NOT respawn a down bridge — only a+  window/tab verb does), then poll `pup_list_windows` with backoff until it answers. **Do NOT spam   opens** while AD says `bridge_starting` — one is enough; it's already starting. (`bridge_list`   `status:"running"` is not liveness; the LED + `browser.bridgeRunning` + an actual verb reply are.)-- Full machine-readable catalog: `adom-desktop --ai-thread "<your thread>" browser_describe '{}'`.+- Full machine-readable catalog: `adom-desktop --ai-thread "<your thread>" pup_describe '{}'`.  ## Verify what you rendered — pup checks it FOR you (v1.9.106) @@ -345,8 +349,8 @@ threads; pass `wikiView:"authed"|"public"` on browser_open_window instead. Detai recently opened its own app, got `ok:true`, and told the user it worked. The page was serving `404 page not found`. -So pup now looks. Every `browser_open_window` / `browser_open_tab` / `browser_navigate` /-`browser_reload` response can carry two fields:+So pup now looks. Every `pup_open_window` / `pup_open_tab` / `pup_navigate` /+`pup_reload` response can carry two fields:  - **`renderCheck`** — a verdict: `{ok, httpStatus, signals[], excerpt, bodyChars, elements}`. Signals   include `http_404`, `page_says_not_found`, `dev_server_cannot_get`, `server_error_text`,@@ -355,7 +359,7 @@ So pup now looks. Every `browser_open_window` / `browser_open_tab` / `browser_na   *"THIS PAGE DID NOT RENDER CORRECTLY — pup checked it, so do NOT report success"*.  **The rule: if `renderCheck.ok === false`, do not tell the user it works.** Screenshot it-(`browser_screenshot {sessionId}`) and READ the image, fix the cause, `browser_reload`, and re-check+(`pup_screenshot {sessionId}`) and READ the image, fix the cause, `pup_reload`, and re-check until the response comes back clean. A 404 or "Cannot GET" usually means the dev server is not running, is on a different port, or the path is wrong; unstyled HTML means the CSS 404'd; a stack trace means the app threw.@@ -389,7 +393,7 @@ AD refuses any command that does not say which AI conversation is asking the verb:  ```bash-adom-desktop --ai-thread "chip-fetcher tab 3" browser_open_window '{"sessionId":"cf-web","url":"..."}'+adom-desktop --ai-thread "chip-fetcher tab 3" pup_open_window '{"sessionId":"cf-web","url":"..."}' ```  Pick a name the USER would recognise for this conversation, never a UUID. It is **attribution, not@@ -411,7 +415,7 @@ looked at the taskbar ON GLASS, because the API reports a badge as applied while  Run ALL SEVEN sections, in this order, and report them as a table: -1. **Sessions (bridge view)** — `browser_list_windows`: count, sessionId, owner, tabCount, profile,+1. **Sessions (bridge view)** — `pup_list_windows`: count, sessionId, owner, tabCount, profile,    ageMinutes, `_degraded`. Flag: owner:null (ownership protection is off), duplicate sessions    pointing at one window, a tabCount that does not match what was opened. 2. **ICONS / TASKBAR (never skip)**@@ -428,7 +432,7 @@ Run ALL SEVEN sections, in this order, and report them as a table:    session (no ghosts, no untracked strays), and each rect at the standard inset frame    (15,15 2531x1499 on this laptop). A maximized window reads ~-11,-11 2582x1550 — that is    maximized, not stranded. `-32000,-32000` = minimized, confirm with IsIconic before calling it lost.-4. **Health** — `browser_eval` per window: `readyState`, `visibilityState`, a `1+1` drivability check.+4. **Health** — `pup_eval` per window: `readyState`, `visibilityState`, a `1+1` drivability check.    `visibilityState:hidden` is NORMAL for a parked/minimized window; only **frontmost-yet-hidden** is    the broken state. Note any crashed tabs. 5. **Log** — `bridge_log_read`, count `[crash] [heal] [health] [unstick] [disconnect] [reclaim]@@ -483,5 +487,5 @@ misbehaving rather than as an agent choosing to interrupt. Related rules that travel with this one: - Native OS input (`desktop_click` / `desktop_type`) needs the full warn-and-wait protocol, not just a   caption, because it goes to whatever window has focus.-- `browser_assisted_click` already implements toast + caption + wait-for-ready + re-verify. Use it+- `pup_assisted_click` already implements toast + caption + wait-for-ready + re-verify. Use it   instead of hand-rolling raise-then-click.