← Commit history

Publish 2.0.320

John Lauer ·85f9e934ff ·1mo ago ·parent 73cb1cb
3 files changed +48−26
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "pup-bridge",-  "version": "2.0.319",+  "version": "2.0.320",   "type": "app",   "description": "The pup SKILL PACK \u2014 the AI skills that teach a cloud/container AI how to drive the Adom Desktop Puppeteer (pup) bridge (browser_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, browsers+Chrome install, and driving the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY \u2014 this is NOT the bridge runtime. The bridge itself (the Node program that drives the browser) runs on the DESKTOP: Adom Desktop bundles it and auto-installs/updates it, and it's published separately on the Releases tab as adom-bridge-puppeteer-v<ver>.zip. The pkg version is kept in lockstep with the Release version so the page's max version row always carries BOTH a pkg tarball (skills) and a runtime zip (bridge) \u2014 a skills-only or runtime-only bump on the top row breaks constraint installs. TL;DR: pkg install = the skills for your AI; the bridge = the Release zip, which Adom Desktop manages for you.",   "keywords": [
page.json+1−1
@@ -4,7 +4,7 @@   "slug": "pup-bridge",   "title": "Pup - Puppeteer Bridge",   "brief": "pup is the AI's own browser: a real, full Chrome on the user's desktop that the AI fully controls (a sandbox, not the user's signed-in browser). Rides Bridge; pup_* verbs open windows and tabs, navigate, screenshot, and eval JS.",-  "version": "2.0.319",+  "version": "2.0.320",   "tags": [     "skills",     "pup",
tools/pup-dashboard+46−24
@@ -371,7 +371,7 @@ async function runDemo() {         execFile('adom-tts', ['say', text, '--out', path.join(os.tmpdir(), 'pup-demo-line.mp3'), '--thread', 'pup demo', '--desc', 'pup demo narration: ' + text.slice(0, 60), '--play'],           { env: ENV, timeout: 45000 }, (err) => resolve(!err));       });-      if (r) { await sleep(Math.max(1500, text.split(/\s+/).length * 330)); return; }   // let the clip play out+      if (r) { await sleep(Math.min(6000, Math.max(1000, text.split(/\s+/).length * 240))); return; }   // let the clip play out, briskly       voice = 'sapi'; lines.push('   (adom-tts unavailable; falling back to the system voice)');     }     const safe = text.replace(/[`$"\\]/g, ' ').replace(/'/g, "''");@@ -384,47 +384,51 @@ async function runDemo() {     return ab('desktop_caption', { text, duration: ms, id: 'pup-demo', position: 'bottom', size: 'medium' }, 8000, tgt.name).then(() => ms);   };   const clearCap = () => ab('desktop_caption', { text: ' ', duration: 300, id: 'pup-demo', position: 'bottom', size: 'medium' }, 8000, tgt.name).catch(() => {});-  const say = async (text, extra) => { await Promise.all([cap(text), speak(text)]); await sleep(extra || 400); };+  const say = async (text, extra) => { await Promise.all([cap(text), speak(text)]); await sleep(extra || 150); }; -  await say('Hi, this is the pup demo. I am an A I browser that lives on your desktop. Watch, I will narrate everything I do.');-  await say('First: I am opening a browser window in the background. Nothing pops over your work, ever. Look at your taskbar: a new teal button just appeared wearing my thread name, pup dock demo, with the page icon badged on its corner.', 200);+  await say('Hi, this is the pup demo: an A I browser living on your desktop. I will narrate everything I do.');+  await say('Opening a window in the BACKGROUND: nothing pops over your work. Look at your taskbar: a new teal button wearing my thread name, with the page icon badged on it.', 100);   const open = await ab('pup_open_window', { sessionId: sid, url: 'https://wiki.adom.inc/adom/pup-bridge', owner: 'pup dock demo' }, 120000, tgt.name);   if (open._err) { await say('I could not open a window: ' + open._err); return lines; }   lines.push('   open: ' + (open.sessionId || sid));    // THE PROGRESS BAR, driven visibly (John: "show how you drive the progress bar... explain you do   // that for the length of any verb or long running process").-  const wi = await ab('pup_window_info', { sessionId: sid }, 20000, tgt.name);-  const hwnd = wi && wi.hwnd;+  let hwnd = null;+  for (let i = 0; i < 4 && !hwnd; i++) { const wi = await ab('pup_window_info', { sessionId: sid }, 20000, tgt.name); hwnd = wi && wi.hwnd; if (!hwnd) await sleep(1500); }   if (hwnd) {-    await say('Now watch that button closely. I am driving its PROGRESS BAR by hand so you can see it: pup paints one of these under the button for the length of ANY operation, a screenshot, an icon apply, a page load, any long-running verb, sized to how long that operation usually takes.', 0);-    for (const v of [12, 30, 48, 66, 82, 95, 100]) { await ab('desktop_taskbar', { hwnd, progress: { state: 'normal', value: v } }, 8000, tgt.name); await sleep(700); }+    await say('Watch that button: I am driving its PROGRESS BAR by hand. pup runs one of these for the length of ANY verb or long operation, sized to how long it usually takes.', 0);+    for (const v of [15, 40, 65, 85, 100]) { await ab('desktop_taskbar', { hwnd, progress: { state: 'normal', value: v } }, 8000, tgt.name); await sleep(550); }     await sleep(400);     await ab('desktop_taskbar', { hwnd, progress: { state: 'none' } }, 8000, tgt.name);-    await say('And done: the bar fills, completes, and clears. That is how you always know when I am working on a window and roughly how far along I am. No orange flashing at you.');+    await say('Fills, completes, clears: that is how you always know when I am working on a window. No orange flashing.');   } else lines.push('   (no hwnd resolved yet; skipped the manual progress sweep)'); -  // The raise: ONE caption only (the foreground caption IS the narration; no demo caption under it).+  // The raise: ONE caption on screen at a time, ever. My caption channel goes quiet, the voice+  // narrates, the raise paints ITS caption, and I wait out that caption's word-scaled duration+  // (plus margin) before my channel speaks with a caption again. (v2.0.320: John saw my next+  // caption land on top of the still-visible foreground caption.)   await clearCap();   const raiseWhy = 'the pup demo you started: bringing the window up so you can watch me drive it';-  await Promise.all([-    speak('You started this demo, so let me bring the window up where you can see it. pup always shows a caption first, saying who is foregrounding a window and why. Here it comes.'),-    (async () => { lines.push('   [caption via raise] ' + raiseWhy); })(),-  ]);+  await speak('Bringing the window up so you can watch. pup always captions a foreground first: who, and why. Here it comes.');+  lines.push('   [caption via raise] ' + raiseWhy);+  const fgCapMs = Math.min(12000, Math.max(2500, 900 + ('AI thread pup dock demo brought ' + sid + ' to the front. Reason: ' + raiseWhy).split(/\s+/).length * 350));   await ab('pup_raise_os_window', { sessionId: sid, foregroundReason: raiseWhy }, 30000, tgt.name);-  await sleep(900);+  const fgShownAt = Date.now();   await ab('pup_hover', { sessionId: sid, x: 700, y: 300, intent: 'this is my cursor: it glides and narrates my intent', steps: 16 }, 20000, tgt.name);-  await say('That teal cursor is me. It glides to wherever I am about to act and says why, so you can always tell what your A I is doing.');+  await speak('That teal cursor is me: it glides to where I am about to act and says why.');+  const fgLeft = fgCapMs + 700 - (Date.now() - fgShownAt);+  if (fgLeft > 0) await sleep(fgLeft);   // never two captions: wait out the foreground caption    // The three toolbar tiles (John: "you should have shown the 3 browser extensions").   await ab('pup_hover', { sessionId: sid, x: 1180, y: 8, intent: 'look UP at the toolbar, right of the address bar', steps: 14 }, 20000, tgt.name).catch(() => {});-  await say('Now look at the TOP RIGHT of this window, next to the address bar: three teal tiles. The pup dots are my menu: annotate, snip, switch wiki views, open my dashboard. The pencil starts the annotation pen with one click. And the third tile is your adom you identity: your avatar means this window is signed in as you; windows that are NOT you wear an anonymous mark instead.', 300);+  await say('Top right, next to the address bar: three teal tiles. The pup dots are my menu. The pencil starts annotation. The avatar is your adom you identity: this window is signed in as YOU.', 150); -  await say('Taking a screenshot now. Watch the taskbar button: the same progress bar runs on its own while I capture. I can do this even when the window is buried in the background.');+  await say('Screenshot now: the same progress bar runs on its own while I capture, even with the window buried in the background.');   const shot = await ab('pup_screenshot', { sessionId: sid }, 60000, tgt.name);   lines.push(shot._err ? '   screenshot failed: ' + shot._err : '   screenshot: ' + shot.width + 'x' + shot.height); -  await say('Now the annotation pen. I will draw on this live page, slowly, in red. Watch the circle.');+  await say('Now why annotation exists: FEEDBACK to your A I. Circle the thing you mean, and the capture goes to the A I thread that owns this window, and onto your clipboard for any A I chat. Watch.');   const arm = await ab('pup_annotate', { sessionId: sid, tool: 'pen' }, 30000, tgt.name);   if (!arm._err) {     const draw = "(async()=>{const r=document.getElementById('__pup_annotate');if(!r)return 'no overlay';const cv=r.querySelector('canvas');if(!cv)return 'no canvas';const mk=(t,x,y)=>new PointerEvent(t,{bubbles:true,cancelable:true,clientX:x,clientY:y,pointerId:1,isPrimary:true,button:0,buttons:t==='pointerup'?0:1});const cx=Math.round(innerWidth*0.35),cy=Math.round(innerHeight*0.4),rad=Math.min(150,Math.round(innerHeight*0.2));cv.dispatchEvent(mk('pointerdown',cx+rad,cy));for(let a=0.12;a<=6.55;a+=0.12){cv.dispatchEvent(mk('pointermove',cx+rad*Math.cos(a),cy+rad*Math.sin(a)));await new Promise(q=>setTimeout(q,55));}cv.dispatchEvent(mk('pointerup',cx+rad,cy));return 'drew';})()";@@ -432,16 +436,34 @@ async function runDemo() {     const drewOk = !drew._err && String(drew.result || '') === 'drew';     if (drewOk) {       lines.push('   drew the red circle live');-      await say('Done. The moment the pen lifted, I captured the circled region and put it on your clipboard, with a caption strip naming this thread and page. Paste it anywhere. The ink fades; the capture is yours.');+      await say('Pen up: captured. It is on your clipboard with a strip naming this thread and page, and the owning A I thread sees it. Your A I now knows exactly what you meant.');     } else { lines.push('   draw did not run: ' + (drew._err || drew.result || 'unknown')); await say('The drawing step did not run on this page, so I will move on.'); }   } else { lines.push('   annotate did not arm: ' + arm._err); await say('The annotation overlay did not arm on this page, so I will move on.'); } -  await say('One more thing: right-click my taskbar button any time for the menu: annotate this page, switch wiki views, open my dashboard, or close every pup window at once.');+  // Auto-open the right-click menu for real (John: "why are you not auto-opening those menus"):+  // find the button's rectangle via UIAutomation, right-click it twice (the shell's known first-+  // click blip), let the menu sit on screen, then dismiss it.+  await say('Every pup button has a right-click menu. Opening it for you now, watch the bottom of your screen.', 0);+  const uia = "Add-Type -AssemblyName UIAutomationClient; $tb=[System.Windows.Automation.AutomationElement]::RootElement.FindFirst('Children',(New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ClassNameProperty,'Shell_TrayWnd'))); $btn=$tb.FindAll('Descendants',(New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty,[System.Windows.Automation.ControlType]::Button))) | Where-Object { $_.Current.Name -like '*pup dock demo*' } | Select-Object -First 1; if ($btn) { $r=$btn.Current.BoundingRectangle; [int]($r.X+$r.Width/2); [int]($r.Y+$r.Height/2) }";+  const rc = await ab('desktop_run_script', { language: 'powershell', body: uia, timeoutSeconds: 30 }, 35000, tgt.name);+  const nums = String(rc.stdout || '').trim().split(/\r?\n/).map(Number).filter(Number.isFinite);+  if (nums.length >= 2) {+    await ab('desktop_right_click', { x: nums[0], y: nums[1] }, 15000, tgt.name); await sleep(1200);+    await ab('desktop_right_click', { x: nums[0], y: nums[1] }, 15000, tgt.name); await sleep(600);+    await say('There: annotate, switch wiki views, my dashboard, move to workspace, close all. One right-click away.', 400);+    await ab('desktop_press_key', { key: 'Escape' }, 10000, tgt.name).catch(() => {});+    lines.push('   opened the jump list at ' + nums[0] + ',' + nums[1]);+  } else {+    lines.push('   (jump-list button rect not found; narrated instead)');+    await say('It holds: annotate this page, switch wiki views, open my dashboard, and close every pup window at once.');+  }   // cursor OFF before leaving (John: "you never turned the cursor off")   await ab('pup_evaluate', { sessionId: sid, expression: "['__adom_cursor__','__pupCursor','__adom_cursor_ring__','__adom_cursor_label__'].forEach(id=>{const e=document.getElementById(id);if(e)e.remove();});'cursor off'" }, 15000, tgt.name).catch(() => {});-  await say('And now I get out of your way: cursor off, back to the background. That is pup. Say, show me in pup, to any Adom A I.');-  await ab('pup_lower_os_window', { sessionId: sid }, 20000, tgt.name);-  await cap('pup demo done. The window stays open in the background; close it from its right-click menu, or say: close the pup demo window.');+  await say('And when I am done, I clean up after myself: closing the window now. Watch the button leave your taskbar.');+  await ab('pup_close_window', { sessionId: sid }, 30000, tgt.name);+  lines.push('   closed the demo window');+  await cap('pup demo done: window closed, taskbar clean. Say, show me in pup, to any Adom AI.');+  await speak('That is pup. Say, show me in pup, to any Adom A I.');   return lines; }