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.
master
John Lauer
Every file the Rust build embeds from outside rust/ (pup-bridge#118): src/extension-nbe/**, src/extension-identity/iden…
c7a8f33
1mo ago
// Adom Identity: the toolbar button IS the user. Chrome lets an extension set its action icon at any time
// (chrome.action.setIcon with imageData), so the button shows the adom-you avatar, drawn onto the same teal
// tile as its siblings, and refreshes on a timer. Before this the button was a static glyph and the avatar
// appeared only inside the popup (John, 2026-09-13).
const CANDIDATE_PORTS = [8851, 64230];
async function findPort() {
let stored = null; try { stored = (await chrome.storage.local.get('pupPort')).pupPort; } catch (e) {}
for (const port of [...new Set([stored, ...CANDIDATE_PORTS].filter(Boolean))]) {
try {
const r = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1200) });
const j = await r.json();
if (j && j.bridge === 'puppeteer') { try { chrome.storage.local.set({ pupPort: port }); } catch (e) {} return port; }
} catch (e) {}
}
return null;
}
function tile(ctx, size) {
const r = Math.round(size * 22 / 128);
ctx.fillStyle = '#00b8b1';
ctx.beginPath(); ctx.roundRect(0, 0, size, size, r); ctx.fill();
}
async function paintAvatar() {
const port = await findPort(); if (!port) return false;
let bmp;
try {
const r = await fetch(`http://127.0.0.1:${port}/identity/avatar`, { cache: 'no-store', signal: AbortSignal.timeout(6000) });
if (!r.ok) return false;
bmp = await createImageBitmap(await r.blob());
} catch (e) { return false; }
const imageData = {};
for (const size of [16, 32, 48, 128]) {
const cv = new OffscreenCanvas(size, size); const cx = cv.getContext('2d');
tile(cx, size);
// the avatar, circle-cropped, inset so a ring of tile shows: the family look, with the person in it
const inset = Math.max(1, Math.round(size * 10 / 128)); const d = size - inset * 2;
cx.save(); cx.beginPath(); cx.arc(size / 2, size / 2, d / 2, 0, Math.PI * 2); cx.clip();
const sc = Math.max(d / bmp.width, d / bmp.height);
cx.drawImage(bmp, (size - bmp.width * sc) / 2, (size - bmp.height * sc) / 2, bmp.width * sc, bmp.height * sc);
cx.restore();
imageData[size] = cx.getImageData(0, 0, size, size);
}
try { await chrome.action.setIcon({ imageData }); await chrome.action.setTitle({ title: 'Surfing as adom-you (your avatar: this window is signed in as you)' }); } catch (e) { return false; }
return true;
}
let _painted = false;
async function tick() { if (!_painted) _painted = await paintAvatar(); else await paintAvatar(); }
chrome.alarms.create('identity-avatar', { periodInMinutes: 10 });
chrome.alarms.onAlarm.addListener((a) => { if (a.name === 'identity-avatar') tick(); });
// until the first success (pup may still be starting), retry every 30 s
const retry = setInterval(async () => { if (_painted) { clearInterval(retry); return; } await tick(); }, 30000);
tick();