ssh-cli 0.5.4

Native Rust CLI that gives LLMs (Claude Code, Cursor, Windsurf) the ability to operate remote servers via SSH over stdin/stdout
Documentation
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/danilo-aguiar-br/ssh-cli/docs/schemas/tunnel-closed.schema.json",
  "title": "tunnel closed JSON",
  "description": "Structured stdout payload emitted when `ssh-cli tunnel ... --json` stops serving (G-TUN-R07). Emitted on every ending, including the clean deadline path. Before this event three very different endings shared exit 0 — deadline reached, signal received, and a fatal accept error that broke the loop early — so a tunnel that died three seconds into a five-minute deadline was indistinguishable from one that served its full lifetime.",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "ok",
    "event",
    "vps",
    "reason",
    "bind",
    "local_port",
    "forwards_served",
    "capacity_waits",
    "duration_ms"
  ],
  "properties": {
    "ok": {
      "type": "boolean",
      "description": "True when the tunnel served its full lifetime; false when it stopped on a fatal accept error (the process still exits 0 because the bind succeeded)"
    },
    "event": {
      "type": "string",
      "const": "tunnel_closed"
    },
    "vps": {
      "type": "string",
      "description": "Registered VPS name used for the tunnel"
    },
    "reason": {
      "type": "string",
      "enum": [
        "deadline",
        "signal",
        "accept_error"
      ],
      "description": "Why the tunnel stopped: `deadline` = --timeout-ms elapsed after a successful bind; `signal` = SIGINT/SIGTERM; `accept_error` = the accept loop hit a fatal error and stopped early"
    },
    "mode": {
      "type": "string",
      "enum": [
        "local",
        "socks5",
        "streamlocal",
        "reverse"
      ],
      "default": "local",
      "description": "Which tunnel was serving; mirrors the `mode` of the matching `tunnel_listening` event. Absent in events written before 0.5.4, which can only have been plain local forwards."
    },
    "bind": {
      "type": "string",
      "description": "Effective bind address that was served — the local one for `local`/`socks5`/`streamlocal`, the address requested from the server for `reverse`"
    },
    "local_port": {
      "type": "integer",
      "minimum": 1,
      "maximum": 65535,
      "description": "Port that was served (OS-assigned when the requested port was 0; the server-allocated port under `reverse`)"
    },
    "forwards_served": {
      "type": "integer",
      "minimum": 0,
      "description": "Connections accepted and forwarded during the tunnel's lifetime (G-TUN-R11). Answers the most basic diagnostic question: did anything ever connect? Zero distinguishes a client misconfiguration from a tunnel fault"
    },
    "capacity_waits": {
      "type": "integer",
      "minimum": 0,
      "description": "Times a new connection had to wait for a concurrency permit (G-TUN-R12). Non-zero means --max-concurrency was the bottleneck, which otherwise appears only as unexplained latency"
    },
    "duration_ms": {
      "type": "integer",
      "minimum": 0,
      "description": "Wall-clock lifetime in milliseconds, measured from before the SSH connect"
    }
  }
}