--- name: hermes-status description: "Read-only dashboard for a self-hosted Hermes v0.22.5 over SSH: version, hermes status, gateway status, memory provider, cron jobs, sessions, pairing and recent logs, shown as the native commands print them. Use when the user asks if their agent is ok. It starts, stops and writes nothing. Works from any AI agent (Hermes, Claude Code, Codex, Cursor, Gemini CLI)." --- # /hermes-status — read-only dashboard for a remote Hermes (SSH-first) You are the engineer looking at a self-hosted Hermes agent on the user's VPS. The agent (Hermes, Claude Code, Codex, Cursor, Gemini CLI — whichever ran you) works over SSH as root against the VPS. **This skill is read-only.** It runs the native report commands and shows what they print. It does not start, stop or restart the gateway, does not write any file or marker, does not read `~/.hermes/.env`, and never turns a command's text into a pass/fail verdict. In particular: - `gateway status` prints `✗ Gateway is not running` when the gateway is stopped, and that line contains the word `running`. Its text, and the `Gateway:` row of `hermes status`, are **informational**: they come from a process and service scan, not from a readiness probe. Quote them; do not summarize them as "healthy". - A real readiness check is `gateway ensure --json`, but `ensure` **starts a gateway when none is running**. A read-only skill must not do that, so the `gw_ready` and `gw_reload` helpers are defined in Step 0 only to keep the shared helper block identical across skills. This skill never calls them. If the user wants a verdict stronger than "a process exists", say so and offer `/hermes-restart` (restarts) or `/hermes-fix` (diagnoses, then repairs with consent). - Model calls and `doctor` are opt-in (Step 6) because they are not free of side effects: see there. **Where commands run.** Every `bash` block below runs on the agent's LOCAL machine (possibly macOS, bash 3.2) and reaches the VPS through the `remote` helper from Step 0, which wraps `ssh` and puts `~/.local/bin` (where `hermes` lives) on the remote PATH. Run all blocks in ONE shell session. **Honest picture.** Checked against the Hermes v0.22.5 source (see "Verified against Hermes v0.22.5"). Not run against a real host: the offline tests drive these blocks against a stub whose command syntax is the real v0.22.5 argparse and whose output is invented. --- ## Before you start — gather (ask once, in one batch) | Variable | What | Where to get it | |----------|------|-----------------| | `$VPS_IP` | IP/hostname of the VPS running Hermes | User's hosting dashboard | | `$VPS_USER` | SSH user (typically `root`) | User's hosting dashboard | | `$DEEP` (optional) | `1` = also run `hermes status --deep` (Step 6) | Leave unset unless asked | | `$RUN_DOCTOR` (optional) | `1` = also run `hermes doctor` (Step 6) | Leave unset unless asked | | `$SMOKE_MODEL` (optional) | `1` = also make one model call with `hermes -z` (Step 6) | Leave unset unless asked | No secrets are needed. Confirm SSH access before doing anything: ```bash ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes \ "$VPS_USER@$VPS_IP" "echo ok" 2>&1 | grep -q '^ok$' \ || { echo "ABORT: SSH to $VPS_USER@$VPS_IP failed. Run /setup-ssh-keys first. Nothing was changed."; exit 1; } ``` --- ## Step 0 — helpers (define first, so nothing can fail before they exist) ```bash # Run every block of this skill in ONE shell session: later steps call these functions. # If your harness starts a fresh shell per command, re-run this block first. SKILL_NAME=hermes-status # Non-login ssh shells miss ~/.local/bin, where the Hermes installer puts `hermes`. remote() { ssh -o BatchMode=yes "$VPS_USER@$VPS_IP" "export PATH=\"\$HOME/.local/bin:\$PATH\"; $*"; } # gw_ready [SECONDS]: native readiness. Succeeds only when `gateway ensure --json` exits 0 and reports "state":"ready" # (the transient pending/draining codes 5 and 6 are retried twice). It never greps `gateway status` text, because the stopped # gateway prints "✗ Gateway is not running", which contains "running". gw_ready() { remote "T=${1:-60} bash -s" <<'REMOTE' n=0 while :; do out=$(hermes -p default gateway ensure --json --timeout "$T") && rc=0 || rc=$? [ "$rc" = 0 ] && break n=$((n + 1)) { [ "$rc" = 5 ] || [ "$rc" = 6 ]; } && [ "$n" -lt 3 ] || { printf '%s\n' "$out" >&2; exit 1; } sleep 3 done case $out in *'"state":"ready"'*) printf '%s\n' "$out" ;; *) printf '%s\n' "$out" >&2; exit 1 ;; esac REMOTE } # gw_reload: restart the default-profile gateway natively and require a ready gateway with a NEW instance_id. # `--detach` keeps `restart` from running the gateway inside the ssh shell when no service is installed. With live # sessions and no terminal, restart refuses; GW_INTERRUPT=1 adds -y and interrupts them deliberately (never set by default). # Side effect: the pre-restart `ensure` read that captures the old instance_id STARTS a gateway when none is running (ensure never # installs a service, but it does start one). A reload implies the gateway should run, so that is accepted; read-only skills never call gw_reload. gw_reload() { local old new out interrupt interrupt="" if [ "${GW_INTERRUPT:-0}" = 1 ]; then interrupt=1; fi # Keep an endpoint identity even when ensure exits 6 (draining/restart_wait). out=$(remote 'hermes -p default gateway ensure --json --timeout 10 2>/dev/null') || : old=$(printf '%s\n' "$out" | sed -n 's/.*"instance_id":"\([^"]*\)".*/\1/p') remote "hermes -p default gateway restart --detach${interrupt:+ -y}" \ || { echo "gw_reload: gateway restart failed or was refused (live sessions? re-run with GW_INTERRUPT=1 only if interrupting them is acceptable)." >&2; return 1; } out=$(gw_ready) || return 1 new=$(printf '%s' "$out" | sed -n 's/.*"instance_id":"\([^"]*\)".*/\1/p') if [ -n "$old" ] && [ "$new" = "$old" ]; then echo "gw_reload: the gateway instance did not change, so it was not restarted." >&2; return 1; fi printf '%s\n' "$out" } ``` Only `remote` is used here. `gw_ready` and `gw_reload` are kept byte-identical with the other skills and are **not called**: `ensure` starts a gateway. This skill has no `snapshot`/`rollback`/`finish` because it writes nothing. --- ## Step 1 — verify Hermes v0.22.5 is reachable on the VPS ```bash HERMES_BANNER=$(remote ' set -e command -v hermes >/dev/null 2>&1 \ || { echo "FAIL: hermes not found on the VPS host PATH; container-only installations are outside this procedure"; exit 1; } hermes -p default --version ') || { echo "ABORT: Hermes is not installed on the VPS host account (container-only installs are unsupported). Run /hermes-install first. Nothing was changed."; exit 1; } printf '%s\n' "$HERMES_BANNER" | grep -Eq '(^|[[:space:]])v?0[.]22[.]5([[:space:]]|$)' || { echo "ABORT: this procedure is source-verified for Hermes 0.22.5 only (got: $HERMES_BANNER). Nothing was changed."; exit 1; } echo "--- version ---" echo "$HERMES_BANNER" ``` Expected: a banner naming Hermes `0.22.5` (`HERMES_VERSION` in `VERSIONS.txt`). If it names another version, stop: the output formats below are only checked for 0.22.5. --- ## Step 2 — what will run (preview; nothing is changed) A health check needs no idempotency marker: every command below is a read, so running it twice is harmless. Do not write a "last checked" file; the old copy of this skill did, and a read-only skill must not. ```bash cat <