app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
Publish 2.0.0
7 files changed
+120−120
package.json+2−2@@ -1,6 +1,6 @@ { "slug": "adom-desktop-puppeteer-bridge",- "version": "1.9.293",+ "version": "2.0.0", "type": "app", "description": "The pup SKILL PACK \u2014 the AI skills that teach a cloud/container AI how to drive the Adom Desktop Puppeteer (pup) bridge (browser_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, browsers+Chrome install, and driving the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY \u2014 this is NOT the bridge runtime. The bridge itself (the Node program that drives the browser) runs on the DESKTOP: Adom Desktop bundles it and auto-installs/updates it, and it's published separately on the Releases tab as adom-bridge-puppeteer-v<ver>.zip. So the pkg version (skills, bumps often) and the Release version (runtime, bumps rarely) differ on purpose. TL;DR: pkg install = the skills for your AI; the bridge = the Release zip, which Adom Desktop manages for you.", "keywords": [@@ -38,4 +38,4 @@ "dependencies": { "adom/adom-desktop": "^1.9.63" }-}+}
page.json+1−1@@ -4,7 +4,7 @@ "slug": "adom-desktop-puppeteer-bridge", "title": "Pup - Puppeteer Bridge", "brief": "The Adom Desktop Puppeteer (pup) bridge — drive a real headful browser on the user's desktop from the cloud: open/screenshot/eval/record. Cold-start drives the browser ALREADY on the machine (installed Chrome, else Microsoft Edge — every Windows PC ships with Edge) with a fresh isolated profile, so the common case needs NO download; Chrome for Testing is fetched only as a last resort. Two artifacts: the Releases zip is the bridge runtime (also bundled in Adom Desktop); the pkg installs the container-side Claude skills.",- "version": "1.9.293",+ "version": "2.0.0", "tags": [ "skills", "pup",
skills/pup-adom-wiki/SKILL.md+8−8@@ -1,7 +1,7 @@ --- name: pup-adom-wiki user-invocable: true-description: "Driving the Adom wiki (wiki.adom.inc) inside a pup window — the common case of loading, viewing, and VERIFYING wiki pages with the browser_* verbs. Use when the user says show me the wiki / open my wiki page in pup / verify my page rendered / check the hero/version/download card / does my published page look right. Covers: pup shows the RENDERED page (use the adom-wiki CLI for API tasks like publish/search/releases — NOT pup); wiki pages are component-heavy with nested scroll, so use the shadow-DOM-survey + scroll-the-element techniques; and the LOGIN situation — every pup window shares ONE durable profile (v1.9.162), so once the user signs into the wiki once it stays logged in everywhere; wikiView:"public" is the explicit logged-OUT view for publish-visibility checks. Trigger words: adom wiki, wiki.adom.inc, show me the wiki, open the wiki in pup, open my wiki page, verify my wiki page, check my hero, wiki version chip, download card, wiki page rendered, is my page live, wiki login pup, log into the wiki."+description: "Driving the Adom wiki (wiki.adom.inc) inside a pup window — the common case of loading, viewing, and VERIFYING wiki pages with the pup_* verbs. Use when the user says show me the wiki / open my wiki page in pup / verify my page rendered / check the hero/version/download card / does my published page look right. Covers: pup shows the RENDERED page (use the adom-wiki CLI for API tasks like publish/search/releases — NOT pup); wiki pages are component-heavy with nested scroll, so use the shadow-DOM-survey + scroll-the-element techniques; and the LOGIN situation — every pup window shares ONE durable profile (v1.9.162), so once the user signs into the wiki once it stays logged in everywhere; wikiView:"public" is the explicit logged-OUT view for publish-visibility checks. Trigger words: adom wiki, wiki.adom.inc, show me the wiki, open the wiki in pup, open my wiki page, verify my wiki page, check my hero, wiki version chip, download card, wiki page rendered, is my page live, wiki login pup, log into the wiki." --- **Parent skill:** adom-desktop-puppeteer-bridge · See also: `pup` (start here), `pup-screenshots-recording` (the tricky-pages techniques), the `adom-wiki` skill (the CLI for API work).@@ -23,9 +23,9 @@ download card is correct. This skill is the wiki-specific playbook. ```bash # Verify a page you just published (own the window, task-prefix the id):-adom-desktop browser_open_window '{"sessionId":"wiki-mypage","owner":"mypage","url":"https://wiki.adom.inc/adom/my-slug?v=1.2.3"}'-adom-desktop browser_eval '{"sessionId":"wiki-mypage","expr":"document.title"}' # confirm it loaded-adom-desktop browser_screenshot '{"sessionId":"wiki-mypage"}' # read it — did the hero/version render?+adom-desktop pup_open_window '{"sessionId":"wiki-mypage","owner":"mypage","url":"https://wiki.adom.inc/adom/my-slug?v=1.2.3"}'+adom-desktop pup_eval '{"sessionId":"wiki-mypage","expr":"document.title"}' # confirm it loaded+adom-desktop pup_screenshot '{"sessionId":"wiki-mypage"}' # read it — did the hero/version render? ``` Reuse **one** window per page (stable `sessionId` like `wiki-<slug>`) so re-publishes refresh the same@@ -36,7 +36,7 @@ window instead of piling up tabs. The wiki renders widgets (download cards, sample-prompt cards, the hero) and often puts content in a **nested scroll container**. So the usual gotchas apply (full detail in `pup-screenshots-recording` → "Driving & verifying tricky pages"):-- To find/count something (download cards, skills, files), **count in `browser_eval` by document-Y**+- To find/count something (download cards, skills, files), **count in `pup_eval` by document-Y** (`rect.top + window.scrollY`), don't eyeball a screenshot that may have overshot. - To screenshot a specific widget, **`el.scrollIntoView({block:'center'})`** — `window.scrollTo` does nothing on the wiki's inner scroll box.@@ -48,11 +48,11 @@ The wiki renders widgets (download cards, sample-prompt cards, the hero) and oft pup can now open a wiki page from **either viewpoint**, and the killer use is showing the user **both** to verify publish visibility: -- **Logged-in (the DEFAULT now, v1.9.162):** a plain `browser_open_window {url:"https://wiki.adom.inc/..."}`+- **Logged-in (the DEFAULT now, v1.9.162):** a plain `pup_open_window {url:"https://wiki.adom.inc/..."}` opens in the ONE shared `adom-you` profile — the same jar every other pup window uses — so once the user has signed into the wiki once, EVERY wiki window (and every other pup window) is already logged in. `wikiView:"authed"` is now just the explicit name for this default.-- **Public / anonymous (EXPLICIT):** `browser_open_window {url:"...", wikiView:"public"}` — this is+- **Public / anonymous (EXPLICIT):** `pup_open_window {url:"...", wikiView:"public"}` — this is the ONLY wiki view that gets a separate jar: a fresh logged-OUT isolated profile, because showing "what the world sees" requires being logged out. It does NOT accumulate anything and is only for a deliberate publish-visibility check.@@ -66,7 +66,7 @@ the source?"* The public view answers that directly (if you can see `src/` there response `_hint` tells you which view you opened and nudges you to open the other. **One-time login, then it accumulates.** If the shared profile has not been signed into the wiki-yet, an authed open comes back `wikiLoginNeeded:true`. Sign in once: `browser_raise_os_window` with+yet, an authed open comes back `wikiLoginNeeded:true`. Sign in once: `pup_raise_os_window` with a `foregroundReason` like `"sign into the Adom wiki"`, the user completes the hydrogen.adom.inc SSO, and the session now lives in the ONE shared `adom-you` jar (v1.9.162) — so it is logged in for every pup window from then on, not just wiki ones. Tell the user it is a one-time login.
skills/pup-browsers-and-chrome/SKILL.md+20−20@@ -1,7 +1,7 @@ --- name: pup-browsers-and-chrome user-invocable: true-description: "How pup (Puppeteer bridge) picks and launches a browser, and every cold-start / readiness case. Covers: the installed-browser-first architecture (launches a FRESH CDP-driven process of installed Chrome → else Edge → else cached Chrome for Testing, spawn-verified with fallthrough, caches the winner as default); browser_use to pin chrome/edge/cft/auto; INSTALLING real Google Chrome on an Edge-only box (browser_use install:true) including the UAC-approval-notify flow on locked-down machines; installing/prewarming Chrome for Testing; browser_readiness (READ-ONLY — the right 'is it ready / which browser' probe, NOT browser_status); the cold-start error table (node_not_found, bridge_restarting, chrome_for_testing_installing, chrome_install_no_disk, CfT 'skipped' is not a failure); AD-managed Node provisioning (no install needed on AD >=1.9.63); and the shell-approval gate. Read on any browser-pick question, a fresh/first-run PC, an install/prewarm, a not-ready error, or a shell-approval prompt. Trigger words: browser_readiness, browser_use, browser_prewarm, chrome for testing, install chrome, chrome_for_testing_installing, node_not_found, bridge_restarting, chrome_install_no_disk, cold start pup, which browser, edge only box, UAC approve chrome, request_shell_approval, shell command approval, adom runtimes, pup not ready."+description: "How pup (Puppeteer bridge) picks and launches a browser, and every cold-start / readiness case. Covers: the installed-browser-first architecture (launches a FRESH CDP-driven process of installed Chrome → else Edge → else cached Chrome for Testing, spawn-verified with fallthrough, caches the winner as default); pup_use to pin chrome/edge/cft/auto; INSTALLING real Google Chrome on an Edge-only box (pup_use install:true) including the UAC-approval-notify flow on locked-down machines; installing/prewarming Chrome for Testing; pup_readiness (READ-ONLY — the right 'is it ready / which browser' probe, NOT pup_status); the cold-start error table (node_not_found, bridge_restarting, chrome_for_testing_installing, chrome_install_no_disk, CfT 'skipped' is not a failure); AD-managed Node provisioning (no install needed on AD >=1.9.63); and the shell-approval gate. Read on any browser-pick question, a fresh/first-run PC, an install/prewarm, a not-ready error, or a shell-approval prompt. Trigger words: pup_readiness, pup_use, pup_prewarm, chrome for testing, install chrome, chrome_for_testing_installing, node_not_found, bridge_restarting, chrome_install_no_disk, cold start pup, which browser, edge only box, UAC approve chrome, request_shell_approval, shell command approval, adom runtimes, pup not ready." --- **Parent skill:** adom-desktop-puppeteer-bridge · See also: `pup` (start here), `pup-windows-sessions-tabs`, `pup-screenshots-recording`.@@ -25,31 +25,31 @@ builds a candidate list and **spawn-verifies** it: 5. **Chrome for Testing is the last resort** — fetched (~150 MB) only if the box has *no* Chrome and *no* Edge and *no* cached CfT. -## "Is it ready / which browser?" — `browser_readiness` (READ-ONLY)+## "Is it ready / which browser?" — `pup_readiness` (READ-ONLY) ```bash-adom-desktop browser_readiness '{}'+adom-desktop pup_readiness '{}' # → {ready, browserKind:"chrome"|"edge"|"chrome-for-testing", browserSource, browserExecutablePath, # candidates:[…the full fallthrough list…], defaultBrowser, diskFreeMb, chromeStablePhase, …} ``` -- **`browser_readiness`** is the right probe — it never triggers a download and tells you exactly which+- **`pup_readiness`** is the right probe — it never triggers a download and tells you exactly which browser will be driven + the candidate list + the default.-- **`browser_status` is NOT a readiness check** — it only lists open sessions.+- **`pup_status` is NOT a readiness check** — it only lists open sessions. - AD-core **`bridge_readiness`** is the platform-level "is this bridge up?" probe. -## Pinning a browser — `browser_use`+## Pinning a browser — `pup_use` pup auto-picks, but you can override (persists across restarts; launch still spawn-verifies + falls back): ```bash-adom-desktop browser_use '{"browser":"chrome"}' # pin installed Chrome-adom-desktop browser_use '{"browser":"edge"}' # pin Edge-adom-desktop browser_use '{"browser":"cft"}' # install Chrome for Testing (~150 MB once) + pin it-adom-desktop browser_use '{"browser":"auto"}' # back to auto-pick+adom-desktop pup_use '{"browser":"chrome"}' # pin installed Chrome+adom-desktop pup_use '{"browser":"edge"}' # pin Edge+adom-desktop pup_use '{"browser":"cft"}' # install Chrome for Testing (~150 MB once) + pin it+adom-desktop pup_use '{"browser":"auto"}' # back to auto-pick ``` -Every `browser_open_window` response also carries `browser` (what was driven), `availableBrowsers`, and+Every `pup_open_window` response also carries `browser` (what was driven), `availableBrowsers`, and `defaultBrowser` — surface it if the user asks which browser pup is using. ## Installing real Google Chrome on an Edge-only box@@ -57,7 +57,7 @@ Every `browser_open_window` response also carries `browser` (what was driven), ` If the user specifically wants Chrome: ```bash-adom-desktop browser_use '{"browser":"chrome","install":true}'+adom-desktop pup_use '{"browser":"chrome","install":true}' ``` - Downloads Google's official installer and installs Chrome, then pins it. **On a normal PC this is@@ -68,15 +68,15 @@ adom-desktop browser_use '{"browser":"chrome","install":true}' (cross-AD). If the prompt expires it re-notifies with a one-tap "Approve now". All autonomous — no AI turn needed. On older AD (no bridge-notify) it raises the UAC once and returns a hint for **you** to relay ("go to the box and click YES"), then you re-run the verb.-- **Poll `browser_readiness`**: `chromeStablePhase` goes `installing → awaiting_uac` (waiting on the user's+- **Poll `pup_readiness`**: `chromeStablePhase` goes `installing → awaiting_uac` (waiting on the user's click) `→ ready`; `browserKind` flips to `"chrome"`. pup keeps driving Edge meanwhile, so nothing is blocked. `elevate:"auto"|true|false` on the call tunes the prompt behavior. ## Installing / prewarming Chrome for Testing (last resort) ```bash-adom-desktop browser_prewarm '{}' # fetch CfT WITHOUT opening a window, then pin it (poll readiness)-adom-desktop browser_use '{"browser":"cft"}' # same, via the pin path+adom-desktop pup_prewarm '{}' # fetch CfT WITHOUT opening a window, then pin it (poll readiness)+adom-desktop pup_use '{"browser":"cft"}' # same, via the pin path ``` Only needed on a box with no Chrome and no Edge. **CfT reported as `skipped` is NOT a failure** — on AD@@ -84,14 +84,14 @@ Only needed on a box with no Chrome and no Edge. **CfT reported as `skipped` is `bridge_readiness` shows the asset `{present:false, skipped:true}`. That's the intended installed-browser-first behavior; don't retry or report an error. -## Cold-start error table — what a `browser_open_window` response can mean+## Cold-start error table — what a `pup_open_window` response can mean | Response | Means | Do | |---|---|---| | `ok:true` / a real session | A browser was found + launched | Proceed — the common case |-| `errorCode:"node_not_found"` / "could not run npm" | The bridge's Node runtime isn't ready yet | **AD ≥ 1.9.63 provisions Node for you** — check `adom-desktop runtimes '{}'`; if `installing`, wait a few seconds and **retry**. No user action. **Known AD false-negative:** if `runtimes` shows node `ready` AND `browser_readiness` works but other verbs still say `node_not_found`, it's an AD verb-gate bug — do NOT loop `desktop_install_node`; tell the user to update AD |-| `errorCode:"bridge_restarting"` (`stillRunning:true`) | AD ≥ 1.9.77: verb landed in the brief gap while the bridge respawns after an install/update | Poll `browser_readiness` until it answers, then retry the same verb — not a failure |-| `errorCode:"chrome_for_testing_installing"` (`installing:true`) | RARE — no Chrome AND no Edge, so pup is fetching CfT in the background | Poll `browser_readiness` until `{ready:true}`, then re-issue. Faster: ask the user to install Chrome/Edge (Edge ships with Windows) |+| `errorCode:"node_not_found"` / "could not run npm" | The bridge's Node runtime isn't ready yet | **AD ≥ 1.9.63 provisions Node for you** — check `adom-desktop runtimes '{}'`; if `installing`, wait a few seconds and **retry**. No user action. **Known AD false-negative:** if `runtimes` shows node `ready` AND `pup_readiness` works but other verbs still say `node_not_found`, it's an AD verb-gate bug — do NOT loop `desktop_install_node`; tell the user to update AD |+| `errorCode:"bridge_restarting"` (`stillRunning:true`) | AD ≥ 1.9.77: verb landed in the brief gap while the bridge respawns after an install/update | Poll `pup_readiness` until it answers, then retry the same verb — not a failure |+| `errorCode:"chrome_for_testing_installing"` (`installing:true`) | RARE — no Chrome AND no Edge, so pup is fetching CfT in the background | Poll `pup_readiness` until `{ready:true}`, then re-issue. Faster: ask the user to install Chrome/Edge (Edge ships with Windows) | | `errorCode:"chrome_install_no_disk"` | No browser + too little disk for CfT (`readiness.diskFreeMb`) | Tell the user how much is free vs the ~800 MB needed; ask them to free space or install Edge. Do NOT retry the download | | `errorCode:"chrome_install_failed"` / `chrome_download_network` / `chrome_install_permission` | No browser AND the CfT fallback download failed | Read `readiness.lastError`; ask the user to install Chrome or Edge | @@ -108,7 +108,7 @@ install Node. On a truly fresh box it may be `installing` for a few seconds afte ## The shell-approval gate (rarely relevant to pup) -Core AD verbs and pup's own `browser_*` / `desktop_record_*` verbs run with **NO desktop prompt**. Only+Core AD verbs and pup's own `pup_*` / `desktop_record_*` verbs run with **NO desktop prompt**. Only open-ended `shell_execute` / `run_script` pop AD's "Shell command needs approval" dialog — which you almost never need (prefer typed verbs). If one returns `needs_approval` / `human_only` on **AD ≥ 1.9.86**: run `adom-desktop --target <box> request_shell_approval` (re-surfaces the dialog), ask the user to click
skills/pup-screenshots-recording/SKILL.md+28−28@@ -1,7 +1,7 @@ --- name: pup-screenshots-recording user-invocable: true-description: "Capturing pup (Puppeteer bridge) windows: SCREENSHOTS (browser_screenshot — lossless PNG, full-page vs viewport, full-resolution via browser_screenshot_full_res, and how to actually READ the image) and RECORDING (browser_record_start/stop records ONE pup tab/window's page content to a video; desktop_record_start/stop records the WHOLE desktop across apps). Covers framing a window at an exact size for a clean shot WITHOUT foregrounding it, and why pup shots are true-to-life (real rendered window, not headless). Read when screenshotting a page, capturing full-page vs above-the-fold, recording a demo/walkthrough of an app, or choosing the window-recorder vs the desktop-recorder. Trigger words: browser_screenshot, browser_screenshot_full_res, full page screenshot, screenshot the page, read the screenshot, browser_record_start, browser_record_stop, desktop_record_start, desktop_record_stop, record my app, record a demo, screen recording, walkthrough video, pup screenshot, pup recording."+description: "Capturing pup (Puppeteer bridge) windows: SCREENSHOTS (pup_screenshot — lossless PNG, full-page vs viewport, full-resolution via pup_screenshot_full_res, and how to actually READ the image) and RECORDING (pup_record_start/stop records ONE pup tab/window's page content to a video; desktop_record_start/stop records the WHOLE desktop across apps). Covers framing a window at an exact size for a clean shot WITHOUT foregrounding it, and why pup shots are true-to-life (real rendered window, not headless). Read when screenshotting a page, capturing full-page vs above-the-fold, recording a demo/walkthrough of an app, or choosing the window-recorder vs the desktop-recorder. Trigger words: pup_screenshot, pup_screenshot_full_res, full page screenshot, screenshot the page, read the screenshot, pup_record_start, pup_record_stop, desktop_record_start, desktop_record_stop, record my app, record a demo, screen recording, walkthrough video, pup screenshot, pup recording." --- **Parent skill:** adom-desktop-puppeteer-bridge · See also: `pup` (start here), `pup-windows-sessions-tabs`, `pup-browsers-and-chrome`.@@ -16,25 +16,25 @@ without disturbing the user. ## Screenshots ```bash-adom-desktop browser_screenshot '{"sessionId":"mytask-web"}' # viewport, lossless PNG-adom-desktop browser_screenshot '{"sessionId":"mytask-web","fullPage":true}' # entire scrollable page-adom-desktop browser_screenshot '{"sessionId":"mytask-web","tabId":"tab-2"}' # a specific tab+adom-desktop pup_screenshot '{"sessionId":"mytask-web"}' # viewport, lossless PNG+adom-desktop pup_screenshot '{"sessionId":"mytask-web","fullPage":true}' # entire scrollable page+adom-desktop pup_screenshot '{"sessionId":"mytask-web","tabId":"tab-2"}' # a specific tab ``` - Returns a **PNG saved on the container** (the machine running the CLI) — the response gives you the path. **`Read` that path** to actually see it; a returned path is not the same as having looked. Downscaled to stay under the image-read limit by default. - **`fullPage:true`** stitches the whole scrollable page; omit for just the viewport (above the fold).-- **`browser_screenshot_full_res`** captures at full native resolution (no downscale) when you need to read+- **`pup_screenshot_full_res`** captures at full native resolution (no downscale) when you need to read fine detail — larger file; use when the default shrink loses text you need. - **Framing for a clean shot without showing the user:** size/position the window on open —- `browser_open_window {width,height,x,y}` — which places it **without foregrounding** (background stays+ `pup_open_window {width,height,x,y}` — which places it **without foregrounding** (background stays background). Great for a deterministic, repeatable capture frame. To capture the OS window *plus* any owned popups/dialogs on top, use AD's `desktop_screenshot_window` instead (it returns the window with its child popups). - **Sanity-check, don't assume:** a page that 404'd or hit a login wall still screenshots — pair with- `browser_eval` on `document.title` when in doubt.-- **Rendering your OWN HTML to screenshot it?** Use **`browser_render_html {sessionId, html}`** — it renders+ `pup_eval` on `document.title` when in doubt.+- **Rendering your OWN HTML to screenshot it?** Use **`pup_render_html {sessionId, html}`** — it renders the string via `page.setContent`, which is **UTF-8-correct**. (A `data:text/html,…` URL mojibakes accented characters — `Café` → `Café` — unless you hand-add `;charset=utf-8`; `render_html` just does it right.) Then screenshot/drive it like any page. Great for previewing generated markup without writing a temp file.@@ -43,20 +43,20 @@ adom-desktop browser_screenshot '{"sessionId":"mytask-web","tabId":"tab-2"}' # a | Recorder | Captures | Use for | |---|---|---|-| **`browser_record_start` / `browser_record_stop`** | ONE pup tab/window — the page content | "record my app", a feature walkthrough, a focused UI flow |+| **`pup_record_start` / `pup_record_stop`** | ONE pup tab/window — the page content | "record my app", a feature walkthrough, a focused UI flow | | **`desktop_record_start` / `desktop_record_stop`** | the WHOLE desktop (multiple apps, KiCad/Fusion, OS dialogs) | anything spanning more than the pup window | ```bash # Recording a pup window the user will watch → DO bring it forward first-adom-desktop browser_raise_os_window '{"sessionId":"demo-web"}'-adom-desktop browser_record_start '{"sessionId":"demo-web"}'+adom-desktop pup_raise_os_window '{"sessionId":"demo-web"}'+adom-desktop pup_record_start '{"sessionId":"demo-web"}' # ...drive the scene: navigate, click, type, scroll...-adom-desktop browser_record_stop '{"sessionId":"demo-web"}' # → { path: "...webm" }-adom-desktop browser_lower_os_window '{"sessionId":"demo-web"}' # tuck it away again when done+adom-desktop pup_record_stop '{"sessionId":"demo-web"}' # → { path: "...webm" }+adom-desktop pup_lower_os_window '{"sessionId":"demo-web"}' # tuck it away again when done ``` -- `browser_record_stop` returns the saved video **path**. Recording status/list verbs:- `browser_record_status` / `browser_record_list` (window recorder) and `desktop_record_status` /+- `pup_record_stop` returns the saved video **path**. Recording status/list verbs:+ `pup_record_status` / `pup_record_list` (window recorder) and `desktop_record_status` / `desktop_record_list` / `desktop_recorder_open` / `desktop_recorder_close` (desktop recorder). - Closing a tab/window mid-recording auto-stops that recording. - For a **narrated** walkthrough, drive the scene here and hand the video to the `video-post` / `adom-tts`@@ -71,35 +71,35 @@ content, `window.scrollTo` no-ops when the page scrolls inside a nested `<div>`, viewport, and a sized window can even come up ~170px wide. As of **v1.8.21 pup has first-class verbs for all of it** — reach for these instead of hand-rolling evals: -- **`browser_screenshot_element { sessionId, selector? | text?, closest?, padding?, deep? }`** — screenshot+- **`pup_screenshot_element { sessionId, selector? | text?, closest?, padding?, deep? }`** — screenshot ONE element. Resolve by CSS **selector** (most reliable) or visible **text** (pierces shadow DOM), scrolled into view first. **This is usually the whole answer to "capture just this card."** Text matches the *tightest* text node — to grab the enclosing card add `closest:".card"`; and if the phrase is split across spans or appears in prose too, use a **selector** (e.g. `.download-card`).-- **`browser_scroll { sessionId, dy?/dx? | toSelector?/toText?, block?, behavior? }`** — scrolls the ACTUAL+- **`pup_scroll { sessionId, dy?/dx? | toSelector?/toText?, block?, behavior? }`** — scrolls the ACTUAL scrolling element (nearest scrollable ancestor, not `window`). Scroll by delta or to an element/text (shadow-piercing). Returns `{ scrollElement, scrollTop, scrollHeight, clientHeight, atBottom }` so you can confirm it landed — no more overshoot from hand-computed offsets.-- **`browser_set_viewport { sessionId, width, height, deviceScaleFactor? }`** — pins the viewport- deterministically (fixes a too-small innerWidth). `browser_open_window { width, height }` now applies it on+- **`pup_set_viewport { sessionId, width, height, deviceScaleFactor? }`** — pins the viewport+ deterministically (fixes a too-small innerWidth). `pup_open_window { width, height }` now applies it on open, so a sized window renders at the size you asked for.-- **`$deep(sel)` / `$$deep(sel)` / `deepText(txt)`** are auto-injected into every page, so `browser_eval` can+- **`$deep(sel)` / `$$deep(sel)` / `deepText(txt)`** are auto-injected into every page, so `pup_eval` can pierce shadow roots directly — no hand-rolled walk: ```bash-adom-desktop browser_eval '{"sessionId":"...","expr":"$$deep(\"a[href]\").length"}' # counts across shadow roots+adom-desktop pup_eval '{"sessionId":"...","expr":"$$deep(\"a[href]\").length"}' # counts across shadow roots ``` -**Still verify by DOM, then by eye.** To count cards/links/rows, count in `browser_eval` by document-Y-(`rect.top + window.scrollY`) — immune to an overshot screenshot — then `browser_screenshot_element` (or-`browser_scroll` + `browser_screenshot`) to confirm. And **first check whether a page is even shadow DOM**+**Still verify by DOM, then by eye.** To count cards/links/rows, count in `pup_eval` by document-Y+(`rect.top + window.scrollY`) — immune to an overshot screenshot — then `pup_screenshot_element` (or+`pup_scroll` + `pup_screenshot`) to confirm. And **first check whether a page is even shadow DOM** before assuming: a shadow survey (`$$deep` finding more than `querySelectorAll`) tells you; often an "empty" result just means a wrong selector on plain DOM. -**fullPage caveat:** `browser_screenshot { fullPage:true }` only covers *document*-level scroll. On a-nested-scroll SPA it returns just the viewport and a `nestedScroll:true` hint — use `browser_screenshot_element`-for one region, or `browser_scroll` through the container.+**fullPage caveat:** `pup_screenshot { fullPage:true }` only covers *document*-level scroll. On a+nested-scroll SPA it returns just the viewport and a `nestedScroll:true` hint — use `pup_screenshot_element`+for one region, or `pup_scroll` through the container. -Reminder: `browser_eval` allows **no semicolons** — use the comma operator or an IIFE. If a+Reminder: `pup_eval` allows **no semicolons** — use the comma operator or an IIFE. If a screenshot lands on the wrong content, don't trust it — it almost always means an inner scroll container; re-scroll the element, don't nudge the window.
skills/pup-vendor-login/SKILL.md+13−13@@ -21,7 +21,7 @@ persists on disk. Email+password works too but needs a credential you do not hav 1. **Open the login page in a durable profile, FRESH window, and pace slowly.** ```- browser_open_window {"sessionId":"autodesk","url":"https://accounts.autodesk.com/logon"}+ pup_open_window {"sessionId":"autodesk","url":"https://accounts.autodesk.com/logon"} ``` Then WAIT ~10s before the first probe. Do NOT machine-gun navigate+eval+screenshot — rapid ops on these heavy SSO pages destabilise the CDP connection (measured: three Chrome crashes from@@ -30,7 +30,7 @@ persists on disk. Email+password works too but needs a credential you do not hav 2. **Click the SSO button** (trusted CDP click via a text match; the button text often has hidden whitespace so match with a regex, not `===`): ```- browser_eval {"sessionId":"autodesk","expr":"(()=>{const b=[...document.querySelectorAll('button,a,[role=button]')].find(e=>/continue with google/i.test(e.textContent||'')&&e.offsetParent!==null); b.click(); return 'clicked';})()"}+ pup_eval {"sessionId":"autodesk","expr":"(()=>{const b=[...document.querySelectorAll('button,a,[role=button]')].find(e=>/continue with google/i.test(e.textContent||'')&&e.offsetParent!==null); b.click(); return 'clicked';})()"} ``` Wait ~8s. @@ -38,16 +38,16 @@ persists on disk. Email+password works too but needs a credential you do not hav - The main window navigates to `accounts.google.com/.../accountchooser` (most common), OR - An OAuth **popup** opens. Autodesk's popup has **no opener**, so pup's popup-tracker skips it and the reclaim sweep adopts it as its **own session** named like `adom-you-win<rand>` at- `signin.autodesk.com`. Run `browser_list_windows` and look for the new session; drive THAT one.+ `signin.autodesk.com`. Run `pup_list_windows` and look for the new session; drive THAT one. 4. **Click the account — Google's "Sign in with Google" chooser (FedCM) REJECTS every in-page and in-app click.** Proven 2026-07-28 signing John into Autodesk: DOM `.click()`, a full synthetic- pointer-event sequence, `page.mouse.click` (browser_click x/y), AND UIA-by-name+ pointer-event sequence, `page.mouse.click` (pup_click x/y), AND UIA-by-name (`desktop_ui_click`) ALL failed — the account lives in a cross-origin FedCM iframe hardened against automation. **The ONLY thing that works is a REAL OS cursor HOVER-then-CLICK** via AD's desktop verbs (this is the breakthrough): ```- browser_raise_os_window {sessionId, foregroundReason:"<the user's words>"} # FedCM needs the window focused+ pup_raise_os_window {sessionId, foregroundReason:"<the user's words>"} # FedCM needs the window focused desktop_screenshot_window {hwnd} # get shotId + coordMap desktop_hover {space:"image", shotId, x:<account px>, y:<account px>} # MOVE the real cursor FIRST — this is the key desktop_click {space:"image", shotId, x:<same>, y:<same>} # then the real SendInput click@@ -68,7 +68,7 @@ persists on disk. Email+password works too but needs a credential you do not hav Measured live 2026-07-28: sign-in reached the Autodesk dashboard, the very next navigation crashed the Chrome, and the login was gone — a later window opened SIGNED OUT (while Gmail, whose cookie was flushed days earlier, survived). So after the dashboard appears:- a. Do nothing heavy for ~10-15s (let Chrome flush), OR better, `browser_close_window` the login+ a. Do nothing heavy for ~10-15s (let Chrome flush), OR better, `pup_close_window` the login window — a graceful close flushes cookies to disk. b. Reopen the vendor site in the durable profile and CONFIRM it is signed in (screenshot) BEFORE promising the user anything. Only a verified reopen proves persistence.@@ -91,7 +91,7 @@ close+reopen+verify flush discipline in step 6 matters even more. - **Persistence needs a durable profile** (default `adom-you`, or a named one). Never `isolated:true` for a login you want to keep. - **Crash isolation argues for a DEDICATED durable profile per vendor**, e.g.- `browser_open_window {"profile":"autodesk", ...}`. A crash/hang during a vendor login on the SHARED+ `pup_open_window {"profile":"autodesk", ...}`. A crash/hang during a vendor login on the SHARED `adom-you` profile takes down EVERY other session in that one Chrome process — measured live: the Autodesk flow crashed the shared Chrome and killed the user's Gmail session twice, and lost the in-flight Autodesk login because its cookie had not flushed before the crash. A per-vendor durable@@ -115,13 +115,13 @@ a license seat, editing users). Do not promise to click through it. Options, in first (close+reopen+verify), THEN, in a SEPARATE dedicated-profile window, hand the admin page to the user — so a portal crash can never cost the sign-in again. -## The RESPECTFUL way to do the FedCM click: browser_assisted_click (v1.9.167)+## The RESPECTFUL way to do the FedCM click: pup_assisted_click (v1.9.167) -Do NOT hand-roll raise + hover + click for the account chooser — use `browser_assisted_click`, which+Do NOT hand-roll raise + hover + click for the account chooser — use `pup_assisted_click`, which does the whole protocol John required (never foreground-and-act while the user is not ready): ``` # first: desktop_screenshot_window {hwnd} to SEE the chooser and read the account's window-pixel x,y-browser_assisted_click {sessionId, x:<account px>, y:<account px>,+pup_assisted_click {sessionId, x:<account px>, y:<account px>, reason:"clicking your Google account to finish signing into Autodesk", label:"your Google account (John Lauer)"} ```@@ -131,7 +131,7 @@ foreground actually IS the window (nothing clicks until then; times out as `user nothing); then runs the TIGHT hover→click that FedCM accepts and captions each step. THE TIGHT SEQUENCE MATTERS: any screenshot/eval BETWEEN the raise and the click steals focus / invalidates the coordMap and the click silently no-ops (measured: interleaved ops failed 5x; the tight raise→capture-→hover→click worked). browser_assisted_click encapsulates that — use it, don't reinvent it.+→hover→click worked). pup_assisted_click encapsulates that — use it, don't reinvent it. ## Pitfalls recap (each paid for on 2026-07-28) - Rapid ops crash the Chrome → pace slowly, one op + settle.@@ -161,7 +161,7 @@ instead of the obvious: the human took their focus back because nobody asked the and half a password typed into the wrong window is real harm. 5. Caption when DONE so they know they can use their keyboard again. -`browser_assisted_click` implements all five — prefer it over hand-rolling raise-then-click. And-remember the ordering rule: **CDP input (`browser_click`/`browser_type`/`browser_hover`) needs no+`pup_assisted_click` implements all five — prefer it over hand-rolling raise-then-click. And+remember the ordering rule: **CDP input (`pup_click`/`pup_type`/`pup_hover`) needs no foreground and disturbs nobody** — native input is the LAST resort, only for what CDP genuinely cannot drive (the FedCM chooser, native OS dialogs, sites that reject synthetic events).
skills/pup-windows-sessions-tabs/SKILL.md+48−48@@ -1,7 +1,7 @@ --- name: pup-windows-sessions-tabs user-invocable: true-description: "pup (Puppeteer bridge) windows, sessions, and tabs: how sessionIds work and why to task-prefix them, WINDOW OWNERSHIP so you never steal another AI thread's window (owner + session_owned_by_another_thread + takeover), the BACKGROUND-by-default rule (foreground:true is the only way to show a window; sizing/positioning does NOT foreground), running MANY TABS in one window instead of many windows, switching/listing/closing sessions and tabs, and verifying what you actually opened. Read when opening/reusing pup windows, managing tabs, deciding background vs foreground, or seeing errorCode session_owned_by_another_thread. Trigger words: pup sessionId, browser_open_window owner, session_owned_by_another_thread, takeover window, pup background, foreground:true, browser_open_tab, browser_switch_tab, browser_list_tabs, browser_switch_window, browser_list_windows, browser_close_window, pup tabs, many tabs one window, don't steal window, browser_lower_os_window, browser_raise_os_window, browser_alert_window."+description: "pup (Puppeteer bridge) windows, sessions, and tabs: how sessionIds work and why to task-prefix them, WINDOW OWNERSHIP so you never steal another AI thread's window (owner + session_owned_by_another_thread + takeover), the BACKGROUND-by-default rule (foreground:true is the only way to show a window; sizing/positioning does NOT foreground), running MANY TABS in one window instead of many windows, switching/listing/closing sessions and tabs, and verifying what you actually opened. Read when opening/reusing pup windows, managing tabs, deciding background vs foreground, or seeing errorCode session_owned_by_another_thread. Trigger words: pup sessionId, pup_open_window owner, session_owned_by_another_thread, takeover window, pup background, foreground:true, pup_open_tab, pup_switch_tab, pup_list_tabs, pup_switch_window, pup_list_windows, pup_close_window, pup tabs, many tabs one window, don't steal window, pup_lower_os_window, pup_raise_os_window, pup_alert_window." --- **Parent skill:** adom-desktop-puppeteer-bridge · See also: `pup` (start here), `pup-screenshots-recording`, `pup-browsers-and-chrome`.@@ -13,7 +13,7 @@ You address everything by `sessionId`. `profile` (defaults to `sessionId`) names ## sessionIds — task-prefix them, and pass `owner` -`browser_open_window {sessionId}` either **creates** a window (new id) or **reuses/navigates** an existing+`pup_open_window {sessionId}` either **creates** a window (new id) or **reuses/navigates** an existing one (id already exists). Because sessions are **shared across every AI thread on this desktop**, a generic id collides: @@ -24,11 +24,11 @@ id collides: ## Window ownership — do NOT steal another AI thread's window -If you `browser_open_window` a sessionId that already exists **with a new url**, pup NAVIGATES that window+If you `pup_open_window` a sessionId that already exists **with a new url**, pup NAVIGATES that window — destroying whatever another thread was doing there. pup guards this: 1. **Declared cross-owner reuse is REFUSED** — `errorCode: session_owned_by_another_thread` (also on- `browser_navigate` / `browser_close` / `browser_close_window`). The `_hint` tells you the current owner+ `pup_navigate` / `pup_close` / `pup_close_window`). The `_hint` tells you the current owner + url + age. 2. **If YOU get that error** — the window isn't yours. Open your OWN with a unique task-prefixed sessionId. Pass **`takeover:true`** ONLY if the user explicitly told you to repurpose that window.@@ -36,14 +36,14 @@ If you `browser_open_window` a sessionId that already exists **with a new url**, put it back) — so ALWAYS declare `owner` to be a good citizen and to get protection. 4. **Reusing your OWN window is good** — same sessionId + same owner = a smooth refresh (e.g. re-rendering a page you just republished). Ownership only blocks *cross-thread* grabs.-5. **Unsure what exists?** `browser_status '{}'` lists every window with its `owner`, current `url`, and+5. **Unsure what exists?** `pup_status '{}'` lists every window with its `owner`, current `url`, and `ageMinutes`. Run it at the start of a task (especially after a context compaction) before picking an id. ```bash # right — owned, task-prefixed-adom-desktop browser_open_window '{"sessionId":"wikifix-preview","owner":"wikifix","url":"<url>"}'+adom-desktop pup_open_window '{"sessionId":"wikifix-preview","owner":"wikifix","url":"<url>"}' # wrong — generic + anonymous: may hijack another thread's window (pup reprimands you in _hint)-adom-desktop browser_open_window '{"sessionId":"wiki","url":"<url>"}'+adom-desktop pup_open_window '{"sessionId":"wiki","url":"<url>"}' ``` ## Background by default — pup NEVER pops onto the user's screen unless asked@@ -58,17 +58,17 @@ windows, no focus stolen) and stays fully drivable + screenshottable there — y (`foregroundDenied:'foreground_reason_required'`). The reason is logged, recorded in `lastAgentUpdate`, and surfaced as `lastForeground` in status — the user can always audit who put a window on their screen and why. Pass it ONLY when the user explicitly wants to WATCH (a- demo, a "show me", a live recording); after the show-me, `browser_lower_os_window` to return it.-- **Sizing/positioning does NOT foreground** — `browser_open_window {width,height,x,y}` places the window+ demo, a "show me", a live recording); after the show-me, `pup_lower_os_window` to return it.+- **Sizing/positioning does NOT foreground** — `pup_open_window {width,height,x,y}` places the window at an exact size/spot **for clean screenshot framing** while it stays in the background. So you can frame a shot without ever disturbing the user's main work.-- **`browser_alert_window`** flashes the taskbar (a non-intrusive "it's ready/done" nudge) — prefer it over+- **`pup_alert_window`** flashes the taskbar (a non-intrusive "it's ready/done" nudge) — prefer it over raising the window. Use it for an explicit end-of-task "it's ready".-- `browser_raise_os_window` / `browser_focus_window` bring a window forward — **GATED the same way:+- `pup_raise_os_window` / `pup_focus_window` bring a window forward — **GATED the same way: REFUSED (`foreground_reason_required`) unless you pass `foregroundReason`.** Use only for an- explicit user show-me; `browser_lower_os_window` tucks it back after. **Raise steals focus and can+ explicit user show-me; `pup_lower_os_window` tucks it back after. **Raise steals focus and can leak a mid-typed keystroke into the page** (pup blurs the page's focused element first to reduce- it) — to just signal "I updated this window", use the auto-flash / `browser_alert_window`, NOT raise.+ it) — to just signal "I updated this window", use the auto-flash / `pup_alert_window`, NOT raise. ## Signalling what you changed — auto-flash + `updateNote` (so the user follows along) @@ -80,23 +80,23 @@ track of WHICH one you just changed. pup handles both, automatically: icon orange — **without stealing focus**, self-suppressed while it's foreground, and cleared when the user clicks it. It's **debounced to one flash per window per ~5s burst**, so a whole `open→navigate→ fill→screenshot` sequence flashes once, not five times.-- **Tune it:** `{"silent":true}` on any one call suppresses that flash; `browser_open_window+- **Tune it:** `{"silent":true}` on any one call suppresses that flash; `pup_open_window {"autoFlash":false}` silences the whole session (for a background poller); and a pure read should pass- `browser_eval {"readOnly":true}` so a polling loop doesn't flash or count as an update.+ `pup_eval {"readOnly":true}` so a polling loop doesn't flash or count as an update. - **Say what you did:** pass **`updateNote":"<short what-I-did>"`** on any mutating verb. pup records `lastAgentUpdate {tabId, verb, note, ts}` and surfaces it **top-level and per-tab** in- `browser_list_tabs` / `browser_list_windows` — so the user (or a Session Monitor) sees which tab you+ `pup_list_tabs` / `pup_list_windows` — so the user (or a Session Monitor) sees which tab you staged and why. Add an `updateNote` on the meaningful steps of a multi-tab task. ```bash # default: background — drive it invisibly, user never sees a thing-adom-desktop browser_open_window '{"sessionId":"job-web","owner":"job","url":"<url>"}'-adom-desktop browser_screenshot '{"sessionId":"job-web"}'+adom-desktop pup_open_window '{"sessionId":"job-web","owner":"job","url":"<url>"}'+adom-desktop pup_screenshot '{"sessionId":"job-web"}' # only if the user asked to SEE it:-adom-desktop browser_open_window '{"sessionId":"demo-web","owner":"demo","url":"<url>","foreground":true}'+adom-desktop pup_open_window '{"sessionId":"demo-web","owner":"demo","url":"<url>","foreground":true}' ``` -## Taskbar look — split vs grouped (user preference, `browser_configure`)+## Taskbar look — split vs grouped (user preference, `pup_configure`) Each pup window wears the Adom Pup icon; how they arrange on the taskbar is the USER's choice: @@ -106,8 +106,8 @@ Each pup window wears the Adom Pup icon; how they arrange on the taskbar is the - **`grouped`:** all pup windows stack under ONE "Adom Pup" button; its badge shows the WINDOW count. Tidy taskbar for users who open many windows. -`browser_configure {"taskbarGrouping":"grouped"}` switches live (persists across updates).-**Ask the user before changing it — it's their taskbar.** Read current: `browser_configure {}`.+`pup_configure {"taskbarGrouping":"grouped"}` switches live (persists across updates).+**Ask the user before changing it — it's their taskbar.** Read current: `pup_configure {}`. ## One window, many tabs (multi-URL tasks) @@ -115,28 +115,28 @@ For a vendor fetch / multi-doc review, put the URLs in **tabs of one window** (o separate windows: ```bash-adom-desktop browser_open_window '{"sessionId":"task-web","owner":"task","url":"https://first.example.com"}'-adom-desktop browser_open_tab '{"sessionId":"task-web","url":"https://second.example.com"}' # → { tabId: "tab-2", tabCount: 2 }-adom-desktop browser_switch_tab '{"sessionId":"task-web","tabId":"tab-2"}'-adom-desktop browser_screenshot '{"sessionId":"task-web","tabId":"tab-2"}' # tabId optional — omit for the active tab-adom-desktop browser_close_tab '{"sessionId":"task-web","tabId":"tab-2"}'+adom-desktop pup_open_window '{"sessionId":"task-web","owner":"task","url":"https://first.example.com"}'+adom-desktop pup_open_tab '{"sessionId":"task-web","url":"https://second.example.com"}' # → { tabId: "tab-2", tabCount: 2 }+adom-desktop pup_switch_tab '{"sessionId":"task-web","tabId":"tab-2"}'+adom-desktop pup_screenshot '{"sessionId":"task-web","tabId":"tab-2"}' # tabId optional — omit for the active tab+adom-desktop pup_close_tab '{"sessionId":"task-web","tabId":"tab-2"}' ``` -- `browser_open_tab` returns `{tabId, tabCount, activeTabId}`; pass the `tabId` to+- `pup_open_tab` returns `{tabId, tabCount, activeTabId}`; pass the `tabId` to `switch`/`close`/`screenshot`/`eval`/`navigate` (omit to target the active tab). - **Closing the last tab closes the window.**-- Popup tabs (e.g. a PDF the page opens) auto-attach and appear in `browser_list_tabs`; a closed popup's- last URL is retained so you can `browser_fetch_url` it if you missed it.+- Popup tabs (e.g. a PDF the page opens) auto-attach and appear in `pup_list_tabs`; a closed popup's+ last URL is retained so you can `pup_fetch_url` it if you missed it. ## Don't double-open; reuse correctly -- New session entirely → `browser_open_window`.-- Same session, new page in the same tab → `browser_navigate` (with `tabId` if multiple tabs).-- A second view in the same session → `browser_open_tab` (NOT a second window).-- Switch which window is "active" → `browser_switch_window`; list them → `browser_list_windows`.+- New session entirely → `pup_open_window`.+- Same session, new page in the same tab → `pup_navigate` (with `tabId` if multiple tabs).+- A second view in the same session → `pup_open_tab` (NOT a second window).+- Switch which window is "active" → `pup_switch_window`; list them → `pup_list_windows`. Opening the same content under a new `sessionId` leaves the user staring at two identical windows — so-`browser_status` first and reuse when the URL is already open.+`pup_status` first and reuse when the URL is already open. ## Clean up after yourself — close windows when the task is done @@ -144,30 +144,30 @@ pup **never auto-closes** a window (a "stale" one might belong to a thread that' between turns). So every window you open lingers on the **user's real desktop** until *you* close it. Left alone, a day of tasks piles up a dozen dead pup windows the user has to find and close. -- **When you finish a task, `browser_close_window` the windows you opened for it.** Same for one-off+- **When you finish a task, `pup_close_window` the windows you opened for it.** Same for one-off windows (a quick screenshot/scrape) — close them right after.-- **Check what's yours:** `browser_status` lists every window with its `owner` and `ageMinutes`, and+- **Check what's yours:** `pup_status` lists every window with its `owner` and `ageMinutes`, and now nudges you (a `🧹 CLEANUP` hint) when windows are old (≥30 min) or piling up. Act on it — close the ones you're done with. Only close windows **you own**; never close another thread's.-- **Long-running work?** Reuse ONE window (`browser_navigate` / `browser_open_tab`) instead of opening+- **Long-running work?** Reuse ONE window (`pup_navigate` / `pup_open_tab`) instead of opening a fresh window each step, so there's only one thing to clean up. - If you truly need a window to persist across turns (a login you'll reuse for hours), that's fine — but it's a deliberate choice, and you still own closing it when done. ## Verify what you opened — `ok:true` is not enough -`browser_open_window` returns `ok:true` as soon as navigation *starts*, not when content rendered. Confirm-with `browser_eval '{"sessionId":"…","expr":"document.title"}'` (fastest — 404s, login walls, anti-bot-blocks all change the title) or a `browser_screenshot` when the title isn't discriminating.+`pup_open_window` returns `ok:true` as soon as navigation *starts*, not when content rendered. Confirm+with `pup_eval '{"sessionId":"…","expr":"document.title"}'` (fastest — 404s, login walls, anti-bot+blocks all change the title) or a `pup_screenshot` when the title isn't discriminating. ## Verbs at a glance | Verb | Does | |---|---|-| `browser_open_window` | Create (or reuse) a session/window. Args: `sessionId`, `owner`, `url?`, `profile?`, `foreground?`, `width/height/x/y?`, `takeover?`, `updateNote?`, `autoFlash?` (false = silence session) |-| `browser_navigate` | Drive an existing tab to a URL (no new window) — `sessionId`, `url`, `tabId?` |-| `browser_open_tab` / `browser_switch_tab` / `browser_close_tab` / `browser_list_tabs` | Tabs within one window |-| `browser_switch_window` / `browser_list_windows` | Multiple sessions |-| `browser_status` | List every session + `owner`/`url`/`ageMinutes` (NOT a readiness check) |-| `browser_lower_os_window` / `browser_raise_os_window` / `browser_focus_window` / `browser_alert_window` | Z-order / focus / taskbar flash |-| `browser_close_window` (one session) / `browser_close` (all) | Close — destructive |+| `pup_open_window` | Create (or reuse) a session/window. Args: `sessionId`, `owner`, `url?`, `profile?`, `foreground?`, `width/height/x/y?`, `takeover?`, `updateNote?`, `autoFlash?` (false = silence session) |+| `pup_navigate` | Drive an existing tab to a URL (no new window) — `sessionId`, `url`, `tabId?` |+| `pup_open_tab` / `pup_switch_tab` / `pup_close_tab` / `pup_list_tabs` | Tabs within one window |+| `pup_switch_window` / `pup_list_windows` | Multiple sessions |+| `pup_status` | List every session + `owner`/`url`/`ageMinutes` (NOT a readiness check) |+| `pup_lower_os_window` / `pup_raise_os_window` / `pup_focus_window` / `pup_alert_window` | Z-order / focus / taskbar flash |+| `pup_close_window` (one session) / `pup_close` (all) | Close — destructive |