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
ship.sh moves into the repo (it kept evaporating from /tmp); taskbar-states: the foreground skip is now a bounded retry, not a permanent leak; selftest: the icon-audit procedure
3 files changed
+116−5
dev-skills/pup-selftest/SKILL.md+9@@ -65,6 +65,15 @@ a failed walk. - After a respawn, WAIT for re-stamps before judging icons: sessions reload with `_curAppId=null` and every identity check lies until the first stamp lands (see #51's prune gate). +## The icon audit (run it when John says "audit your taskbar icons")++UIA names + rects -> fresh tray capture -> red box EVERY pup button -> 8x crops captioned with the+real button name -> compare each against the mode's contract (tiles = thread-name tile; aumid =+Adom Pup category; plain = one stock CfT group). Two failure classes only this catches:+a button whose NAME is stock while its ART is pup's (identity stamped, shell never rebuilt), and+pup's own chip reporting green while the glass disagrees. Cross-check pup_status sessions against+UIA button count: a session with no button of its own in tiles mode IS the finding.+ ## Standing traps this file exists to remember - **Version lockstep**: ship → `bridge_install` → KILL the node process → verify
skills/pup-taskbar-states/SKILL.md+12−5@@ -54,13 +54,20 @@ preview is DERIVED from the same sources listed here, so preview and reality can - Windows with no owning AI thread (rare) fall back to aumid-style identity even in tiles mode. - Everything is Windows-only; on other platforms these settings store but paint nothing. -## The foreground-window tradeoff (by design)+## The foreground-window tradeoff (bounded, and it self-heals for real now) A mode switch rebuilds every button via a brief hide+re-show — EXCEPT the window you are actively-using: pup will not blink the foreground window. Its identity is wiped/stamped immediately, but its-BUTTON keeps the previous art until the window's next natural minimize/restore or focus change.-So after a switch, one button (the window you were looking at) can lag the new mode briefly — that-is the deliberate cost of never disturbing the active window.+using: pup will not blink the foreground window. Its identity is stamped immediately, but its+BUTTON keeps the previous name and art until that window stops being focused.++**This used to be permanent** (found by John's icon audit, 2026-08-18: a thread's window sat under+the stock "Google Chrome for Testing" button wearing stale teal art, hours after it was stamped).+The skip was a dead end — nothing ever retried it, despite this section once claiming it healed on+the next focus change. Since 2.0.227 a skipped nudge becomes a PENDING state with a bounded backoff+(6s, 15s, 45s, 2m, 5m); each retry re-checks the foreground, so a window you keep using simply+re-queues, and the button rebuilds the moment you look away. While it is pending, the dashboard+marks that button RED with the reason — pup never reports a green match against a taskbar it knows+it has not applied. ## Known state gaps (tracked)
tools/ship.shadded+95@@ -0,0 +1,95 @@+#!/usr/bin/env bash+# ship.sh — THE publish pipeline for this bridge, living IN THE REPO.+#+# It used to live in /tmp and evaporated with every /tmp clear (twice now, mid-session), so the+# recipe in PUBLISHING.md had to be hand-run from memory. It belongs here.+#+# tools/ship.sh <version> "<changelog sentence>" [PUBLISH_PKG=0|1]+#+# Steps: verify the tree parses -> bump BRIDGE_VERSION + bridge.json (lockstep) -> zip src/ at the+# zip ROOT (no node_modules) -> release create/upload -> point the manifest at the release asset with+# its REAL sha256/size -> repo push the text files -> DIFF-VERIFY the version actually landed.+#+# Env: FORCE_PUSH=1 pushes even when the working tree looks unchanged.+set -euo pipefail+VER="${1:?usage: tools/ship.sh <version> \"<changelog>\" [publish_pkg]}"+LOG="${2:?changelog sentence required}"+PUBLISH_PKG="${3:-0}"+REPO="$(cd "$(dirname "$0")/.." && pwd)"+PAGE="adom/pup-bridge"+ZIP="adom-bridge-puppeteer-v${VER}.zip"+cd "$REPO"++echo "== 0. static gates =="+node --check src/server.js+node tests/check_dashboard_script.js >/dev/null+echo " server.js + served dashboard script parse"++echo "== 1. version lockstep =="+printf '%s' "$VER" > src/BRIDGE_VERSION+python3 - "$VER" <<'PY'+import json, sys+v = sys.argv[1]+p = 'src/bridge.json'+d = json.load(open(p))+d['version'] = v+json.dump(d, open(p, 'w'), indent=2)+open(p, 'a').write('\n')+print(f" src/BRIDGE_VERSION + src/bridge.json -> {v}")+PY++echo "== 2. build the zip (src/ at zip root, no node_modules) =="+python3 - "$ZIP" <<'PY'+import zipfile, os, hashlib, sys+out = sys.argv[1]+with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as z:+ for dirpath, dirs, files in os.walk('src'):+ if 'node_modules' in dirs: dirs.remove('node_modules')+ for f in files:+ full = os.path.join(dirpath, f)+ z.write(full, os.path.relpath(full, 'src'))+print(f" {out} sha256={hashlib.sha256(open(out,'rb').read()).hexdigest()} size={os.path.getsize(out)}")+PY++echo "== 3. release =="+adom-wiki release create "$PAGE" "$VER" >/dev/null 2>&1 || true+UP=$(adom-wiki release upload "$PAGE" "$VER" "$ZIP" --json 2>&1 | tail -c 4000)+python3 - "$UP" "$VER" "$ZIP" <<'PY'+import json, sys, re, datetime+raw, ver, zipname = sys.argv[1], sys.argv[2], sys.argv[3]+i = raw.find('{')+d = json.loads(raw[i:]) if i >= 0 else {}+a = d.get('data') or d.get('asset') or d+sha, size = a.get('sha256'), a.get('size')+url = a.get('download_url') or f"https://wiki.adom.inc/download/adom/pup-bridge/{ver}/{zipname}"+if not url.startswith('http'): url = 'https://wiki.adom.inc' + url+if not (sha and size): # fall back to the local file (same bytes we just uploaded)+ import hashlib, os+ sha = hashlib.sha256(open(zipname, 'rb').read()).hexdigest(); size = os.path.getsize(zipname)+m = json.load(open('adom-bridge-puppeteer-manifest.json'))+m.update({'version': ver, 'url': url, 'sha256': sha, 'size': size,+ 'released_at': datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')})+json.dump(m, open('adom-bridge-puppeteer-manifest.json', 'w'), indent=2)+open('adom-bridge-puppeteer-manifest.json', 'a').write('\n')+print(f" manifest -> {url}\n sha256={sha} size={size}")+PY++echo "== 4. push the text files =="+adom-wiki repo push "$PAGE" \+ --files adom-bridge-puppeteer-manifest.json src/bridge.json src/BRIDGE_VERSION src/server.js src/chrome.js SKILL.md \+ -m "v${VER}: ${LOG}" >/dev/null 2>&1 || {+ [ "${FORCE_PUSH:-0}" = "1" ] || { echo " PUSH FAILED (set FORCE_PUSH=1 to retry)"; exit 1; }+ }++if [ "$PUBLISH_PKG" = "1" ]; then+ echo "== 4b. pkg publish =="+ adom-wiki pkg publish --org adom >/dev/null 2>&1 || echo " (pkg publish skipped/failed — non-fatal)"+fi++echo "== 5. DIFF-VERIFY (a no-op ship is the classic silent failure) =="+ON_DISK=$(cat src/BRIDGE_VERSION)+MAN=$(python3 -c "import json;print(json.load(open('adom-bridge-puppeteer-manifest.json'))['version'])")+BJ=$(python3 -c "import json;print(json.load(open('src/bridge.json'))['version'])")+[ "$ON_DISK" = "$VER" ] && [ "$MAN" = "$VER" ] && [ "$BJ" = "$VER" ] \+ && echo "VERIFIED $VER on disk (BRIDGE_VERSION + bridge.json + manifest in lockstep)" \+ || { echo "LOCKSTEP BROKEN: disk=$ON_DISK bridge.json=$BJ manifest=$MAN"; exit 1; }