← Commit history

Add nbrowser_describe verb: returns per-verb schemas for AD's Verbs tab (issue #13)

John Lauer ·3046b7e544 ·4mo ago ·parent d84a83f
2 files changed +59−2
bridge/bridge.json+3−2
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser (Chrome + Edge)",-  "version": "0.0.14",+  "version": "0.0.15",   "description": "Drives the user's REAL signed-in Chrome/Edge via an installed extension (native-messaging transport). Mirrors the puppeteer bridge's verb surface as nbrowser_* but runs inside the actual logged-in profile, so cookies/SSO/saved-logins/captcha human-trust all work. The bridge binds an ephemeral loopback port (PORTS.md compliant) and writes a discovery file; the native-messaging host pumps stdio <-> this bridge; the extension never touches a TCP port.",   "homepage": "https://wiki.adom.inc/adom/adom-browser-extension",   "author": "Adom Inc.",@@ -62,7 +62,8 @@     "nbrowser_flash",     "nbrowser_flash_stop",     "nbrowser_flash_mode",-    "nbrowser_taskbar"+    "nbrowser_taskbar",+    "nbrowser_describe"   ],   "dependencies": {     "node": ">=18"
bridge/server.js+56
@@ -379,6 +379,62 @@ function dispatch(verb, args) {     if (verb === 'nbrowser_help') {       return resolve({ success: true, output: JSON.stringify({ ok: true, bridge: BRIDGE, version: VERSION, extConnected: anyConnected(), flashMode, ...HELP }) });     }+    // nbrowser_describe — per-verb schemas for AD's Verbs tab + inline runner (AD v1.9.23+).+    // AD POSTs {command:"nbrowser_describe",args:{}} and renders each verb expandable with these I/O+    // docs, prefilling the runner with `example`. One entry per declared verb (+ describe itself).+    if (verb === 'nbrowser_describe') {+      const S = 'optional sessionId of a window opened via nbrowser_open_window';+      const P = 'optional profile key "chrome:<email>" / "edge:<email>" (default = active)';+      const d = (name, summary, input, output, timeoutSeconds, opts = {}) =>+        ({ name, summary, input, output, timeoutSeconds, statusVerb: opts.statusVerb || null, longRunning: !!opts.longRunning, example: opts.example || {} });+      const verbs = [+        d('nbrowser_status', 'Is the extension connected? Reports ext id/version + active browser.', {}, { ok: 'bool', ext: 'str', version: 'str', engine: 'str', browser: '"chrome"|"edge"' }, 15),+        d('nbrowser_ping', 'Round-trip liveness check through to the extension.', {}, { ok: 'bool', pong: 'bool' }, 15),+        d('nbrowser_open_window', 'Open a NEW window in the BACKGROUND (unfocused) and return a sessionId to drive it.', { url: 'required str', profile: P, focused: 'optional bool (default false = background)' }, { ok: 'bool', sessionId: 'str', tabId: 'str', windowId: 'int', url: 'str' }, 30, { example: { url: 'https://wiki.adom.inc' } }),+        d('nbrowser_close_window', 'Close a window the agent opened.', { sessionId: 'required str', profile: P }, { ok: 'bool', closed: 'bool', sessionId: 'str' }, 20, { example: { sessionId: 'work' } }),+        d('nbrowser_switch_window', 'Target a window for subsequent verbs; does NOT raise it unless focused:true.', { sessionId: 'required str', focused: 'optional bool (default false)' }, { ok: 'bool', focused: 'bool' }, 15, { example: { sessionId: 'work' } }),+        d('nbrowser_list_windows', 'List the live windows/sessions.', { profile: P }, { ok: 'bool', sessions: '[{id,windowId,tabCount,active,state,bounds,url}]', count: 'int' }, 20, { example: {} }),+        d('nbrowser_window_state', 'Set window state (fullscreen/maximized/normal/minimized) or move/resize via left/top/width/height.', { sessionId: 'required str', state: 'optional fullscreen|maximized|normal|minimized', left: 'optional int', top: 'optional int', width: 'optional int', height: 'optional int' }, { ok: 'bool', windowId: 'int', state: 'str' }, 20, { example: { sessionId: 'work', state: 'maximized' } }),+        d('nbrowser_fullscreen', 'Toggle the window to fullscreen (hides tabs+URL bar).', { sessionId: 'required str' }, { ok: 'bool', state: '"fullscreen"' }, 20, { example: { sessionId: 'work' } }),+        d('nbrowser_login_state', 'Report sign-in state for the profile.', { profile: P }, { ok: 'bool' }, 20, { example: {} }),+        d('nbrowser_credentials', "Read-only query of the browser's saved-login store (host+username only; never passwords).", { match: 'optional vendor substring', browser: 'optional "chrome"|"edge"', profile: P }, { ok: 'bool', count: 'int', credentials: '[{browser,profile,host,username}]' }, 20, { example: { match: 'digikey' } }),+        d('nbrowser_profiles', 'List connected BROWSERĂ—PROFILE targets + which is active.', {}, { ok: 'bool', profiles: '[{profile,email,browser,active,blocked,live}]', active: 'str' }, 15, { example: {} }),+        d('nbrowser_use_profile', 'Set the default profile action verbs target.', { profile: 'required str (key or email)' }, { ok: 'bool', active: 'str' }, 15, { example: { profile: 'chrome:[email protected]' } }),+        d('nbrowser_profile_block', 'Blocklist a profile so it is NEVER driven (persists).', { profile: 'required str' }, { ok: 'bool', blocklist: '[str]' }, 15, { example: { profile: 'edge:[email protected]' } }),+        d('nbrowser_profile_unblock', 'Remove a profile from the blocklist.', { profile: 'required str' }, { ok: 'bool', blocklist: '[str]' }, 15, { example: { profile: 'edge:[email protected]' } }),+        d('nbrowser_rescan', 'Reconcile the session map against live windows (drop closed).', {}, { ok: 'bool', sessions: '[...]' }, 20, { example: {} }),+        d('nbrowser_open_tab', 'Open a tab in the BACKGROUND (active:false) in a window.', { sessionId: S, url: 'required str', focused: 'optional bool (default false)' }, { ok: 'bool', sessionId: 'str', tabId: 'str', url: 'str', title: 'str' }, 30, { example: { sessionId: 'work', url: 'https://example.com' } }),+        d('nbrowser_close_tab', 'Close a tab.', { sessionId: S, tabId: 'required str' }, { ok: 'bool', tabId: 'str' }, 20, { example: { sessionId: 'work', tabId: '123' } }),+        d('nbrowser_switch_tab', 'Make a tab active within its window.', { sessionId: S, tabId: 'required str' }, { ok: 'bool', tabId: 'str' }, 15, { example: { sessionId: 'work', tabId: '123' } }),+        d('nbrowser_list_tabs', 'List tabs.', { profile: P, sessionId: S }, { ok: 'bool', tabs: '[{tabId,url,title,active}]' }, 20, { example: { sessionId: 'work' } }),+        d('nbrowser_navigate', 'Navigate the active (or given) tab to a URL.', { sessionId: S, tabId: 'optional str', url: 'required str' }, { ok: 'bool', url: 'str', tabId: 'str' }, 45, { example: { sessionId: 'work', url: 'https://example.com' } }),+        d('nbrowser_reload', 'Reload the tab.', { sessionId: S, tabId: 'optional str' }, { ok: 'bool' }, 45, { example: { sessionId: 'work' } }),+        d('nbrowser_back', 'History back.', { sessionId: S, tabId: 'optional str' }, { ok: 'bool' }, 30, { example: { sessionId: 'work' } }),+        d('nbrowser_forward', 'History forward.', { sessionId: S, tabId: 'optional str' }, { ok: 'bool' }, 30, { example: { sessionId: 'work' } }),+        d('nbrowser_wait', 'Sleep server-side for ms (or wait on the tab).', { sessionId: S, ms: 'optional int' }, { ok: 'bool' }, 60, { example: { ms: 1000 } }),+        d('nbrowser_screenshot', 'Screenshot the tab via CDP (captures a BACKGROUND/occluded window; resized <=1568).', { profile: P, sessionId: S, tabId: 'optional str', maxWidth: 'optional int' }, { ok: 'bool', base64: 'str', filePath: 'str?' }, 45, { example: { sessionId: 'work', maxWidth: 1500 } }),+        d('nbrowser_screenshot_full_res', 'Full-resolution / full-page screenshot via CDP.', { profile: P, sessionId: S, tabId: 'optional str', fullPage: 'optional bool' }, { ok: 'bool', base64: 'str' }, 60, { example: { sessionId: 'work', fullPage: true } }),+        d('nbrowser_eval', 'Run JS in the page context and return the result.', { sessionId: S, tabId: 'optional str', expression: 'required str (JS)' }, { ok: 'bool', result: 'any' }, 45, { example: { sessionId: 'work', expression: 'document.title' } }),+        d('nbrowser_evaluate', 'Alias of nbrowser_eval.', { sessionId: S, tabId: 'optional str', expression: 'required str (JS)' }, { ok: 'bool', result: 'any' }, 45, { example: { sessionId: 'work', expression: '1+1' } }),+        d('nbrowser_input_dispatch', 'Low-level isTrusted CDP input.', { sessionId: S, tabId: 'optional str', type: 'required "click"|"move"|"type"|"key"', x: 'int?', y: 'int?', text: 'str?', key: 'str?' }, { ok: 'bool' }, 30, { example: { sessionId: 'work', type: 'click', x: 200, y: 300 } }),+        d('nbrowser_click', 'Click by visible text / selector / xy; optional gliding cursor + intent label.', { sessionId: S, tabId: 'optional str', text: 'optional str', selector: 'optional str', x: 'int?', y: 'int?', cursor: 'optional bool', intent: 'optional str' }, { ok: 'bool', clicked: 'bool' }, 30, { example: { sessionId: 'work', text: 'Sign in', cursor: true, intent: 'Clicking Sign in' } }),+        d('nbrowser_type', 'Type text into the focused field (isTrusted).', { sessionId: S, tabId: 'optional str', text: 'required str' }, { ok: 'bool' }, 30, { example: { sessionId: 'work', text: 'hello world' } }),+        d('nbrowser_press_key', 'Press a key (Enter, Tab, etc.).', { sessionId: S, tabId: 'optional str', key: 'required str' }, { ok: 'bool' }, 30, { example: { sessionId: 'work', key: 'Enter' } }),+        d('nbrowser_fetch_url', "Fetch a URL using the profile's real cookies/session; optional save to desktop.", { url: 'required str', profile: P, desktopSaveTo: 'optional Windows path' }, { ok: 'bool', status: 'int', body: 'str?', savedTo: 'str?' }, 60, { longRunning: true, example: { url: 'https://example.com/api.json' } }),+        d('nbrowser_errors', 'Return buffered page/console errors for the tab.', { sessionId: S, tabId: 'optional str' }, { ok: 'bool', errors: '[...]' }, 20, { example: { sessionId: 'work' } }),+        d('nbrowser_close', 'Detach the debugger / close managed windows. No sessionId = all.', { sessionId: 'optional str' }, { ok: 'bool' }, 20, { example: {} }),+        d('nbrowser_record_start', 'Start a background window recording (method:"wgc" = native WGC, no picker/banner, captures occluded).', { method: 'required "wgc"|"getdisplaymedia"|"tabcapture"', hwnd: 'int? (or titleContains)', titleContains: 'str?', fps: 'optional int (default 30)', codec: 'optional "h264"|"hevc"' }, { ok: 'bool', recordingId: 'str', path: 'str' }, 60, { statusVerb: 'nbrowser_record_status', longRunning: true, example: { method: 'wgc', titleContains: 'chip-fetcher' } }),+        d('nbrowser_record_stop', 'Stop a recording and finalize the mp4.', { method: 'required "wgc"', recordingId: 'required str' }, { ok: 'bool', path: 'str', frames: 'int' }, 60, { example: { method: 'wgc', recordingId: 'rec-...' } }),+        d('nbrowser_record_status', 'Poll a recording.', { method: 'required "wgc"', recordingId: 'optional str' }, { ok: 'bool', status: 'str' }, 20, { example: { method: 'wgc' } }),+        d('nbrowser_record_list', 'List recent recordings.', {}, { ok: 'bool', recordings: '[...]' }, 20, { example: {} }),+        d('nbrowser_flash', "Flash the driven window's taskbar button ('come look' cue).", { sessionId: 'required str' }, { ok: 'bool' }, 15, { example: { sessionId: 'work' } }),+        d('nbrowser_flash_stop', 'Stop the taskbar flash.', { sessionId: 'required str' }, { ok: 'bool' }, 15, { example: { sessionId: 'work' } }),+        d('nbrowser_flash_mode', 'Set auto-flash verbosity.', { mode: 'required "quiet"|"chatty"|"off"' }, { ok: 'bool', mode: 'str' }, 15, { example: { mode: 'quiet' } }),+        d('nbrowser_taskbar', 'Paint progress/overlay/flash on the window taskbar button.', { sessionId: 'required str', progress: 'optional {state,value?}', overlay: 'optional {badge}', flash: 'optional "until_focused"|"stop"' }, { ok: 'bool', applied: '[str]' }, 15, { example: { sessionId: 'work', progress: { state: 'indeterminate' } } }),+        d('nbrowser_describe', 'This verb: returns per-verb schemas for the AD Verbs tab.', {}, { success: 'bool', verbs: '[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example}]' }, 15, { example: {} }),+      ];+      return resolve({ success: true, verbs });+    }     // --- multi-profile management (bridge-local) ---     if (verb === 'nbrowser_profiles') {       const list = [...conns.entries()].map(([k, c]) => ({ profile: k, label: profileLabel(c.profile), email: c.profile.email || '', browser: c.profile.browser || 'chrome', active: k === activeProfile, blocked: isBlocked(k), live: !!(c.sock && !c.sock.destroyed) }));