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
# Integrations

> **0.5.4** — security and agent-native release. Fixes a remote pre-auth DoS in the SSH banner path (A1), stops server-sent setuid bits landing on downloaded files (A3), closes the world-readable window on ACME/mTLS private keys (A2), and adds payload-shaping flags (`--select`, `--filter`, `--limit`, `--sort`, `--dedupe-by`, `--count-only`, `--truncate-content`, `--max-output-bytes`) applied before serialization. BREAKING: partial multi-host failure now exits **1** (was 65); a non-loopback `--bind` requires `--i-accept-network-exposure`. New `tunnel_closed` event.


> Connect 10+ AI coding agents to remote servers with one-shot ssh-cli.

- Read this document in [Portuguese (pt-BR)](INTEGRATIONS.pt-BR.md).
- Pair this catalog with [docs/AGENTS.md](docs/AGENTS.md) and [skills/ssh-cli-en/SKILL.md](skills/ssh-cli-en/SKILL.md).


## Flag Aliases
### camelCase aliases implemented in clap (do not invent others)
- Use `--sudoPassword` as alias of `--sudo-password`.
- Use `--suPassword` as alias of `--su-password`.
- Use `--maxChars` as legacy alias mapping to `max_command_chars`.
- Use `--disableSudo` as alias of `--disable-sudo`.
- **No** camelCase aliases for `--config-dir`, `--output-format`, or `--no-color` — use the kebab-case forms exactly.


## New Flags by Version
### Track surface growth without reading source
- `0.5.4` **tunnel modes (G-TUN-R01/R02/R03):** `tunnel --reverse` (server listens and delivers back; `REMOTE_PORT 0` = server allocates), `tunnel --socks5` (local SOCKS5 proxy, RFC 1928 no-auth + CONNECT, destination per connection, `REMOTE_HOST`/`REMOTE_PORT` omitted), `tunnel --remote-socket <PATH>` (remote Unix socket via `direct-streamlocal@openssh.com`; absolute path or exit **64**). JSON `mode` is `local` | `reverse` | `socks5` | `streamlocal`.
- `0.5.4` **agent-native payload shaping:** global `--select` (alias `--fields`), `--filter` (`key=value` | `key!=value` | `key~substring`, repeatable AND), `--limit`, `--sort`, `--dedupe-by`, `--count-only` (→ `{"count": N}`), `--truncate-content` (characters, never bytes), `--max-output-bytes` (drops trailing records, never slices JSON) — all applied **before** serialization. Plus global `--no-input` and `--dry-run` (honoured only by `vps remove`, `vps import`, `sftp rm`, `sftp rmdir`, `secrets init`, `secrets reencrypt`; elsewhere exit **64**).
- `0.5.4` **BREAKING + security:** partial multi-host failure exits **1** (was 65; G-ERR-R02); non-loopback `tunnel --bind` requires `--i-accept-network-exposure` (G-TUN-R13); new `tunnel_closed` event (`reason`, `forwards_served`, `capacity_waits`); `schema dry-run` / `schema tunnel-closed` now exit **0**; A1 applies the pre-existing `AUTH_BANNER_MAX_CHARS` (512) banner log cap on character boundaries instead of byte indices, removing a remotely triggerable abort, A2 creates ACME/mTLS keys at `0600` instead of tightening after creation, A3 masks inbound modes with `SFTP_PERM_MASK_UNTRUSTED` (`0o0777`) so setuid/setgid/sticky cannot ride a download.
- `0.5.3` **SFTP integrity + agent safety (G1–G19):** SFTP upload no longer truncates destinations to 0 bytes (`FileAttributes::empty`); SETSTAT sends atime+mtime together; `set_metadata` fail-closed; permission bits via `SFTP_PERM_MASK` (`0o7777`); download surfaces local `set_permissions` failures; SCP download `sync_data` before rename; batch cancel fills cancelled remainder (`results.len() == input.len()`); `exec --json` single NDJSON object; E2E SFTP checksums E17/E18. Prefer **0.5.3+** for any SFTP transfer.
- `0.5.3` **graduated verbosity (G2/G14):** `-v` info / `-vv` debug / `-vvv` trace via `ArgAction::Count`, always crate-scoped `warn,ssh_cli=*` — never bare global debug (no russh password dump). Ambient `RUST_LOG` still ignored. Even `-vvv` does **not** enable russh encrypted channel dumps.
- `0.5.2+` **SFTP subsystem (G-SFTP):** `ssh-cli sftp upload|download|ls|mkdir|rmdir|rm|stat|rename` via `russh-sftp` 2.3 (SFTP v3). Upload/download support `--recursive` trees (symlink **no-follow**), multi-host `--all`/`--hosts`, and JSON events `sftp-transfer` / `sftp-list` / `sftp-fs-op` / `sftp-batch`. Stream I/O only (no full-file heap). SCP remains regular-file-only. **Use 0.5.3+** for SFTP integrity fixes above.
- `0.5.2+` **multi-host fan-out (bounded concurrency):** `exec|sudo-exec|su-exec|scp|sftp|health-check --all` runs concurrent SSH sessions gated by global `--max-concurrency N` (1..=64; auto CPUs×4 vs free RAM/2 / 16 MiB when omitted). Batch envelopes: `health-check-batch` / `exec-batch` / `scp-batch` / `sftp-batch` (`docs/schemas/*-batch.schema.json`, field `max_concurrency`). Prefer one process with `--all` over N single-host spawns for fleet work. Tunnel accepts share the same admission gate.
- `0.5.2` **residual E2E + agent-first export/import + wire v3**: root `schema`/`doctor`; single `vps-added` + `secrets_key_auto_created`; ambient `RUST_LOG` ignored; ACME permanent 64; `vps add --use-agent`; export redacted `***` (`FIXED_MASK`); no product GH Actions; plus the `vps export` body follows the resolved output format (JSON on any non-TTY stdout, TOML only with `--output-format text`); `vps import` accepts TOML (EN keys + PT aliases) **or** JSON `vps-export` envelopes; dual-read EN serialize + PT aliases; host **schema v3**; CLI flags `--allow-plaintext-secrets`, `--secrets-key-file`, `--use-keyring` (prefer over env); `secrets init|reencrypt --json` events `secrets-init` / `secrets-reencrypt`; first secret write sets `secrets_key_auto_created` on the same `vps-added` document; CRUD success paths use `emit_success` JSON events; `--include-secrets` to pipe/non-TTY requires `-o`/`--output` or `--i-understand-secrets-on-stdout`; tunnel `--bind` (default `127.0.0.1`); import `TomlDe` → exit **65**; `SshAuthentication` → **77**; SCP missing message `file not found: <path>` (exit **66**); timeout warn if `<1000` ms; password-on-argv stderr warn; doctor `secrets_plaintext_opt_out` is **bool**.
- `0.4.2` tunnel ephemeral port `local_port=0` reports OS-assigned port after bind (TUN-003); SCP remote missing → exit **66** (IO-010); `vps export --json` envelope `event: "vps-export"`; e2e E15/E16; suite `gaps_v042`.
- `0.4.1` SCP **regular files only** auth parity + scp event + export/tunnel fixes (inherits 0.4.0 wire; no `-r` / no SFTP); solid SCP wire (avoid crates.io **0.3.9** broken SCP); scp flags `--timeout`, `--password-stdin`, `--key`, `--key-passphrase` / `--key-passphrase-stdin`, `--json` → `docs/schemas/scp-transfer.schema.json` with required `event: "scp-transfer"` (IO-009); download writes `{path}.ssh-cli.partial` then rename; preserve mtime/mode bi-dir; upload streams 32 KiB; `tunnel --json` emits `tunnel_listening` after bind; scp JSON error envelope on stderr when `--json`; **EXP-001** redacted export empty secrets stay `""` (never `sshcli-enc:` of empty); **TUN-002** tunnel post-bind deadline exits **0** after `tunnel_listening` (pre-bind timeout still **74**); **CLI-005** tunnel auth stdin/passphrase parity; **CLI-006** health-check auth parity.
- `0.4.0` solid SCP wire protocol (fixes crates.io **0.3.9** broken SCP); file-only transfers; `tunnel --json` / `tunnel_listening`.
- `0.3.9` default tracing filter `error` (agent-first); empty password serializes as JSON `null` on key-only hosts; `health-check --timeout <ms>`; product-line docs audit.
- `0.3.8` russh 0.62.2; tunnel agent stdout clean; no-active-VPS exits `66` (`EX_NOINPUT`); `cargo deny` with `yanked=deny`.
- `0.3.7` `--output-format` on VPS CRUD; `health-check --json`; `--quiet`; JSON error envelope; tunnel timeout covers connect.
- `0.3.6` adds default at-rest encryption, `secrets status|init|reencrypt`, `SSH_CLI_ALLOW_PLAINTEXT_SECRETS`, doctor fields `secrets_key_file` / `secrets_plaintext_opt_out`, `scripts/e2e_real_ssh.sh`.
- `0.3.5` adds `--key-passphrase-stdin` runtime paths, auto JSON on non-TTY, doctor `secrets_at_rest`, residual atomic export and AEAD (then optional).
- `0.3.4` adds `--key`, `--key-passphrase`, `--password-stdin`, `--sudo-password-stdin`, `--su-password-stdin`, `--timeout-ms` (tunnel), `--disable-sudo`, `--description`, `--replace-host-key`, `max_command_chars`, `max_output_chars`, `vps doctor`, `vps export`, `vps import`, `su-exec`.
- `0.2.0` adds runtime `--password`, `--sudo-password`, `--timeout` overrides and camelCase aliases.
- Prefer **0.5.3+** for SFTP integrity (G1–G19), graduated crate-scoped verbosity, export/import roundtrip, wire schema v3, working SCP + `tunnel --json` / `--bind`, full SSH automation, default secret encryption, and clean supply-chain.

## Discovery for agents
- `ssh-cli commands` — full command tree (vps, connect, exec, sudo-exec, su-exec, scp, sftp, tunnel, health-check, secrets, completions, commands, schema, doctor, locale, tls).
- `ssh-cli schema [NAME]` — embedded JSON Schema catalog/body.
- `ssh-cli doctor` — root alias of `vps doctor` (local + optional SSH probe).
- `ssh-cli locale show|set|clear` — UI locale preference (XDG; not product env).
- `ssh-cli tls provider|paths|mtls|acme` — optional SSH-over-TLS material under XDG `tls/`.


## Summary Table

| Agent / Platform | Integration style | JSON | Notes |
| --- | --- | --- | --- |
| Claude Code | subprocess CLI + skill | yes | Prefer skill package |
| Cursor | shell / agent tools | yes | Use `--json` |
| Windsurf | shell tool | yes | One-shot per task |
| Codex CLI | shell tool | yes | Map sysexits |
| OpenCode | shell tool | yes | One-shot only |
| Aider | shell commands | yes | Store hosts once |
| Continue | custom command | yes | XDG multi-host |
| Gemini CLI | shell tool | yes | Prefer stdin secrets |
| OpenHands | sandbox shell | yes | Bound tunnel timeouts |
| Generic bash/zsh | direct install | yes | Completions available |


## Claude Code
- Install `ssh-cli` on the host PATH with `cargo install ssh-cli --locked`.
- Load [skills/ssh-cli-en/SKILL.md](skills/ssh-cli-en/SKILL.md) or the pt package.
- Register hosts once with `vps add` (prefer `--password-stdin`) then call `exec` per task.
- Prefer `--json` envelopes for structured tool results.
- Parse stdout only; default stderr is silent at tracing level `error` (pass `-v`/`-vv`/`-vvv` when debugging — crate-scoped allowlist; ambient `RUST_LOG` is ignored).
- Use `ssh-cli secrets status` / `vps doctor --json` / `ssh-cli commands` as preflight for encryption, paths, and surface discovery.


## Cursor
- Add a project rule that prefers `ssh-cli` over long-lived Node SSH processes.
- Keep credentials out of chat by using stored hosts and stdin secret flags.
- Parse JSON stdout only; default stderr is silent at tracing level `error` (ignore tracing unless you pass `-v`/`-vv`/`-vvv`; ambient `RUST_LOG` is ignored).


## Windsurf
- Invoke one-shot commands after host registration.
- Never keep a tunnel open without `--timeout-ms`.


## Codex CLI
- Treat non-zero exits as typed failures using the exit code table in README.
- Retry only on transient IO/timeout codes, never on auth or usage errors.


## OpenCode
- Use shell tool mode with explicit argv arrays.
- Avoid embedding passwords in prompt text; use registry or stdin.


## Aider
- Document host names in the repo without secrets.
- Call `ssh-cli exec <name> "..."` for remote ops during edit loops.


## Continue
- Map custom commands to `ssh-cli` subcommands with `--json`.
- Use `vps doctor --json` as a health preflight for agent sessions.


## Gemini CLI
- Prefer key auth and masked `vps show` for verification.
- Keep elevation disabled unless the task requires root.


## OpenHands
- Run inside the sandbox with network policy that allows only target hosts.
- Force bounded tunnels and short timeouts.


## Generic Shell
- Install completions with `ssh-cli completions <shell>`.
- Use `--config-dir` only for isolated test sandboxes (product does not read `SSH_CLI_HOME`).