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
dev/publish skills are now source-only reference docs (not SKILL.md) so the wiki won't index/list/install them; everyday users get only the pup skill via the pkg
3 files changed
+263
dev-skills/README.mdadded+17@@ -0,0 +1,17 @@+# Developer skills (source-only)++Reference skills for people **building / publishing** the pup bridge — NOT everyday users (they want+the `pup` skill, which installs via the pkg). These are intentionally **not named `SKILL.md`** so the+wiki does not index them as installable skills or list them on the page's Skills tab. You only get them+by checking out the source: `adom-wiki repo clone adom/adom-desktop-puppeteer-bridge`.++- **`pup-bridge-dev.md`** — architecture, ownership boundary, the 3-artifact model, cold-start+ internals, and the auto-discover → auto-install flow.+- **`pup-bridge-publish.md`** — the exact publish recipe + every wiki gotcha hit live.++To load one as a live Claude/Codex skill while developing this bridge:++```bash+mkdir -p ~/.claude/skills/pup-bridge-dev && cp dev-skills/pup-bridge-dev.md ~/.claude/skills/pup-bridge-dev/SKILL.md+mkdir -p ~/.claude/skills/pup-bridge-publish && cp dev-skills/pup-bridge-publish.md ~/.claude/skills/pup-bridge-publish/SKILL.md+```
dev-skills/pup-bridge-dev.mdadded+125@@ -0,0 +1,125 @@+---+name: pup-bridge-dev+user-invocable: false+description: "DEVELOPER skill for building, publishing, and maintaining the Adom Desktop Puppeteer (pup) bridge. NOT needed by general users — they want the `pup` skill. Read this when editing the bridge code, cutting a new version, publishing to the wiki, wiring the cold-start Chrome-for-Testing self-heal, or understanding the pkg-vs-release-vs-bundled artifact model. Trigger words: pup bridge dev, publish pup bridge, build puppeteer bridge, bridge_install pup, adom-desktop-puppeteer-bridge, cold-start self-heal, browser_readiness internals, pup release, pup manifest, ship pup bridge."+---++# pup-bridge-dev — building & shipping the Puppeteer bridge++The canonical source of truth for the pup bridge is **this wiki repo**+(`wiki.adom.inc/adom/adom-desktop-puppeteer-bridge`), extracted from `adom-desktop/plugins/puppeteer`+on 2026-06-29 to be cloud-owned like the kicad/fusion bridges. Read `CLAUDE.md` (ownership boundary)+and `PUBLISHING.md` (step-by-step recipe) in this repo alongside this skill.++## The THREE artifacts — know which is which (this is the #1 confusion)++A user installs TWO different things from this one page. They are not the same:++| Artifact | What it is | Who installs it | How |+|---|---|---|---|+| **pkg tarball** (Packages tab) | the **container-side Claude skills** (`SKILL.md` + `skills/*`) — docs that teach the cloud AI how to drive pup. **No bridge runtime.** | a cloud **container** | `adom-wiki pkg install adom/adom-desktop-puppeteer-bridge`, or auto via AD's `sync_skills` |+| **release zip** (Releases tab) | the **bridge RUNTIME** (`server.js`, `chrome.js`, `bridge.json`, node deps reconstructed on spawn) — the code that actually drives Chrome on the desktop | **Adom Desktop**, streamed into `bridges-cache\puppeteer` | `bridge_install {manifestUrl}` / `refresh_bridges` (via `updateManifestUrl`) |+| **bundled seed** (inside the AD installer) | a copy of the same runtime, shipped in the NSIS for first-run/offline | every AD install | ships with Adom Desktop; superseded by a newer cache copy (cache-over-bundled, numeric version) |++So: **the pkg is skills (container). The bridge is the zip (desktop) + the bundled seed.** Keep the+pkg description saying exactly that, or people think the tarball contains the bridge — it does not.++## How pup reaches a container — auto-discovery → auto-install → verbs++The full chain when a user on a fresh container says *"open my app in pup"*:++1. **Discovery.** The wiki's `discover` index (fed by this page's `discovery_triggers` — which now lead+ with `pup`, `open in pup`, `open my app in pup`, screenshot/record/tabs) matches the phrase. The+ container's regenerated `adom-wiki-discover` skill surfaces this page, so the AI proposes it.+2. **Auto-install the pkg.** The AI runs `adom-wiki skills install adom/adom-desktop-puppeteer-bridge`+ (a.k.a. `pkg install`). That extracts the tarball and runs `install.sh`, which drops `SKILL.md` ++ `skills/*` into **both `~/.claude/skills/` and `~/.codex/skills/`**. The `pup` skill is now loaded.+3. **The skill calls the bridge.** `pup`'s SKILL.md tells the AI to run `adom-desktop browser_*` verbs.+ AD routes `browser_*` to the puppeteer bridge.+4. **The bridge runtime.** It's already on the desktop: AD ships pup as a **bundled seed** (and supersedes+ it from this page's release zip via `updateManifestUrl` / `bridge_install`). On a truly fresh PC the+ cold-start self-heal (chrome.js) installs Chrome for Testing on first `browser_open_window`.++**Two install paths, keep BOTH healthy:**+- **Discovery path (above)** — works once `discovery_triggers` include the user phrases (done) + the+ discover index is regenerated. This is the "user said pup → AI installs the skill" path.+- **AD `sync_skills` path** — AD's daily `maybe_background_skill_sync` reads `bridge_list` → each bridge's+ `skillPkg` → `pkg install`s it into the container. For pup this requires AD to report+ `skillPkg = adom/adom-desktop-puppeteer-bridge` for the `puppeteer` bridge. **Today AD hardcodes it to+ `null`** (`AD_CORE_SKILL_BRIDGES = ["puppeteer"]` in `bridge_registry.rs`, from when pup's skill lived+ in the AD-core pkg). That must be removed now that the skill ships from this page — it's in the AD handoff.++## Repo layout++- `src/` — the runnable bridge (what the release zip contains, at zip-root). `server.js`, `chrome.js`,+ `credential_vault.js`, `bridge.json`, `BRIDGE_VERSION`, node `package.json`/`package-lock.json`, html/icons.+- `SKILL.md` (root) — the **user** skill (`name: pup`). Deployed to `~/.claude/skills/adom-desktop-puppeteer-bridge/`.+- `dev-skills/pup-bridge-dev/` — THIS dev skill. `dev-skills/pup-bridge-publish/` — the publish recipe.+ Both are **source-only** (in `dev-skills/`, NOT `skills/`): NOT in the installable pkg and NOT on the+ wiki Skills tab — you get them via `adom-wiki repo clone`. Everyday users only get the `pup` user skill.+- `adom-bridge-puppeteer-manifest.json` (root) — the streaming manifest AD's `updateManifestUrl` points at.+- `package.json` (root) — the **adompkg descriptor** (slug/version/type/description) for `pkg publish`. NOT the node one.+- `install.sh` / `uninstall.sh` — deploy `SKILL.md` + `skills/*` into the container's `~/.claude/skills/`.+- `page.json`, `README.md`, `*.png` heroes — the wiki page presentation (the heroes are gitignored so the+ container pkg stays lean — they're already uploaded as page assets).++## Cold-start self-heal — the architecture (what 1.1.0 added, the reason this repo exists)++On a fresh PC (HD bundles AD; the installer pre-installs neither Node nor Chrome for Testing), the first+`browser_open_window` used to fail with a generic "Failed to launch Chrome after 3 attempts" + a dev+string. Now:++- **`src/chrome.js`** — `detectChrome()` (validates the ACTUAL exe, not "a dir exists"), `installChrome()`+ (programmatic via the bundled `@puppeteer/browsers`, pinned to the buildId puppeteer expects — never+ 'stable', or launch() won't find it), an install-state singleton (dedup + progress), `ensureChromeReady()`+ (the gate — background install, returns immediately), `readiness()` (powers the verb + chip).+- **`browser_open_window` gate** (CfT path only; native-drive skips it): missing CfT → kick off a+ non-blocking install + return `{errorCode:"chrome_for_testing_installing", statusVerb:"browser_readiness",+ _hint:"poll then retry"}`. Install fail → `chrome_install_failed`/`chrome_download_failed` (no silent+ continue-to-doomed-launch).+- **`browser_readiness`** (poll) + **`browser_prewarm`** (install without opening a window).+- **`/status`** emits `led`/`summary`/`tooltip` truthfully (yellow installing, red failed, green ready).+- **Node** is handled by AD core BEFORE the bridge spawns (`node_not_found` → `desktop_install_node`) —+ not this bridge's job (the bridge is a node process; it can't run without node).+- **The one thing this bridge can't do:** pre-warm before first use (it isn't running until the first+ call). AD/HD must call `browser_prewarm` on embedded first-run — tracked in `HANDOFF-TO-ADOM-DESKTOP.md`.++## Publishing (the wiki has TWO storage layers — don't mix them)++Full recipe in `PUBLISHING.md`. The load-bearing rules:++1. **The bridge `.zip` → a RELEASE asset** (`adom-wiki release create` + `release upload`). NEVER+ `repo push` a `.zip` — it's gitignored and silently skipped; the manifest URL would 404.+2. **The manifest JSON + source → the git repo** (`adom-wiki repo push`). The manifest's `url` is the+ RELEASE download URL; its `sha256`/`size` MUST match the served asset (releases preserve your sha;+ re-download to confirm or `bridge_install` rejects it).+3. **The skills → a PKG** (`adom-wiki pkg publish`). The pkg description (root `package.json`) must say+ "container-side skills." `pkg pack` honors `.gitignore` for exclusion (no separate npmignore) — that's+ why the heroes are gitignored, to keep the tarball lean. Set per-version notes with `adom-wiki pkg notes`.+4. **Version display gotcha:** a RELEASE shows up in `adom-wiki pkg info` as a row with `type:null` (a+ wiki UI conflation). The real pkg rows have `type:"app"`. Use `release list` for true releases.+5. **`bridge.json` + `BRIDGE_VERSION` lockstep**, and keep the version **>** the AD bundled seed so+ cache-over-bundled wins.++## Testing a new version on a real desktop++```bash+adom-desktop targets # multiple desktops → --target <name> is required+adom-desktop --target <name> bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-puppeteer-bridge/files/adom-bridge-puppeteer-manifest.json","force":true}'+adom-desktop --target <name> browser_readiness '{}' # proves the new code is live+```++- **No Node on the box** → `browser_*` returns `node_not_found` before the bridge spawns (AD core; correct).+ That's a genuine cold-start demo of the FIRST link; it can't exercise `chrome.js` (no node to run it).+- **A box with Node but no Chrome for Testing** is the only place to see the full Chrome self-install live+ (`browser_open_window` → `chrome_for_testing_installing` → poll `browser_readiness` → opens). Most dev+ boxes already have CfT, so `readiness()` just returns `ready:true`.+- The streamed zip is **source-only**; AD runs `npm install` in the cache on first spawn (sharp/keytar+ ship prebuilds). The bundled seed already has node_modules. Don't put node_modules in the zip.++## Don't++- Don't edit `adom-desktop/plugins/puppeteer` as if it's live, and don't `release-bridge.sh puppeteer`+ from the adom-desktop repo — that clobbers this cloud-owned page.+- Don't ship the bridge runtime in the pkg tarball or the skills in the release zip. Two artifacts, two homes.+- Don't install 'stable' Chrome — pin to puppeteer's expected buildId (`chrome.js` does this).
dev-skills/pup-bridge-publish.mdadded+121@@ -0,0 +1,121 @@+---+name: pup-bridge-publish+user-invocable: false+description: "DEVELOPER skill — the exact, battle-tested recipe for publishing a new version of the Adom Desktop Puppeteer (pup) bridge to wiki.adom.inc, plus every gotcha hit live. NOT for general users. Read before cutting a release: which artifact goes where (pkg=skills, release=bridge zip), the version-lockstep rules, the `files` allowlist + `skills/**` glob trap, the hero-bloat fix, sha matching, and the verify steps. Trigger words: publish pup bridge, ship pup bridge, release pup, pkg publish, repo push, adom-wiki release, bridge manifest, pup version bump, adom-desktop-puppeteer-bridge publish."+---++# pup-bridge-publish — shipping a new pup bridge version++The pup bridge is cloud-owned at `wiki.adom.inc/adom/adom-desktop-puppeteer-bridge`. This is the+step-by-step ship recipe + the traps that cost real time. Companion: `pup-bridge-dev` (architecture /+ownership), `PUBLISHING.md` (terse recipe), `CLAUDE.md` (ownership boundary).++## What goes WHERE — three artifacts, three homes (memorize this)++| Artifact | Home | Command | Carries |+|---|---|---|---|+| **bridge runtime `.zip`** | a **Release** asset | `adom-wiki release upload` | `src/` at zip-root (server.js, chrome.js, bridge.json, node deps). The thing AD runs. |+| **streaming manifest JSON** | the **git repo** | `adom-wiki repo push` | points at the release zip URL + its sha256/size |+| **container skills tarball** | a **Package** | `adom-wiki pkg publish` | `SKILL.md` + `skills/*` (the Claude skills). NO runtime. |++> The `.zip` is a RELEASE, never a git file: `*.zip` is gitignored and `repo push --files X.zip`+> reports `ok` but silently skips it (the URL then 404s). Binaries → `release upload`.++## Auth (once per container)++```bash+export ADOM_WIKI_TOKEN=$(cat /var/run/adom/api-key 2>/dev/null) # container default; or session.json's session_token+adom-wiki whoami # MUST show a real user (e.g. [email protected]). Strict-auth rejects the shared "Developer" token.+```+Every mutation needs a real `--changelog` / `-m` (≥10 chars, ≥2 words) or the server 400s.++## The recipe++### 1. Bump version IN LOCKSTEP, and keep it > the AD bundled seed++```bash+printf '1.1.1' > src/BRIDGE_VERSION+sed -i 's/"version": "1.1.0"/"version": "1.1.1"/' src/bridge.json # bridge runtime+sed -i 's/"version": "1.1.0"/"version": "1.1.1"/' package.json # adompkg descriptor (the pkg)+```+`src/BRIDGE_VERSION` and `src/bridge.json` MUST match (AD uses BRIDGE_VERSION to detect a stale running+process). Keep the number **above** the seed in `adom-desktop/plugins/puppeteer` so cache-over-bundled wins.++### 2. Build the bridge zip (source-only, `src/` at zip ROOT — no top-level dir)++`zip` may be absent → Python. node_modules is NEVER included (AD `npm install`s it on spawn; the seed+ships it; an 88 MB zip would blow wiki limits):++```python+import zipfile, os, hashlib+root, out = "src", "adom-bridge-puppeteer-v1.1.1.zip"+with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:+ for dp, dirs, files in os.walk(root):+ for skip in ("node_modules","screenshots","profiles"):+ if skip in dirs: dirs.remove(skip)+ for f in files: z.write(os.path.join(dp,f), os.path.relpath(os.path.join(dp,f), root))+print("sha256:", hashlib.sha256(open(out,"rb").read()).hexdigest(), "size:", os.path.getsize(out))+# SANITY: assert chrome.js + your change are inside before shipping.+```++### 3. Publish the zip as a Release (releases PRESERVE your sha)++```bash+adom-wiki release create adom/adom-desktop-puppeteer-bridge 1.1.1 --title "..." --changelog "<≥2 words>"+adom-wiki release upload adom/adom-desktop-puppeteer-bridge 1.1.1 adom-bridge-puppeteer-v1.1.1.zip --json+# the response's assets[].sha256/size are what the manifest MUST carry. Then VERIFY the PUBLIC url:+curl -sL https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.1.1/adom-bridge-puppeteer-v1.1.1.zip | sha256sum+```++### 4. Point the manifest at the release + push it (git repo)++Edit `adom-bridge-puppeteer-manifest.json` → `version`, `url` (the release download URL), `sha256`,+`size` (from step 3 — **must match the served asset or `bridge_install` rejects it**). Then:++```bash+adom-wiki repo push adom/adom-desktop-puppeteer-bridge \+ --files adom-bridge-puppeteer-manifest.json src/bridge.json src/BRIDGE_VERSION src/server.js src/chrome.js \+ -m "v1.1.1: <what changed>"+```++### 5. Publish the container skills as a pkg — keep it LEAN, and MIND THE TRAPS++```bash+adom-wiki pkg pack # ALWAYS pack + inspect BEFORE publish+tar tzf *.tgz # confirm: SKILL.md + skills/*/SKILL.md + install.sh, and NOTHING heavy+adom-wiki pkg publish --org adom --version 1.1.1+adom-wiki pkg notes adom/[email protected] -m "Container skills: <what changed>"+```++The pkg description lives in the root **`package.json`** `description` — keep it explicit that this is+**container-side skills only; the bridge runtime is the Release zip + bundled in Adom Desktop.**++## Traps hit live (don't relearn these)++- **`pkg pack` includes EVERYTHING by default** → a 16 MB tarball (all the hero PNGs + `src/` runtime).+ Fix: the heroes are gitignored (`*.png`) since they're already page assets, AND `package.json` has a+ **`files` allowlist**. With both, the tarball drops to ~12 KB.+- **`files` directory entries need a GLOB.** Listing `"skills"` (bare dir) silently includes ZERO skill+ files. Use **`"skills/**"`** (and/or the explicit `"skills/<name>/SKILL.md"`). Always `tar tzf` to+ confirm your skills actually made it in — a missing skill = an empty Skills tab.+- **`pkg pack` honors `.gitignore`, not a separate npmignore.** So you can't gitignore something you+ need in git (e.g. `src/`) to drop it from the pkg — use the `files` allowlist for that.+- **A RELEASE shows up in `adom-wiki pkg info` as a row with `type:null`** (wiki UI conflation). The real+ pkg rows are `type:"app"`. Use `adom-wiki release list` for true releases; don't think your pkg shipped+ just because a version appears in `pkg info`.+- **sha/size mismatch → `bridge_install` rejects the manifest.** Releases preserve the uploaded sha, so+ use the `release upload` response values verbatim and re-download to confirm.+- **The Skills tab is populated from the published PKG, not `page.json`** (`page.skills` is null even on+ pages with a full Skills tab). If skills aren't showing, you didn't `pkg publish` them (or the `files`+ glob dropped them).++## Verify (every ship)++```bash+# manifest serves at the updateManifestUrl path:+curl -s https://wiki.adom.inc/api/v1/pages/adom-desktop-puppeteer-bridge/files/adom-bridge-puppeteer-manifest.json | jq .sha256+# live install + new verbs on a real desktop (multi-desktop → --target; `adom-desktop targets` lists them):+adom-desktop --target <name> bridge_install '{"manifestUrl":".../files/adom-bridge-puppeteer-manifest.json","force":true}'+adom-desktop --target <name> browser_readiness '{}'+# render the page in pup + READ the screenshot (curl 200 is NOT verification). Confirm version + Skills tab.+```