app
Adom Desktop - RustDesk Bridge
Public Made by Adomby adom
Drive RustDesk remote-desktop sessions from cloud Claude via Adom Desktop — connect out to any host by ID or IP (with a password), and list/close sessions. Works where RDP can't (Windows Home).
← Commit history
Sync 0.3.0 source + ship source-only dev/publish skills (SDK v1.9.47 re-audit)
5 files changed
+149−24
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.2.0+0.3.0
bridge.json+37−22@@ -2,9 +2,9 @@ "manifest_version": 1, "name": "rustdesk", "displayName": "RustDesk",- "version": "0.2.0",+ "version": "0.3.0", "description": "Drive RustDesk remote-desktop connections (client side): connect out to an ID/IP, list and close sessions.",- "homepage": "https://wiki.adom.inc/adom/adom-desktop-rustdesk-bridge",+ "homepage": "https://wiki.adom.inc/adom/adom-desktop", "docs": "https://wiki.adom.inc/adom/adom-desktop-rustdesk-bridge", "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-rustdesk-bridge/files/rustdesk-bridge-manifest.json", "author": "adom",@@ -13,35 +13,50 @@ "kind": "python", "entrypoint": "server.py", "port": 0,- "healthEndpoint": "/status",+ "healthEndpoint": "/health", "stopMethod": "kill",- "killImageName": "python.exe"+ "killImageName": "python.exe",+ "persistent": true },- "verbPrefixes": ["rustdesk_"],+ "verbPrefixes": [+ "rustdesk_"+ ], "verbs": [ "rustdesk_connect", "rustdesk_disconnect", "rustdesk_client_status",- "rustdesk_ensure_client"+ "rustdesk_ensure_client",+ "rustdesk_readiness",+ "rustdesk_prewarm",+ "rustdesk_describe" ], "statusVerb": "rustdesk_client_status",- "detect": {- "hostApp": "RustDesk",- "appPathsExe": "rustdesk.exe",- "paths": {- "windows": [- "%LOCALAPPDATA%\\rustdesk\\rustdesk.exe",- "%ProgramFiles%\\RustDesk\\rustdesk.exe",- "%USERPROFILE%\\Downloads\\rustdesk-*.exe"- ]- }+ "dependencies": {+ "python": ">=3.8" },- "dependencies": { "python": ">=3.8" }, "category": "Remote Access",- "tags": ["rustdesk", "remote desktop", "remote access", "screen share", "vnc", "rdp alternative"],+ "tags": [+ "rustdesk",+ "remote desktop",+ "remote access",+ "screen share",+ "vnc",+ "rdp alternative"+ ], "platforms": {- "windows": { "supported": true },- "macos": { "supported": false, "reason": "Untested; exe resolution + tasklist/taskkill are Windows-specific in this version." },- "linux": { "supported": false, "reason": "Untested; Windows-specific process/window handling so far." }+ "windows": {+ "supported": true+ },+ "macos": {+ "supported": false,+ "reason": "Untested; exe resolution + tasklist/taskkill are Windows-specific in this version."+ },+ "linux": {+ "supported": false,+ "reason": "Untested; Windows-specific process/window handling so far."+ }+ },+ "prewarm": {+ "assets": [] }-}+}
dev-skills/dev.mdadded+15@@ -0,0 +1,15 @@+# RustDesk Bridge — DEV skill (source-only; NOT named SKILL.md)++For whoever edits this bridge. The AI never reads this file — **it reads your verb OUTPUT.**++## THE ONE RULE: rich hints in every verb's output+Every verb MUST return an actionable `_hint` (+ `_next`/`related`/`pitfalls` where useful) and a stable `errorCode` on machine-actionable failures. The hint IS the UI.++## Architecture+- `server.py` — stdlib HTTP server. `GET /health` + `/status` → status chip; `POST /command {command,args}` → dispatch. AD spawns `python server.py --port <ephemeral>`; parse `--port`.+- Verbs wrap the `rustdesk.exe` CLI (`--connect <id> [--password <pw>]`) + Windows `tasklist`/`taskkill` for session discovery.+- **Cold start:** the client is a self-downloaded runtime. `rustdesk_prewarm` kicks off a background thread; `rustdesk_readiness` is READ-ONLY (never downloads). No verb blocks on the ~24 MB fetch.+- `bridge.json`: `port:0`, `healthEndpoint:/health` inside spawn, `persistent:true`, **NO `detect`** (we bring our own runtime; AD infers Python from `spawn.kind`).++## Test locally+`python server.py --port 9999`, then curl `/health` + POST `/command`.
dev-skills/publish.mdadded+17@@ -0,0 +1,17 @@+# RustDesk Bridge — PUBLISH skill (source-only; NOT named SKILL.md)++The release dance. Three artifacts:+1. **Runtime zip** (server.py + bridge.json + BRIDGE_VERSION) → a **RELEASE asset** (`release create`/`release upload`), NOT a /files blob. Manifest `url` → `/download/...`.+2. **Skills pkg** (`pkg publish --org adom`) → the USER `SKILL.md` only. `files` allowlist lean (no zip/server.py/hero).+3. **dev-skills/*.md** → live on the page via `repo push`, source-only, NEVER named `SKILL.md`.++## Flow+1. Bump version in bridge.json + BRIDGE_VERSION + package.json (lockstep).+2. Rebuild zip → `release create <ref> <v>` + `release upload <ref> <v> <zip> --platform any`.+3. Update manifest (version, url→/download/<v>/, sha256, size).+4. `pkg publish --org adom --public`; then `repo push` runtime source + dev-skills to /files.+5. Verify: `bridge_install {manifestUrl}`; `discover preview "<phrase>"` → top-3.++## Traps+- `repo push --files X.zip` gitignores zips → 404. Zips are RELEASE assets only.+- `homepage` must be a `wiki.adom.inc` URL, never github.
server.py+79−1@@ -26,7 +26,7 @@ import urllib.request from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer BRIDGE_NAME = "rustdesk"-BRIDGE_VERSION = "0.2.0"+BRIDGE_VERSION = "0.3.0" DEFAULT_PORT = 8810 RUSTDESK_DL_URL = "https://github.com/rustdesk/rustdesk/releases/download/1.4.8/rustdesk-1.4.8-x86_64.exe" @@ -194,11 +194,89 @@ def v_ensure_client(args): "_hint": "NON-BLOCKING: the ~24 MB portable client is downloading in the background (returns instantly). Poll rustdesk_client_status — when downloadState becomes 'done'/installed:true, rustdesk_connect works."} +def v_readiness(args):+ """READ-ONLY cold-start probe. Never downloads. {ready, installing, installProgressPct, lastError}."""+ exe = find_rustdesk()+ with _dl_lock:+ dl = dict(_dl)+ ready = bool(exe)+ installing = (not ready) and dl["state"] == "downloading"+ if ready:+ hint = "Ready - rustdesk_connect {to,password} works now."+ elif installing:+ hint = "Client downloading in the background (%d%%). Re-poll rustdesk_readiness; never block on it." % dl["pct"]+ else:+ hint = "Client not present. Call rustdesk_prewarm (non-blocking) to fetch it, then poll here."+ return {"success": True, "ready": ready, "installing": installing,+ "installProgressPct": (dl["pct"] if installing else (100 if ready else 0)),+ "lastError": dl["error"], "_hint": hint}+++def v_prewarm(args):+ """Kick off the client download in the background; supports {\"wait\":true} to block until ready."""+ if find_rustdesk():+ return {"success": True, "ready": True, "installing": False, "output": "already warm",+ "_hint": "Client already present - nothing to prewarm; rustdesk_connect is ready."}+ started = False+ with _dl_lock:+ if _dl["state"] != "downloading":+ _dl.update(state="downloading", pct=0, path=None, error=None)+ started = True+ if started:+ threading.Thread(target=_download_client, daemon=True).start()+ if args.get("wait"):+ deadline = time.time() + 50 # stay under AD's ~60s verb cap+ while time.time() < deadline:+ if find_rustdesk():+ return {"success": True, "ready": True, "installing": False, "output": "warmed",+ "_hint": "Client downloaded - rustdesk_connect is ready."}+ with _dl_lock:+ err = _dl["error"]+ if err:+ return {"success": False, "ready": False, "error": err,+ "_hint": "Download failed: %s. Retry rustdesk_prewarm." % err}+ time.sleep(1.5)+ with _dl_lock:+ pct = _dl["pct"]+ return {"success": True, "ready": False, "installing": True, "installProgressPct": pct,+ "_hint": "Still downloading (%d%%) after the wait window; poll rustdesk_readiness." % pct}+ with _dl_lock:+ pct = _dl["pct"]+ return {"success": True, "ready": False, "installing": True, "installProgressPct": pct,+ "_hint": "NON-BLOCKING prewarm started; poll rustdesk_readiness until ready:true (pass {\"wait\":true} to block)."}+++_CATALOG = [+ {"name": "rustdesk_connect", "summary": "Open a remote-control session to a host by RustDesk ID or IP.",+ "input": {"to": "ID or IP (required)", "password": "optional, auto-authenticates"},+ "output": "{to, pid, withPassword}", "example": {"to": "123456789", "password": "<pw>"}},+ {"name": "rustdesk_disconnect", "summary": "Close a session window (or all sessions).",+ "input": {"to": "optional ID; omit = close all"}, "output": "{disconnected[], count}"},+ {"name": "rustdesk_client_status", "summary": "Installed path + active sessions + download state (may spawn the bridge).",+ "input": {}, "output": "{installed, path, sessions[], downloadState, downloadPct}"},+ {"name": "rustdesk_readiness", "summary": "READ-ONLY cold-start probe; never downloads.",+ "input": {}, "output": "{ready, installing, installProgressPct, lastError}"},+ {"name": "rustdesk_prewarm", "summary": "Kick off the client download in the background; {wait:true} blocks until ready.",+ "input": {"wait": "optional bool"}, "output": "{ready, installing, installProgressPct}"},+ {"name": "rustdesk_ensure_client", "summary": "Ensure the client binary is present (non-blocking download).",+ "input": {}, "output": "{present|downloading, path}"},+ {"name": "rustdesk_describe", "summary": "This machine-readable verb catalog.", "input": {}, "output": "{verbs[]}"},+]+++def v_describe(args):+ return {"success": True, "verbs": _CATALOG, "version": BRIDGE_VERSION,+ "_hint": "80%% path: rustdesk_connect / rustdesk_client_status / rustdesk_disconnect. Cold start: rustdesk_readiness (read-only) + rustdesk_prewarm. Connect with {to,password}."}++ VERBS = { "rustdesk_connect": v_connect, "rustdesk_disconnect": v_disconnect, "rustdesk_client_status": v_client_status, "rustdesk_ensure_client": v_ensure_client,+ "rustdesk_readiness": v_readiness,+ "rustdesk_prewarm": v_prewarm,+ "rustdesk_describe": v_describe, }