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
v2.0.674 (insiders): pup-bridge#124: one verb surface (bridge.json and pup_describe generated from the same 96 routed verbs; the build stops if one lacks a catalog entry; pup_describe carries verbCount and its notes). Every image reply carries ab's fullPath and safePath so ab auto-pulls the picture. pup now lifts its output fields onto every reply and always states ok true/false itself, because ab 2.1.14x stopped doing both: ok had vanished from error replies and pup_record_stop reported the call time as the recording's duration.
5 files changed
+1064−766
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.671"+version = "2.0.674" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.671"+version = "2.0.674" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.671"+version = "2.0.674" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-core", "pup-host",@@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.671"+version = "2.0.674" dependencies = [ "fontdue", "image",@@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-core", "serde",@@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.671"+version = "2.0.674" dependencies = [ "pup-core", "pup-host",@@ -757,7 +757,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.671"+version = "2.0.674" dependencies = [ "base64", "png 0.17.16",
rust/Cargo.toml+1−1@@ -4,7 +4,7 @@ members = ["core", "host", "host-win", "host-mac", "cdp", "browser", "session", # One version for the whole bridge, in lockstep with src/BRIDGE_VERSION (tools/ship.sh bumps both). [workspace.package]-version = "2.0.671"+version = "2.0.674" edition = "2021" license = "MIT"
rust/verbs/assets/verb-catalog.json+971−750@@ -27,6 +27,98 @@ } }, "verbs": [+ {+ "example": {},+ "hint": "This list \u2014 the bridge's verbs + schemas + hints, for AD's Verbs tab. Read it once to learn the surface.",+ "input": {},+ "longRunning": false,+ "name": "pup_describe",+ "output": {+ "success": "bool"+ },+ "pitfalls": [],+ "related": [+ "pup_status"+ ],+ "statusVerb": null,+ "summary": "This list \u2014 the bridge's verbs + schemas, for AD's Verbs tab.",+ "timeoutSeconds": 10+ },+ {+ "example": {},+ "hint": "Cold-start readiness probe: {ready, chromeForTestingInstalled, installing, installProgressPct}. On a fresh PC, poll this until ready:true before pup_open_window \u2014 the bridge auto-installs Chrome for Testing on its own.",+ "input": {},+ "longRunning": false,+ "name": "pup_readiness",+ "output": {+ "success": "bool"+ },+ "pitfalls": [+ "ready:false with installing:true just means Chrome for Testing is still downloading \u2014 wait + poll, do NOT treat it as an error",+ "installPhase:\"failed\" \u2192 surface lastError to the user (usually network/proxy blocking the download, or low disk)"+ ],+ "related": [+ "pup_prewarm",+ "pup_open_window"+ ],+ "statusVerb": null,+ "summary": "Readiness + which browser pup will drive (installed Chrome/Edge, or cached Chrome for Testing) + the full candidate list and current default. READ-ONLY; never downloads.",+ "timeoutSeconds": 10+ },+ {+ "example": {},+ "hint": "Installs Chrome for Testing (the browser pup drives) WITHOUT opening a window \u2014 the warm-before-first-use path. Returns fast; poll pup_readiness for completion.",+ "input": {+ "setDefault": "optional bool (default true) \u2014 pin CfT as the default",+ "wait": "optional bool \u2014 block until installed"+ },+ "longRunning": false,+ "name": "pup_prewarm",+ "output": {+ "success": "bool"+ },+ "pitfalls": [+ "the ~150 MB download needs network access to storage.googleapis.com",+ "does not open a window \u2014 call pup_open_window once readiness reports ready:true"+ ],+ "related": [+ "pup_readiness",+ "pup_open_window"+ ],+ "statusVerb": null,+ "summary": "Install Chrome for Testing WITHOUT opening a window, and pin it as the default. Use when you specifically want CfT rather than the machine's installed Chrome/Edge.",+ "timeoutSeconds": 240+ },+ {+ "example": {},+ "hint": "List all open pup sessions + their tabs (NOT a readiness check \u2014 use pup_readiness for that).",+ "input": {},+ "longRunning": false,+ "name": "pup_status",+ "output": {+ "success": "bool"+ },+ "pitfalls": [],+ "related": [],+ "statusVerb": null,+ "summary": "List all open pup sessions + their tabs (NOT a readiness check \u2014 use pup_readiness for that).",+ "timeoutSeconds": 10+ },+ {+ "example": {},+ "hint": "List every open session/window with its tabs.",+ "input": {},+ "longRunning": false,+ "name": "pup_list_windows",+ "output": {+ "success": "bool"+ },+ "pitfalls": [],+ "related": [],+ "statusVerb": null,+ "summary": "List every open session/window with its tabs.",+ "timeoutSeconds": 10+ }, { "example": {}, "hint": "START HERE for window management: everything YOUR thread owns \u2014 windows, tabs, urls, titles, state \u2014 with ready-to-run next steps. Zero args. You NEVER need an hwnd, desktop_find_window, or desktop_screenshot_window for a pup window: pup keys every window to its owning --ai-thread name and resolves ids for you \u2014 omit sessionId in any pup verb to target your own window, or pass \"ai-thread\" to address a thread by name (the CLI's --ai-thread flag, same spelling). pup_screenshot {} screenshots it; pup_reload {} refreshes it.",@@ -44,6 +136,23 @@ "summary": "START HERE for window management: everything YOUR thread owns \u2014 windows, tabs, urls, titles, state \u2014 with ready-to-run next steps. Zero args. You NEVER need an hwnd, desktop_find_window, or desktop_screenshot_window for a pup window: pup keys every window to its owning --ai-thread name and resolves ids for you \u2014 omit sessionId in any pup verb to target your own window, or pass \"ai-thread\" to address a thread by name (the CLI's --ai-thread flag, same spelling). pup_screenshot {} screenshots it; pup_reload {} refreshes it.", "timeoutSeconds": 10 },+ {+ "example": {},+ "hint": "START HERE for window management: everything YOUR thread owns \u2014 windows, tabs, urls, titles, state \u2014 with ready-to-run next steps. Zero args. You NEVER need an hwnd, desktop_find_window, or desktop_screenshot_window for a pup window: pup keys every window to its owning --ai-thread name and resolves ids for you \u2014 omit sessionId in any pup verb to target your own window, or pass \"ai-thread\" to address a thread by name (the CLI's --ai-thread flag, same spelling). pup_screenshot {} screenshots it; pup_reload {} refreshes it.",+ "input": {+ "ai-thread": "optional str \u2014 inspect another thread's windows (read-only listing); default = the calling thread. The CLI flag's exact spelling; aiThread also accepted"+ },+ "longRunning": false,+ "name": "pup_list_sessions",+ "output": {+ "success": "bool"+ },+ "pitfalls": [],+ "related": [],+ "statusVerb": null,+ "summary": "Alias of pup_my_windows: list the sessions owned by the calling ai-thread. Passive, never launches a browser.",+ "timeoutSeconds": 10+ }, { "example": { "height": 540,@@ -100,54 +209,101 @@ "sessionId": "work", "url": "https://example.com" },- "hint": "Adds a tab to an existing session; returns the new tabId.",+ "hint": "Drives the active tab to url and waits for load. Verify with a screenshot before asserting success.", "input": { "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "silent": "optional bool \u2014 suppress the auto-flash for this call",- "updateNote": "optional str \u2014 short \"what I did\" note surfaced in list_tabs as lastAgentUpdate",- "url": "optional str"+ "url": "required str" }, "longRunning": false,- "name": "pup_open_tab",+ "name": "pup_navigate", "output": { "success": "bool" }, "pitfalls": [- "the session must already exist (pup_open_window first)"+ "navigation can race SPA routing \u2014 confirm with pup_screenshot",+ "some sites need pup_wait for a selector after navigate" ], "related": [- "pup_switch_tab",- "pup_list_tabs",- "pup_close_tab"+ "pup_screenshot",+ "pup_wait",+ "pup_reload" ], "statusVerb": null,- "summary": "Open a new tab in an EXISTING session/window (returns {tabId, tabCount, activeTabId} \u2014 no need to diff list_tabs). This is the right verb for multi-URL work: ONE window with many tabs (one taskbar button), not many windows. The window stays in the background; the tab auto-flashes the taskbar (debounced) so the user notices. The USER can click the window's taskbar button at any time to view it \u2014 pup never pulls it back down.",+ "summary": "Navigate the active tab to a URL.",+ "timeoutSeconds": 45+ },+ {+ "example": {},+ "hint": "",+ "input": {},+ "longRunning": false,+ "name": "pup_eval",+ "output": {+ "success": "bool"+ },+ "pitfalls": [+ "the expression must be serializable JSON in its return value",+ "runs in the page context, not Node"+ ],+ "related": [+ "pup_evaluate"+ ],+ "statusVerb": null,+ "summary": "Alias of pup_evaluate: evaluate JS in the page and return the result. Add frame:\"<name|url-substring|index>\" to run inside an iframe.", "timeoutSeconds": 30 }, { "example": {+ "expression": "document.title", "sessionId": "work" },- "hint": "Closes one tab; the session stays open.",+ "hint": "Runs JS in the page and returns the result \u2014 use it to read DOM state or verify selectors before acting.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "tabId": "optional str (default active)"+ "expression": "required str \u2014 aliases accepted: expr, script, js, code",+ "frame": "optional str \u2014 run inside this iframe instead of the top document",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_close_tab",+ "name": "pup_evaluate", "output": { "success": "bool" }, "pitfalls": [- "omit tabId to close the active tab \u2014 closing the last tab may close the window"+ "the expression must be serializable JSON in its return value",+ "runs in the page context, not Node" ], "related": [- "pup_close_window",- "pup_list_tabs"+ "pup_click",+ "pup_fetch_url" ], "statusVerb": null,- "summary": "Close a tab.",- "timeoutSeconds": 15+ "summary": "Evaluate JS in the page and return the result \u2014 THE verb for reading DOM state / asserting on a page (same handler as pup_eval). Add frame:\"<name|url-substring|index>\" to run inside an iframe.",+ "timeoutSeconds": 30+ },+ {+ "example": {+ "sessionId": "work"+ },+ "hint": "PNG of the page. Your primary way to SEE state \u2014 call it after navigate/click to confirm.",+ "input": {+ "fullPage": "optional bool",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ },+ "longRunning": false,+ "name": "pup_screenshot",+ "output": {+ "success": "bool"+ },+ "pitfalls": [+ "default is the viewport; pass fullPage:true for the whole scroll height"+ ],+ "related": [+ "pup_screenshot_full_res",+ "pup_navigate"+ ],+ "statusVerb": null,+ "summary": "Screenshot the page (PNG; fullPage optional). NOTE: fullPage only covers DOCUMENT-level scroll \u2014 on SPAs that scroll inside a nested container it returns just the viewport + a nestedScroll hint; use pup_screenshot_element or pup_scroll for those.",+ "timeoutSeconds": 30 }, { "example": {@@ -174,159 +330,174 @@ }, { "example": {},- "hint": "Everything about ONE pup window as JSON \u2014 owner AI thread, container, profile, tabs, age, last agent action, and the window's hwnd (for desktop_screenshot_window when you need the browser chrome in frame). For an AI caller this is a PURE DATA query: nothing is drawn on the user's screen. Visual surfaces are user-initiated only \u2014 a jump-list click shows the native taskbar caption, a toolbar-popup click shows the in-page floaty card. showCaption:true opts an AI caller into the caption ONLY when the user asked to have the window identified on screen. Read-only, never owner-gated.",- "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "showCaption": "optional bool \u2014 draw the ~12s taskbar caption too; ONLY when the user asked to see which window this is"- },+ "hint": "Close ALL pup sessions + windows at once (destructive). NOTE: `{}` here does NOT mean \"the active session\" the way it does on other session-scoped verbs \u2014 with no args this closes EVERY window, across every AI thread on the machine, with no confirmation. To close ONE window use pup_close_window {sessionId}. Pass {sessionId} here only if you genuinely mean that one session.",+ "input": {}, "longRunning": false,- "name": "pup_window_info",+ "name": "pup_close", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Everything about ONE pup window as JSON \u2014 owner AI thread, container, profile, tabs, age, last agent action, and the window's hwnd (for desktop_screenshot_window when you need the browser chrome in frame). For an AI caller this is a PURE DATA query: nothing is drawn on the user's screen. Visual surfaces are user-initiated only \u2014 a jump-list click shows the native taskbar caption, a toolbar-popup click shows the in-page floaty card. showCaption:true opts an AI caller into the caption ONLY when the user asked to have the window identified on screen. Read-only, never owner-gated.",- "timeoutSeconds": 15+ "summary": "Close ALL pup sessions + windows at once (destructive). NOTE: `{}` here does NOT mean \"the active session\" the way it does on other session-scoped verbs \u2014 with no args this closes EVERY window, across every AI thread on the machine, with no confirmation. To close ONE window use pup_close_window {sessionId}. Pass {sessionId} here only if you genuinely mean that one session.",+ "timeoutSeconds": 20 }, { "example": {- "sessionId": "wiki-bmv080",- "view": "authed"+ "sessionId": "work" },- "hint": "Flip a WIKI pup window between the logged-in and public view IN PLACE: every wiki tab reloads with the server-side ?view=public param appended (public) or stripped (authed) \u2014 adom/wiki#126 renders the request exactly as a signed-out visitor while the session stays untouched, so there is no relaunch, no cookie wipe, no profile switch (the legacy jar relaunch survives only as a fallback for servers that ignore the param). This is the callback for the per-window taskbar jump-list task (WINDOWS ONLY) \u2014 a USER-initiated action. AI threads should NOT call this; pass wikiView:\"authed\"|\"public\" on pup_open_window instead.",+ "hint": "Closes the whole session and all its tabs. Destructive.",+ "input": {+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ },+ "longRunning": false,+ "name": "pup_close_all_windows",+ "output": {+ "success": "bool"+ },+ "pitfalls": [+ "this ends the session \u2014 its sessionId is no longer valid afterward"+ ],+ "related": [+ "pup_list_windows"+ ],+ "statusVerb": null,+ "summary": "Close every pup window owned by the calling ai-thread (pup_close_window with all:true). Other threads' windows are never touched.",+ "timeoutSeconds": 20+ },+ {+ "example": {},+ "hint": "Everything about ONE pup window as JSON \u2014 owner AI thread, container, profile, tabs, age, last agent action, and the window's hwnd (for desktop_screenshot_window when you need the browser chrome in frame). For an AI caller this is a PURE DATA query: nothing is drawn on the user's screen. Visual surfaces are user-initiated only \u2014 a jump-list click shows the native taskbar caption, a toolbar-popup click shows the in-page floaty card. showCaption:true opts an AI caller into the caption ONLY when the user asked to have the window identified on screen. Read-only, never owner-gated.", "input": { "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "view": "required \"authed\"|\"public\"|\"toggle\" \u2014 toggle flips whatever the window currently shows (the Alt+Shift+V hotkey form)"+ "showCaption": "optional bool \u2014 draw the ~12s taskbar caption too; ONLY when the user asked to see which window this is" }, "longRunning": false,- "name": "pup_wiki_set_view",+ "name": "pup_window_info", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Flip a WIKI pup window between the logged-in and public view IN PLACE: every wiki tab reloads with the server-side ?view=public param appended (public) or stripped (authed) \u2014 adom/wiki#126 renders the request exactly as a signed-out visitor while the session stays untouched, so there is no relaunch, no cookie wipe, no profile switch (the legacy jar relaunch survives only as a fallback for servers that ignore the param). This is the callback for the per-window taskbar jump-list task (WINDOWS ONLY) \u2014 a USER-initiated action. AI threads should NOT call this; pass wikiView:\"authed\"|\"public\" on pup_open_window instead.",- "timeoutSeconds": 45+ "summary": "Everything about ONE pup window as JSON \u2014 owner AI thread, container, profile, tabs, age, last agent action, and the window's hwnd (for desktop_screenshot_window when you need the browser chrome in frame). For an AI caller this is a PURE DATA query: nothing is drawn on the user's screen. Visual surfaces are user-initiated only \u2014 a jump-list click shows the native taskbar caption, a toolbar-popup click shows the in-page floaty card. showCaption:true opts an AI caller into the caption ONLY when the user asked to have the window identified on screen. Read-only, never owner-gated.",+ "timeoutSeconds": 15 }, { "example": { "sessionId": "work", "url": "https://example.com" },- "hint": "Drives the active tab to url and waits for load. Verify with a screenshot before asserting success.",+ "hint": "Adds a tab to an existing session; returns the new tabId.", "input": { "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "url": "required str"+ "silent": "optional bool \u2014 suppress the auto-flash for this call",+ "updateNote": "optional str \u2014 short \"what I did\" note surfaced in list_tabs as lastAgentUpdate",+ "url": "optional str" }, "longRunning": false,- "name": "pup_navigate",+ "name": "pup_open_tab", "output": { "success": "bool" }, "pitfalls": [- "navigation can race SPA routing \u2014 confirm with pup_screenshot",- "some sites need pup_wait for a selector after navigate"+ "the session must already exist (pup_open_window first)" ], "related": [- "pup_screenshot",- "pup_wait",- "pup_reload"+ "pup_switch_tab",+ "pup_list_tabs",+ "pup_close_tab" ], "statusVerb": null,- "summary": "Navigate the active tab to a URL.",- "timeoutSeconds": 45+ "summary": "Open a new tab in an EXISTING session/window (returns {tabId, tabCount, activeTabId} \u2014 no need to diff list_tabs). This is the right verb for multi-URL work: ONE window with many tabs (one taskbar button), not many windows. The window stays in the background; the tab auto-flashes the taskbar (debounced) so the user notices. The USER can click the window's taskbar button at any time to view it \u2014 pup never pulls it back down.",+ "timeoutSeconds": 30 }, { "example": { "sessionId": "work" },- "hint": "History back on the active tab.",+ "hint": "List the tabs in a session.", "input": { "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_back",+ "name": "pup_list_tabs", "output": { "success": "bool" }, "pitfalls": [],- "related": [- "pup_forward",- "pup_navigate"- ],+ "related": [], "statusVerb": null,- "summary": "History back.",- "timeoutSeconds": 20+ "summary": "List the tabs in a session.",+ "timeoutSeconds": 10 }, { "example": {- "sessionId": "work"+ "sessionId": "work",+ "tabId": "tab-2" },- "hint": "History forward on the active tab.",+ "hint": "Activate a tab WITHIN its session/window (idempotent \u2014 safe to call when the tab is already active). Does NOT raise a background window to the user's screen: the tab becomes the window's visible tab wherever the window is, and the taskbar auto-flash tells the user something changed. Also self-heals the window (re-park if a bug ever stranded it off-screen) and refreshes the Adom taskbar badge \u2014 good citizenship for long-lived shared windows.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "silent": "optional bool",+ "tabId": "required str",+ "updateNote": "optional str" }, "longRunning": false,- "name": "pup_forward",+ "name": "pup_switch_tab", "output": { "success": "bool" }, "pitfalls": [],- "related": [- "pup_back",- "pup_navigate"- ],+ "related": [], "statusVerb": null,- "summary": "History forward.",- "timeoutSeconds": 20+ "summary": "Activate a tab WITHIN its session/window (idempotent \u2014 safe to call when the tab is already active). Does NOT raise a background window to the user's screen: the tab becomes the window's visible tab wherever the window is, and the taskbar auto-flash tells the user something changed. Also self-heals the window (re-park if a bug ever stranded it off-screen) and refreshes the Adom taskbar badge \u2014 good citizenship for long-lived shared windows.",+ "timeoutSeconds": 10 }, {- "example": {},- "hint": "Paint the Windows taskbar PROGRESS BAR on your window during a LONG multi-stage task (crawling a site, login->search->download loops, batch harvesting datasheets/STEP files) so the user sees work happening at a glance. mode:\"indeterminate\" (pulsing) to start, mode:\"value\" with value:0-100 as stages complete, mode:\"off\" when done or aborted (always clear!). Optional note:\"12/40 datasheets\" shows on the button hover tooltip. YOUR judgment: a single quick page open does NOT warrant a progress bar. See the pup-progress skill.",+ "example": {+ "sessionId": "work"+ },+ "hint": "Closes one tab; the session stays open.", "input": {- "mode": "\"indeterminate\" | \"value\" | \"off\"",- "note": "optional short stage label for the hover tooltip",- "sessionId": "optional \u2014 defaults to your thread's own window",- "value": "optional 0-100 (mode:\"value\")"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "tabId": "optional str (default active)" }, "longRunning": false,- "name": "pup_progress",+ "name": "pup_close_tab", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "omit tabId to close the active tab \u2014 closing the last tab may close the window"+ ],+ "related": [+ "pup_close_window",+ "pup_list_tabs"+ ], "statusVerb": null,- "summary": "Paint the Windows taskbar PROGRESS BAR on your window during a LONG multi-stage task (crawling a site, login->search->download loops, batch harvesting datasheets/STEP files) so the user sees work happening at a glance. mode:\"indeterminate\" (pulsing) to start, mode:\"value\" with value:0-100 as stages complete, mode:\"off\" when done or aborted (always clear!). Optional note:\"12/40 datasheets\" shows on the button hover tooltip. YOUR judgment: a single quick page open does NOT warrant a progress bar. See the pup-progress skill.",- "timeoutSeconds": 8+ "summary": "Close a tab.",+ "timeoutSeconds": 15 }, { "example": { "sessionId": "work" },- "hint": "Reloads the active tab.",+ "hint": "Make a session the active one (for verbs that default to active).", "input": { "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_reload",+ "name": "pup_switch_window", "output": { "success": "bool" },- "pitfalls": [- "A page with unsaved changes raises a native \"Reload site?\" dialog that BLOCKS the window: the reload appears to time out and so does everything else. Answer it with pup_dialog."- ],- "related": [- "pup_navigate",- "pup_wait",- "pup_dialog"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Reload the active tab.",- "timeoutSeconds": 30+ "summary": "Make a session the active one (for verbs that default to active).",+ "timeoutSeconds": 10 }, { "example": {@@ -363,31 +534,55 @@ }, { "example": {- "label": "your Google account",- "reason": "clicking your Google account to finish signing into Autodesk",- "sessionId": "adsk",- "x": 900,- "y": 379+ "selector": "select[name=timezone]",+ "label": "Central Time" },- "hint": "RESPECTFUL real-cursor click for a surface pup CDP cannot drive (a NATIVE OS dialog, or a cross-origin FedCM \"Sign in with Google\" account chooser that rejects every in-page click). Do NOT hand-roll raise+desktop_click for these \u2014 use THIS. It: captions the reason + toasts BEFORE touching the screen; foregrounds; then WAITS, detecting if the user alt-tabbed away and holding (captioning \"Adom is waiting \u2014 click back when ready\") until the user is actually on the window; then runs the TIGHT real-cursor hover->click that FedCM accepts (interleaving any screenshot/eval between raise and click makes the click silently no-op); captions each step. Times out as user_not_ready clicking NOTHING if they never return. First take a desktop_screenshot_window of the session to read the target's WINDOW-pixel x,y.",+ "hint": "Sets the option through the native setter and fires input+change so framework-bound selects update; works on both lanes (DevTools evaluate, so page CSP and Trusted Types cannot refuse it). For custom dropdowns (Material, React Select) use pup_click on the control, then pup_click the option by text. option_not_found lists the first 30 options.", "input": {- "label": "required str \u2014 WHAT you are clicking (e.g. \"your Google account\")",- "maxWaitMs": "optional int \u2014 how long to wait for the user (default 120000)",- "reason": "required str \u2014 WHY you must click (shown to the user as a caption)",+ "selector": "required str",+ "value": "optional str (option value)",+ "label": "optional str (visible text, case-insensitive, substring ok)",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ },+ "longRunning": false,+ "name": "pup_select",+ "output": {+ "success": "bool"+ },+ "pitfalls": [],+ "related": [+ "pup_fill",+ "pup_click",+ "pup_type"+ ],+ "statusVerb": null,+ "summary": "Choose a <select> option by value or visible label, in the background (fires input+change).",+ "timeoutSeconds": 20+ },+ {+ "example": {+ "selector": "#menu",+ "sessionId": "work"+ },+ "hint": "Move the pointer over a target WITHOUT clicking (reveals tooltips / hover menus). Glides the visible Adom cursor there too.",+ "input": {+ "intent": "optional str \u2014 narrated on the cursor label",+ "selector": "optional str", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "x": "required int \u2014 WINDOW-image pixel of the target (from desktop_screenshot_window)",- "y": "required int"+ "steps": "optional int (default 8)",+ "x": "optional int",+ "y": "optional int" }, "longRunning": false,- "name": "pup_assisted_click",+ "name": "pup_hover", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "RESPECTFUL real-cursor click for a surface pup CDP cannot drive (a NATIVE OS dialog, or a cross-origin FedCM \"Sign in with Google\" account chooser that rejects every in-page click). Do NOT hand-roll raise+desktop_click for these \u2014 use THIS. It: captions the reason + toasts BEFORE touching the screen; foregrounds; then WAITS, detecting if the user alt-tabbed away and holding (captioning \"Adom is waiting \u2014 click back when ready\") until the user is actually on the window; then runs the TIGHT real-cursor hover->click that FedCM accepts (interleaving any screenshot/eval between raise and click makes the click silently no-op); captions each step. Times out as user_not_ready clicking NOTHING if they never return. First take a desktop_screenshot_window of the session to read the target's WINDOW-pixel x,y.",- "timeoutSeconds": 130+ "summary": "Move the pointer over a target WITHOUT clicking (reveals tooltips / hover menus). Glides the visible Adom cursor there too.",+ "timeoutSeconds": 15 }, { "example": {@@ -420,29 +615,6 @@ "summary": "Type text into a field via real CDP keystrokes (isTrusted). Reads the value back and returns valueLanded \u2014 real keystrokes only register in a FOREGROUNDED window, so if valueLanded:false the field stayed empty (the silent no-op): use pup_fill (background-safe) for a non-React input, or pup_login {realKeystrokes:true} for a login form. Pass clear:true to REPLACE instead of appending.", "timeoutSeconds": 20 },- {- "example": {- "selector": "#email",- "sessionId": "work",- "text": "[email protected]"- },- "hint": "Set a field's value in the BACKGROUND (no foreground, no keystrokes): writes .value via the native setter and fires input+change so React/Vue/Angular controlled inputs pick it up. The fix for pup_type's silent no-op on a backgrounded window. Reads the value back and returns valueLanded. For React-MASKED password fields that reject programmatic writes, use pup_login {realKeystrokes:true} instead.",- "input": {- "selector": "required str",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "text": "required str"- },- "longRunning": false,- "name": "pup_fill",- "output": {- "success": "bool"- },- "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Set a field's value in the BACKGROUND (no foreground, no keystrokes): writes .value via the native setter and fires input+change so React/Vue/Angular controlled inputs pick it up. The fix for pup_type's silent no-op on a backgrounded window. Reads the value back and returns valueLanded. For React-MASKED password fields that reject programmatic writes, use pup_login {realKeystrokes:true} instead.",- "timeoutSeconds": 20- }, { "example": { "key": "a",@@ -474,736 +646,760 @@ "timeoutSeconds": 15 }, {- "example": {},- "hint": "\u26d4 READ BEFORE USING AD's desktop_click / desktop_type / desktop_press_key ON A PUP WINDOW. Native OS input does NOT target a window \u2014 it goes to WHATEVER WINDOW HAS OS FOCUS at that instant. This user runs many AI threads and alt-tabs away from a foregrounded window almost immediately. If you fire native input without warning them, your keystrokes land in THEIR document (this happened: a username was typed into the user's own window). REQUIRED: notify_user toast + desktop_caption FIRST, tell them explicitly \"don't type or click until I say done\", wait until the window is genuinely foreground, re-verify immediately before EACH event, and caption when done. pup_assisted_click already does all of this \u2014 use it. And ALWAYS prefer CDP input (pup_click/pup_type/pup_hover): it needs no foreground and disturbs nobody. A native verb returning ok:true means the event was dispatched to the OS, NOT that it reached your window.",- "input": {},+ "example": {+ "sessionId": "work",+ "toText": "Download"+ },+ "hint": "Scroll the ACTUAL scrolling element (nearest ancestor whose scrollHeight>clientHeight, NOT just window \u2014 window.scrollTo no-ops on nested-scroll SPAs). Scroll by delta {dy,dx} OR to an element {toSelector|toText} (pierces shadow DOM). Returns {scrollElement, scrollTop, scrollHeight, clientHeight} so you can confirm it landed.",+ "input": {+ "behavior": "optional auto|smooth",+ "block": "optional start|center|end (for scroll-to; default center)",+ "dx": "optional int px",+ "dy": "optional int px (down+/up-)",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "toSelector": "optional CSS selector to scroll to",+ "toText": "optional visible text to scroll to"+ }, "longRunning": false,- "name": "pup_NATIVE_INPUT_WARNING",+ "name": "pup_scroll", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "\u26d4 READ BEFORE USING AD's desktop_click / desktop_type / desktop_press_key ON A PUP WINDOW. Native OS input does NOT target a window \u2014 it goes to WHATEVER WINDOW HAS OS FOCUS at that instant. This user runs many AI threads and alt-tabs away from a foregrounded window almost immediately. If you fire native input without warning them, your keystrokes land in THEIR document (this happened: a username was typed into the user's own window). REQUIRED: notify_user toast + desktop_caption FIRST, tell them explicitly \"don't type or click until I say done\", wait until the window is genuinely foreground, re-verify immediately before EACH event, and caption when done. pup_assisted_click already does all of this \u2014 use it. And ALWAYS prefer CDP input (pup_click/pup_type/pup_hover): it needs no foreground and disturbs nobody. A native verb returning ok:true means the event was dispatched to the OS, NOT that it reached your window.",- "timeoutSeconds": 0+ "summary": "Scroll the ACTUAL scrolling element (nearest ancestor whose scrollHeight>clientHeight, NOT just window \u2014 window.scrollTo no-ops on nested-scroll SPAs). Scroll by delta {dy,dx} OR to an element {toSelector|toText} (pierces shadow DOM). Returns {scrollElement, scrollTop, scrollHeight, clientHeight} so you can confirm it landed.",+ "timeoutSeconds": 30 }, { "example": {- "reset": true,- "sessionId": "work"+ "selector": "#email",+ "sessionId": "work",+ "text": "[email protected]" },- "hint": "Pin the page viewport (CDP device-metrics) to an exact size for deterministic screenshots \u2014 OR clear that pin. \u26a0 A pin PERSISTS: it survives navigation and hard reload, and while set the window stops tracking OS resizing (a human resizing it sees no reflow, which looks exactly like an app layout bug). ALWAYS clear it when done. Reported as emulatedViewport in pup_status / pup_list_windows.",+ "hint": "Set a field's value in the BACKGROUND (no foreground, no keystrokes): writes .value via the native setter and fires input+change so React/Vue/Angular controlled inputs pick it up. The fix for pup_type's silent no-op on a backgrounded window. Reads the value back and returns valueLanded. For React-MASKED password fields that reject programmatic writes, use pup_login {realKeystrokes:true} instead.", "input": {- "deviceScaleFactor": "optional number (default 1)",- "height": "int \u2014 required unless reset",- "reset": "optional bool \u2014 CLEAR the override and track the OS window again (width/height not needed)",+ "selector": "required str", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "width": "int \u2014 required unless reset"+ "text": "required str" }, "longRunning": false,- "name": "pup_set_viewport",+ "name": "pup_fill", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Pin the page viewport (CDP device-metrics) to an exact size for deterministic screenshots \u2014 OR clear that pin. \u26a0 A pin PERSISTS: it survives navigation and hard reload, and while set the window stops tracking OS resizing (a human resizing it sees no reflow, which looks exactly like an app layout bug). ALWAYS clear it when done. Reported as emulatedViewport in pup_status / pup_list_windows.",- "timeoutSeconds": 15+ "summary": "Set a field's value in the BACKGROUND (no foreground, no keystrokes): writes .value via the native setter and fires input+change so React/Vue/Angular controlled inputs pick it up. The fix for pup_type's silent no-op on a backgrounded window. Reads the value back and returns valueLanded. For React-MASKED password fields that reject programmatic writes, use pup_login {realKeystrokes:true} instead.",+ "timeoutSeconds": 20 }, {- "example": {},- "hint": "Status/trigger for the toolbar extension, which AUTO-INSTALLS invisibly at every profile launch (transient pipe-launch + CDP Extensions.loadUnpacked; persists in the profile). Running profiles pick it up on their next launch; cold profiles install immediately.",+ "example": {+ "ms": 500,+ "sessionId": "work"+ },+ "hint": "Wait N ms (or for a selector) before the next step.", "input": {- "profile": "optional, default adom-you"+ "ms": "optional int",+ "selector": "optional str",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_install_toolbar",+ "name": "pup_wait", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Status/trigger for the toolbar extension, which AUTO-INSTALLS invisibly at every profile launch (transient pipe-launch + CDP Extensions.loadUnpacked; persists in the profile). Running profiles pick it up on their next launch; cold profiles install immediately.",- "timeoutSeconds": 30+ "summary": "Wait N ms (or for a selector) before the next step.",+ "timeoutSeconds": 60 }, {- "example": {},- "hint": "TEST SEAM: simulate a user click on the floaty pencil (fires the same host click handler a mouse does; the sweep then starts annotate within ~4s). Built for the recurring floaty ralph \u2014 assert the annotate overlay appears after calling this.",+ "example": {+ "sessionId": "work"+ },+ "hint": "Reloads the active tab.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "tabId": "optional tab"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_floaty_click",+ "name": "pup_reload", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "A page with unsaved changes raises a native \"Reload site?\" dialog that BLOCKS the window: the reload appears to time out and so does everything else. Answer it with pup_dialog."+ ],+ "related": [+ "pup_navigate",+ "pup_wait",+ "pup_dialog"+ ], "statusVerb": null,- "summary": "TEST SEAM: simulate a user click on the floaty pencil (fires the same host click handler a mouse does; the sweep then starts annotate within ~4s). Built for the recurring floaty ralph \u2014 assert the annotate overlay appears after calling this.",- "timeoutSeconds": 10+ "summary": "Reload the active tab.",+ "timeoutSeconds": 30 }, {- "example": {},- "hint": "Start the HUMAN annotation overlay on a window's active tab (pen/arrow/box/text toolbar injected in-page; also on every window's taskbar jump list as \"Annotate this page\"). Two capture modes the user can also flip in the toolbar: \"auto\" (default) captures the instant the mouse lifts \u2014 the one-red-circle case; \"multi\" accumulates shapes (color swatches: red/blue/yellow one click, more in a flyout) and captures ONCE on Done. Done captures a PNG AND leaves the drawing in the DOM, so the owning thread's next pup_screenshot shows exactly what the user marked \u2014 check session.lastAnnotation via pup_list_windows. action:\"cancel\" removes the overlay.",+ "example": {+ "sessionId": "work"+ },+ "hint": "History back on the active tab.", "input": {- "action": "optional \"cancel\"",- "mode": "optional \"auto\"|\"multi\"", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_annotate",+ "name": "pup_back", "output": { "success": "bool" }, "pitfalls": [],- "related": [],+ "related": [+ "pup_forward",+ "pup_navigate"+ ], "statusVerb": null,- "summary": "Start the HUMAN annotation overlay on a window's active tab (pen/arrow/box/text toolbar injected in-page; also on every window's taskbar jump list as \"Annotate this page\"). Two capture modes the user can also flip in the toolbar: \"auto\" (default) captures the instant the mouse lifts \u2014 the one-red-circle case; \"multi\" accumulates shapes (color swatches: red/blue/yellow one click, more in a flyout) and captures ONCE on Done. Done captures a PNG AND leaves the drawing in the DOM, so the owning thread's next pup_screenshot shows exactly what the user marked \u2014 check session.lastAnnotation via pup_list_windows. action:\"cancel\" removes the overlay.",- "timeoutSeconds": 15+ "summary": "History back.",+ "timeoutSeconds": 20 }, {- "example": {},- "hint": "Open the LIVE pup management dashboard in its OWN window (its own profile, so its own taskbar icon). Shows every window pup is driving as taskbar button -> window(s) -> tabs with each tab favicon, owning AI thread, foreground/background + the reason given to foreground it, highFps lease, uptime, and WHY each overlay icon was chosen. Live over SSE. Reuses the one window on repeat calls; backgrounded by default like any pup window.",- "input": {},- "longRunning": false,- "name": "pup_dashboard",- "output": {- "success": "bool"+ "example": {+ "sessionId": "work"+ },+ "hint": "History forward on the active tab.",+ "input": {+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" },- "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Open the LIVE pup management dashboard in its OWN window (its own profile, so its own taskbar icon). Shows every window pup is driving as taskbar button -> window(s) -> tabs with each tab favicon, owning AI thread, foreground/background + the reason given to foreground it, highFps lease, uptime, and WHY each overlay icon was chosen. Live over SSE. Reuses the one window on repeat calls; backgrounded by default like any pup window.",- "timeoutSeconds": 30- },- {- "example": {},- "hint": "Report pup dependency health (wiki issue #15): depsPresent, selfInstalledThisBoot, lastError. Use this to answer \"is pup broken?\" without reading spawn logs.",- "input": {}, "longRunning": false,- "name": "pup_deps",+ "name": "pup_forward", "output": { "success": "bool" }, "pitfalls": [],- "related": [],+ "related": [+ "pup_back",+ "pup_navigate"+ ], "statusVerb": null,- "summary": "Report pup dependency health (wiki issue #15): depsPresent, selfInstalledThisBoot, lastError. Use this to answer \"is pup broken?\" without reading spawn logs.",- "timeoutSeconds": 10+ "summary": "History forward.",+ "timeoutSeconds": 20 }, { "example": {- "selector": "#menu",+ "reset": true, "sessionId": "work" },- "hint": "Move the pointer over a target WITHOUT clicking (reveals tooltips / hover menus). Glides the visible Adom cursor there too.",+ "hint": "Pin the page viewport (CDP device-metrics) to an exact size for deterministic screenshots \u2014 OR clear that pin. \u26a0 A pin PERSISTS: it survives navigation and hard reload, and while set the window stops tracking OS resizing (a human resizing it sees no reflow, which looks exactly like an app layout bug). ALWAYS clear it when done. Reported as emulatedViewport in pup_status / pup_list_windows.", "input": {- "intent": "optional str \u2014 narrated on the cursor label",- "selector": "optional str",+ "deviceScaleFactor": "optional number (default 1)",+ "height": "int \u2014 required unless reset",+ "reset": "optional bool \u2014 CLEAR the override and track the OS window again (width/height not needed)", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "steps": "optional int (default 8)",- "x": "optional int",- "y": "optional int"+ "width": "int \u2014 required unless reset" }, "longRunning": false,- "name": "pup_hover",+ "name": "pup_set_viewport", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Move the pointer over a target WITHOUT clicking (reveals tooltips / hover menus). Glides the visible Adom cursor there too.",+ "summary": "Pin the page viewport (CDP device-metrics) to an exact size for deterministic screenshots \u2014 OR clear that pin. \u26a0 A pin PERSISTS: it survives navigation and hard reload, and while set the window stops tracking OS resizing (a human resizing it sees no reflow, which looks exactly like an app layout bug). ALWAYS clear it when done. Reported as emulatedViewport in pup_status / pup_list_windows.", "timeoutSeconds": 15 }, { "example": { "sessionId": "work" },- "hint": "Maximize (or restore/fullscreen/minimize) a pup window via CDP \u2014 no AD desktop verb required. Does NOT foreground it.",+ "hint": "Console errors + failed network requests for a session/tab (clears by default).", "input": {+ "clear": "optional bool (default true)", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "state": "optional \"maximized\"|\"normal\"|\"minimized\"|\"fullscreen\" (default maximized)"+ "tabId": "optional str" }, "longRunning": false,- "name": "pup_maximize",+ "name": "pup_errors", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Maximize (or restore/fullscreen/minimize) a pup window via CDP \u2014 no AD desktop verb required. Does NOT foreground it.",- "timeoutSeconds": 15+ "summary": "Console errors + failed network requests for a session/tab (clears by default).",+ "timeoutSeconds": 10 }, {- "example": {- "avatarB64": "<base64 png>",- "name": "John Lauer"- },- "hint": "Set the Adom identity shown in the floating owner chip on every pup window (avatar + name). Chrome's own profile chip cannot take a custom local image (Google sign-in only), so the user's Adom avatar lives here instead. Pass a pre-circle-cropped PNG as base64. Applies to live windows immediately + persists.",+ "name": "pup_dialog",+ "summary": "Answer a JavaScript dialog (alert, confirm, prompt, beforeunload) on the page: accept or dismiss, with promptText for a prompt.", "input": {- "avatarB64": "optional str \u2014 base64 PNG (ideally ~48px, circle-cropped); the leading data: prefix is stripped if present",- "clear": "optional bool \u2014 remove the stored avatar + name",- "enabled": "optional bool \u2014 turn the whole owner chip on/off",- "name": "optional str \u2014 display name shown in the chip tooltip"+ "action": "accept | dismiss (default accept)",+ "promptText": "optional str: the answer typed into a prompt()",+ "window": "optional: the pup window (default the calling thread's)" },- "longRunning": false,- "name": "pup_set_identity",- "output": {- "success": "bool"+ "example": {+ "action": "accept" },+ "hint": "A dialog blocks every other verb on that tab until it is answered. pup_errors and the verb that tripped it name the open dialog.",+ "related": [+ "pup_errors",+ "pup_click"+ ],+ "timeoutSeconds": 30,+ "output": {}, "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Set the Adom identity shown in the floating owner chip on every pup window (avatar + name). Chrome's own profile chip cannot take a custom local image (Google sign-in only), so the user's Adom avatar lives here instead. Pass a pre-circle-cropped PNG as base64. Applies to live windows immediately + persists.",- "timeoutSeconds": 15+ "longRunning": false,+ "statusVerb": "pup_readiness" }, { "example": {- "sessionId": "work"+ "sessionId": "work",+ "type": "mousePressed",+ "x": 100,+ "y": 200 },- "hint": "PNG of the page. Your primary way to SEE state \u2014 call it after navigate/click to confirm.",+ "hint": "Low-level CDP mouse/keyboard input at coords \u2014 beats some overlays/iframes that swallow synthetic clicks.", "input": {- "fullPage": "optional bool",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "type": "required str",+ "x": "optional int",+ "y": "optional int" }, "longRunning": false,- "name": "pup_screenshot",+ "name": "pup_input_dispatch", "output": { "success": "bool" }, "pitfalls": [- "default is the viewport; pass fullPage:true for the whole scroll height"+ "coords are viewport pixels",+ "last-resort when pup_click can't reach the element \u2014 still cannot pass Cloudflare interactive challenges" ], "related": [- "pup_screenshot_full_res",- "pup_navigate"+ "pup_click",+ "pup_press_key" ], "statusVerb": null,- "summary": "Screenshot the page (PNG; fullPage optional). NOTE: fullPage only covers DOCUMENT-level scroll \u2014 on SPAs that scroll inside a nested container it returns just the viewport + a nestedScroll hint; use pup_screenshot_element or pup_scroll for those.",- "timeoutSeconds": 30+ "summary": "Low-level input (mouse/keyboard) at coords \u2014 beats some overlays.",+ "timeoutSeconds": 20 }, { "example": {- "selector": ".download-card",+ "html": "<h1>Caf\u00e9</h1>", "sessionId": "work" },- "hint": "Screenshot ONE element, resolved by CSS selector OR visible text (pierces shadow DOM), scrolled into view first. The clean way to grab a single card/widget on a component-heavy page. Text matches the TIGHTEST text node \u2014 to capture the enclosing CARD, add closest (e.g. {text:\"Download\u2026\", closest:\".download-card\"}); with closest, an ambiguous phrase still lands on the real widget (skips prose that has no such ancestor). Returns base64 + filePath like pup_screenshot.",+ "hint": "Render a local HTML string in the tab via page.setContent \u2014 UTF-8-correct (a data: URL mojibakes accented chars without an explicit charset). Great for previewing generated markup without a temp file. Auto-flashes the window.", "input": {- "closest": "optional selector \u2014 climb to the nearest ancestor matching it (the card container)",- "deep": "optional bool \u2014 pierce shadow DOM (default true)",- "padding": "optional int px around the element",- "selector": "CSS selector (or use text)",+ "html": "required HTML string", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "text": "visible text to match (or use selector)"+ "silent": "optional bool \u2014 suppress the auto-flash",+ "updateNote": "optional note for lastAgentUpdate",+ "waitUntil": "optional (domcontentloaded default)" }, "longRunning": false,- "name": "pup_screenshot_element",+ "name": "pup_render_html", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Screenshot ONE element, resolved by CSS selector OR visible text (pierces shadow DOM), scrolled into view first. The clean way to grab a single card/widget on a component-heavy page. Text matches the TIGHTEST text node \u2014 to capture the enclosing CARD, add closest (e.g. {text:\"Download\u2026\", closest:\".download-card\"}); with closest, an ambiguous phrase still lands on the real widget (skips prose that has no such ancestor). Returns base64 + filePath like pup_screenshot.",+ "summary": "Render a local HTML string in the tab via page.setContent \u2014 UTF-8-correct (a data: URL mojibakes accented chars without an explicit charset). Great for previewing generated markup without a temp file. Auto-flashes the window.", "timeoutSeconds": 30 }, { "example": { "sessionId": "work",- "toText": "Download"+ "url": "https://api.example.com" },- "hint": "Scroll the ACTUAL scrolling element (nearest ancestor whose scrollHeight>clientHeight, NOT just window \u2014 window.scrollTo no-ops on nested-scroll SPAs). Scroll by delta {dy,dx} OR to an element {toSelector|toText} (pierces shadow DOM). Returns {scrollElement, scrollTop, scrollHeight, clientHeight} so you can confirm it landed.",+ "hint": "Fetches a URL using the tab's live cookies/session \u2014 for authed APIs the bare CLI can't reach. Returns bodyBase64.", "input": {- "behavior": "optional auto|smooth",- "block": "optional start|center|end (for scroll-to; default center)",- "dx": "optional int px",- "dy": "optional int px (down+/up-)", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "toSelector": "optional CSS selector to scroll to",- "toText": "optional visible text to scroll to"+ "url": "required str" }, "longRunning": false,- "name": "pup_scroll",+ "name": "pup_fetch_url", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "for session-scoped URLs pass the tabId whose cookies you need",+ "returns base64 \u2014 decode it caller-side"+ ],+ "related": [+ "pup_navigate",+ "pup_evaluate"+ ], "statusVerb": null,- "summary": "Scroll the ACTUAL scrolling element (nearest ancestor whose scrollHeight>clientHeight, NOT just window \u2014 window.scrollTo no-ops on nested-scroll SPAs). Scroll by delta {dy,dx} OR to an element {toSelector|toText} (pierces shadow DOM). Returns {scrollElement, scrollTop, scrollHeight, clientHeight} so you can confirm it landed.",- "timeoutSeconds": 30+ "summary": "Fetch a URL's content via the browser (cookies/session intact).",+ "timeoutSeconds": 45 }, { "example": {- "height": 900,- "sessionId": "work",- "width": 1280+ "sessionId": "work" },- "hint": "Set the page viewport (CDP device-metrics) deterministically, independent of the OS window size \u2014 fixes a too-small innerWidth so fullPage/element shots render at a known width. width+height required.",+ "hint": "Maximize (or restore/fullscreen/minimize) a pup window via CDP \u2014 no AD desktop verb required. Does NOT foreground it.", "input": {- "deviceScaleFactor": "optional number (default 1)",- "height": "required int px", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "width": "required int px"+ "state": "optional \"maximized\"|\"normal\"|\"minimized\"|\"fullscreen\" (default maximized)" }, "longRunning": false,- "name": "pup_set_viewport",+ "name": "pup_maximize", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Set the page viewport (CDP device-metrics) deterministically, independent of the OS window size \u2014 fixes a too-small innerWidth so fullPage/element shots render at a known width. width+height required.",+ "summary": "Maximize (or restore/fullscreen/minimize) a pup window via CDP \u2014 no AD desktop verb required. Does NOT foreground it.", "timeoutSeconds": 15 }, { "example": {- "expression": "document.title",- "sessionId": "work"+ "foregroundReason": "user said \"show me the wiki page\"" },- "hint": "Runs JS in the page and returns the result \u2014 use it to read DOM state or verify selectors before acting.",+ "hint": "Bring a pup window to the FOREGROUND (user's screen). GATED (v1.8.70): REFUSED with foreground_reason_required unless you pass foregroundReason \u2014 a quote/paraphrase of what the user said that justifies it (min 10 chars; logged, recorded in lastAgentUpdate, auditable). Allowed ONLY when the user EXPLICITLY must SEE it (a demo, recording, \"show me\"). It STEALS focus \u2014 to just signal \"I updated this window\", use pup_alert_window (or the auto-flash), NOT raise. After the show-me: pup_lower_os_window to return it to the background. Alias: pup_focus_window. IMPORTANT: if you are foregrounding in order to CLICK a native dialog or a cross-origin FedCM/Google account chooser, do NOT raise-then-click yourself \u2014 use pup_assisted_click, which waits for the user to actually be on the window (alt-tab aware) and narrates via captions, so you never act while they are not ready.", "input": {- "expression": "required str \u2014 aliases accepted: expr, script, js, code",- "frame": "optional str \u2014 run inside this iframe instead of the top document",+ "foregroundReason": "REQUIRED \u2014 why the user asked to see this window", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_evaluate",+ "name": "pup_raise_os_window", "output": { "success": "bool" },- "pitfalls": [- "the expression must be serializable JSON in its return value",- "runs in the page context, not Node"- ],- "related": [- "pup_click",- "pup_fetch_url"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Evaluate JS in the page and return the result \u2014 THE verb for reading DOM state / asserting on a page (same handler as pup_eval). Add frame:\"<name|url-substring|index>\" to run inside an iframe.",- "timeoutSeconds": 30+ "summary": "Bring a pup window to the FOREGROUND (user's screen). GATED (v1.8.70): REFUSED with foreground_reason_required unless you pass foregroundReason \u2014 a quote/paraphrase of what the user said that justifies it (min 10 chars; logged, recorded in lastAgentUpdate, auditable). Allowed ONLY when the user EXPLICITLY must SEE it (a demo, recording, \"show me\"). It STEALS focus \u2014 to just signal \"I updated this window\", use pup_alert_window (or the auto-flash), NOT raise. After the show-me: pup_lower_os_window to return it to the background. Alias: pup_focus_window. IMPORTANT: if you are foregrounding in order to CLICK a native dialog or a cross-origin FedCM/Google account chooser, do NOT raise-then-click yourself \u2014 use pup_assisted_click, which waits for the user to actually be on the window (alt-tab aware) and narrates via captions, so you never act while they are not ready.",+ "timeoutSeconds": 15 }, {- "example": {- "sessionId": "work",- "url": "https://api.example.com"- },- "hint": "Fetches a URL using the tab's live cookies/session \u2014 for authed APIs the bare CLI can't reach. Returns bodyBase64.",- "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "url": "required str"- },+ "example": {},+ "hint": "",+ "input": {}, "longRunning": false,- "name": "pup_fetch_url",+ "name": "pup_focus_window", "output": { "success": "bool" },- "pitfalls": [- "for session-scoped URLs pass the tabId whose cookies you need",- "returns base64 \u2014 decode it caller-side"- ],+ "pitfalls": [], "related": [- "pup_navigate",- "pup_evaluate"+ "pup_raise_os_window" ], "statusVerb": null,- "summary": "Fetch a URL's content via the browser (cookies/session intact).",- "timeoutSeconds": 45+ "summary": "Alias of pup_raise_os_window (foregroundReason required). It is also what a pup taskbar button's relaunch command calls when the user clicks it; a human caller gets no warning toast.",+ "timeoutSeconds": 30 }, {- "example": {- "selector": "button.download",- "sessionId": "work"- },- "hint": "Download a file THROUGH THE BROWSER, exactly as the user clicking it would \u2014 real session, real cookies, real anti-bot cookies \u2014 and Chrome saves it to the Downloads folder. This is the right verb for vendor files (datasheets, KiCad/Altium libraries, STEP 3D models) that a server-side fetch/curl can never get past the site's bot wall. Pass selector to click the actual download button (authentic), or url to force-download via an in-page a[download]. Confirms completion via Chrome's download events and returns the ACTUAL saved path + byte count + file magic. For a gated file, log in on the window first (pup_login).",+ "example": {},+ "hint": "Send a pup window to the BACKGROUND (behind the user's windows) without stealing focus \u2014 still fully drivable. The default for open; use this to re-hide a window you foregrounded.", "input": {- "saveAs": "optional str \u2014 filename override",- "saveDir": "optional str \u2014 default the user's Downloads folder",- "selector": "optional str \u2014 the real download control to click",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "url": "optional str \u2014 download this URL via an in-page a[download] click"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_download",+ "name": "pup_lower_os_window", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Download a file THROUGH THE BROWSER, exactly as the user clicking it would \u2014 real session, real cookies, real anti-bot cookies \u2014 and Chrome saves it to the Downloads folder. This is the right verb for vendor files (datasheets, KiCad/Altium libraries, STEP 3D models) that a server-side fetch/curl can never get past the site's bot wall. Pass selector to click the actual download button (authentic), or url to force-download via an in-page a[download]. Confirms completion via Chrome's download events and returns the ACTUAL saved path + byte count + file magic. For a gated file, log in on the window first (pup_login).",- "timeoutSeconds": 100+ "summary": "Send a pup window to the BACKGROUND (behind the user's windows) without stealing focus \u2014 still fully drivable. The default for open; use this to re-hide a window you foregrounded.",+ "timeoutSeconds": 15 }, {- "example": {- "sessionId": "work",- "type": "mousePressed",- "x": 100,- "y": 200- },- "hint": "Low-level CDP mouse/keyboard input at coords \u2014 beats some overlays/iframes that swallow synthetic clicks.",+ "example": {},+ "hint": "Nudge the user about a pup window WITHOUT stealing focus \u2014 on Windows it flashes the taskbar button; on macOS (no taskbar, and no API to bounce another app's Dock tile) it unhides the window at its z-position and posts an AD toast whose click focuses it. Same contract on both, so call it identically; just never tell a Mac user to look for a flashing icon. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window \u2014 use this for an explicit end-of-task \"it's ready\" nudge on top of that. STRONGLY prefer this (or the auto-flash) over pup_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.", "input": { "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "type": "required str",- "x": "optional int",- "y": "optional int"+ "stop": "optional bool \u2014 CLEAR the highlight instead of flashing" }, "longRunning": false,- "name": "pup_input_dispatch",+ "name": "pup_alert_window", "output": { "success": "bool" },- "pitfalls": [- "coords are viewport pixels",- "last-resort when pup_click can't reach the element \u2014 still cannot pass Cloudflare interactive challenges"- ],- "related": [- "pup_click",- "pup_press_key"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Low-level input (mouse/keyboard) at coords \u2014 beats some overlays.",- "timeoutSeconds": 20+ "summary": "Nudge the user about a pup window WITHOUT stealing focus \u2014 on Windows it flashes the taskbar button; on macOS (no taskbar, and no API to bounce another app's Dock tile) it unhides the window at its z-position and posts an AD toast whose click focuses it. Same contract on both, so call it identically; just never tell a Mac user to look for a flashing icon. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window \u2014 use this for an explicit end-of-task \"it's ready\" nudge on top of that. STRONGLY prefer this (or the auto-flash) over pup_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.",+ "timeoutSeconds": 15 }, {- "example": {- "sessionId": "work"- },- "hint": "Starts video recording of the page for this session.",+ "example": {},+ "hint": "Paint the Windows taskbar PROGRESS BAR on your window during a LONG multi-stage task (crawling a site, login->search->download loops, batch harvesting datasheets/STEP files) so the user sees work happening at a glance. mode:\"indeterminate\" (pulsing) to start, mode:\"value\" with value:0-100 as stages complete, mode:\"off\" when done or aborted (always clear!). Optional note:\"12/40 datasheets\" shows on the button hover tooltip. YOUR judgment: a single quick page open does NOT warrant a progress bar. See the pup-progress skill.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "mode": "\"indeterminate\" | \"value\" | \"off\"",+ "note": "optional short stage label for the hover tooltip",+ "sessionId": "optional \u2014 defaults to your thread's own window",+ "value": "optional 0-100 (mode:\"value\")" }, "longRunning": false,- "name": "pup_record_start",+ "name": "pup_progress", "output": { "success": "bool" },- "pitfalls": [- "one recording per session \u2014 stop the current one before starting another"- ],- "related": [- "pup_record_stop",- "pup_record_status"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Start recording the page (video).",- "timeoutSeconds": 20+ "summary": "Paint the Windows taskbar PROGRESS BAR on your window during a LONG multi-stage task (crawling a site, login->search->download loops, batch harvesting datasheets/STEP files) so the user sees work happening at a glance. mode:\"indeterminate\" (pulsing) to start, mode:\"value\" with value:0-100 as stages complete, mode:\"off\" when done or aborted (always clear!). Optional note:\"12/40 datasheets\" shows on the button hover tooltip. YOUR judgment: a single quick page open does NOT warrant a progress bar. See the pup-progress skill.",+ "timeoutSeconds": 8 }, { "example": {- "sessionId": "work"+ "taskbarGrouping": "grouped" },- "hint": "Stops recording and returns the saved file path.",+ "hint": "Get/set user-facing pup preferences (persisted across bridge updates). taskbarGrouping: \"split\" (default \u2014 one taskbar button PER WINDOW, each badged with its own tab count; best for telling AI threads apart) | \"grouped\" (all pup windows stack under ONE \"Adom Pup\" button, badge shows window count; tidy taskbar). Applies to live windows immediately. Ask the USER which they prefer before changing it \u2014 this is their taskbar. WINDOWS ONLY: macOS has no taskbar, so the setting is stored but INERT there; the reply carries platform + taskbarSupported so branch on those, not on this text.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "autoLogin": "optional \"on\"|\"off\" \u2014 auto-fill a vault-matched login form on page load (fill only, never submits), on by default",+ "overlaySize": "optional \"50\"|\"100\" \u2014 how big the favicon badge is drawn on the taskbar button. \"50\" (the DEFAULT) draws it at half size so it stays clear of the thread tile's bottom line of text; \"100\" is the older, larger badge",+ "reportIssues": "optional \"on\"|\"off\" \u2014 pup's bug-report invitation (_reportIssues), on by default",+ "taskbarActivity": "optional \"progress\"|\"flash\"|\"off\" \u2014 how pup signals it is working on a window, on that window's taskbar button: a determinate progress bar sized to the operation's running-average time (default), the old orange flash, or nothing. Replaces the former openFlash/autoFlash/agentActivity toggles.",+ "taskbarGrouping": "optional \"split\"|\"grouped\"" }, "longRunning": false,- "name": "pup_record_stop",+ "name": "pup_configure", "output": { "success": "bool" },- "pitfalls": [- "no-op if nothing is recording for this session"- ],- "related": [- "pup_record_start",- "pup_record_list"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Stop recording; returns the saved file.",- "timeoutSeconds": 30+ "summary": "Get/set user-facing pup preferences (persisted across bridge updates). taskbarGrouping: \"split\" (default \u2014 one taskbar button PER WINDOW, each badged with its own tab count; best for telling AI threads apart) | \"grouped\" (all pup windows stack under ONE \"Adom Pup\" button, badge shows window count; tidy taskbar). Applies to live windows immediately. Ask the USER which they prefer before changing it \u2014 this is their taskbar. WINDOWS ONLY: macOS has no taskbar, so the setting is stored but INERT there; the reply carries platform + taskbarSupported so branch on those, not on this text.",+ "timeoutSeconds": 15 }, {- "example": {- "sessionId": "work"- },- "hint": "Whether a recording is active for the session.",+ "example": {},+ "hint": "Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). \"cft\" fetches Chrome for Testing then pins it. With {browser:\"chrome\", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it \u2014 SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native \"click YES on the UAC\" notification and, if it expires, re-notifies with a one-tap \"Approve now\" (all autonomous \u2014 no AI turn). Poll pup_readiness.chromeStablePhase: installing \u2192 awaiting_uac \u2192 ready. Persists across restarts; launch still spawn-verifies + falls back.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "browser": "\"chrome\" | \"edge\" | \"cft\" | \"auto\"",+ "elevate": "optional \"auto\"(default)|true|false \u2014 auto: quiet install, prompt only if it needs admin; false: never prompt; true: go straight to the prompt",+ "executablePath": "optional \u2014 pin an exact browser binary",+ "install": "optional bool \u2014 with browser:\"chrome\", install real Chrome if missing" }, "longRunning": false,- "name": "pup_record_status",+ "name": "pup_use", "output": { "success": "bool" }, "pitfalls": [],- "related": [- "pup_record_start",- "pup_record_stop"- ],+ "related": [], "statusVerb": null,- "summary": "Recording status for a session.",- "timeoutSeconds": 10+ "summary": "Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). \"cft\" fetches Chrome for Testing then pins it. With {browser:\"chrome\", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it \u2014 SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native \"click YES on the UAC\" notification and, if it expires, re-notifies with a one-tap \"Approve now\" (all autonomous \u2014 no AI turn). Poll pup_readiness.chromeStablePhase: installing \u2192 awaiting_uac \u2192 ready. Persists across restarts; launch still spawn-verifies + falls back.",+ "timeoutSeconds": 240 }, {- "example": {},- "hint": "Lists saved page recordings.",- "input": {},- "longRunning": false,- "name": "pup_record_list",- "output": {- "success": "bool"+ "name": "pup_clear_cookies",+ "summary": "Delete cookies in the window's profile for one site (domain or origin), optionally only the named cookies.",+ "input": {+ "domain": "str, e.g. example.com (or origin / origins)",+ "origin": "optional str",+ "origins": "optional [str]",+ "names": "optional [str]: only these cookie names" },- "pitfalls": [],+ "example": {+ "domain": "example.com"+ },+ "hint": "Clears the SHARED profile's cookies for that site: every pup window on the profile is signed out of it. Use it to test a logged-out flow, not as a reset.", "related": [- "pup_record_start"+ "pup_clear_site_data",+ "pup_credentials" ],- "statusVerb": null,- "summary": "List recordings.",- "timeoutSeconds": 10+ "timeoutSeconds": 30,+ "output": {},+ "pitfalls": [],+ "longRunning": false,+ "statusVerb": "pup_readiness" }, {- "example": {},- "hint": "Open pup's Chrome screen-share recorder HUD (WebM; consent = the visible HUD). Canonical name (pup-bridge#94).",+ "name": "pup_clear_site_data",+ "summary": "Clear storage for a site (cookies, localStorage, IndexedDB, cache) in the window profile.", "input": {- "reason": "str (required)"+ "origin": "str: https://example.com (or origins)",+ "origins": "optional [str]",+ "all": "optional bool: every origin in the profile",+ "everyone": "optional bool: required with all:true because it signs every pup window out of everything" },- "longRunning": false,- "name": "pup_recorder_open",- "output": {- "success": "bool"+ "example": {+ "origin": "https://example.com" },+ "hint": "all:true wipes the whole shared profile and is refused without everyone:true.",+ "related": [+ "pup_clear_cookies"+ ],+ "timeoutSeconds": 30,+ "output": {}, "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Open pup's Chrome screen-share recorder HUD (WebM; consent = the visible HUD). Canonical name (pup-bridge#94).",- "timeoutSeconds": 20+ "longRunning": false,+ "statusVerb": "pup_readiness" }, {+ "name": "pup_quit_idle_browsers",+ "summary": "Quit every pup browser that has no windows (so the next open loads freshly staged extensions).",+ "input": {}, "example": {},- "hint": "Start a whole-screen recording through Chrome screen-share (WebM VP9, system audio + mic). Canonical name; for a background no-browser MP4 of one window use ab-core desktop_record_window_start.",+ "hint": "A browser with live windows is never touched.",+ "related": [+ "pup_restart_browser"+ ],+ "timeoutSeconds": 30,+ "output": {},+ "pitfalls": [],+ "longRunning": false,+ "statusVerb": "pup_readiness"+ },+ {+ "example": {},+ "hint": "Recover orphaned sessions: reconnect dropped CDP sockets + rebuild sessions from window titles. Pass adoptOrphans:true to also adopt untagged tabs.", "input": {- "audio": "bool",- "confirmDesktopNotTabRecording": "true",- "fps": "int",- "maxDurationMs": "int",- "reason": "str (required)"+ "adoptOrphans": "optional bool" }, "longRunning": false,- "name": "pup_screen_record_start",+ "name": "pup_rescan", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Start a whole-screen recording through Chrome screen-share (WebM VP9, system audio + mic). Canonical name; for a background no-browser MP4 of one window use ab-core desktop_record_window_start.",- "timeoutSeconds": 20+ "summary": "Recover orphaned sessions: reconnect dropped CDP sockets + rebuild sessions from window titles. Pass adoptOrphans:true to also adopt untagged tabs.",+ "timeoutSeconds": 30 }, {+ "name": "pup_restart_browser",+ "summary": "Restart pup's browser for a profile; every window on it comes back at its page, in the background.",+ "input": {+ "profile": "optional str (default adom-you)"+ }, "example": {},- "hint": "Stop the Chrome screen recording; returns the .webm path.",- "input": {},+ "hint": "Use it when a lane is wedged. Windows are relaunched from their saved state; in-page drafts are lost.",+ "related": [+ "pup_quit_idle_browsers",+ "pup_readiness"+ ],+ "timeoutSeconds": 90,+ "output": {},+ "pitfalls": [], "longRunning": false,- "name": "pup_screen_record_stop",+ "statusVerb": "pup_readiness"+ },+ {+ "example": {+ "avatarB64": "<base64 png>",+ "name": "John Lauer"+ },+ "hint": "Set the Adom identity shown in the floating owner chip on every pup window (avatar + name). Chrome's own profile chip cannot take a custom local image (Google sign-in only), so the user's Adom avatar lives here instead. Pass a pre-circle-cropped PNG as base64. Applies to live windows immediately + persists.",+ "input": {+ "avatarB64": "optional str \u2014 base64 PNG (ideally ~48px, circle-cropped); the leading data: prefix is stripped if present",+ "clear": "optional bool \u2014 remove the stored avatar + name",+ "enabled": "optional bool \u2014 turn the whole owner chip on/off",+ "name": "optional str \u2014 display name shown in the chip tooltip"+ },+ "longRunning": false,+ "name": "pup_set_identity", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Stop the Chrome screen recording; returns the .webm path.",- "timeoutSeconds": 30+ "summary": "Set the Adom identity shown in the floating owner chip on every pup window (avatar + name). Chrome's own profile chip cannot take a custom local image (Google sign-in only), so the user's Adom avatar lives here instead. Pass a pre-circle-cropped PNG as base64. Applies to live windows immediately + persists.",+ "timeoutSeconds": 15 }, {- "example": {},- "hint": "Status of the Chrome screen recorder + any active clip.",+ "name": "pup_audit_icons",+ "summary": "Read back each pup window's taskbar identity from Windows and compare it with what pup stamped.", "input": {},- "longRunning": false,- "name": "pup_screen_record_status",- "output": {- "success": "bool"- },+ "example": {},+ "hint": "Detector only: nothing is re-stamped.",+ "related": [+ "pup_set_identity",+ "pup_sweep_identities"+ ],+ "timeoutSeconds": 30,+ "output": {}, "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Status of the Chrome screen recorder + any active clip.",- "timeoutSeconds": 10+ "longRunning": false,+ "statusVerb": "pup_readiness" }, { "example": {},- "hint": "List completed Chrome screen recordings on disk.",+ "hint": "Open the LIVE pup management dashboard in its OWN window (its own profile, so its own taskbar icon). Shows every window pup is driving as taskbar button -> window(s) -> tabs with each tab favicon, owning AI thread, foreground/background + the reason given to foreground it, highFps lease, uptime, and WHY each overlay icon was chosen. Live over SSE. Reuses the one window on repeat calls; backgrounded by default like any pup window.", "input": {}, "longRunning": false,- "name": "pup_screen_record_list",+ "name": "pup_dashboard", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "List completed Chrome screen recordings on disk.",- "timeoutSeconds": 10+ "summary": "Open the LIVE pup management dashboard in its OWN window (its own profile, so its own taskbar icon). Shows every window pup is driving as taskbar button -> window(s) -> tabs with each tab favicon, owning AI thread, foreground/background + the reason given to foreground it, highFps lease, uptime, and WHY each overlay icon was chosen. Live over SSE. Reuses the one window on repeat calls; backgrounded by default like any pup window.",+ "timeoutSeconds": 30 }, { "example": {},- "hint": "Close the Chrome screen-recorder HUD (auto-stops a clip).",+ "hint": "List saved logins (host + username only; passwords are in the OS keychain and NEVER returned). pup captures these automatically as you sign into sites and auto-fills them in the background on return visits.", "input": {}, "longRunning": false,- "name": "pup_recorder_close",+ "name": "pup_credentials", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Close the Chrome screen-recorder HUD (auto-stops a clip).",- "timeoutSeconds": 15+ "summary": "List saved logins (host + username only; passwords are in the OS keychain and NEVER returned). pup captures these automatically as you sign into sites and auto-fills them in the background on return visits.",+ "timeoutSeconds": 10 }, { "example": {},- "hint": "Cold-start readiness probe: {ready, chromeForTestingInstalled, installing, installProgressPct}. On a fresh PC, poll this until ready:true before pup_open_window \u2014 the bridge auto-installs Chrome for Testing on its own.",+ "hint": "", "input": {}, "longRunning": false,- "name": "pup_readiness",+ "name": "credential_list", "output": { "success": "bool" },- "pitfalls": [- "ready:false with installing:true just means Chrome for Testing is still downloading \u2014 wait + poll, do NOT treat it as an error",- "installPhase:\"failed\" \u2192 surface lastError to the user (usually network/proxy blocking the download, or low disk)"- ],+ "pitfalls": [], "related": [- "pup_prewarm",- "pup_open_window"+ "pup_credentials" ], "statusVerb": null,- "summary": "Readiness + which browser pup will drive (installed Chrome/Edge, or cached Chrome for Testing) + the full candidate list and current default. READ-ONLY; never downloads.",- "timeoutSeconds": 10+ "summary": "Alias of pup_credentials: list saved logins (host and username only, never passwords).",+ "timeoutSeconds": 30 }, {- "example": {},- "hint": "Installs Chrome for Testing (the browser pup drives) WITHOUT opening a window \u2014 the warm-before-first-use path. Returns fast; poll pup_readiness for completion.",+ "name": "credential_set",+ "summary": "Store a login in pup's vault for a host.", "input": {- "setDefault": "optional bool (default true) \u2014 pin CfT as the default",- "wait": "optional bool \u2014 block until installed"+ "host": "str",+ "username": "str",+ "password": "str (never echoed or logged)" },- "longRunning": false,- "name": "pup_prewarm",- "output": {- "success": "bool"+ "example": {+ "host": "example.com",+ "username": "[email protected]",+ "password": "<secret>" },- "pitfalls": [- "the ~150 MB download needs network access to storage.googleapis.com",- "does not open a window \u2014 call pup_open_window once readiness reports ready:true"- ],+ "hint": "Matched across the site's whole registrable domain on return visits.", "related": [- "pup_readiness",- "pup_open_window"+ "pup_credentials",+ "credential_delete",+ "pup_login" ],- "statusVerb": null,- "summary": "Install Chrome for Testing WITHOUT opening a window, and pin it as the default. Use when you specifically want CfT rather than the machine's installed Chrome/Edge.",- "timeoutSeconds": 240+ "timeoutSeconds": 30,+ "output": {},+ "pitfalls": [],+ "longRunning": false,+ "statusVerb": "pup_readiness" }, {- "example": {},- "hint": "Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). \"cft\" fetches Chrome for Testing then pins it. With {browser:\"chrome\", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it \u2014 SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native \"click YES on the UAC\" notification and, if it expires, re-notifies with a one-tap \"Approve now\" (all autonomous \u2014 no AI turn). Poll pup_readiness.chromeStablePhase: installing \u2192 awaiting_uac \u2192 ready. Persists across restarts; launch still spawn-verifies + falls back.",+ "name": "credential_delete",+ "summary": "Remove a saved login from pup's vault.", "input": {- "browser": "\"chrome\" | \"edge\" | \"cft\" | \"auto\"",- "elevate": "optional \"auto\"(default)|true|false \u2014 auto: quiet install, prompt only if it needs admin; false: never prompt; true: go straight to the prompt",- "executablePath": "optional \u2014 pin an exact browser binary",- "install": "optional bool \u2014 with browser:\"chrome\", install real Chrome if missing"+ "host": "str",+ "username": "optional str: only this account" },- "longRunning": false,- "name": "pup_use",- "output": {- "success": "bool"+ "example": {+ "host": "example.com" },+ "hint": "",+ "related": [+ "pup_credentials",+ "credential_set"+ ],+ "timeoutSeconds": 30,+ "output": {}, "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). \"cft\" fetches Chrome for Testing then pins it. With {browser:\"chrome\", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it \u2014 SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native \"click YES on the UAC\" notification and, if it expires, re-notifies with a one-tap \"Approve now\" (all autonomous \u2014 no AI turn). Poll pup_readiness.chromeStablePhase: installing \u2192 awaiting_uac \u2192 ready. Persists across restarts; launch still spawn-verifies + falls back.",- "timeoutSeconds": 240+ "longRunning": false,+ "statusVerb": "pup_readiness" }, {- "example": {},- "hint": "Send a pup window to the BACKGROUND (behind the user's windows) without stealing focus \u2014 still fully drivable. The default for open; use this to re-hide a window you foregrounded.",+ "example": {+ "sessionId": "work",+ "submit": true,+ "submitReason": "confirmed the tab is on the real vendor domain I intended to sign into"+ },+ "hint": "Fill (and optionally submit) an HTML login form in the CURRENT tab, in the BACKGROUND \u2014 no Chrome autofill dropdown, no foregrounding, React/Vue-safe. Creds from the keychain vault (matched on the tab URL; captured automatically as you log in, or stored via credential_set / save:true) or passed explicitly. Passwords are NEVER echoed back. Vault-matched pages AUTO-FILL on load (_autoLogin on the response). SUBMIT IS REASON-GATED: submit:true requires submitReason AND makes you confirm the live domain (the refusal regurgitates the exact host so a lookalike/phishing site is caught before the secret is sent). pup_configure {autoLogin:\"off\"} disables auto-fill; {captureLogins:\"off\"} stops auto-capture.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "host": "optional str \u2014 override the vault host key when saving",+ "password": "optional str \u2014 with username, fills these instead of the vault",+ "realKeystrokes": "optional bool \u2014 type with REAL key events instead of a background .value-set. Needed for React-controlled MASKED fields (Gusto/Keycloak SSO) that ignore .value and would submit an empty password. It briefly FOREGROUNDS the window (real keystrokes only register on-screen). Use when a normal fill returned filledPassword:false / reactMaskedFieldSuspected, or when the login-recipe for the site says so.",+ "save": "optional bool \u2014 with explicit creds, store them (OS keychain) so next time is automatic",+ "sessionId": "optional str \u2014 omit to target the window your own thread owns",+ "submit": "optional bool \u2014 submit after filling; REQUIRES submitReason",+ "submitReason": "required WITH submit:true \u2014 why you are confident the CURRENT domain is the correct, intended site to send these credentials to (min 10 chars; the refusal shows you the live host to confirm; logged, auditable)",+ "username": "optional str" }, "longRunning": false,- "name": "pup_lower_os_window",+ "name": "pup_login", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Send a pup window to the BACKGROUND (behind the user's windows) without stealing focus \u2014 still fully drivable. The default for open; use this to re-hide a window you foregrounded.",- "timeoutSeconds": 15+ "summary": "Fill (and optionally submit) an HTML login form in the CURRENT tab, in the BACKGROUND \u2014 no Chrome autofill dropdown, no foregrounding, React/Vue-safe. Creds from the keychain vault (matched on the tab URL; captured automatically as you log in, or stored via credential_set / save:true) or passed explicitly. Passwords are NEVER echoed back. Vault-matched pages AUTO-FILL on load (_autoLogin on the response). SUBMIT IS REASON-GATED: submit:true requires submitReason AND makes you confirm the live domain (the refusal regurgitates the exact host so a lookalike/phishing site is caught before the secret is sent). pup_configure {autoLogin:\"off\"} disables auto-fill; {captureLogins:\"off\"} stops auto-capture.",+ "timeoutSeconds": 20 }, {- "example": {- "foregroundReason": "user said \"show me the wiki page\""- },- "hint": "Bring a pup window to the FOREGROUND (user's screen). GATED (v1.8.70): REFUSED with foreground_reason_required unless you pass foregroundReason \u2014 a quote/paraphrase of what the user said that justifies it (min 10 chars; logged, recorded in lastAgentUpdate, auditable). Allowed ONLY when the user EXPLICITLY must SEE it (a demo, recording, \"show me\"). It STEALS focus \u2014 to just signal \"I updated this window\", use pup_alert_window (or the auto-flash), NOT raise. After the show-me: pup_lower_os_window to return it to the background. Alias: pup_focus_window. IMPORTANT: if you are foregrounding in order to CLICK a native dialog or a cross-origin FedCM/Google account chooser, do NOT raise-then-click yourself \u2014 use pup_assisted_click, which waits for the user to actually be on the window (alt-tab aware) and narrates via captions, so you never act while they are not ready.",+ "example": {},+ "hint": "Each account comes back with kind (work or personal, from its domain), the browser profile labels the user gave it (\"John Personal\"), whether pup is already signed in, whether pup holds its password, and likelyStale for an account found only in pup's vault. pup never chooses between accounts: pick from the task, the user's work and personal skills, or ask the user.", "input": {- "foregroundReason": "REQUIRED \u2014 why the user asked to see this window",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "refresh": "optional bool (redo the whole search now)" }, "longRunning": false,- "name": "pup_raise_os_window",+ "name": "pup_google_accounts", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "An account only in pup's vault and signed into no browser here is usually years old; Google will demand a second step for it."+ ],+ "related": [+ "pup_google_signin",+ "pup_credentials",+ "pup_import_browser_logins"+ ], "statusVerb": null,- "summary": "Bring a pup window to the FOREGROUND (user's screen). GATED (v1.8.70): REFUSED with foreground_reason_required unless you pass foregroundReason \u2014 a quote/paraphrase of what the user said that justifies it (min 10 chars; logged, recorded in lastAgentUpdate, auditable). Allowed ONLY when the user EXPLICITLY must SEE it (a demo, recording, \"show me\"). It STEALS focus \u2014 to just signal \"I updated this window\", use pup_alert_window (or the auto-flash), NOT raise. After the show-me: pup_lower_os_window to return it to the background. Alias: pup_focus_window. IMPORTANT: if you are foregrounding in order to CLICK a native dialog or a cross-origin FedCM/Google account chooser, do NOT raise-then-click yourself \u2014 use pup_assisted_click, which waits for the user to actually be on the window (alt-tab aware) and narrates via captions, so you never act while they are not ready.",- "timeoutSeconds": 15+ "summary": "List the user's Google accounts: every Chrome/Edge profile, nb's browser profiles, pup's vault, and which ones pup's own profile is already signed into. Cached a day.",+ "timeoutSeconds": 10 }, { "example": {- "sessionId": "work",- "submit": true,- "submitReason": "confirmed the tab is on the real vendor domain I intended to sign into"+ "account": "[email protected]",+ "submitReason": "the task is the work calendar, so the work account" },- "hint": "Fill (and optionally submit) an HTML login form in the CURRENT tab, in the BACKGROUND \u2014 no Chrome autofill dropdown, no foregrounding, React/Vue-safe. Creds from the keychain vault (matched on the tab URL; captured automatically as you log in, or stored via credential_set / save:true) or passed explicitly. Passwords are NEVER echoed back. Vault-matched pages AUTO-FILL on load (_autoLogin on the response). SUBMIT IS REASON-GATED: submit:true requires submitReason AND makes you confirm the live domain (the refusal regurgitates the exact host so a lookalike/phishing site is caught before the secret is sent). pup_configure {autoLogin:\"off\"} disables auto-fill; {captureLogins:\"off\"} stops auto-capture.",+ "hint": "Already signed into pup: no password is sent, the window just opens the account. Otherwise pup fills the email and the password saved for exactly that account and submits. If Google asks for a second step the answer is google_verification_required with the options or the code field, and the exact call to finish it; a text code can be read by a thread with the user's standing permission to read their texts. google_password_missing names where the account is signed in and how to import its password (pup_import_browser_logins {hostFilter:\"google.com\"}). The account is confirmed by name at the end: Gmail silently opens another signed-in account when the requested one is not signed in.", "input": {- "host": "optional str \u2014 override the vault host key when saving",- "password": "optional str \u2014 with username, fills these instead of the vault",- "realKeystrokes": "optional bool \u2014 type with REAL key events instead of a background .value-set. Needed for React-controlled MASKED fields (Gusto/Keycloak SSO) that ignore .value and would submit an empty password. It briefly FOREGROUNDS the window (real keystrokes only register on-screen). Use when a normal fill returned filledPassword:false / reactMaskedFieldSuspected, or when the login-recipe for the site says so.",- "save": "optional bool \u2014 with explicit creds, store them (OS keychain) so next time is automatic",- "sessionId": "optional str \u2014 omit to target the window your own thread owns",- "submit": "optional bool \u2014 submit after filling; REQUIRES submitReason",- "submitReason": "required WITH submit:true \u2014 why you are confident the CURRENT domain is the correct, intended site to send these credentials to (min 10 chars; the refusal shows you the live host to confirm; logged, auditable)",- "username": "optional str"+ "account": "optional str: an email, or \"work\" / \"personal\" when exactly one account of that kind exists. Omit and pup lists the accounts and asks (google_account_choice_required).",+ "submitReason": "str, 10+ chars: required before pup sends a saved password (why this account, for what task)",+ "continue": "optional str: where to land after sign-in (default: that account's Gmail)",+ "window": "optional str: the pup window to use (a new tab in it); default the calling thread's window",+ "code": "optional str: finish a verification step with the code Google sent (with window)",+ "challenge": "optional str: pick a verification option by its words, e.g. \"text message\" (with window)",+ "refresh": "optional bool: redo the account search first" }, "longRunning": false,- "name": "pup_login",+ "name": "pup_google_signin", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "Do not restart a sign-in that stopped on a verification step: that sends the password again and a new code. Finish it with {window, code} or {window, challenge}.",+ "Never ask the user to type their password to you: import it or let them sign in once in the pup window."+ ],+ "related": [+ "pup_google_accounts",+ "pup_login",+ "pup_import_browser_logins"+ ], "statusVerb": null,- "summary": "Fill (and optionally submit) an HTML login form in the CURRENT tab, in the BACKGROUND \u2014 no Chrome autofill dropdown, no foregrounding, React/Vue-safe. Creds from the keychain vault (matched on the tab URL; captured automatically as you log in, or stored via credential_set / save:true) or passed explicitly. Passwords are NEVER echoed back. Vault-matched pages AUTO-FILL on load (_autoLogin on the response). SUBMIT IS REASON-GATED: submit:true requires submitReason AND makes you confirm the live domain (the refusal regurgitates the exact host so a lookalike/phishing site is caught before the secret is sent). pup_configure {autoLogin:\"off\"} disables auto-fill; {captureLogins:\"off\"} stops auto-capture.",+ "summary": "Sign the user into Google as a named account, hands-free: account search, email step, saved password, and the verification step handed back with exact next calls.", "timeoutSeconds": 20 },- {- "example": {},- "hint": "List saved logins (host + username only; passwords are in the OS keychain and NEVER returned). pup captures these automatically as you sign into sites and auto-fills them in the background on return visits.",- "input": {},- "longRunning": false,- "name": "pup_credentials",- "output": {- "success": "bool"- },- "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "List saved logins (host + username only; passwords are in the OS keychain and NEVER returned). pup captures these automatically as you sign into sites and auto-fills them in the background on return visits.",- "timeoutSeconds": 10- }, { "example": { "host": "github.com",@@ -1304,244 +1500,291 @@ }, { "example": {- "hostFilter": [- "snapeda",- "ultralibrarian",- "digikey"- ]+ "sessionId": "wiki-bmv080",+ "view": "authed" },- "hint": "Seed pup's vault from the user's OWN saved Chrome/Edge/Brave passwords, so pup can sign in as them on sites they already use (vendor CAD portals, etc). Runs the SYSTEM-elevated decrypt LOCALLY \u2014 the user approves ONE consent dialog + ONE Windows UAC prompt on the desktop (a cloud/relay caller cannot do this; only pup, a local bridge, can ask). Merges the identity-tagged rows into the keychain vault and DROPS the plaintext. Returns COUNTS ONLY. hostFilter narrows the import to matching hosts (surgical, e.g. [\"snapeda\",\"digikey\"]); omit to import everything saved. Pair with pup_import_scan (read-only preview of how many logins exist).",+ "hint": "Flip a WIKI pup window between the logged-in and public view IN PLACE: every wiki tab reloads with the server-side ?view=public param appended (public) or stripped (authed) \u2014 adom/wiki#126 renders the request exactly as a signed-out visitor while the session stays untouched, so there is no relaunch, no cookie wipe, no profile switch (the legacy jar relaunch survives only as a fallback for servers that ignore the param). This is the callback for the per-window taskbar jump-list task (WINDOWS ONLY) \u2014 a USER-initiated action. AI threads should NOT call this; pass wikiView:\"authed\"|\"public\" on pup_open_window instead.", "input": {- "browsers": "optional str[] subset of [\"chrome\",\"edge\",\"brave\"] (default: all found)",- "hostFilter": "optional str or str[] \u2014 only import logins whose host contains one of these (surgical import)"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "view": "required \"authed\"|\"public\"|\"toggle\" \u2014 toggle flips whatever the window currently shows (the Alt+Shift+V hotkey form)" }, "longRunning": false,- "name": "pup_import_browser_logins",+ "name": "pup_wiki_set_view", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Seed pup's vault from the user's OWN saved Chrome/Edge/Brave passwords, so pup can sign in as them on sites they already use (vendor CAD portals, etc). Runs the SYSTEM-elevated decrypt LOCALLY \u2014 the user approves ONE consent dialog + ONE Windows UAC prompt on the desktop (a cloud/relay caller cannot do this; only pup, a local bridge, can ask). Merges the identity-tagged rows into the keychain vault and DROPS the plaintext. Returns COUNTS ONLY. hostFilter narrows the import to matching hosts (surgical, e.g. [\"snapeda\",\"digikey\"]); omit to import everything saved. Pair with pup_import_scan (read-only preview of how many logins exist).",- "timeoutSeconds": 190+ "summary": "Flip a WIKI pup window between the logged-in and public view IN PLACE: every wiki tab reloads with the server-side ?view=public param appended (public) or stripped (authed) \u2014 adom/wiki#126 renders the request exactly as a signed-out visitor while the session stays untouched, so there is no relaunch, no cookie wipe, no profile switch (the legacy jar relaunch survives only as a fallback for servers that ignore the param). This is the callback for the per-window taskbar jump-list task (WINDOWS ONLY) \u2014 a USER-initiated action. AI threads should NOT call this; pass wikiView:\"authed\"|\"public\" on pup_open_window instead.",+ "timeoutSeconds": 45 }, {- "example": {- "dryRun": true- },- "hint": "Delete LEAKED isolated profile directories \u2014 the iso-* throwaway jars minted by isolated:true opens (and by wikiView:public before 2.0.132, when the public view moved to the server-side ?view=public param) whose window is long gone (they used to pile up, 100s per machine). Only removes an iso-* dir that no live session or session file owns, that no Chrome is holding, and that is >15 min old; durable jars (adom-you, named profiles) are NEVER touched. Runs automatically every 15 min too. dryRun:true previews the list without deleting.",+ "example": {},+ "hint": "Start the HUMAN annotation overlay on a window's active tab (pen/arrow/box/text toolbar injected in-page; also on every window's taskbar jump list as \"Annotate this page\"). Two capture modes the user can also flip in the toolbar: \"auto\" (default) captures the instant the mouse lifts \u2014 the one-red-circle case; \"multi\" accumulates shapes (color swatches: red/blue/yellow one click, more in a flyout) and captures ONCE on Done. Done captures a PNG AND leaves the drawing in the DOM, so the owning thread's next pup_screenshot shows exactly what the user marked \u2014 check session.lastAnnotation via pup_list_windows. action:\"cancel\" removes the overlay.", "input": {- "dryRun": "optional bool \u2014 preview which iso-* profiles would be removed, delete nothing"+ "action": "optional \"cancel\"",+ "mode": "optional \"auto\"|\"multi\"",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_reap_profiles",+ "name": "pup_annotate", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Delete LEAKED isolated profile directories \u2014 the iso-* throwaway jars minted by isolated:true opens (and by wikiView:public before 2.0.132, when the public view moved to the server-side ?view=public param) whose window is long gone (they used to pile up, 100s per machine). Only removes an iso-* dir that no live session or session file owns, that no Chrome is holding, and that is >15 min old; durable jars (adom-you, named profiles) are NEVER touched. Runs automatically every 15 min too. dryRun:true previews the list without deleting.",- "timeoutSeconds": 20+ "summary": "Start the HUMAN annotation overlay on a window's active tab (pen/arrow/box/text toolbar injected in-page; also on every window's taskbar jump list as \"Annotate this page\"). Two capture modes the user can also flip in the toolbar: \"auto\" (default) captures the instant the mouse lifts \u2014 the one-red-circle case; \"multi\" accumulates shapes (color swatches: red/blue/yellow one click, more in a flyout) and captures ONCE on Done. Done captures a PNG AND leaves the drawing in the DOM, so the owning thread's next pup_screenshot shows exactly what the user marked \u2014 check session.lastAnnotation via pup_list_windows. action:\"cancel\" removes the overlay.",+ "timeoutSeconds": 15 }, { "example": {},- "hint": "MOVE this pup window's page into the user's Hydrogen workspace: the active tab's URL is handed to Hydrogen (ah) on this machine via its Browser Picker (webview by default, honoring the user's per-domain choice), then the pup window closes \u2014 its owner initiated the move. keepWindow:true keeps the pup window open too. Needs Hydrogen running locally (hydrogen_unreachable otherwise; the window is left untouched on failure). Cloud Hydrogen (hc) is not yet supported.",+ "hint": "TEST SEAM: simulate a user click on the floaty pencil (fires the same host click handler a mouse does; the sweep then starts annotate within ~4s). Built for the recurring floaty ralph \u2014 assert the annotate overlay appears after calling this.", "input": {- "keepWindow": "optional bool \u2014 hand the URL over but keep the pup window open",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "tabId": "optional tab" }, "longRunning": false,- "name": "pup_move_to_workspace",+ "name": "pup_floaty_click", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "MOVE this pup window's page into the user's Hydrogen workspace: the active tab's URL is handed to Hydrogen (ah) on this machine via its Browser Picker (webview by default, honoring the user's per-domain choice), then the pup window closes \u2014 its owner initiated the move. keepWindow:true keeps the pup window open too. Needs Hydrogen running locally (hydrogen_unreachable otherwise; the window is left untouched on failure). Cloud Hydrogen (hc) is not yet supported.",- "timeoutSeconds": 25+ "summary": "TEST SEAM: simulate a user click on the floaty pencil (fires the same host click handler a mouse does; the sweep then starts annotate within ~4s). Built for the recurring floaty ralph \u2014 assert the annotate overlay appears after calling this.",+ "timeoutSeconds": 10 }, { "example": {- "dryRun": true+ "sessionId": "work" },- "hint": "DESTRUCTIVE \u2014 cleanly remove pup's WHOLE footprint from this machine: the HKCU AppUserModelId Adom.Pup registry keys, all staged extensions, ALL pup profiles + sessions, the OS-keychain credential vault, pup-settings.json, the badge-debug temp dir, and the jump-list launcher. ALWAYS preview first with dryRun:true (removes nothing, returns the plan). To actually remove, pass confirm:\"uninstall\". keepCredentials:true preserves the saved-login vault; keepProfiles:true preserves profiles/sessions. Chrome-for-Testing + firewall rules are SHARED and left alone unless removeChrome:true / removeFirewallRules:true. Closes every window + quits every browser first to release profile locks. (The bridge itself is managed by Adom Bridge \u2014 remove pup from Bridge to stop it re-seeding.)",+ "hint": "Starts video recording of the page for this session.", "input": {- "confirm": "required str \"uninstall\" to actually remove",- "dryRun": "optional bool \u2014 preview the removal plan without removing anything (do this first)",- "keepCredentials": "optional bool \u2014 keep the saved-login vault",- "keepProfiles": "optional bool \u2014 keep pup profiles + sessions",- "removeChrome": "optional bool \u2014 also delete the Chrome-for-Testing install (shared; default keep)",- "removeFirewallRules": "optional bool \u2014 also remove the CfT firewall rules (needs admin)"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_uninstall",+ "name": "pup_record_start", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "one recording per session \u2014 stop the current one before starting another"+ ],+ "related": [+ "pup_record_stop",+ "pup_record_status"+ ], "statusVerb": null,- "summary": "DESTRUCTIVE \u2014 cleanly remove pup's WHOLE footprint from this machine: the HKCU AppUserModelId Adom.Pup registry keys, all staged extensions, ALL pup profiles + sessions, the OS-keychain credential vault, pup-settings.json, the badge-debug temp dir, and the jump-list launcher. ALWAYS preview first with dryRun:true (removes nothing, returns the plan). To actually remove, pass confirm:\"uninstall\". keepCredentials:true preserves the saved-login vault; keepProfiles:true preserves profiles/sessions. Chrome-for-Testing + firewall rules are SHARED and left alone unless removeChrome:true / removeFirewallRules:true. Closes every window + quits every browser first to release profile locks. (The bridge itself is managed by Adom Bridge \u2014 remove pup from Bridge to stop it re-seeding.)",- "timeoutSeconds": 60+ "summary": "Start recording the page (video).",+ "timeoutSeconds": 20 }, {- "example": {},- "hint": "Nudge the user about a pup window WITHOUT stealing focus \u2014 on Windows it flashes the taskbar button; on macOS (no taskbar, and no API to bounce another app's Dock tile) it unhides the window at its z-position and posts an AD toast whose click focuses it. Same contract on both, so call it identically; just never tell a Mac user to look for a flashing icon. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window \u2014 use this for an explicit end-of-task \"it's ready\" nudge on top of that. STRONGLY prefer this (or the auto-flash) over pup_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.",+ "example": {+ "sessionId": "work"+ },+ "hint": "Stops recording and returns the saved file path.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "stop": "optional bool \u2014 CLEAR the highlight instead of flashing"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_alert_window",+ "name": "pup_record_stop", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "no-op if nothing is recording for this session"+ ],+ "related": [+ "pup_record_start",+ "pup_record_list"+ ], "statusVerb": null,- "summary": "Nudge the user about a pup window WITHOUT stealing focus \u2014 on Windows it flashes the taskbar button; on macOS (no taskbar, and no API to bounce another app's Dock tile) it unhides the window at its z-position and posts an AD toast whose click focuses it. Same contract on both, so call it identically; just never tell a Mac user to look for a flashing icon. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window \u2014 use this for an explicit end-of-task \"it's ready\" nudge on top of that. STRONGLY prefer this (or the auto-flash) over pup_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.",- "timeoutSeconds": 15+ "summary": "Stop recording; returns the saved file.",+ "timeoutSeconds": 30 }, { "example": {- "html": "<h1>Caf\u00e9</h1>", "sessionId": "work" },- "hint": "Render a local HTML string in the tab via page.setContent \u2014 UTF-8-correct (a data: URL mojibakes accented chars without an explicit charset). Great for previewing generated markup without a temp file. Auto-flashes the window.",+ "hint": "Whether a recording is active for the session.", "input": {- "html": "required HTML string",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "silent": "optional bool \u2014 suppress the auto-flash",- "updateNote": "optional note for lastAgentUpdate",- "waitUntil": "optional (domcontentloaded default)"+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_render_html",+ "name": "pup_record_status", "output": { "success": "bool" }, "pitfalls": [],- "related": [],+ "related": [+ "pup_record_start",+ "pup_record_stop"+ ], "statusVerb": null,- "summary": "Render a local HTML string in the tab via page.setContent \u2014 UTF-8-correct (a data: URL mojibakes accented chars without an explicit charset). Great for previewing generated markup without a temp file. Auto-flashes the window.",- "timeoutSeconds": 30+ "summary": "Recording status for a session.",+ "timeoutSeconds": 10 }, { "example": {},- "hint": "List all open pup sessions + their tabs (NOT a readiness check \u2014 use pup_readiness for that).",+ "hint": "Lists saved page recordings.", "input": {}, "longRunning": false,- "name": "pup_status",+ "name": "pup_record_list", "output": { "success": "bool" }, "pitfalls": [],- "related": [],+ "related": [+ "pup_record_start"+ ], "statusVerb": null,- "summary": "List all open pup sessions + their tabs (NOT a readiness check \u2014 use pup_readiness for that).",+ "summary": "List recordings.", "timeoutSeconds": 10 }, { "example": {},- "hint": "List every open session/window with its tabs.",- "input": {},+ "hint": "Open pup's Chrome screen-share recorder HUD (WebM; consent = the visible HUD). Canonical name (pup-bridge#94).",+ "input": {+ "reason": "str (required)"+ }, "longRunning": false,- "name": "pup_list_windows",+ "name": "pup_recorder_open", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "List every open session/window with its tabs.",- "timeoutSeconds": 10+ "summary": "Open pup's Chrome screen-share recorder HUD (WebM; consent = the visible HUD). Canonical name (pup-bridge#94).",+ "timeoutSeconds": 20 }, {- "example": {- "expr": "document.querySelector(\"#userId\").value",- "frame": "logonbox",- "sessionId": "work"+ "example": {},+ "hint": "Close the Chrome screen-recorder HUD (auto-stops a clip).",+ "input": {},+ "longRunning": false,+ "name": "pup_recorder_close",+ "output": {+ "success": "bool" },- "hint": "pup_eval accepts frame:\"<name|url-substring|index>\" to run inside an IFRAME (login forms usually live in one). Cross-origin frames work. Omit for the top document; a bad value returns frame_not_found WITH the list of frames in the tab.",+ "pitfalls": [],+ "related": [],+ "statusVerb": null,+ "summary": "Close the Chrome screen-recorder HUD (auto-stops a clip).",+ "timeoutSeconds": 15+ },+ {+ "example": {},+ "hint": "Start a whole-screen recording through Chrome screen-share (WebM VP9, system audio + mic). Canonical name; for a background no-browser MP4 of one window use ab-core desktop_record_window_start.", "input": {- "expr": "required str",- "frame": "optional str",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "audio": "bool",+ "confirmDesktopNotTabRecording": "true",+ "fps": "int",+ "maxDurationMs": "int",+ "reason": "str (required)" }, "longRunning": false,- "name": "pup_eval_frame_note",+ "name": "pup_screen_record_start", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "pup_eval accepts frame:\"<name|url-substring|index>\" to run inside an IFRAME (login forms usually live in one). Cross-origin frames work. Omit for the top document; a bad value returns frame_not_found WITH the list of frames in the tab.",+ "summary": "Start a whole-screen recording through Chrome screen-share (WebM VP9, system audio + mic). Canonical name; for a background no-browser MP4 of one window use ab-core desktop_record_window_start.", "timeoutSeconds": 20 }, {- "example": {- "sessionId": "work"- },- "hint": "Make a session the active one (for verbs that default to active).",- "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "example": {},+ "hint": "Stop the Chrome screen recording; returns the .webm path.",+ "input": {},+ "longRunning": false,+ "name": "pup_screen_record_stop",+ "output": {+ "success": "bool" },+ "pitfalls": [],+ "related": [],+ "statusVerb": null,+ "summary": "Stop the Chrome screen recording; returns the .webm path.",+ "timeoutSeconds": 30+ },+ {+ "example": {},+ "hint": "Status of the Chrome screen recorder + any active clip.",+ "input": {}, "longRunning": false,- "name": "pup_switch_window",+ "name": "pup_screen_record_status", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Make a session the active one (for verbs that default to active).",+ "summary": "Status of the Chrome screen recorder + any active clip.", "timeoutSeconds": 10 }, {- "example": {- "sessionId": "work"- },- "hint": "List the tabs in a session.",- "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"- },+ "example": {},+ "hint": "List completed Chrome screen recordings on disk.",+ "input": {}, "longRunning": false,- "name": "pup_list_tabs",+ "name": "pup_screen_record_list", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "List the tabs in a session.",+ "summary": "List completed Chrome screen recordings on disk.", "timeoutSeconds": 10 }, {+ "name": "pup_highfps_extend",+ "summary": "Renew the highFps lease on a window that must keep rendering while in the background.",+ "input": {+ "minutes": "int, max 120",+ "window": "optional: the pup window (default the calling thread's)"+ }, "example": {- "sessionId": "work",- "tabId": "tab-2"+ "minutes": 20 },- "hint": "Activate a tab WITHIN its session/window (idempotent \u2014 safe to call when the tab is already active). Does NOT raise a background window to the user's screen: the tab becomes the window's visible tab wherever the window is, and the taskbar auto-flash tells the user something changed. Also self-heals the window (re-park if a bug ever stranded it off-screen) and refreshes the Adom taskbar badge \u2014 good citizenship for long-lived shared windows.",+ "hint": "The lease auto-expires so a background window can never drain the battery; renew only while you still need it.",+ "related": [+ "pup_open_window"+ ],+ "timeoutSeconds": 30,+ "output": {},+ "pitfalls": [],+ "longRunning": false,+ "statusVerb": "pup_readiness"+ },+ {+ "example": {+ "selector": ".download-card",+ "sessionId": "work"+ },+ "hint": "Screenshot ONE element, resolved by CSS selector OR visible text (pierces shadow DOM), scrolled into view first. The clean way to grab a single card/widget on a component-heavy page. Text matches the TIGHTEST text node \u2014 to capture the enclosing CARD, add closest (e.g. {text:\"Download\u2026\", closest:\".download-card\"}); with closest, an ambiguous phrase still lands on the real widget (skips prose that has no such ancestor). Returns base64 + filePath like pup_screenshot.", "input": {+ "closest": "optional selector \u2014 climb to the nearest ancestor matching it (the card container)",+ "deep": "optional bool \u2014 pierce shadow DOM (default true)",+ "padding": "optional int px around the element",+ "selector": "CSS selector (or use text)", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "silent": "optional bool",- "tabId": "required str",- "updateNote": "optional str"+ "text": "visible text to match (or use selector)" }, "longRunning": false,- "name": "pup_switch_tab",+ "name": "pup_screenshot_element", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Activate a tab WITHIN its session/window (idempotent \u2014 safe to call when the tab is already active). Does NOT raise a background window to the user's screen: the tab becomes the window's visible tab wherever the window is, and the taskbar auto-flash tells the user something changed. Also self-heals the window (re-park if a bug ever stranded it off-screen) and refreshes the Adom taskbar badge \u2014 good citizenship for long-lived shared windows.",- "timeoutSeconds": 10+ "summary": "Screenshot ONE element, resolved by CSS selector OR visible text (pierces shadow DOM), scrolled into view first. The clean way to grab a single card/widget on a component-heavy page. Text matches the TIGHTEST text node \u2014 to capture the enclosing CARD, add closest (e.g. {text:\"Download\u2026\", closest:\".download-card\"}); with closest, an ambiguous phrase still lands on the real widget (skips prose that has no such ancestor). Returns base64 + filePath like pup_screenshot.",+ "timeoutSeconds": 30 }, { "example": {@@ -1565,241 +1808,185 @@ }, { "example": {+ "selector": "button.download", "sessionId": "work" },- "hint": "Console errors + failed network requests for a session/tab (clears by default).",+ "hint": "Download a file THROUGH THE BROWSER, exactly as the user clicking it would \u2014 real session, real cookies, real anti-bot cookies \u2014 and Chrome saves it to the Downloads folder. This is the right verb for vendor files (datasheets, KiCad/Altium libraries, STEP 3D models) that a server-side fetch/curl can never get past the site's bot wall. Pass selector to click the actual download button (authentic), or url to force-download via an in-page a[download]. Confirms completion via Chrome's download events and returns the ACTUAL saved path + byte count + file magic. For a gated file, log in on the window first (pup_login).", "input": {- "clear": "optional bool (default true)",+ "saveAs": "optional str \u2014 filename override",+ "saveDir": "optional str \u2014 default the user's Downloads folder",+ "selector": "optional str \u2014 the real download control to click", "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",- "tabId": "optional str"- },- "longRunning": false,- "name": "pup_errors",- "output": {- "success": "bool"- },- "pitfalls": [],- "related": [],- "statusVerb": null,- "summary": "Console errors + failed network requests for a session/tab (clears by default).",- "timeoutSeconds": 10- },- {- "example": {- "ms": 500,- "sessionId": "work"- },- "hint": "Wait N ms (or for a selector) before the next step.",- "input": {- "ms": "optional int",- "selector": "optional str",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "url": "optional str \u2014 download this URL via an in-page a[download] click" }, "longRunning": false,- "name": "pup_wait",+ "name": "pup_download", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Wait N ms (or for a selector) before the next step.",- "timeoutSeconds": 60+ "summary": "Download a file THROUGH THE BROWSER, exactly as the user clicking it would \u2014 real session, real cookies, real anti-bot cookies \u2014 and Chrome saves it to the Downloads folder. This is the right verb for vendor files (datasheets, KiCad/Altium libraries, STEP 3D models) that a server-side fetch/curl can never get past the site's bot wall. Pass selector to click the actual download button (authentic), or url to force-download via an in-page a[download]. Confirms completion via Chrome's download events and returns the ACTUAL saved path + byte count + file magic. For a gated file, log in on the window first (pup_login).",+ "timeoutSeconds": 100 }, { "example": {},- "hint": "Recover orphaned sessions: reconnect dropped CDP sockets + rebuild sessions from window titles. Pass adoptOrphans:true to also adopt untagged tabs.",- "input": {- "adoptOrphans": "optional bool"- },+ "hint": "",+ "input": {}, "longRunning": false,- "name": "pup_rescan",+ "name": "pup_launch", "output": { "success": "bool" },- "pitfalls": [],- "related": [],+ "pitfalls": [+ "sessionId is OPTIONAL: omit it and your thread gets its OWN window (keyed on your --ai-thread name; later opens become tabs in it). Pass one only for a deliberate separate window \u2014 it is then your handle for every later call",+ "profile defaults to the durable shared 'adom-you' profile (logins/localStorage persist + are shared across all pup windows); isolated:true for a private throwaway \u2014 omit it for a plain \"open in pup\"",+ "sessionIds are SHARED across all AI threads on this desktop \u2014 a generic id (\"wiki\", \"test\") may collide with another thread's window and navigating it away steals their work. Use a task-prefixed sessionId + pass owner:\"<your-task>\" so pup can protect it",+ "reusing an existing sessionId with a new url NAVIGATES that window \u2014 check pup_status (shows owner per window) first if unsure"+ ],+ "related": [+ "pup_open_window"+ ], "statusVerb": null,- "summary": "Recover orphaned sessions: reconnect dropped CDP sockets + rebuild sessions from window titles. Pass adoptOrphans:true to also adopt untagged tabs.",+ "summary": "Alias of pup_open_window (url defaults to about:blank).", "timeoutSeconds": 30 }, { "example": {},- "hint": "Close ALL pup sessions + windows at once (destructive). NOTE: `{}` here does NOT mean \"the active session\" the way it does on other session-scoped verbs \u2014 with no args this closes EVERY window, across every AI thread on the machine, with no confirmation. To close ONE window use pup_close_window {sessionId}. Pass {sessionId} here only if you genuinely mean that one session.",+ "hint": "Report pup dependency health (wiki issue #15): depsPresent, selfInstalledThisBoot, lastError. Use this to answer \"is pup broken?\" without reading spawn logs.", "input": {}, "longRunning": false,- "name": "pup_close",+ "name": "pup_deps", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Close ALL pup sessions + windows at once (destructive). NOTE: `{}` here does NOT mean \"the active session\" the way it does on other session-scoped verbs \u2014 with no args this closes EVERY window, across every AI thread on the machine, with no confirmation. To close ONE window use pup_close_window {sessionId}. Pass {sessionId} here only if you genuinely mean that one session.",- "timeoutSeconds": 20+ "summary": "Report pup dependency health (wiki issue #15): depsPresent, selfInstalledThisBoot, lastError. Use this to answer \"is pup broken?\" without reading spawn logs.",+ "timeoutSeconds": 10 }, {- "example": {- "taskbarGrouping": "grouped"- },- "hint": "Get/set user-facing pup preferences (persisted across bridge updates). taskbarGrouping: \"split\" (default \u2014 one taskbar button PER WINDOW, each badged with its own tab count; best for telling AI threads apart) | \"grouped\" (all pup windows stack under ONE \"Adom Pup\" button, badge shows window count; tidy taskbar). Applies to live windows immediately. Ask the USER which they prefer before changing it \u2014 this is their taskbar. WINDOWS ONLY: macOS has no taskbar, so the setting is stored but INERT there; the reply carries platform + taskbarSupported so branch on those, not on this text.",+ "example": {},+ "hint": "Status/trigger for the toolbar extension, which AUTO-INSTALLS invisibly at every profile launch (transient pipe-launch + CDP Extensions.loadUnpacked; persists in the profile). Running profiles pick it up on their next launch; cold profiles install immediately.", "input": {- "autoLogin": "optional \"on\"|\"off\" \u2014 auto-fill a vault-matched login form on page load (fill only, never submits), on by default",- "overlaySize": "optional \"50\"|\"100\" \u2014 how big the favicon badge is drawn on the taskbar button. \"50\" (the DEFAULT) draws it at half size so it stays clear of the thread tile's bottom line of text; \"100\" is the older, larger badge",- "reportIssues": "optional \"on\"|\"off\" \u2014 pup's bug-report invitation (_reportIssues), on by default",- "taskbarActivity": "optional \"progress\"|\"flash\"|\"off\" \u2014 how pup signals it is working on a window, on that window's taskbar button: a determinate progress bar sized to the operation's running-average time (default), the old orange flash, or nothing. Replaces the former openFlash/autoFlash/agentActivity toggles.",- "taskbarGrouping": "optional \"split\"|\"grouped\""+ "profile": "optional, default adom-you" }, "longRunning": false,- "name": "pup_configure",+ "name": "pup_install_toolbar", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Get/set user-facing pup preferences (persisted across bridge updates). taskbarGrouping: \"split\" (default \u2014 one taskbar button PER WINDOW, each badged with its own tab count; best for telling AI threads apart) | \"grouped\" (all pup windows stack under ONE \"Adom Pup\" button, badge shows window count; tidy taskbar). Applies to live windows immediately. Ask the USER which they prefer before changing it \u2014 this is their taskbar. WINDOWS ONLY: macOS has no taskbar, so the setting is stored but INERT there; the reply carries platform + taskbarSupported so branch on those, not on this text.",- "timeoutSeconds": 15- },- {- "example": {},- "hint": "This list \u2014 the bridge's verbs + schemas + hints, for AD's Verbs tab. Read it once to learn the surface.",- "input": {},- "longRunning": false,- "name": "pup_describe",- "output": {- "success": "bool"- },- "pitfalls": [],- "related": [- "pup_status"- ],- "statusVerb": null,- "summary": "This list \u2014 the bridge's verbs + schemas, for AD's Verbs tab.",- "timeoutSeconds": 10+ "summary": "Status/trigger for the toolbar extension, which AUTO-INSTALLS invisibly at every profile launch (transient pipe-launch + CDP Extensions.loadUnpacked; persists in the profile). Running profiles pick it up on their next launch; cold profiles install immediately.",+ "timeoutSeconds": 30 }, { "example": {- "selector": "select[name=timezone]",- "label": "Central Time"+ "dryRun": true },- "hint": "Sets the option through the native setter and fires input+change so framework-bound selects update; works on both lanes (DevTools evaluate, so page CSP and Trusted Types cannot refuse it). For custom dropdowns (Material, React Select) use pup_click on the control, then pup_click the option by text. option_not_found lists the first 30 options.",+ "hint": "Delete LEAKED isolated profile directories \u2014 the iso-* throwaway jars minted by isolated:true opens (and by wikiView:public before 2.0.132, when the public view moved to the server-side ?view=public param) whose window is long gone (they used to pile up, 100s per machine). Only removes an iso-* dir that no live session or session file owns, that no Chrome is holding, and that is >15 min old; durable jars (adom-you, named profiles) are NEVER touched. Runs automatically every 15 min too. dryRun:true previews the list without deleting.", "input": {- "selector": "required str",- "value": "optional str (option value)",- "label": "optional str (visible text, case-insensitive, substring ok)",- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "dryRun": "optional bool \u2014 preview which iso-* profiles would be removed, delete nothing" }, "longRunning": false,- "name": "pup_select",+ "name": "pup_reap_profiles", "output": { "success": "bool" }, "pitfalls": [],- "related": [- "pup_fill",- "pup_click",- "pup_type"- ],+ "related": [], "statusVerb": null,- "summary": "Choose a <select> option by value or visible label, in the background (fires input+change).",+ "summary": "Delete LEAKED isolated profile directories \u2014 the iso-* throwaway jars minted by isolated:true opens (and by wikiView:public before 2.0.132, when the public view moved to the server-side ?view=public param) whose window is long gone (they used to pile up, 100s per machine). Only removes an iso-* dir that no live session or session file owns, that no Chrome is holding, and that is >15 min old; durable jars (adom-you, named profiles) are NEVER touched. Runs automatically every 15 min too. dryRun:true previews the list without deleting.", "timeoutSeconds": 20 }, {+ "name": "pup_sweep_identities",+ "summary": "Unregister every pup taskbar identity (registry key and any Start Menu anchor) that no live window holds, and delete stale tile/icon generations.",+ "input": {}, "example": {},- "hint": "Each account comes back with kind (work or personal, from its domain), the browser profile labels the user gave it (\"John Personal\"), whether pup is already signed in, whether pup holds its password, and likelyStale for an account found only in pup's vault. pup never chooses between accounts: pick from the task, the user's work and personal skills, or ask the user.",- "input": {- "refresh": "optional bool (redo the whole search now)"- },- "longRunning": false,- "name": "pup_google_accounts",- "output": {- "success": "bool"- },- "pitfalls": [- "An account only in pup's vault and signed into no browser here is usually years old; Google will demand a second step for it."- ],+ "hint": "pup runs this a minute after every boot and releases a window identity on close (2.0.666+); on demand it is for a box that ran older builds. Returns unregistered, shortcutsRemoved, iconDirsRemoved.", "related": [- "pup_google_signin",- "pup_credentials",- "pup_import_browser_logins"+ "pup_audit_icons" ],- "statusVerb": null,- "summary": "List the user's Google accounts: every Chrome/Edge profile, nb's browser profiles, pup's vault, and which ones pup's own profile is already signed into. Cached a day.",- "timeoutSeconds": 10+ "timeoutSeconds": 30,+ "output": {},+ "pitfalls": [],+ "longRunning": false,+ "statusVerb": "pup_readiness" }, { "example": {- "account": "[email protected]",- "submitReason": "the task is the work calendar, so the work account"+ "dryRun": true },- "hint": "Already signed into pup: no password is sent, the window just opens the account. Otherwise pup fills the email and the password saved for exactly that account and submits. If Google asks for a second step the answer is google_verification_required with the options or the code field, and the exact call to finish it; a text code can be read by a thread with the user's standing permission to read their texts. google_password_missing names where the account is signed in and how to import its password (pup_import_browser_logins {hostFilter:\"google.com\"}). The account is confirmed by name at the end: Gmail silently opens another signed-in account when the requested one is not signed in.",+ "hint": "DESTRUCTIVE \u2014 cleanly remove pup's WHOLE footprint from this machine: the HKCU AppUserModelId Adom.Pup registry keys, all staged extensions, ALL pup profiles + sessions, the OS-keychain credential vault, pup-settings.json, the badge-debug temp dir, and the jump-list launcher. ALWAYS preview first with dryRun:true (removes nothing, returns the plan). To actually remove, pass confirm:\"uninstall\". keepCredentials:true preserves the saved-login vault; keepProfiles:true preserves profiles/sessions. Chrome-for-Testing + firewall rules are SHARED and left alone unless removeChrome:true / removeFirewallRules:true. Closes every window + quits every browser first to release profile locks. (The bridge itself is managed by Adom Bridge \u2014 remove pup from Bridge to stop it re-seeding.)", "input": {- "account": "optional str: an email, or \"work\" / \"personal\" when exactly one account of that kind exists. Omit and pup lists the accounts and asks (google_account_choice_required).",- "submitReason": "str, 10+ chars: required before pup sends a saved password (why this account, for what task)",- "continue": "optional str: where to land after sign-in (default: that account's Gmail)",- "window": "optional str: the pup window to use (a new tab in it); default the calling thread's window",- "code": "optional str: finish a verification step with the code Google sent (with window)",- "challenge": "optional str: pick a verification option by its words, e.g. \"text message\" (with window)",- "refresh": "optional bool: redo the account search first"+ "confirm": "required str \"uninstall\" to actually remove",+ "dryRun": "optional bool \u2014 preview the removal plan without removing anything (do this first)",+ "keepCredentials": "optional bool \u2014 keep the saved-login vault",+ "keepProfiles": "optional bool \u2014 keep pup profiles + sessions",+ "removeChrome": "optional bool \u2014 also delete the Chrome-for-Testing install (shared; default keep)",+ "removeFirewallRules": "optional bool \u2014 also remove the CfT firewall rules (needs admin)" }, "longRunning": false,- "name": "pup_google_signin",+ "name": "pup_uninstall", "output": { "success": "bool" },- "pitfalls": [- "Do not restart a sign-in that stopped on a verification step: that sends the password again and a new code. Finish it with {window, code} or {window, challenge}.",- "Never ask the user to type their password to you: import it or let them sign in once in the pup window."- ],- "related": [- "pup_google_accounts",- "pup_login",- "pup_import_browser_logins"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Sign the user into Google as a named account, hands-free: account search, email step, saved password, and the verification step handed back with exact next calls.",- "timeoutSeconds": 20+ "summary": "DESTRUCTIVE \u2014 cleanly remove pup's WHOLE footprint from this machine: the HKCU AppUserModelId Adom.Pup registry keys, all staged extensions, ALL pup profiles + sessions, the OS-keychain credential vault, pup-settings.json, the badge-debug temp dir, and the jump-list launcher. ALWAYS preview first with dryRun:true (removes nothing, returns the plan). To actually remove, pass confirm:\"uninstall\". keepCredentials:true preserves the saved-login vault; keepProfiles:true preserves profiles/sessions. Chrome-for-Testing + firewall rules are SHARED and left alone unless removeChrome:true / removeFirewallRules:true. Closes every window + quits every browser first to release profile locks. (The bridge itself is managed by Adom Bridge \u2014 remove pup from Bridge to stop it re-seeding.)",+ "timeoutSeconds": 60 }, { "example": {},- "hint": "START HERE for window management: everything YOUR thread owns \u2014 windows, tabs, urls, titles, state \u2014 with ready-to-run next steps. Zero args. You NEVER need an hwnd, desktop_find_window, or desktop_screenshot_window for a pup window: pup keys every window to its owning --ai-thread name and resolves ids for you \u2014 omit sessionId in any pup verb to target your own window, or pass \"ai-thread\" to address a thread by name (the CLI's --ai-thread flag, same spelling). pup_screenshot {} screenshots it; pup_reload {} refreshes it.",+ "hint": "MOVE this pup window's page into the user's Hydrogen workspace: the active tab's URL is handed to Hydrogen (ah) on this machine via its Browser Picker (webview by default, honoring the user's per-domain choice), then the pup window closes \u2014 its owner initiated the move. keepWindow:true keeps the pup window open too. Needs Hydrogen running locally (hydrogen_unreachable otherwise; the window is left untouched on failure). Cloud Hydrogen (hc) is not yet supported.", "input": {- "ai-thread": "optional str \u2014 inspect another thread's windows (read-only listing); default = the calling thread. The CLI flag's exact spelling; aiThread also accepted"+ "keepWindow": "optional bool \u2014 hand the URL over but keep the pup window open",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)" }, "longRunning": false,- "name": "pup_list_sessions",+ "name": "pup_move_to_workspace", "output": { "success": "bool" }, "pitfalls": [], "related": [], "statusVerb": null,- "summary": "Alias of pup_my_windows: list the sessions owned by the calling ai-thread. Passive, never launches a browser.",- "timeoutSeconds": 10+ "summary": "MOVE this pup window's page into the user's Hydrogen workspace: the active tab's URL is handed to Hydrogen (ah) on this machine via its Browser Picker (webview by default, honoring the user's per-domain choice), then the pup window closes \u2014 its owner initiated the move. keepWindow:true keeps the pup window open too. Needs Hydrogen running locally (hydrogen_unreachable otherwise; the window is left untouched on failure). Cloud Hydrogen (hc) is not yet supported.",+ "timeoutSeconds": 25 }, { "example": {- "sessionId": "work"+ "label": "your Google account",+ "reason": "clicking your Google account to finish signing into Autodesk",+ "sessionId": "adsk",+ "x": 900,+ "y": 379 },- "hint": "Closes the whole session and all its tabs. Destructive.",+ "hint": "RESPECTFUL real-cursor click for a surface pup CDP cannot drive (a NATIVE OS dialog, or a cross-origin FedCM \"Sign in with Google\" account chooser that rejects every in-page click). Do NOT hand-roll raise+desktop_click for these \u2014 use THIS. It: captions the reason + toasts BEFORE touching the screen; foregrounds; then WAITS, detecting if the user alt-tabbed away and holding (captioning \"Adom is waiting \u2014 click back when ready\") until the user is actually on the window; then runs the TIGHT real-cursor hover->click that FedCM accepts (interleaving any screenshot/eval between raise and click makes the click silently no-op); captions each step. Times out as user_not_ready clicking NOTHING if they never return. First take a desktop_screenshot_window of the session to read the target's WINDOW-pixel x,y.", "input": {- "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)"+ "label": "required str \u2014 WHAT you are clicking (e.g. \"your Google account\")",+ "maxWaitMs": "optional int \u2014 how long to wait for the user (default 120000)",+ "reason": "required str \u2014 WHY you must click (shown to the user as a caption)",+ "sessionId": "optional str \u2014 OMIT IT: zero-id addressing resolves your --ai-thread name to your own window. Pass a window label only when one thread runs several windows (`window` is the new name for this param; sessionId is the legacy alias)",+ "x": "required int \u2014 WINDOW-image pixel of the target (from desktop_screenshot_window)",+ "y": "required int" }, "longRunning": false,- "name": "pup_close_all_windows",+ "name": "pup_assisted_click", "output": { "success": "bool" },- "pitfalls": [- "this ends the session \u2014 its sessionId is no longer valid afterward"- ],- "related": [- "pup_list_windows"- ],+ "pitfalls": [],+ "related": [], "statusVerb": null,- "summary": "Close every pup window owned by the calling ai-thread (pup_close_window with all:true). Other threads' windows are never touched.",- "timeoutSeconds": 20+ "summary": "RESPECTFUL real-cursor click for a surface pup CDP cannot drive (a NATIVE OS dialog, or a cross-origin FedCM \"Sign in with Google\" account chooser that rejects every in-page click). Do NOT hand-roll raise+desktop_click for these \u2014 use THIS. It: captions the reason + toasts BEFORE touching the screen; foregrounds; then WAITS, detecting if the user alt-tabbed away and holding (captioning \"Adom is waiting \u2014 click back when ready\") until the user is actually on the window; then runs the TIGHT real-cursor hover->click that FedCM accepts (interleaving any screenshot/eval between raise and click makes the click silently no-op); captions each step. Times out as user_not_ready clicking NOTHING if they never return. First take a desktop_screenshot_window of the session to read the target's WINDOW-pixel x,y.",+ "timeoutSeconds": 130 }, { "name": "pup_import_scan",@@ -1819,6 +2006,30 @@ ], "pitfalls": [] },+ {+ "example": {+ "hostFilter": [+ "snapeda",+ "ultralibrarian",+ "digikey"+ ]+ },+ "hint": "Seed pup's vault from the user's OWN saved Chrome/Edge/Brave passwords, so pup can sign in as them on sites they already use (vendor CAD portals, etc). Runs the SYSTEM-elevated decrypt LOCALLY \u2014 the user approves ONE consent dialog + ONE Windows UAC prompt on the desktop (a cloud/relay caller cannot do this; only pup, a local bridge, can ask). Merges the identity-tagged rows into the keychain vault and DROPS the plaintext. Returns COUNTS ONLY. hostFilter narrows the import to matching hosts (surgical, e.g. [\"snapeda\",\"digikey\"]); omit to import everything saved. Pair with pup_import_scan (read-only preview of how many logins exist).",+ "input": {+ "browsers": "optional str[] subset of [\"chrome\",\"edge\",\"brave\"] (default: all found)",+ "hostFilter": "optional str or str[] \u2014 only import logins whose host contains one of these (surgical import)"+ },+ "longRunning": false,+ "name": "pup_import_browser_logins",+ "output": {+ "success": "bool"+ },+ "pitfalls": [],+ "related": [],+ "statusVerb": null,+ "summary": "Seed pup's vault from the user's OWN saved Chrome/Edge/Brave passwords, so pup can sign in as them on sites they already use (vendor CAD portals, etc). Runs the SYSTEM-elevated decrypt LOCALLY \u2014 the user approves ONE consent dialog + ONE Windows UAC prompt on the desktop (a cloud/relay caller cannot do this; only pup, a local bridge, can ask). Merges the identity-tagged rows into the keychain vault and DROPS the plaintext. Returns COUNTS ONLY. hostFilter narrows the import to matching hosts (surgical, e.g. [\"snapeda\",\"digikey\"]); omit to import everything saved. Pair with pup_import_scan (read-only preview of how many logins exist).",+ "timeoutSeconds": 190+ }, { "name": "pup_vault_sync", "summary": "Merge this desktop's adom-you logins with another desktop's, BOTH directions, so both boxes hold the union (newest-wins on conflict). One consent toast on this box; passwords move pup-to-pup and never touch an AI.",@@ -1876,5 +2087,15 @@ ], "pitfalls": [] }+ ],+ "notes": [+ {+ "name": "pup_NATIVE_INPUT_WARNING",+ "summary": "\u26d4 READ BEFORE USING AD's desktop_click / desktop_type / desktop_press_key ON A PUP WINDOW. Native OS input does NOT target a window \u2014 it goes to WHATEVER WINDOW HAS OS FOCUS at that instant. This user runs many AI threads and alt-tabs away from a foregrounded window almost immediately. If you fire native input without warning them, your keystrokes land in THEIR document (this happened: a username was typed into the user's own window). REQUIRED: notify_user toast + desktop_caption FIRST, tell them explicitly \"don't type or click until I say done\", wait until the window is genuinely foreground, re-verify immediately before EACH event, and caption when done. pup_assisted_click already does all of this \u2014 use it. And ALWAYS prefer CDP input (pup_click/pup_type/pup_hover): it needs no foreground and disturbs nobody. A native verb returning ok:true means the event was dispatched to the OS, NOT that it reached your window."+ },+ {+ "name": "pup_eval_frame_note",+ "summary": "pup_eval accepts frame:\"<name|url-substring|index>\" to run inside an IFRAME (login forms usually live in one). Cross-origin frames work. Omit for the top document; a bad value returns frame_not_found WITH the list of frames in the tab."+ } ] }
rust/verbs/src/lib.rs+34−1@@ -681,6 +681,11 @@ pub fn dispatch(ctx: &Ctx, caller: &Caller, verb: &str, args: &Value) -> Value { v if v.starts_with("pup_") || v.starts_with("credential_") => err("not_in_rust_yet", format!("{v} is not served by the Rust bridge yet (Phase 1 carries {} verbs)", PHASE1_VERBS.len()), Some("This is the Rust port of pup (docs/PLAN-rust-port.md). The verb exists in the contract and lands in a later phase; the Node build still serves it on the public tier.")), v => err("unknown_verb", format!("unknown verb {v}"), Some("pup_describe lists every verb this bridge serves.")), };+ // 2.0.672 (pup-bridge#124): ab's screenshot delivery contract is fullPath (+ safePath, the <=1568 px copy). When a+ // reply carries those, ab's CLI pulls the image to the caller and adds localPath/localSafePath, so a container AI can+ // Read it without a second call. pup answered with filePath only, so nothing was pulled. Every image reply now+ // carries both keys (filePath stays for older callers). The image pup writes is already the shrunk copy when needed.+ image_delivery(&mut r); // lastAgentUpdate {tabId, verb, note, ts}: every driving verb that names a window (Node: touch sites) if !PROBE_VERBS.contains(&verb) && !PASSIVE_VERBS.contains(&verb) { if let Some(sid) = r.get("sessionId").and_then(|v| v.as_str()).map(|s| s.to_string()) {@@ -689,6 +694,18 @@ pub fn dispatch(ctx: &Ctx, caller: &Caller, verb: &str, args: &Value) -> Value { } } if let Some(o) = r.as_object_mut() {+ // 2.0.672: many handlers put their real fields only in the `output` JSON string. ab used to lift those onto+ // the reply; ab 2.1.14x stopped (measured 2026-09-22: "ok" gone from every error reply, and pup_record_stop's+ // durationMs became the 615 ms CALL time instead of the 8 s recording, failing the smoke). pup lifts them itself+ // now, never overwriting a top-level key, and BEFORE the call time is stamped.+ if let Some(inner) = o.get("output").and_then(|v| v.as_str()).filter(|s| s.starts_with('{')).and_then(|s| serde_json::from_str::<Value>(s).ok()) {+ if let Some(io) = inner.as_object() { for (k, v) in io { if !o.contains_key(k) { o.insert(k.clone(), v.clone()); } } }+ }+ // and every reply says ok true/false itself (the contract fixtures expect it; ab used to add it)+ if !o.contains_key("ok") {+ let failed = o.get("success") == Some(&json!(false)) || o.contains_key("errorCode") || o.get("error").and_then(|e| e.as_str()).map(|e| !e.is_empty()).unwrap_or(false);+ o.insert("ok".into(), json!(!failed));+ } o.entry("durationMs").or_insert(json!(t0.elapsed().as_millis() as u64)); o.insert("shell".into(), json!("rust")); // Node sendJSON: the catalog's canonical hint/related/next/pitfalls when the handler set none if let Some(m) = catalog_meta(verb) {@@ -1065,11 +1082,27 @@ pub fn refresh_tab(cdp: &Cdp, t: &mut Tab) { // ── the verbs ────────────────────────────────────────────────────────────────────────── +fn image_delivery(r: &mut Value) {+ fn fix(v: &mut Value) {+ let Some(fp) = v.get("filePath").and_then(|x| x.as_str()).map(|x| x.to_string()) else { return };+ let low = fp.to_lowercase();+ if ![".png", ".jpg", ".jpeg", ".webp"].iter().any(|e| low.ends_with(e)) || v.get("fullPath").is_some() { return; }+ v["fullPath"] = json!(fp);+ let fits = |k: &str| v.get(k).and_then(|x| x.as_u64()).map(|n| n <= 1568).unwrap_or(false);+ if fits("width") && fits("height") { v["safePath"] = json!(fp); }+ }+ fix(r);+ // the same keys inside the JSON string many verbs also carry in `output`+ if let Some(o) = r.get("output").and_then(|x| x.as_str()).map(|x| x.to_string()) {+ if let Ok(mut ov) = serde_json::from_str::<Value>(&o) { if ov.is_object() { let before = ov.clone(); fix(&mut ov); if ov != before { r["output"] = json!(ov.to_string()); } } }+ }+}+ fn describe(ctx: &Ctx) -> Value { let c = catalog(); let body = json!({ "success": true, "bridge": "puppeteer", "version": pup_core::version(), "shell": "rust",- "progressContract": c["progressContract"], "verbs": c["verbs"], "statusVerb": "pup_readiness",+ "progressContract": c["progressContract"], "verbs": c["verbs"], "verbCount": c["verbs"].as_array().map(|a| a.len()).unwrap_or(0), "notes": c["notes"], "statusVerb": "pup_readiness", "readiness": ctx.installer.readiness(&*ctx.host, ctx.prefer_cft()), "_hint": "This list — the bridge's verbs + schemas + hints, for AD's Verbs tab. Read it once to learn the surface. This build is pup as one Rust exe (spawn.kind exe): no Node, no npm, no postinstall; same contract as the Node build.", "_next": ["pup_status"], "related": ["pup_status"],
tools/gen-assets.py+46−2@@ -117,5 +117,49 @@ try: except Exception: pass for _n in CATALOG_EXTRA: if _n not in _have and _n in _existing: _verbs.append(_existing[_n])-json.dump({'progressContract': _o['progressContract'], 'verbs': _verbs}, open(os.path.join(root, 'rust', 'verbs', 'assets', 'verb-catalog.json'), 'w'), indent=1)-print(f"verb-catalog.json: {len(_verbs)} verbs")+# 2.0.672 (pup-bridge#124): ONE source for the verb surface. Every verb the Rust dispatch routes (PHASE1_VERBS in+# rust/verbs/src/lib.rs) must have exactly one catalog entry, and bridge.json's `verbs` is written from the same list,+# so `adom-bridge commands` (the manifest) and pup_describe can never disagree again (55 vs 85 on 2026-09-22). A+# hand-written entry already in the catalog is kept for any routed verb; a routed verb with no entry anywhere stops+# the build. The two advisory notes that used to sit in the verb list move to `notes` (they are not callable).+import re as _re+_lib = open(os.path.join(root, 'rust', 'verbs', 'src', 'lib.rs')).read()+_routed = list(dict.fromkeys(_re.findall(r'"([a-z_]+)"', _re.search(r'PHASE1_VERBS: &\[&str\] = &\[(.*?)\];', _lib, _re.S).group(1))))+_have = {v['name'] for v in _verbs}+for _n in _routed:+ if _n not in _have and _n in _existing: _verbs.append(_existing[_n]); _have.add(_n)+def _entry(name, summary, inp, example, hint, related, timeout=30, like=None):+ base = json.loads(json.dumps(next((v for v in _verbs if v['name'] == like), {}))) if like else {}+ base.update({'name': name, 'summary': summary, 'input': inp, 'example': example, 'hint': hint, 'related': related, 'timeoutSeconds': timeout})+ base.setdefault('output', {}); base.setdefault('pitfalls', []); base.setdefault('longRunning', False); base.setdefault('statusVerb', 'pup_readiness')+ return base+_STUBS = {+ 'pup_eval': lambda: dict(_entry('pup_eval', 'Alias of pup_evaluate: evaluate JS in the page and return the result. Add frame:"<name|url-substring|index>" to run inside an iframe.', {}, {}, '', ['pup_evaluate'], like='pup_evaluate'), summary='Alias of pup_evaluate: evaluate JS in the page and return the result. Add frame:"<name|url-substring|index>" to run inside an iframe.'),+ 'pup_focus_window': lambda: dict(_entry('pup_focus_window', '', {}, {}, '', ['pup_raise_os_window'], like='pup_raise_os_window'), summary="Alias of pup_raise_os_window (foregroundReason required). It is also what a pup taskbar button's relaunch command calls when the user clicks it; a human caller gets no warning toast."),+ 'pup_dialog': lambda: _entry('pup_dialog', 'Answer a JavaScript dialog (alert, confirm, prompt, beforeunload) on the page: accept or dismiss, with promptText for a prompt.', {'action': 'accept | dismiss (default accept)', 'promptText': 'optional str: the answer typed into a prompt()', 'window': 'optional: the pup window (default the calling thread\'s)'}, {'action': 'accept'}, 'A dialog blocks every other verb on that tab until it is answered. pup_errors and the verb that tripped it name the open dialog.', ['pup_errors', 'pup_click']),+ 'pup_clear_cookies': lambda: _entry('pup_clear_cookies', "Delete cookies in the window's profile for one site (domain or origin), optionally only the named cookies.", {'domain': 'str, e.g. example.com (or origin / origins)', 'origin': 'optional str', 'origins': 'optional [str]', 'names': 'optional [str]: only these cookie names'}, {'domain': 'example.com'}, "Clears the SHARED profile's cookies for that site: every pup window on the profile is signed out of it. Use it to test a logged-out flow, not as a reset.", ['pup_clear_site_data', 'pup_credentials']),+ 'pup_clear_site_data': lambda: _entry('pup_clear_site_data', 'Clear storage for a site (cookies, localStorage, IndexedDB, cache) in the window profile.', {'origin': 'str: https://example.com (or origins)', 'origins': 'optional [str]', 'all': 'optional bool: every origin in the profile', 'everyone': 'optional bool: required with all:true because it signs every pup window out of everything'}, {'origin': 'https://example.com'}, 'all:true wipes the whole shared profile and is refused without everyone:true.', ['pup_clear_cookies']),+ 'pup_restart_browser': lambda: _entry('pup_restart_browser', "Restart pup's browser for a profile; every window on it comes back at its page, in the background.", {'profile': 'optional str (default adom-you)'}, {}, 'Use it when a lane is wedged. Windows are relaunched from their saved state; in-page drafts are lost.', ['pup_quit_idle_browsers', 'pup_readiness'], timeout=90),+ 'pup_quit_idle_browsers': lambda: _entry('pup_quit_idle_browsers', 'Quit every pup browser that has no windows (so the next open loads freshly staged extensions).', {}, {}, 'A browser with live windows is never touched.', ['pup_restart_browser']),+ 'pup_highfps_extend': lambda: _entry('pup_highfps_extend', 'Renew the highFps lease on a window that must keep rendering while in the background.', {'minutes': 'int, max 120', 'window': "optional: the pup window (default the calling thread's)"}, {'minutes': 20}, 'The lease auto-expires so a background window can never drain the battery; renew only while you still need it.', ['pup_open_window']),+ 'pup_audit_icons': lambda: _entry('pup_audit_icons', "Read back each pup window's taskbar identity from Windows and compare it with what pup stamped.", {}, {}, 'Detector only: nothing is re-stamped.', ['pup_set_identity', 'pup_sweep_identities']),+ 'pup_sweep_identities': lambda: _entry('pup_sweep_identities', 'Unregister every pup taskbar identity (registry key and any Start Menu anchor) that no live window holds, and delete stale tile/icon generations.', {}, {}, 'pup runs this a minute after every boot and releases a window identity on close (2.0.666+); on demand it is for a box that ran older builds. Returns unregistered, shortcutsRemoved, iconDirsRemoved.', ['pup_audit_icons']),+ 'credential_list': lambda: dict(_entry('credential_list', '', {}, {}, '', ['pup_credentials'], like='pup_credentials'), summary='Alias of pup_credentials: list saved logins (host and username only, never passwords).'),+ 'credential_set': lambda: _entry('credential_set', "Store a login in pup's vault for a host.", {'host': 'str', 'username': 'str', 'password': 'str (never echoed or logged)'}, {'host': 'example.com', 'username': '[email protected]', 'password': '<secret>'}, 'Matched across the site\'s whole registrable domain on return visits.', ['pup_credentials', 'credential_delete', 'pup_login']),+ 'credential_delete': lambda: _entry('credential_delete', "Remove a saved login from pup's vault.", {'host': 'str', 'username': 'optional str: only this account'}, {'host': 'example.com'}, 'Removes the saved password from the vault only; a session the profile is already signed into stays signed in (pup_clear_cookies ends that).', ['pup_credentials', 'credential_set']),+ 'pup_launch': lambda: dict(_entry('pup_launch', '', {}, {}, '', ['pup_open_window'], like='pup_open_window'), summary='Alias of pup_open_window (url defaults to about:blank).'),+}+_missing = []+for _n in _routed:+ if _n in _have: continue+ if _n in _STUBS: _verbs.append(_STUBS[_n]()); _have.add(_n)+ else: _missing.append(_n)+if _missing: raise SystemExit(f"VERB SURFACE: routed but no catalog entry: {_missing}. Add each to _STUBS in tools/gen-assets.py (pup-bridge#124).")+_notes = [v for v in _verbs if v['name'] not in _routed] + [v for v in _existing.values() if v['name'] in ('pup_NATIVE_INPUT_WARNING', 'pup_eval_frame_note') and v['name'] not in _have]+_seen = set(); _verbs = [v for v in _verbs if v['name'] in _routed and not (v['name'] in _seen or _seen.add(v['name']))]+_order = {n: i for i, n in enumerate(_routed)}; _verbs.sort(key=lambda v: _order[v['name']])+json.dump({'progressContract': _o['progressContract'], 'verbs': _verbs, 'notes': [{'name': v['name'], 'summary': v.get('summary') or v.get('hint') or ''} for v in _notes]}, open(os.path.join(root, 'rust', 'verbs', 'assets', 'verb-catalog.json'), 'w'), indent=1)+_bjp = os.path.join(root, 'src', 'bridge.json'); _bj = json.load(open(_bjp))+_bj['verbs'] = [v['name'] for v in _verbs]+open(_bjp, 'w').write(json.dumps(_bj, indent=2) + '\n')+print(f"verb-catalog.json: {len(_verbs)} verbs, {len(_notes)} notes; bridge.json verbs: {len(_bj['verbs'])} (one list)")