From 6aaf913366b813c9c9faf17a9a860dfebc49df12 Mon Sep 17 00:00:00 2001 From: OpenCode Agent <> Date: Mon, 17 Aug 2026 15:15:05 +0000 Subject: [PATCH] feat: hermes sshx-link plugin - shared terminal from dashboard --- __init__.py | 106 +++++++++++++++++++++++++++++++ dashboard/dist/index.js | 132 ++++++++++++++++++++++++++++++++++++++ dashboard/manifest.json | 13 ++++ dashboard/plugin_api.py | 136 ++++++++++++++++++++++++++++++++++++++++ plugin.yaml | 8 +++ 5 files changed, 395 insertions(+) create mode 100644 __init__.py create mode 100644 dashboard/dist/index.js create mode 100644 dashboard/manifest.json create mode 100644 dashboard/plugin_api.py create mode 100644 plugin.yaml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e89fbf6 --- /dev/null +++ b/__init__.py @@ -0,0 +1,106 @@ +""" +sshx-link plugin +================ +Downloads sshx, starts a shared terminal session, +and returns the shareable link. +""" + +import subprocess +import threading +import re +import logging +import shutil +import os + +logger = logging.getLogger(__name__) + +_sshx_process = None +_sshx_link = None + + +def _find_link_from_output(line: str) -> str | None: + """Extract sshx link from process output.""" + # sshx outputs links like: https://sshx.io/s/xxxxx + match = re.search(r"https://sshx\.io/s/[A-Za-z0-9_-]+", line) + return match.group(0) if match else None + + +def _ensure_sshx_installed() -> bool: + """Download and install sshx binary if not present.""" + if shutil.which("sshx"): + logger.info("sshx already installed at %s", shutil.which("sshx")) + return True + + logger.info("Downloading sshx...") + try: + result = subprocess.run( + ["sh", "-c", "curl -sSf https://sshx.io/get | sh"], + capture_output=True, + text=True, + timeout=60, + ) + if result.returncode != 0: + logger.error("sshx install failed: %s", result.stderr) + return False + return True + except subprocess.TimeoutExpired: + logger.error("sshx install timed out") + return False + + +def _run_sshx(tool_ctx): + """Run sshx serve in background, capture the link.""" + global _sshx_process, _sshx_link + + if _sshx_process and _sshx_process.poll() is None: + # Already running + tool_ctx.yield_result({"status": "already_running", "link": _sshx_link}) + return + + if not _ensure_sshx_installed(): + tool_ctx.yield_result({"status": "error", "message": "Failed to install sshx"}) + return + + logger.info("Starting sshx serve...") + _sshx_process = subprocess.Popen( + ["sshx", "serve"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + ) + + _sshx_link = None + + def _reader(): + global _sshx_link + for line in _sshx_process.stdout: + line = line.strip() + logger.debug("sshx: %s", line) + link = _find_link_from_output(line) + if link: + _sshx_link = link + tool_ctx.yield_result({"status": "running", "link": link}) + return + # Process ended without outputting a link + tool_ctx.yield_result({"status": "error", "message": "sshx exited without producing a link"}) + + threading.Thread(target=_reader, daemon=True).start() + + +def register(ctx): + """Register the sshx_start tool.""" + ctx.register_tool( + name="sshx_start", + toolset="sshx-link", + schema={ + "description": "Start a sshx shared terminal session and return the shareable link. The link lets others join your terminal via browser.", + "parameters": { + "type": "object", + "properties": {}, + "required": [], + }, + }, + handler=_run_sshx, + ) + logger.info("sshx-link plugin registered") diff --git a/dashboard/dist/index.js b/dashboard/dist/index.js new file mode 100644 index 0000000..5daf332 --- /dev/null +++ b/dashboard/dist/index.js @@ -0,0 +1,132 @@ +(function() { + const SDK = window.__HERMES_PLUGIN_SDK__; + const { useState, useEffect, useCallback } = React; + + function SSHXPage() { + const [link, setLink] = useState(null); + const [status, setStatus] = useState("idle"); // idle | loading | running | error + const [error, setError] = useState(null); + + const startSSHX = useCallback(async () => { + setStatus("loading"); + setError(null); + try { + const res = await SDK.fetchJSON("/api/plugins/sshx-link/start"); + if (res.link) { + setLink(res.link); + setStatus("running"); + } else { + setError(res.message || "No link returned"); + setStatus("error"); + } + } catch (err) { + setError(err.message || "Failed to start sshx"); + setStatus("error"); + } + }, []); + + const stopSSHX = useCallback(async () => { + try { + await fetch("/api/plugins/sshx-link/stop", { method: "POST" }); + setLink(null); + setStatus("idle"); + } catch (err) { + setError(err.message); + } + }, []); + + const checkStatus = useCallback(async () => { + try { + const res = await SDK.fetchJSON("/api/plugins/sshx-link/status"); + if (res.status === "running" && res.link) { + setLink(res.link); + setStatus("running"); + } + } catch (_) {} + }, []); + + useEffect(() => { checkStatus(); }, [checkStatus]); + + return ( +
+ Start a shared terminal session. Others can join via the link. +
+ + {status === "running" && link ? ( + + ) : ( + + )} + + {error && ( ++ Error: {error} +
+ )} +