---
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 launches a FRESH, isolated process of the browser ALREADY on the machine (installed Chrome, else Microsoft Edge — every Windows PC has Edge), 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."
---

# Pup — browser control on the user's desktop (start here)

Drive a browser on the user's desktop from a cloud container via the `adom-desktop` CLI. Every verb is a
**Bash command** (NOT an MCP tool):

```bash
adom-desktop browser_open_window '{"sessionId":"mytask-web","owner":"mytask","url":"https://example.com"}'
adom-desktop browser_screenshot  '{"sessionId":"mytask-web"}'
```

## 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
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** (every Windows PC ships Edge). So there's usually **no download**. It uses
   their *browser binary*, never their *profile*.
2. **…with its own isolated `--user-data-dir` + a CDP debugging port** (`--remote-debugging-port`). That's
   a **fresh, empty profile** — no cookies, no logins, no extensions, its own cookie jar keyed by your
   `profile` name — and pup drives it over the Chrome DevTools Protocol. It is never the user's signed-in
   Chrome/Edge. (Need their real profile/cookies/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.
   But pup immediately z-orders it to the **bottom** (Win32 `SetWindowPos` with `SWP_NOACTIVATE`), so it
   renders **without stealing focus and without covering the user's active window**. You drive and
   screenshot it fully while it stays hidden. `foreground:true` is the ONLY thing that shows it.
4. **Spawn-verifies + falls through.** The pick order is **Chrome → Edge → cached Chrome for Testing**; pup
   actually launches each and, if one won't start (corrupt install), falls to the next — so an open never
   hard-fails on a broken browser. The one that launches is **cached as the default**. Only a box with *no*
   Chromium at all fetches **Chrome for Testing** (~150 MB, last resort).
5. **Is driven by `sessionId`**, and **one session = one window that holds many tabs**.

Keep this model in mind and every verb makes sense: it's a disposable, isolated, backgrounded copy of the
user's own browser, driven by CDP.

## When pup — vs the Adom extension (`nbrowser_*`)

| The task | Tool |
|---|---|
| Screenshot / scrape / test / demo a page; drive an app you're building; anything that does NOT need the user's logins | **pup (this)** |
| Log into a site, use saved passwords, fill a form on a signed-in page, download behind a login, sail past captchas | **Adom browser extension** (`nbrowser_*`, the `adom-browser-extension` skill) — drives their REAL signed-in Chrome/Edge |

pup is **generic only** — a fresh empty profile every time. It cannot use the user's cookies/SSO. That's by
design; the extension is its counterpart for the real profile.

## Skill map — this is a pack

| Skill | Read it for |
|---|---|
| **`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-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
repo — not shipped here.)*

## Quick start — the 90% path

```bash
# Open in the BACKGROUND (automatic — never disturbs the user), then drive it invisibly.
adom-desktop browser_open_window '{"sessionId":"mytask-web","owner":"mytask","url":"http://localhost:3000"}'
adom-desktop browser_eval        '{"sessionId":"mytask-web","expr":"document.title"}'   # verify it really loaded
adom-desktop browser_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
  `pup-windows-sessions-tabs`.
- **`ok:true` means navigation *started*, not that content rendered** — sanity-check with `browser_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`.

## Taskbar preference — the USER can ask for "combined mode"

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
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`.

## Window-type taskbar icons (split mode) — what the user sees per window

In split mode every pup window's taskbar icon states its CONTENT KIND. All five share the teal
Adom tile with a solid browser-window drawing; the glyph inside the window is the category:

| Glyph inside the window | Category |
|---|---|
| open book | Adom wiki, PUBLIC view (logged out) |
| book + person | Adom wiki, SIGNED IN (the user's logged-in view) |
| the Adom mark | an Adom APP (localhost / cloud-slug proxy URL) |
| globe | the web (any other site) |
| two panes | MIXED (tabs span more than one category) |

The category derives from ALL tabs and updates live (about a second). The corner overlay stays the
ACTIVE TAB'S FAVICON (or window/tab counters in grouped mode) — never repurpose it. Adom wiki tab
titles also lead with a text glyph: solid dot = signed in, hollow dot = public. You never manage
any of this; pup does it automatically. It is worth KNOWING because the user can see login state
and window kind at a glance — 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.

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`.

## 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>`.
- **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.
- **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.
- **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
  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 browser_describe '{}'`.
