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_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

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, 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

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.
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."