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.335",+ "version": "2.0.336", "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.335",+ "version": "2.0.336", "tags": [ "skills", "pup",
tools/pup-dashboard+42−12@@ -89,6 +89,8 @@ async function sendToNewThread(phrase) { // the crash family dead on the gate-fixed build (33ac2074, six raw-CDP typing helpers that ran // outside the concurrency gate), ~20 min of the kill condition with zero process deaths. if (!SAY.includes(phrase)) return { ok: false, why: 'unknown phrase' };+ const guard = await shellSupportsSend();+ if (!guard.ok) return { ok: false, why: guard.why }; // 45s, per the skill: "Opening drives the editor (extension command, dual detection, tab // placement) and legitimately takes 10-30s on a busy workbench; every CDP touch also serializes // through one gate ... A 5-10s client timeout manufactures phantom failures." The old code used@@ -105,16 +107,38 @@ async function sendToNewThread(phrase) { return { ok: true, sid: j.sid || null, deliveredTo: j.delivered_to || null }; } -// NO VERSION GATE (removed 2026-08-24). The previous build_epoch gate was measured, not guessed,-// but it gated on the wrong thing and would have refused honest sends forever: Hydrogen's control-// API exposes NO release version anywhere the dashboard can read. Measured today on AdomLapper:-// GET /version returns an endpoint list; GET /health and GET /buildinfo return only build_sha /-// build_epoch / build_stamp / build_local; and the shell page carries no version global. So-// "gate on release version >= 1.0.142" is not implementable from the browser, and gating on-// build_epoch instead pins the feature to ONE machine's incremental build clock -- every user on a-// stock signed release (whose epoch is older) would be refused even though their shell is fine.-// The failure path already handles a shell that cannot serve the call: the request fails, the toast-// says why, and the phrase is copied. That is a capability probe, and it is honest on every build.+// RELEASE-VERSION GATE, on the ONE endpoint that actually carries a release version.+// Corrected twice, so the record matters:+// build_sha - LIES on incremental builds (the build thread had to verify by code marker).+// build_epoch - ACTIVELY HARMFUL as a gate: it pins the feature to one machine's build clock,+// so every user on a stock signed release (older epoch) is refused forever.+// /version, /health, /buildinfo, page globals - carry NO release version (probed 2026-08-24).+// GET /updater/status -> updater.current_version - DOES. Measured on AdomLapper: "1.0.142".+// The gate exists because a pre-1.0.142 shell still carries the crash family that took John's+// session out this morning, and the user on a stock old release is exactly the person least able+// to diagnose it. UNREADABLE FAILS OPEN: refusing on a field we cannot read would block legitimate+// users, and the send path already fails honestly (toast + copy) when the shell cannot serve it.+const SEND_MIN_RELEASE = [1, 0, 142];+function cmpver(a, b) {+ for (let i = 0; i < 3; i++) { const d = (a[i] || 0) - (b[i] || 0); if (d) return d < 0 ? -1 : 1; }+ return 0;+}+let _shellGate = { at: 0, ok: true, why: '' };+async function shellSupportsSend() {+ if (Date.now() - _shellGate.at < 60000) return _shellGate;+ const r = await hget('/updater/status');+ const b = (r && r.json) || null;+ const u = (b && (b.updater || b)) || null;+ const raw = u && typeof u.current_version === 'string' ? u.current_version.trim() : '';+ const m = raw.match(/^(\d+)\.(\d+)\.(\d+)/);+ let out;+ if (!m) out = { ok: true, why: '' }; // endpoint down or version unparseable: FAIL OPEN+ else if (cmpver([+m[1], +m[2], +m[3]], SEND_MIN_RELEASE) < 0) {+ out = { ok: false, why: 'Your Hydrogen (' + raw + ') predates the Send to AI fix. Update to 1.0.142 or newer to enable it; the phrase was copied instead.' };+ } else out = { ok: true, why: '' };+ _shellGate = { at: Date.now(), ...out };+ return _shellGate;+} let TOOL_VERSION = '0'; try { TOOL_VERSION = String(require(path.join(__dirname, '..', 'package.json')).version || '0'); } catch (e) {}@@ -348,12 +372,18 @@ body{margin:0;background:#0d1117;color:var(--text);font:14px/1.5 var(--sans)} .play-sub{font-size:12px;color:var(--muted);margin-top:8px} /* prompts TABLE, not pills */ table.prompts{width:100%;border-collapse:collapse;table-layout:fixed}-table.prompts tr{height:40px;border-bottom:1px solid var(--line);cursor:pointer}+table.prompts tr{height:40px;border-bottom:1px solid var(--line);cursor:pointer;display:grid;grid-template-columns:minmax(0,max-content) 72px;justify-content:start;align-items:center}+/* The TABLE still spans the full width; the ROW packs its content to the left so Copy and+ Send sit just after the text they act on. Without this, deleting the layout cap put the+ icons ~2000px from their phrase at a 2500px viewport (measured). minmax(0,max-content),+ not plain max-content: the 0 floor lets the phrase cell shrink below its natural width+ at narrow viewports so the ellipsis still works, while max-content stops it from+ stretching to the far edge when there is room to spare. */ table.prompts tr:last-child{border-bottom:0} table.prompts tr:hover{background:rgba(255,255,255,.03)} table.prompts td.phrase{font-size:14px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px} table.prompts td.phrase em{color:var(--teal);font-style:normal}-table.prompts td.acts{width:72px;text-align:right;white-space:nowrap}+table.prompts td.acts{width:72px;text-align:right;white-space:nowrap;justify-self:end} table.prompts td.acts .ibtn{display:inline-flex;vertical-align:middle;margin-left:6px} .newbie.closed{display:none} /* 1.13.0: the panel FULLY closes (no residual one-liner) and the app UI moves up to fill the space;