DEPRECATED: Bridge SDK (moved into Adom Bridge)
Public Made by Adomby adom
Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.
name: bridge-user-skill-template user-invocable: true description: "TEMPLATE for an Adom Desktop bridge's CONSUMER skill — copy it, then replace // and the verb glosses. It teaches a cloud Claude/Codex AI how to DRIVE on the user's desktop via the adom-desktop CLI: <one-line value prop — what the bridge controls and why>. Includes a FIRST-USE / COLD-START warm-up section so the AI doesn't panic on a fresh machine. Keep a trigger-words line like this one so the skill activates. Trigger words: , , , , _readiness, _prewarm, <2-3 plain-English phrases a user would say, e.g. 'open in ', ' screenshot'>."
Template — copy into your bridge's pkg as
skills/<bridge>/SKILL.md, replace<bridge>/<prefix>.
— (with first-use warm-up)
Drive on the user's desktop from the cloud via the adom-desktop CLI. All verbs are
Bash commands (NOT MCP tools):
adom-desktop <command> '<json_args>'
⭐ FIRST-TIME / COLD-START — read this BEFORE you panic that "_ did nothing"
On a brand-new machine (e.g. the user just installed the app that bundles Adom Desktop), something needs may NOT be pre-installed by the installer — a runtime, a large dependency, an external app, or a one-time download. The bridge handles this gracefully — your job is to read the structured response and follow it, not to give up.
When you run a <prefix>_<verb> on a cold machine, one of these happens — handle each:
| Response | What it means | What you do |
|---|---|---|
errorCode: "<dependency>_not_found" |
A required runtime/dependency isn't installed (the bridge can't start). | Run the bridge's documented install verb (e.g. adom-desktop <prefix>_install_<dep> '{}'), wait for it, then retry the original verb. |
errorCode: "<resource>_installing" (installing:true) |
A needed resource is missing; the bridge is auto-downloading/installing it now (one time). This is EXPECTED on a fresh machine — NOT a failure. | Poll <prefix>_readiness until ready:true, THEN re-issue the exact same verb. You do NOT install anything yourself. Do NOT report failure to the user. |
errorCode: "<resource>_install_failed" / <resource>_download_failed |
The download/install itself failed. | Read readiness.lastError. It's usually no network / a proxy / low disk — NOT your fault. Retry <prefix>_prewarm; if it persists, surface lastError to the user, and use any documented fallback mode. |
ok:true / a real session |
Everything was already warm. | Proceed normally. |
The clean cold-start recipe
# 1) (optional but nice) Warm the bridge WITHOUT doing real work first:
adom-desktop <prefix>_prewarm '{}' # returns fast; kicks off any first-use install
# 2) Poll readiness until ready (the bridge sets itself up):
adom-desktop <prefix>_readiness '{}' # → {ready:false, installing:true, installProgressPct:42, ...}
# ...wait a few seconds, poll again until → {ready:true, ...}
# 3) Now do the real work — it's instant:
adom-desktop <prefix>_<verb> '{"sessionId":"<id>", ...}'
<prefix>_prewarm and <prefix>_readiness are the two cold-start verbs. <prefix>_prewarm '{"wait":true}'
blocks until setup finishes instead of returning immediately. Always surface a verb's _hint
field verbatim to the user when something isn't ready — it tells you (and them) exactly what's happening.
Concrete example (the Puppeteer/pup bridge): on a fresh PC,
browser_open_windowreturnserrorCode: "chrome_for_testing_installing"(installing:true) because Chrome for Testing (~150 MB) is auto-downloading. That is NOT a failure — you pollbrowser_readinessuntil{ready:true}, then re-issue the samebrowser_open_window. Before the bridge handled this, a cold PC failed with a generic "Failed to launch after 3 attempts" and the AI flailed. Don't reintroduce the panic; read the structured response and follow the table.
How to Run Commands
adom-desktop <prefix>_<verb> '{"sessionId":"myjob", ...}' # do a unit of work
adom-desktop <prefix>_screenshot '{"sessionId":"myjob"}' # capture current state (lossless PNG)
adom-desktop <prefix>_status '{}' # list sessions + readiness
These are Bash commands, not MCP tools. If your verbs are session-scoped, always pass sessionId —
'{}' targets the active session, which may not be the one you mean.
Identify yourself on every call
Say which AI conversation you are on every single call:
adom-desktop --ai-thread "<your conversation name>" <prefix>_<verb> '<json>'
--container-name "<your container>" is optional. Both are global flags like --target: they go
before the verb and work on every verb. AD refuses an unidentified relayed call with
errorCode: caller_identity_required, so this is not optional politeness, it is the contract.
Why it matters to you: the user runs roughly 20 AI threads against this one desktop. Without a name, every thread looks identical to them: the Activity Log is an undifferentiated stream and an approval toast says only that "some AI" wants to do something. With a name, they can see which tab drove an action, and they can approve yours instead of denying everything. Your thread name is one of the two things they read before clicking Approve.
- Pick a name the user would recognise for this tab:
chip-fetcher tab 3,kicad board review,nightly wiki sync. Never a UUID, neversession_1, neveragent. - It is attribution, never authorization. Nothing is gated on it, it grants you nothing, and you should never tell a user that a thread name proves anything.
- Do not
export ADOM_AI_THREAD. That advice was withdrawn: roughly 20 threads share one container and one$HOME, so the variable names the shell rather than the thread and every thread then reports the same name (worse than no name, because it is confident and wrong). A non-interactive tool shell also skips~/.bashrcpast itscase $- in *i*early return, so the export silently never runs at all.
Background by default — don't disrupt the user
is the AI's workspace, not the user's. Keep its windows/output OUT of the user's way unless they asked to watch.
- After opening/driving something for automation (a screenshot, a scrape, a debug loop), send it to the
back / leave it un-focused so it doesn't sit on top of what the user is doing (e.g. a
<prefix>_lower_os_windowor equivalent). - Never force your window to the foreground unless the user must SEE it — a recording, a demo, or a finished result you're explicitly showing them.
- Use a non-intrusive nudge (flash the taskbar via a
<prefix>_alert_window-style verb, or just report in chat) for "it's ready / done" instead of stealing focus. - For long-running output the user WILL look at, a dedicated window is fine — but still don't re-raise it on every update; nudge instead of grabbing focus.
adom-desktop <prefix>_<verb> '{"sessionId":"job", ...}'
adom-desktop <prefix>_lower_os_window '{"sessionId":"job"}' # get it off the user's screen; keep driving headlessly
# ...do work as needed (works while lowered/backgrounded)...
adom-desktop <prefix>_alert_window '{"sessionId":"job"}' # non-intrusive "done" when you want their attention
Core verbs
| Command | Description | Key args |
|---|---|---|
<prefix>_readiness |
Cold-start probe: is the bridge set up / still installing? | none |
<prefix>_prewarm |
Do first-use setup WITHOUT starting real work | wait (optional bool) |
<prefix>_open_window (or your "start a session" verb) |
Spawn a new session/window | sessionId, profile, ... |
<prefix>_<do-work> |
The bridge's primary action verb(s) | sessionId, ... |
<prefix>_screenshot |
Capture current state (lossless PNG) | sessionId, fullPage? |
<prefix>_status |
All sessions + readiness | none |
<prefix>_lower_os_window |
Send the window to the back (keep it off the user's screen) | sessionId |
<prefix>_raise_os_window / <prefix>_focus_window |
Bring to foreground (use sparingly — only to show the user) | sessionId |
<prefix>_alert_window |
Flash the taskbar (non-intrusive attention) | sessionId |
<prefix>_close_window |
Close one session + its window (destructive) | sessionId |
<prefix>_close |
Close ALL sessions | none |
Run adom-desktop <prefix>_describe '{}' for the full machine-readable verb catalog.
Sessions & persistence (if your bridge has sessions)
If one session = one long-lived context (a window, a connection, a document), say so here, and tell the AI how to reuse instead of re-open:
- New context entirely →
<prefix>_open_window(or your start verb). - Reuse an existing one → drive it directly (a
<prefix>_navigate/equivalent), don't open a second.
Run <prefix>_status '{}' before opening a new session at the start of a conversation (especially
after a context compaction) — if a session already matches what you're about to do, reuse it. Opening a
duplicate leaves the user staring at two identical windows. Pick a stable profile/identifier across
runs so any persisted state (logins, cookies, caches) survives — the user sets up once, reuses for
months.
Verify what you did — ok:true is not enough
Many start/action verbs return ok:true as soon as the operation begins, not when it finished /
rendered. Sanity-check with the cheapest signal: a <prefix>_eval/read verb that reflects real state
(fastest), or a <prefix>_screenshot when a value isn't discriminating. Don't report success to the
user on ok:true alone for anything that can silently land on an error/empty state.
Key rules
- Always pass
--ai-thread "<your conversation name>"so the user can see which tab is driving their machine. An unidentified relayed call is refused witherrorCode: caller_identity_required. - Always pass
sessionIdfor session-scoped verbs. Never'{}'when you mean a specific session. - It's
adom-desktop <prefix>_<cmd> <json>— there is no top-level<bridge>subcommand. - Surface
_hintverbatim whenever a verb reports it isn't ready or failed — it's written for both you and the user. - Reuse, don't reflexively close+reopen — persisted state is keyed by
profile/identifier; prefer a reload/navigate over a destroy+recreate. - Never broadly kill the underlying app/process — use the bridge's per-session close
(
<prefix>_close_window) so you don't nuke unrelated work. - Document any per-verb gotchas here (escaping rules, arg quirks, timeouts) — e.g. pup's
"no semicolons in
browser_eval— use the comma operator or an IIFE."
---
name: bridge-user-skill-template
user-invocable: true
description: "TEMPLATE for an Adom Desktop bridge's CONSUMER skill — copy it, then replace <bridge>/<Bridge Name>/<prefix> and the verb glosses. It teaches a cloud Claude/Codex AI how to DRIVE <Bridge Name> on the user's desktop via the adom-desktop CLI: <one-line value prop — what the bridge controls and why>. Includes a FIRST-USE / COLD-START warm-up section so the AI doesn't panic on a fresh machine. Keep a trigger-words line like this one so the skill activates. Trigger words: <bridge>, <Bridge Name>, <prefix>_<verb1>, <prefix>_<verb2>, <prefix>_readiness, <prefix>_prewarm, <2-3 plain-English phrases a user would say, e.g. 'open in <bridge>', '<bridge> screenshot'>."
---
> Template — copy into your bridge's pkg as `skills/<bridge>/SKILL.md`, replace `<bridge>`/`<prefix>`.
# <Bridge Name> — <one-line capability summary> (with first-use warm-up)
Drive <Bridge Name> on the user's desktop from the cloud via the `adom-desktop` CLI. All verbs are
Bash commands (NOT MCP tools):
```bash
adom-desktop <command> '<json_args>'
```
## ⭐ FIRST-TIME / COLD-START — read this BEFORE you panic that "<prefix>_<verb> did nothing"
On a brand-new machine (e.g. the user just installed the app that bundles Adom Desktop),
**something <Bridge Name> needs may NOT be pre-installed by the installer** — a runtime, a large
dependency, an external app, or a one-time download. The bridge handles this gracefully — your job is
to **read the structured response and follow it, not to give up.**
When you run a `<prefix>_<verb>` on a cold machine, one of these happens — handle each:
| Response | What it means | What you do |
|---|---|---|
| `errorCode: "<dependency>_not_found"` | A required runtime/dependency isn't installed (the bridge can't start). | Run the bridge's documented install verb (e.g. `adom-desktop <prefix>_install_<dep> '{}'`), wait for it, then retry the original verb. |
| `errorCode: "<resource>_installing"` (`installing:true`) | **A needed resource is missing; the bridge is auto-downloading/installing it now (one time).** This is EXPECTED on a fresh machine — NOT a failure. | **Poll `<prefix>_readiness` until `ready:true`**, THEN re-issue the exact same verb. You do NOT install anything yourself. Do NOT report failure to the user. |
| `errorCode: "<resource>_install_failed"` / `<resource>_download_failed` | The download/install itself failed. | Read `readiness.lastError`. It's usually no network / a proxy / low disk — NOT your fault. Retry `<prefix>_prewarm`; if it persists, surface `lastError` to the user, and use any documented fallback mode. |
| `ok:true` / a real session | Everything was already warm. | Proceed normally. |
### The clean cold-start recipe
```bash
# 1) (optional but nice) Warm the bridge WITHOUT doing real work first:
adom-desktop <prefix>_prewarm '{}' # returns fast; kicks off any first-use install
# 2) Poll readiness until ready (the bridge sets itself up):
adom-desktop <prefix>_readiness '{}' # → {ready:false, installing:true, installProgressPct:42, ...}
# ...wait a few seconds, poll again until → {ready:true, ...}
# 3) Now do the real work — it's instant:
adom-desktop <prefix>_<verb> '{"sessionId":"<id>", ...}'
```
`<prefix>_prewarm` and `<prefix>_readiness` are the two cold-start verbs. `<prefix>_prewarm '{"wait":true}'`
blocks until setup finishes instead of returning immediately. **Always surface a verb's `_hint`
field verbatim to the user when something isn't ready** — it tells you (and them) exactly what's happening.
> **Concrete example (the Puppeteer/pup bridge):** on a fresh PC, `browser_open_window` returns
> `errorCode: "chrome_for_testing_installing"` (`installing:true`) because Chrome for Testing (~150 MB)
> is auto-downloading. That is NOT a failure — you poll `browser_readiness` until `{ready:true}`, then
> re-issue the same `browser_open_window`. Before the bridge handled this, a cold PC failed with a
> generic "Failed to launch after 3 attempts" and the AI flailed. Don't reintroduce the panic; read
> the structured response and follow the table.
## How to Run Commands
```bash
adom-desktop <prefix>_<verb> '{"sessionId":"myjob", ...}' # do a unit of work
adom-desktop <prefix>_screenshot '{"sessionId":"myjob"}' # capture current state (lossless PNG)
adom-desktop <prefix>_status '{}' # list sessions + readiness
```
**These are Bash commands, not MCP tools.** If your verbs are session-scoped, always pass `sessionId` —
`'{}'` targets the *active* session, which may not be the one you mean.
## Identify yourself on every call
Say which AI conversation you are on every single call:
```bash
adom-desktop --ai-thread "<your conversation name>" <prefix>_<verb> '<json>'
```
`--container-name "<your container>"` is optional. Both are **global flags** like `--target`: they go
before the verb and work on every verb. AD refuses an unidentified relayed call with
`errorCode: caller_identity_required`, so this is not optional politeness, it is the contract.
**Why it matters to you:** the user runs roughly 20 AI threads against this one desktop. Without a name,
every thread looks identical to them: the Activity Log is an undifferentiated stream and an approval toast
says only that "some AI" wants to do something. With a name, they can see which tab drove an action, and
they can approve yours instead of denying everything. Your thread name is one of the two things they read
before clicking Approve.
- Pick a name **the user would recognise for this tab**: `chip-fetcher tab 3`, `kicad board review`,
`nightly wiki sync`. Never a UUID, never `session_1`, never `agent`.
- It is **attribution, never authorization.** Nothing is gated on it, it grants you nothing, and you should
never tell a user that a thread name proves anything.
- Do **not** `export ADOM_AI_THREAD`. That advice was withdrawn: roughly 20 threads share one container and
one `$HOME`, so the variable names the shell rather than the thread and every thread then reports the
same name (worse than no name, because it is confident and wrong). A non-interactive tool shell also
skips `~/.bashrc` past its `case $- in *i*` early return, so the export silently never runs at all.
## Background by default — don't disrupt the user
**<Bridge Name> is the AI's workspace, not the user's. Keep its windows/output OUT of the user's way unless they asked to watch.**
- After opening/driving something for automation (a screenshot, a scrape, a debug loop), send it to the
back / leave it un-focused so it doesn't sit on top of what the user is doing (e.g. a
`<prefix>_lower_os_window` or equivalent).
- **Never** force your window to the foreground unless the user must SEE it — a recording, a demo, or a
finished result you're explicitly showing them.
- Use a **non-intrusive nudge** (flash the taskbar via a `<prefix>_alert_window`-style verb, or just
report in chat) for "it's ready / done" instead of stealing focus.
- For long-running output the user WILL look at, a dedicated window is fine — but still don't re-raise
it on every update; nudge instead of grabbing focus.
```bash
adom-desktop <prefix>_<verb> '{"sessionId":"job", ...}'
adom-desktop <prefix>_lower_os_window '{"sessionId":"job"}' # get it off the user's screen; keep driving headlessly
# ...do work as needed (works while lowered/backgrounded)...
adom-desktop <prefix>_alert_window '{"sessionId":"job"}' # non-intrusive "done" when you want their attention
```
## Core verbs
| Command | Description | Key args |
|---|---|---|
| `<prefix>_readiness` | Cold-start probe: is the bridge set up / still installing? | none |
| `<prefix>_prewarm` | Do first-use setup WITHOUT starting real work | `wait` (optional bool) |
| `<prefix>_open_window` *(or your "start a session" verb)* | Spawn a new session/window | `sessionId`, `profile`, ... |
| `<prefix>_<do-work>` | The bridge's primary action verb(s) | `sessionId`, ... |
| `<prefix>_screenshot` | Capture current state (lossless PNG) | `sessionId`, `fullPage?` |
| `<prefix>_status` | All sessions + readiness | none |
| `<prefix>_lower_os_window` | Send the window to the back (keep it off the user's screen) | `sessionId` |
| `<prefix>_raise_os_window` / `<prefix>_focus_window` | Bring to foreground (use sparingly — only to show the user) | `sessionId` |
| `<prefix>_alert_window` | Flash the taskbar (non-intrusive attention) | `sessionId` |
| `<prefix>_close_window` | Close one session + its window (destructive) | `sessionId` |
| `<prefix>_close` | Close ALL sessions | none |
Run `adom-desktop <prefix>_describe '{}'` for the full machine-readable verb catalog.
## Sessions & persistence (if your bridge has sessions)
If one session = one long-lived context (a window, a connection, a document), say so here, and tell the
AI **how to reuse instead of re-open**:
- New context entirely → `<prefix>_open_window` (or your start verb).
- Reuse an existing one → drive it directly (a `<prefix>_navigate`/equivalent), don't open a second.
**Run `<prefix>_status '{}'` before opening a new session** at the start of a conversation (especially
after a context compaction) — if a session already matches what you're about to do, reuse it. Opening a
duplicate leaves the user staring at two identical windows. Pick a **stable `profile`/identifier across
runs** so any persisted state (logins, cookies, caches) survives — the user sets up once, reuses for
months.
## Verify what you did — `ok:true` is not enough
Many start/action verbs return `ok:true` as soon as the operation *begins*, not when it *finished* /
rendered. Sanity-check with the cheapest signal: a `<prefix>_eval`/read verb that reflects real state
(fastest), or a `<prefix>_screenshot` when a value isn't discriminating. Don't report success to the
user on `ok:true` alone for anything that can silently land on an error/empty state.
## Key rules
- **Always pass `--ai-thread "<your conversation name>"`** so the user can see which tab is driving their
machine. An unidentified relayed call is refused with `errorCode: caller_identity_required`.
- **Always pass `sessionId`** for session-scoped verbs. Never `'{}'` when you mean a specific session.
- **It's `adom-desktop <prefix>_<cmd> <json>`** — there is no top-level `<bridge>` subcommand.
- **Surface `_hint` verbatim** whenever a verb reports it isn't ready or failed — it's written for both
you and the user.
- **Reuse, don't reflexively close+reopen** — persisted state is keyed by `profile`/identifier; prefer a
reload/navigate over a destroy+recreate.
- **Never broadly kill the underlying app/process** — use the bridge's per-session close
(`<prefix>_close_window`) so you don't nuke unrelated work.
- **Document any per-verb gotchas** here (escaping rules, arg quirks, timeouts) — e.g. pup's
"no semicolons in `browser_eval` — use the comma operator or an IIFE."
---