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
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "pup-bridge",- "version": "2.0.317",+ "version": "2.0.318", "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.317",+ "version": "2.0.318", "tags": [ "skills", "pup",
tools/pup-dashboard+52−35@@ -333,64 +333,81 @@ load(); setInterval(load,15000); </script>`; // ── demo ────────────────────────────────────────────────────────────────────────────────────────+async function demoTarget() {+ // v2.0.318 (John ran the demo and saw NOTHING: with two desktops connected the demo drove the+ // FIRST target, which was the other machine). The user is at the desktop they just clicked+ // "Run the demo" on, and that machine's input-idle is seconds while the other's is minutes:+ // pick the target with the LOWEST desktop_user_idle_ms.+ const t = await resolveTarget();+ if (!t.all || t.all.length <= 1) return t;+ const idles = await Promise.all(t.all.map(async (name) => {+ const r = await ab('desktop_user_idle_ms', {}, 6000, name);+ const ms = Number(r.idleMs != null ? r.idleMs : (r.ms != null ? r.ms : NaN));+ return { name, idle: Number.isFinite(ms) ? ms : Infinity };+ }));+ idles.sort((a, b) => a.idle - b.idle);+ return { name: idles[0].name, all: t.all, at: Date.now(), idles };+}+ async function runDemo() {- // The dock-card showcase. v2.0.317 (John: "you lost me in that demo. i had no idea what was going- // on... show non-stop ab captions step by step, take over the cursor so i see you moving"): the- // narration lives ON THE DESKTOP now. Every act announces itself with an on-screen caption BEFORE- // it happens (duration scaled to reading speed), the foreground act glides the visible Adom cursor- // with intent labels, and the drawing is slow enough to watch. The console lines mirror the- // captions for the record; the show itself is on screen.+ // v2.0.318 (John: "be more in-your-face: bring the pup window to foreground, show more ab+ // captions, maybe even do some text to speech"). The demo now plays on the desktop the user is+ // actually AT (lowest input idle), narrates every act with an on-screen caption AND speaks it+ // (Windows SAPI through ab), and brings the window up early since the user asked for a show. const lines = []; const sid = 'pup-dock-demo'; const sleep = (ms) => new Promise(r => setTimeout(r, ms));- const tgt = await resolveTarget();- const cap = async (text) => {+ const tgt = await demoTarget();+ lines.push('Playing on desktop: ' + tgt.name + (tgt.idles ? ' (you are at it: idle ' + Math.round(tgt.idles[0].idle / 1000) + 's; ' + tgt.idles.slice(1).map(i => i.name + ' idle ' + Math.round(i.idle / 1000) + 's').join(', ') + ')' : ''));+ let ttsOk = true;+ const speak = (text) => {+ if (!ttsOk) return Promise.resolve();+ const safe = text.replace(/[`$"\\]/g, ' ').replace(/'/g, "''");+ return ab('desktop_run_script', { language: 'powershell', body: "Add-Type -AssemblyName System.Speech; $v = New-Object System.Speech.Synthesis.SpeechSynthesizer; $v.Rate = 1; $v.Speak('" + safe + "')", timeoutSeconds: 30 }, 35000, tgt.name).then(r => { if (r._err) ttsOk = false; });+ };+ const cap = (text) => { const ms = Math.min(12000, Math.max(2500, 900 + text.trim().split(/\s+/).length * 350));- await ab('desktop_caption', { text, duration: ms, id: 'pup-demo', position: 'bottom', size: 'medium' }, 8000, tgt.name); lines.push(' [caption] ' + text);- return ms;+ return ab('desktop_caption', { text, duration: ms, id: 'pup-demo', position: 'bottom', size: 'medium' }, 8000, tgt.name).then(() => ms); };- const capWait = async (text, extra) => { const ms = await cap(text); await sleep(ms + (extra || 0)); };+ // caption AND voice together; the spoken line paces the step (speech is slower than reading)+ const say = async (text, extra) => { const [ms] = await Promise.all([cap(text), speak(text)]); await sleep(extra || 400); return ms; }; - lines.push('The demo narrates ON YOUR SCREEN with captions, step by step. Watch the desktop.');- await capWait('pup demo, step 1 of 6: I am opening a browser window IN THE BACKGROUND. Watch your TASKBAR, not your screen. Nothing will pop over your work.', 500);+ await say('Hi, this is the pup demo. I am an AI 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); 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 cap('The demo could not open a window: ' + open._err); lines.push(' could not open: ' + open._err); return lines; }+ if (open._err) { await say('I could not open a window: ' + open._err); return lines; } lines.push(' open: ' + (open.sessionId || sid));+ await say('The little bar filling under that button is my progress signal. I time every operation and fill the bar over how long it usually takes. No orange flashing at you.'); - await capWait('Step 2: look at the NEW TASKBAR BUTTON at the bottom of your screen. It is a teal tile wearing this thread\'s name, "pup dock demo", with the page favicon badged on its corner. Every AI thread gets its own named button.', 1000);- await capWait('The small bar FILLING UNDER that button is pup\'s progress signal: it times every operation and fills over how long it usually takes. No orange flashing.', 800);+ await say('You started this demo, so let me bring the window up where you can see it. I always show a caption first saying who is foregrounding and why, like the one coming now.');+ await ab('pup_raise_os_window', { sessionId: sid, foregroundReason: 'the pup demo you started: watch me drive this window in front of you' }, 30000, tgt.name);+ await sleep(700);+ 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 AI is doing.'); - await capWait('Step 3: I am taking a SCREENSHOT of that background window right now. Watch the progress bar run under its button. Your screen is not touched: the page renders off-screen.', 300);+ await say('Taking a screenshot now. Watch the taskbar button: the progress bar runs while I capture. I can do this even when the window is 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 capWait('Step 4: that button also carries a RIGHT-CLICK menu. Try it later: Annotate this page, switch wiki views, open the pup dashboard, move the page to your workspace, close all pup windows.', 500);-- await capWait('Step 5, the finale: I am about to bring the window UP ON YOUR SCREEN to demonstrate annotation. pup always captions a foreground with who is doing it and why, like this.', 500);- await ab('pup_raise_os_window', { sessionId: sid, foregroundReason: 'the pup demo you started: watch the annotation feature draw on this page' }, 30000, tgt.name);- await sleep(800);- // glide the visible Adom cursor so the eye lands where the action will be- await ab('pup_hover', { sessionId: sid, x: 700, y: 380, intent: 'the demo is about to draw HERE', steps: 14 }, 20000, tgt.name);- await sleep(600);+ await say('Now the annotation pen. I will draw on this live page, slowly, in red. Watch the circle.'); const arm = await ab('pup_annotate', { sessionId: sid, tool: 'pen' }, 30000, tgt.name); if (!arm._err) {- await cap('Drawing on the live page with the annotation pen now. Watch the red ink.');- await sleep(400);+ await ab('pup_hover', { sessionId: sid, x: 620, y: 380, intent: 'drawing here', steps: 10 }, 20000, tgt.name).catch(() => {}); 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';})()"; const drew = await ab('pup_evaluate', { sessionId: sid, expression: draw }, 40000, tgt.name); const drewOk = !drew._err && String(drew.result || '') === 'drew'; if (drewOk) { lines.push(' drew the red circle live');- await capWait('The pen-up CAPTURED the circled region: it is on your CLIPBOARD right now, with a caption strip naming the thread and the page. Paste it into any AI chat. The ink fades; the capture persists.', 1200);- } else { lines.push(' annotation draw did not run: ' + (drew._err || drew.result || 'unknown')); await cap('The drawing step did not run on this page; the rest of the demo continues.'); }- } else { lines.push(' annotate did not arm: ' + arm._err); await cap('The annotation overlay did not arm on this page; the rest of the demo continues.'); }- await capWait('Step 6: and back to the BACKGROUND, out of your way. The window stays open. That is pup: real browser windows an AI drives without ever stealing your screen.', 300);+ 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.');+ } 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.');+ await say('And now I get out of your way: back to the background. That is pup. Say, show me in pup, to any Adom AI.'); await ab('pup_lower_os_window', { sessionId: sid }, 20000, tgt.name);- await cap('pup demo done. Right-click the "pup dock demo" taskbar button to explore, or say: close the pup demo window.');- lines.push('');- lines.push('Not shown: window recording (full frame rate in the background with highFps), the');- lines.push('credential vault that signs into sites with no Chrome popups. Say "show me in pup".');+ 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.');+ if (!ttsOk) lines.push(' (voice narration unavailable on this machine; captions carried the demo)'); return lines; }