name: pup user-invocable: true description: "Control Puppeteer browser windows on the user's desktop via the adom-desktop CLI. Use for: opening/closing browser windows, reloading pages, taking screenshots, evaluating JS, flashing taskbar alerts, navigating URLs, checking errors, managing multi-session Chrome. Trigger words: pup, puppeteer, browser window, browser reload, browser screenshot, pup reload, pup screenshot, pup alert, browser debug, visual debug, open in browser, reload browser, flash taskbar, browser_reload, browser_alert_window, browser_open_window, browser_screenshot, browser_eval."

Pup — Puppeteer Browser Control

Control Chrome browser windows on the user's desktop via the adom-desktop CLI.

How to Run Commands

All browser_* commands are run via the adom-desktop CLI using Bash:

adom-desktop <command> '<json_args>'

Examples:

adom-desktop browser_reload '{"sessionId":"dart2"}'
adom-desktop browser_alert_window '{"sessionId":"dart2"}'
adom-desktop browser_screenshot '{"sessionId":"dart2"}'
adom-desktop browser_open_window '{"sessionId":"myapp","profile":"myapp","url":"http://localhost:3000"}'
adom-desktop browser_eval '{"sessionId":"dart2","expr":"document.title"}'

IMPORTANT: These are NOT MCP tools. They are Bash commands. Use the Bash tool to run them.

Available Commands

Session Management

Command Description Required Args
browser_open_window Spawns a new pup window. Only use when there isn't an existing session of that name. sessionId, profile, url
browser_close_window Close a specific session sessionId
browser_focus_window Bring window to foreground sessionId (optional)
browser_alert_window Flash taskbar icon (non-intrusive) sessionId (optional)
browser_switch_window Switch global active session sessionId
browser_list_windows List all open sessions none

⚠ Don't double-open windows — browser_navigate reuses, browser_open_window spawns

If a session already exists (verify with browser_list_windows or browser_list_tabs), point an existing tab at a new URL with browser_navigate — passing tabId if you want to be explicit:

adom-desktop browser_navigate '{"sessionId":"chip-fetcher","tabId":"tab-1","url":"<new-url>"}'

Using browser_open_window against a session that's already alive spawns a second physical pup window with the same title. The new tab is tracked but the old window is orphaned — the user sees two windows showing the same content. Verified painful 2026-05-04: opened a chip-fetcher dashboard verification tab while the original UL Pro detail tab was still active; user noticed the duplicate before I did. Cleanup required desktop_close_window and a tracked-tab marker dance.

Decision tree:

  • Need a new session entirely (different sessionId) → browser_open_window.
  • Need to reuse an existing session's tab → browser_navigate (with tabId if multiple tabs).
  • Need a second tab in the same session → browser_open_tab (not browser_open_window).

⚠ Cross-sessionId duplicates — same app, two windows

The double-open trap above catches same-sessionId re-opens. There's a second flavor that catches you across conversation boundaries: a previous session left a pup window open under one sessionId, and the new conversation opens the same app under a different sessionId — adom-desktop happily creates a second window because the keys differ. The user sees two pup windows showing the same content.

Always run browser_status '{}' before browser_open_window at the start of a new conversation, especially after a context compaction. Look for any session whose URL maps to the app you're about to open. Reuse it (browser_navigate) instead of creating a peer.

Verified 2026-05-07 with chipsmith: an earlier session had sessionId: cs-v2 open at …/proxy/8872/. New conversation opened sessionId: chipsmith at the same port — two windows for the same chip. User noticed the duplicate first.

When closing a duplicate, prefer the OLDER one (lower cb= timestamp in URL); the newer one usually has the working state you just built up.

Page Commands (all accept optional sessionId)

Command Description Key Args
browser_reload Reload page + clear error log sessionId
browser_navigate Navigate to new URL url, sessionId
browser_screenshot Capture page screenshot (lossless PNG) sessionId, maxWidth, fullPage
browser_eval Evaluate JS expression in page expr, sessionId
browser_errors Get console/page errors sessionId
browser_wait Wait for content to settle ms (default 3000)
browser_status Check all sessions none
browser_close Close ALL sessions none

Common Patterns

Reload + Alert (after code changes)

adom-desktop browser_reload '{"sessionId":"dart2"}' && \
adom-desktop browser_alert_window '{"sessionId":"dart2"}'

Debug Loop

  1. Edit code
  2. browser_reload
  3. browser_wait (3-5s for 3D tiles)
  4. browser_screenshot
  5. browser_errors
  6. Analyze, repeat if needed

Check Connectivity

adom-desktop ping

Pick ONE surface — respect the user's window-mode preference

Hydrogen stores a per-session window mode preference: webview (default) or pup. Check it before opening a window:

adom-cli hydrogen probe  # → { ..., "window_mode": "webview" | "pup" }
  • webview → open as a Hydrogen webview tab (webview open-or-refresh)
  • pup → open via Adom Desktop (pup browser_open_window). If Adom Desktop isn't reachable, fall back to webview and warn.

Never open the same content in both at once. User feedback 2026-04-26: "why the fuck did you open something in pup and some stuff in webview. pick one or the other."

Two costs of dual-opening:

  1. Two copies of the page → user sees changes in one but not the other → "why doesn't my upload show up?" or "which one is real?".
  2. Background WS / polling traffic doubles, which makes the Cloudflare-blipping problem worse.

When window-mode is webview, the decision rule:

Use case Surface Why
Automated test, screenshot, eval, ralph loop, scripted multi-step demo pup (even in webview mode — this is automation, not user-facing) scriptable; doesn't compete with the user's tabs; pup's browser_eval works on every tab
Showing the user a finished result they should look at Hydrogen webview tab (adom-cli hydrogen webview open-or-refresh) the tab persists in their workspace; they can interact normally
Both at once for the SAME content don't pick whichever role the situation calls for and stick with it

When window-mode is pup, everything goes through Adom Desktop — including user-facing results.

If you've opened the user's Hydrogen tab for a dashboard / viewer, use that tab for screenshots too via adom-cli hydrogen screenshot panel --name "<tab-name>". Don't ALSO open a pup window of the same URL. If you started in pup for testing and the user wants to see the final state, route them to a Hydrogen tab once and close the pup window.

Memory rule still applies for testing: pup is the testing surface, NOT Hydrogen. The point of THIS rule is: don't open both for the SAME content at the SAME time.

Multi-URL workflows — ONE window, MANY tabs

When you'd open more than one URL for the same user task (vendor fetch, multi-doc review, compare-pages workflows), put them in tabs of one window, not separate windows. User feedback 2026-04-26: "when you make all those pup windows, they clutter up the user's desktop, so you should open all of those as tabs in one pup window."

# 1) Create the window with the first URL.
adom-desktop browser_open_window '{
  "sessionId":"my-task","profile":"my-task",
  "url":"https://first.example.com"
}'

# 2) Add the rest as tabs in the SAME session.
adom-desktop browser_open_tab '{"sessionId":"my-task","url":"https://second.example.com"}'
adom-desktop browser_open_tab '{"sessionId":"my-task","url":"https://third.example.com"}'

# 3) Activate a specific tab when you want to screenshot or eval it.
adom-desktop browser_switch_tab '{"sessionId":"my-task","tabId":"tab-2"}'
adom-desktop browser_screenshot   '{"sessionId":"my-task","maxWidth":1500}'
adom-desktop browser_eval         '{"sessionId":"my-task","expr":"document.title"}'

browser_open_tab lives alongside the documented browser_* verbs. Same JSON-args convention; same sessionId semantics.

Pick a stable profile name across runs of the same task so the cookie jar persists — the user logs in once and reuses for 12+ months. Cross-vendor cookie blending is fine because cookies are per-domain.

Verify what you opened — "ok": true is not enough

browser_open_window / browser_open_tab returns "ok": true as soon as the navigation starts, NOT when the page actually rendered useful content. Always sanity-check the result:

# Title check — fastest. 404s, anti-bot blocks, and login walls
# all change the title.
adom-desktop browser_eval '{"sessionId":"my-task","expr":"document.title"}'

# Screenshot — slowest, most thorough. Use when title isn't
# discriminating (e.g. SPA that updates title async).
adom-desktop browser_screenshot '{"sessionId":"my-task"}'

Never blindly template a part number into a vendor URL pattern without verifying the page exists. The cost of a 404 you didn't catch is the user spotting it instead. (Real example: shipped a manufacturer-search-URL pattern that 404'd on ti.com/product/ and nxp.com/ for parts those vendors don't make. Using a google.com/search?q=… link routes through Google's resolver and gets a real product hit on the first result.)

Key Rules

  • Always pass sessionId — never use '{}' as args. The default targets the active session which may not be what you want.
  • adom-desktop browser_<cmd> <json> — never adom-desktop pup <cmd> --flags. There is no pup subcommand at the top level; that pattern fails with "Invalid JSON args".
  • No semicolons in browser_eval — use comma operator or IIFE instead.
  • Sessions persist — IndexedDB, cookies, localStorage survive restarts. Profile name is the persistence key.
  • Each session = independent Chrome window — crash-isolated. Multiple tabs share one session and one cookie jar.
  • Never kill Chrome broadly — use browser_close_window for specific sessions.
  • Always reload + alert after browser-facing code changes — the user expects to see updates immediately.

Full Reference

For detailed docs (profiles, sleep/wake recovery, troubleshooting, first-time setup), see ~/.claude/skills/adom/guides/pup.md.

Pup windows beat Hydrogen webviews for dashboards

For long-running dashboards (chip-fetcher progress, chipfit batch results, watchdog status, build watcher), prefer a dedicated pup browser window over a Hydrogen webview tab. Reasons:

  1. Hydrogen real estate — webviews occupy a panel slot. The user is already coding/browsing in Hydrogen; a permanent dashboard tab pushes other content off-screen.
  2. Two-pup pattern — works well: one pup window pinned as the dashboard, a second pup used for ad-hoc scraping/clicking. They don't fight each other.
  3. Persistence — pup windows survive Hydrogen reloads.

The pattern applies to chip-fetcher, chipfit, chipsmith, any tool that emits a continuous progress UI.

Reuse pup sessions — don't recreate

Each browser_open_window call with the same sessionId reuses the existing window if it's already open. Don't close + reopen reflexively; just browser_reload or navigate. Closing and reopening loses any auth state the window had.

Don't close tabs the user didn't ask to close

The agent owns the windows it opened (by session-id). The user's pre-existing tabs in those same windows are NOT yours to close. When in doubt: open a new session-id, work there, close that one when done. Past pattern: closed a user's research tab while cleaning up after a verification run.