← Commit history

Sync 0.3.0 source + ship source-only dev/publish skills (SDK v1.9.47 re-audit)

John Lauer ·b14f38a52b ·4mo ago ·parent 92e4b9f
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": []   }-}+}\ No newline at end of file
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, }