ssh-cli 0.5.5

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/dry-run.schema.json",
  "title": "dry-run plan JSON",
  "description": "Structured stdout payload emitted instead of executing a destructive operation when `--dry-run` is passed (C2). Emitted as JSON even in text mode: the point of a preview is that a machine can diff it against what it intended, and prose cannot be diffed. `--dry-run` is accepted only by `vps remove`, `vps import`, `sftp rm`, `sftp rmdir`, `secrets init` and `secrets reencrypt`; on any other command it is rejected with exit 64 rather than accepted and ignored.",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "ok",
    "event",
    "operation",
    "dry_run",
    "executed"
  ],
  "properties": {
    "ok": {
      "type": "boolean",
      "const": true,
      "description": "True: producing a plan is itself a success. A plan for an operation that would fail is not produced — preconditions that the real run checks (host exists, import validates) run first, so a refusal surfaces as the same error the real run would give."
    },
    "event": {
      "type": "string",
      "const": "dry-run"
    },
    "operation": {
      "type": "string",
      "enum": [
        "vps-remove",
        "vps-import",
        "sftp-rm",
        "sftp-rmdir",
        "secrets-init",
        "secrets-reencrypt"
      ],
      "description": "Which operation was previewed"
    },
    "dry_run": {
      "type": "boolean",
      "const": true
    },
    "executed": {
      "type": "boolean",
      "const": false,
      "description": "Always false. Present as an explicit field so a consumer never has to infer non-execution from the absence of a success event."
    },
    "name": {
      "type": "string",
      "description": "`vps-remove`: the host that would be removed"
    },
    "config_path": {
      "type": "string",
      "description": "Registry file that would be rewritten"
    },
    "source": {
      "type": "string",
      "description": "`vps-import`: the file that would be read"
    },
    "imported": {
      "type": "integer",
      "minimum": 0,
      "description": "`vps-import`: how many hosts would be written"
    },
    "hosts": {
      "description": "`vps-import`: one entry per host that would be written. `secrets-reencrypt` / `secrets-init`: a count instead, since no host is named.",
      "oneOf": [
        {
          "type": "integer",
          "minimum": 0
        },
        {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "name",
              "replaces_existing",
              "incomplete"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "replaces_existing": {
                "type": "boolean",
                "description": "True when a host of the same name already exists and would be overwritten. This is the field that decides whether the import is safe to run; a bare count would hide it."
              },
              "incomplete": {
                "type": "boolean",
                "description": "True when the host lacks full auth and is only accepted because of --allow-incomplete"
              }
            }
          }
        }
      ]
    },
    "vps": {
      "type": "string",
      "description": "`sftp-rm` / `sftp-rmdir`: the registered host the operation targets"
    },
    "remote": {
      "type": "string",
      "description": "`sftp-rm` / `sftp-rmdir`: the remote path that would be removed. Not confirmed to exist: doing so would cost a full connect and authentication, which would make the preview visible in the server's auth log exactly like a real run."
    },
    "force": {
      "type": "boolean",
      "description": "`secrets-init`: whether the primary key would be rotated"
    },
    "keyring": {
      "type": "boolean",
      "description": "`secrets-init`: whether the key would be stored in the OS keyring"
    },
    "hosts_to_reencrypt": {
      "type": "integer",
      "minimum": 0,
      "description": "`secrets-init --force`: how many hosts would be re-encrypted under the new key. Rotating without re-encrypting these would permanently lose their at-rest secrets, so the count is the number that matters before proceeding."
    }
  }
}