Adom Desktop - Puppeteer Bridge
Public Made by Adomby adom
The Adom Desktop Puppeteer (pup) bridge — drive a real headful browser on the user's desktop from the cloud: open/screenshot/eval/record. Cold-start drives the browser ALREADY on the machine (installed Chrome, else Microsoft Edge — every Windows PC ships with Edge) with a fresh isolated profile, so the common case needs NO download; Chrome for Testing is fetched only as a last resort. Two artifacts: the Releases zip is the bridge runtime (also bundled in Adom Desktop); the pkg installs the container-side Claude skills.
name: pup-bridge-publish
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), version-lockstep, the files allowlist, keeping the tarball lean (NO zip / NO src / NO heroes / NO node deps), the pkg ships ONLY the pup USER skill (dev/publish skills are source-only in dev-skills/ + publish-skills/, never in the pkg — open-vs-closed source doesn't matter), the skillpack package.json declaring dependencies:{ adom/adom-desktop } to pull the AD CLI + core skills, deploying skills to .claude AND .codex, scrubbing stale retired-wiki (wiki-ufypy5dpx93o.adom.cloud) URLs, user-first discovery triggers, sha matching, and verify steps. Trigger words: publish pup bridge, ship pup bridge, release pup, pkg publish, repo push, adom-wiki release, bridge manifest, pup version bump, no zip in tarball, dev skill source only, scrub old wiki url, codex skills, discovery triggers, adom-desktop-puppeteer-bridge publish."
Parent skill: adom-desktop-puppeteer-bridge
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
.zipis a RELEASE, never a git file:*.zipis gitignored andrepo push --files X.zipreportsokbut silently skips it (the URL then 404s). Binaries →release upload.
Auth (once per container)
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
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 installs it on spawn; the seed
ships it; an 88 MB zip would blow wiki limits):
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)
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:
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
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 packincludes 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, ANDpackage.jsonhas afilesallowlist. With both, the tarball drops to ~12 KB.files[]lists ONLY user files —SKILL.md,install.sh,uninstall.sh,README.md,package.json. The dev/publish skills are source-only and are NOT infiles[]. (If you ever add an extra USER skill underskills/, list itsskills/<name>/SKILL.mdexplicitly — a bare"skills"dir entry silently includes ZERO files.) Alwaystar tzfto confirm exactly what shipped.pkg packhonors.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 thefilesallowlist for that.- A RELEASE shows up in
adom-wiki pkg infoas a row withtype:null(wiki UI conflation). The real pkg rows aretype:"app". Useadom-wiki release listfor true releases; don't think your pkg shipped just because a version appears inpkg info. - sha/size mismatch →
bridge_installrejects the manifest. Releases preserve the uploaded sha, so use therelease uploadresponse values verbatim and re-download to confirm. - The Skills tab +
skill_count+adom-wiki skills installcome from the GIT REPO, not the pkg. The wiki indexes everySKILL.mdin the page repo (root = main skill;<dir>/SKILL.md= sub-skills) —page.skillsis null; it's a repo scan. So a file namedSKILL.mdANYWHERE in the repo becomes an indexed, tab-listed,skills install-able skill. (This is separate frompkg install, which installs the pkg tarball governed by thefilesallowlist.) Corollary → see "Keep dev/publish skills source-only".
Verify (every ship)
# 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.
What the pkg ships: USER skills only; dev/publish are source-only (Bridge SDK)
The Bridge SDK ("Where every file lives", corrected 2026-07-06) is authoritative:
- The pkg carries ONLY your USER skill(s). For pup that's the root
SKILL.md(name: pup). It's the single entry infiles[]'s skill list; the container can't drive the verbs without it. - DEV + PUBLISH skills are SOURCE-ONLY — this skill and
pup-bridge-dev. They live in the repo underpublish-skills/<name>/SKILL.mdanddev-skills/<name>/SKILL.md, are NOT inpackage.json files[], are NOT installed byinstall.sh, and do NOT needuser-invocable:false(they never reach a container to trigger). Maintainers read them viarepo clone/ the Files tab. - Open-vs-closed source does NOT matter — the pkg ships only user skills either way. (Earlier SDK wording gated this on openness; that was wrong and is corrected.)
The pkg's package.json DECLARES its dependency on AD so pkg install pulls the AD CLI + core
skills into the container:
"dependencies": { "adom/adom-desktop": "^1.9.x" } // pkg dep — pulls the AD CLI + core skills
This is the ONE dependency the skillpack package.json carries. Your SERVER's npm deps (puppeteer, …)
live ONLY in the Release zip's src/package.json, never here.
Verify after adom-wiki pkg pack: shipped 1 user skill(s); tar tzf *.tgz shows exactly
SKILL.md, install.sh, uninstall.sh, README.md, package.json — no skills/, no dev-skills/,
no *.zip, no src/, no *.png; and package.json has dependencies: { "adom/adom-desktop": … }.
History (so nobody re-thrashes this — it flipped 3×): the pkg ships ONLY the pup user skill; dev +
publish are source-only in dev-skills//publish-skills/; the skillpack package.json declares the
adom/adom-desktop dep. Settled 1.8.23 (2026-07-06) on the maintainer's corrected guidance.
Keep the pkg tarball LEAN — NO zip, NO src/, NO heroes
The pkg tarball is docs only — the pup USER skill + scripts; never the bridge runtime zip, src/,
hero PNGs, node_modules, or the dev/publish skills. Guards: the package.json files allowlist
(SKILL.md, install.sh, uninstall.sh, README.md, package.json) plus .gitignore (*.zip,
*.png, *.bundle, *.tgz, the old *-source.tar.gz). ALWAYS adom-wiki pkg pack then
tar tzf *.tgz and confirm: no *.zip, no src/, no *.png, no skills/, no dev-skills/;
shipped 1 user skill(s); and package.json has dependencies: { "adom/adom-desktop": … }. ~12 KB is
right (one SKILL.md + scripts); MEGABYTES means a binary/src/ leaked in (it was 16 MB before the
guards). rm -f *.tgz after.
Skills install to BOTH .claude AND .codex
install.sh deploys the user skill into both ~/.claude/skills/ and ~/.codex/skills/ (Codex only
if ~/.codex exists); uninstall.sh removes from both. Codex is a real second agent skills home now —
don't ship an install.sh that only does .claude.
Scrub stale retired-wiki URLs (the #1 fork/extract gotcha)
The only wiki host is wiki.adom.inc. wiki-ufypy5dpx93o.adom.cloud is RETIRED — as are the
/static/apps/... and /wiki/apps/... path formats. When you fork/extract a page, the inherited
README.md and the page.json readme body carry OLD URLs (this bit us live — a dead static/apps
zip link). Before every publish:
grep -rnI 'wiki-ufypy5dpx93o\|adom\.cloud\|/static/apps\|/wiki/apps' . --exclude-dir=node_modules
Fix every hit (README + the page.json readme body are the usual culprits), then VERIFY the live
entity (not just local):
curl -s https://wiki.adom.inc/api/pages/adom/adom-desktop-puppeteer-bridge | grep -c wiki-ufypy5dpx93o # want 0
Also repo rm any stale source-mirror artifacts left on the page (old *-source.tar.gz / *.bundle).
(https://adom.inc/fonts/... is fine — that's the brand font CDN, not a wiki host.)
Canonical wiki.adom.inc URL formats
- Page:
https://wiki.adom.inc/adom/<slug> - File (served, anon):
https://wiki.adom.inc/api/v1/pages/<slug>/files/<name> - Release download:
https://wiki.adom.inc/download/adom/<slug>/<ver>/<file> - Sibling pages:
/adom/adom-desktop-bridges,/adom/adom-desktop-kicad-bridge,/adom/adom-desktop-fusion-bridge,/adom/adom-desktop
Discovery triggers — lead with everyday USER phrases
Lead discovery_triggers with everyday phrases, not dev jargon — the single most important is bare
pup, plus open in pup, open my app in pup, screenshot the page, record a browser window,
etc. The wiki auto-generates the discover snippet from discovery_triggers + discovery_pitch
(there is no separate "upload a snippet" — those two fields ARE the snippet). Set them via
adom-wiki discover triggers <ref> --add ... OR page.json discovery_triggers/discovery_pitch + push.
Verify: adom-wiki discover preview "open my app in pup" should rank this page #1.
⚠ Node-bridge gotcha: the cache copy needs node_modules (don't outrun the bundled seed)
pup is a Node bridge with heavy native deps (puppeteer, sharp, keytar). The release zip is
SOURCE-ONLY (no node_modules). kicad/fusion (Python) stream fine, but a wiki-streamed pup cache copy
crashes MODULE_NOT_FOUND because AD spawns node server.js without installing node_modules (the
bespoke start_browser_bridge npm-installs only the BUNDLED copy; the cache copy gets nothing). So:
- The working runtime delivery for pup is the BUNDLED NSIS seed (it keeps node_modules). Wiki-stream the SKILLS (pkg) freely; the RUNTIME effectively ships with the AD installer until AD is fixed.
- Do NOT publish a wiki RUNTIME (
src/) version NEWER than the AD bundled seed — a box with ourupdateManifestUrlpersisted would pull the newer source-only zip into the cache and crash (it supersedes the good bundled copy). Keep the wiki runtime version == the bundled seed version. - The real fix is AD-side (give cache-installed node bridges their node_modules —
npm installthe cache or reuse the bundled node_modules). Tracked inHANDOFF-TO-ADOM-DESKTOP.md. Once it ships, normal wiki-streaming +updateManifestUrlauto-update works for pup like the other bridges.
Final step of EVERY publish — show it in pup (standing rule)
After any release/repo-push/pkg-publish, refresh the LIVE wiki page in a pup window the user can
see (browser_navigate the existing session to https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge?v=<ver>,
cache-busted), screenshot it, and hand over the link. Never report "published" without showing it.
Binary/hero pushes: ok:true, commit:null = the server honored the repo .gitignore
repo push (and the raw files API) return {ok:true, files_count:1} but commit:null and the file
NEVER lands when the repo's committed .gitignore matches it (this repo ignores *.png). No error is
raised — the only tell is commit:null + a 404 on the file. Fix: add a !hero-vN.png unignore line,
push .gitignore FIRST (real commit hash returned), then push the PNG, then verify anonymously:
HTTP 200 AND md5sum matches the local file. Use a NEW versioned filename per hero (CDN staleness).
Pushing an update LIVE on a desktop (AD >= 1.9.76)
refresh_bridges '{"name":"puppeteer"}' downloads the update AND auto-restarts a running bridge —
no manual kill, no bridge_install force needed for the update path. Then PROVE it went live:
bridge_check_updates must show liveVersion == current and staleProcess:false (liveVersion reads
the running process's /status version — keep BRIDGE_VERSION correct). If a process serves stale code,
restart_bridge '{"name":"puppeteer"}' kills (tracked PIDs + port orphans) and respawns from cache.
Drop any taskkill/manual-PID habits.
Per-verb timeout budgets (AD >= 1.9.79 contract)
bridge.json declares timeouts with BARE verb keys (no browser_ prefix): resolution = exact verbs ->
first matching prefixes -> default -> AD global 60s. pup declares: prewarm 240 (blocking CfT download),
use 240 (browser_use can trigger installs), open_window 150 (spawn-verified fallback worst case = 3
candidates x 3 attempts x 30s CDP wait), record* 90. An AD timeout is NON-terminal: callers get
{stillRunning:true, statusVerb, timeoutSeconds} and poll browser_readiness. When adding a verb that can
block >60s, add its bare key here AND bump runtime version (bridge.json+BRIDGE_VERSION+manifest+zip).
---
name: pup-bridge-publish
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), version-lockstep, the `files` allowlist, keeping the tarball lean (NO zip / NO src / NO heroes / NO node deps), the pkg ships ONLY the pup USER skill (dev/publish skills are source-only in dev-skills/ + publish-skills/, never in the pkg — open-vs-closed source doesn't matter), the skillpack package.json declaring dependencies:{ adom/adom-desktop } to pull the AD CLI + core skills, deploying skills to .claude AND .codex, scrubbing stale retired-wiki (wiki-ufypy5dpx93o.adom.cloud) URLs, user-first discovery triggers, sha matching, and verify steps. Trigger words: publish pup bridge, ship pup bridge, release pup, pkg publish, repo push, adom-wiki release, bridge manifest, pup version bump, no zip in tarball, dev skill source only, scrub old wiki url, codex skills, discovery triggers, adom-desktop-puppeteer-bridge publish."
---
Parent skill: **adom-desktop-puppeteer-bridge**
# 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[]` lists ONLY user files** — `SKILL.md`, `install.sh`, `uninstall.sh`, `README.md`,
`package.json`. The dev/publish skills are source-only and are NOT in `files[]`. (If you ever add an
extra USER skill under `skills/`, list its `skills/<name>/SKILL.md` explicitly — a bare `"skills"` dir
entry silently includes ZERO files.) Always `tar tzf` to confirm exactly what shipped.
- **`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 + `skill_count` + `adom-wiki skills install` come from the GIT REPO, not the pkg.**
The wiki indexes **every `SKILL.md`** in the page repo (root = main skill; `<dir>/SKILL.md` = sub-skills)
— `page.skills` is null; it's a repo scan. So a file named `SKILL.md` ANYWHERE in the repo becomes an
indexed, tab-listed, `skills install`-able skill. (This is *separate* from `pkg install`, which installs
the pkg tarball governed by the `files` allowlist.) Corollary → see "Keep dev/publish skills source-only".
## 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.
```
## What the pkg ships: USER skills only; dev/publish are source-only (Bridge SDK)
The Bridge SDK ("Where every file lives", corrected 2026-07-06) is authoritative:
- **The pkg carries ONLY your USER skill(s).** For pup that's the root `SKILL.md` (`name: pup`). It's the
single entry in `files[]`'s skill list; the container can't drive the verbs without it.
- **DEV + PUBLISH skills are SOURCE-ONLY** — this skill and `pup-bridge-dev`. They live in the repo under
`publish-skills/<name>/SKILL.md` and `dev-skills/<name>/SKILL.md`, are **NOT** in `package.json files[]`,
are **NOT** installed by `install.sh`, and do **NOT** need `user-invocable:false` (they never reach a
container to trigger). Maintainers read them via `repo clone` / the Files tab.
- **Open-vs-closed source does NOT matter** — the pkg ships only user skills either way. (Earlier SDK
wording gated this on openness; that was wrong and is corrected.)
**The pkg's `package.json` DECLARES its dependency on AD** so `pkg install` pulls the AD CLI + core
skills into the container:
```jsonc
"dependencies": { "adom/adom-desktop": "^1.9.x" } // pkg dep — pulls the AD CLI + core skills
```
This is the ONE dependency the skillpack package.json carries. Your SERVER's npm deps (`puppeteer`, …)
live ONLY in the Release zip's `src/package.json`, never here.
**Verify after `adom-wiki pkg pack`:** `shipped 1 user skill(s)`; `tar tzf *.tgz` shows exactly
`SKILL.md`, `install.sh`, `uninstall.sh`, `README.md`, `package.json` — **no `skills/`, no `dev-skills/`,
no `*.zip`, no `src/`, no `*.png`**; and `package.json` has `dependencies: { "adom/adom-desktop": … }`.
History (so nobody re-thrashes this — it flipped 3×): the pkg ships ONLY the `pup` user skill; dev +
publish are source-only in `dev-skills/`/`publish-skills/`; the skillpack `package.json` declares the
`adom/adom-desktop` dep. Settled 1.8.23 (2026-07-06) on the maintainer's corrected guidance.
## Keep the pkg tarball LEAN — NO zip, NO src/, NO heroes
The pkg tarball is **docs only** — the `pup` USER skill + scripts; never the bridge runtime zip, `src/`,
hero PNGs, `node_modules`, or the dev/publish skills. Guards: the `package.json` `files` allowlist
(`SKILL.md`, `install.sh`, `uninstall.sh`, `README.md`, `package.json`) **plus** `.gitignore` (`*.zip`,
`*.png`, `*.bundle`, `*.tgz`, the old `*-source.tar.gz`). **ALWAYS** `adom-wiki pkg pack` then
`tar tzf *.tgz` and confirm: **no `*.zip`, no `src/`, no `*.png`, no `skills/`, no `dev-skills/`;
`shipped 1 user skill(s)`; and `package.json` has `dependencies: { "adom/adom-desktop": … }`**. ~12 KB is
right (one SKILL.md + scripts); MEGABYTES means a binary/`src/` leaked in (it was 16 MB before the
guards). `rm -f *.tgz` after.
## Skills install to BOTH .claude AND .codex
`install.sh` deploys the user skill into **both `~/.claude/skills/` and `~/.codex/skills/`** (Codex only
if `~/.codex` exists); `uninstall.sh` removes from both. Codex is a real second agent skills home now —
don't ship an install.sh that only does `.claude`.
## Scrub stale retired-wiki URLs (the #1 fork/extract gotcha)
**The only wiki host is `wiki.adom.inc`. `wiki-ufypy5dpx93o.adom.cloud` is RETIRED** — as are the
`/static/apps/...` and `/wiki/apps/...` path formats. When you fork/extract a page, the inherited
`README.md` and the `page.json` `readme` body carry OLD URLs (this bit us live — a dead `static/apps`
zip link). Before every publish:
```bash
grep -rnI 'wiki-ufypy5dpx93o\|adom\.cloud\|/static/apps\|/wiki/apps' . --exclude-dir=node_modules
```
Fix every hit (README + the `page.json` `readme` body are the usual culprits), then VERIFY the **live**
entity (not just local):
```bash
curl -s https://wiki.adom.inc/api/pages/adom/adom-desktop-puppeteer-bridge | grep -c wiki-ufypy5dpx93o # want 0
```
Also `repo rm` any stale source-mirror artifacts left on the page (old `*-source.tar.gz` / `*.bundle`).
(`https://adom.inc/fonts/...` is fine — that's the brand font CDN, not a wiki host.)
## Canonical wiki.adom.inc URL formats
- **Page:** `https://wiki.adom.inc/adom/<slug>`
- **File (served, anon):** `https://wiki.adom.inc/api/v1/pages/<slug>/files/<name>`
- **Release download:** `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>`
- **Sibling pages:** `/adom/adom-desktop-bridges`, `/adom/adom-desktop-kicad-bridge`,
`/adom/adom-desktop-fusion-bridge`, `/adom/adom-desktop`
## Discovery triggers — lead with everyday USER phrases
Lead `discovery_triggers` with everyday phrases, not dev jargon — the single most important is bare
**`pup`**, plus `open in pup`, `open my app in pup`, `screenshot the page`, `record a browser window`,
etc. The wiki **auto-generates** the discover snippet from `discovery_triggers` + `discovery_pitch`
(there is no separate "upload a snippet" — those two fields ARE the snippet). Set them via
`adom-wiki discover triggers <ref> --add ...` OR `page.json` `discovery_triggers`/`discovery_pitch` + push.
**Verify:** `adom-wiki discover preview "open my app in pup"` should rank this page #1.
## ⚠ Node-bridge gotcha: the cache copy needs node_modules (don't outrun the bundled seed)
pup is a **Node** bridge with heavy native deps (puppeteer, sharp, keytar). The release zip is
SOURCE-ONLY (no node_modules). kicad/fusion (Python) stream fine, but a wiki-streamed pup **cache** copy
**crashes** `MODULE_NOT_FOUND` because AD spawns `node server.js` without installing node_modules (the
bespoke `start_browser_bridge` npm-installs only the BUNDLED copy; the cache copy gets nothing). So:
- **The working runtime delivery for pup is the BUNDLED NSIS seed** (it keeps node_modules). Wiki-stream
the SKILLS (pkg) freely; the RUNTIME effectively ships with the AD installer until AD is fixed.
- **Do NOT publish a wiki RUNTIME (`src/`) version NEWER than the AD bundled seed** — a box with our
`updateManifestUrl` persisted would pull the newer source-only zip into the cache and crash (it
supersedes the good bundled copy). Keep the wiki runtime version == the bundled seed version.
- The real fix is AD-side (give cache-installed node bridges their node_modules — `npm install` the cache
or reuse the bundled node_modules). Tracked in `HANDOFF-TO-ADOM-DESKTOP.md`. Once it ships, normal
wiki-streaming + `updateManifestUrl` auto-update works for pup like the other bridges.
## Final step of EVERY publish — show it in pup (standing rule)
After any release/repo-push/pkg-publish, refresh the LIVE wiki page in a pup window the user can
see (`browser_navigate` the existing session to `https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge?v=<ver>`,
cache-busted), screenshot it, and hand over the link. Never report "published" without showing it.
## Binary/hero pushes: `ok:true, commit:null` = the server honored the repo .gitignore
`repo push` (and the raw files API) return `{ok:true, files_count:1}` but `commit:null` and the file
NEVER lands when the repo's committed `.gitignore` matches it (this repo ignores `*.png`). No error is
raised — the only tell is `commit:null` + a 404 on the file. Fix: add a `!hero-vN.png` unignore line,
push `.gitignore` FIRST (real commit hash returned), then push the PNG, then verify anonymously:
HTTP 200 AND `md5sum` matches the local file. Use a NEW versioned filename per hero (CDN staleness).
## Pushing an update LIVE on a desktop (AD >= 1.9.76)
`refresh_bridges '{"name":"puppeteer"}'` downloads the update AND auto-restarts a running bridge —
no manual kill, no bridge_install force needed for the update path. Then PROVE it went live:
`bridge_check_updates` must show `liveVersion == current` and `staleProcess:false` (liveVersion reads
the running process's /status version — keep BRIDGE_VERSION correct). If a process serves stale code,
`restart_bridge '{"name":"puppeteer"}'` kills (tracked PIDs + port orphans) and respawns from cache.
Drop any taskkill/manual-PID habits.
## Per-verb timeout budgets (AD >= 1.9.79 contract)
bridge.json declares `timeouts` with BARE verb keys (no browser_ prefix): resolution = exact `verbs` ->
first matching `prefixes` -> `default` -> AD global 60s. pup declares: prewarm 240 (blocking CfT download),
use 240 (browser_use can trigger installs), open_window 150 (spawn-verified fallback worst case = 3
candidates x 3 attempts x 30s CDP wait), record* 90. An AD timeout is NON-terminal: callers get
{stillRunning:true, statusVerb, timeoutSeconds} and poll browser_readiness. When adding a verb that can
block >60s, add its bare key here AND bump runtime version (bridge.json+BRIDGE_VERSION+manifest+zip).