Skip to main content

hermes-ssh

CI License: MIT Python 3.11+

SSH remote operations plugin for Hermes Agent.

Run commands, transfer files, track background sessions, and reuse connections across a named machine registry.

/ssh web1 uptime
ssh_transfer action=upload machine=web1 source="./dist/app.tar.gz" destination="/srv/releases/app.tar.gz"
ssh_machines action=add name=web1 host=192.168.1.50 user=deploy

quick start

hermes-ssh is for named multi-host operations. Hermes's core SSH backend is useful for one configured remote terminal; this plugin adds a reusable machine inventory, aliases, per-command targeting, file transfer, background sessions, and audit history.

Requires Python 3.11+, Hermes Agent, and OpenSSH clients named ssh and sftp.

Install the package into the same Python environment that runs Hermes:

python -m pip install hermes-ssh
hermes plugins enable hermes-ssh --no-allow-tool-override

Run /reset or restart Hermes, then verify:

python -m pip show hermes-ssh
hermes plugins list --enabled --plain

If Hermes cannot see the package, the python command above was not Hermes's Python. Follow the environment procedure in the repository AGENTS.md.

For source development:

git clone https://github.com/TheEpTic/hermes-plugins.git
cd hermes-plugins/hermes-ssh
./deploy.sh
hermes plugins enable hermes-ssh --no-allow-tool-override

Run /reset or restart Hermes after changing the source tree.

features

ssh_terminal — run commands

Execute any command on a remote machine. Commands run through bash -c with pipefail, so pipelines work correctly.

# synchronous
ssh_terminal machine=web1 command="df -h"

# background
ssh_terminal machine=web1 command="tail -f /var/log/syslog" background=true

# custom timeout
ssh_terminal machine=web1 command="make -j4" timeout=300

When output exceeds max_output_chars (default: 50,000), the full output is saved under the plugin's restricted output directory and a summary with the path is returned.

Long-running commands can spool stdout and stderr to restricted files in the background:

ssh_terminal poll=<session_id>
ssh_terminal read_output=<session_id>

ssh_sessions action=poll session_id=<session_id>
ssh_sessions action=read_output session_id=<session_id>

ssh_transfer — upload and download files

Transfer a regular file or directory between the Hermes host and a registered machine. The tool uses OpenSSH SFTP, reuses the same ControlMaster connection settings as ssh_terminal, and records transfer metadata in the existing audit log.

Use ssh_transfer instead of raw scp or ssh -i ... in the terminal. It is the audited, policy-checked transfer surface: registered machines, no shell interpolation, staged finalisation, and approval coverage for sensitive destinations. If you reach for scp or a raw sftp pipeline, stop — ssh_transfer covers uploads, downloads, and recursive trees with explicit overwrite semantics.

The LLM-facing tool schema and description live in src/ssh_tools/schemas.py as SSH_TRANSFER_SCHEMA (registered in src/ssh_tools/__init__.py; the handler is handlers/transfer.py). Its description: "Upload or download a file or directory using a registered SSH machine and OpenSSH SFTP. Transfers default to no overwrite. Credential paths and symbolic links are blocked."

# upload a release
ssh_transfer action=upload machine=web1 source="./dist/app.tar.gz" destination="/srv/releases/app.tar.gz"

# download a log
ssh_transfer action=download machine=web1 source="/var/log/app.log" destination="./app.log"

# upload a directory
ssh_transfer action=upload machine=web1 source="./public" destination="/srv/app/public" recursive=true

# explicitly replace an existing regular file
ssh_transfer action=upload machine=web1 source="./app.tar.gz" destination="/srv/app.tar.gz" overwrite=true

Transfer behaviour is intentionally conservative:

  • Existing files are not replaced unless overwrite=true.
  • Directories require recursive=true.
  • Existing directories are never merged or replaced.
  • Uploads and downloads stage through generated temporary paths before final rename.
  • Local and remote credential paths are blocked.
  • Symbolic links, special files, traversal segments, wildcard remote paths, and recursive trees containing links are rejected.
  • Upload and download paths are explicit destinations, not shell expressions.

ssh_machines — machine registry

Register servers once, then refer to them by name or alias.

ssh_machines action=add name=web1 host=192.168.1.50 user=deploy key=~/.ssh/id_ed25519
ssh_machines action=add name=prod-web host=10.0.0.1 aliases=web1 tags=production,web
ssh_machines action=list
ssh_machines action=test name=web1
ssh_machines action=inspect name=web1

Machine names must be alphanumeric with dots, hyphens, or underscores (1-64 characters). Slashes, spaces, and glob characters are rejected.

Registry guard (check-before-create). Adding a machine whose host and user already exist under a different name does not fail, but the response carries a non-blocking warning plus a hint naming the existing registration, so agents reuse it instead of creating throwaway aliases:

ssh_machines action=add name=web1-staging host=192.168.1.50 user=deploy
# -> success: true
#    warning: "host 192.168.1.50 with user deploy already registered as name web1"
#    hint:    "If you meant to reuse that host, use the existing registration (web1) ..."

Only re-adding the exact same name takes the update path (no warning). Diagnose collisions with ssh_machines action=list or ssh_machines action=inspect name=<existing>.

ssh_sessions — session tracking

Background commands are tracked as sessions with their process, machine, command count, and idle time.

ssh_sessions action=list
ssh_sessions action=kill session_id=<session_id>
ssh_sessions action=cleanup
ssh_sessions action=prune

Idle sessions are automatically killed after 30 minutes. Closed sessions are pruned after 24 hours.

/ssh slash command

Quick terminal access from chat:

/ssh
/ssh web1
/ssh web1 uptime
/ssh web1 docker ps
/ssh test
/ssh cleanup
/ssh help

File transfers use the ssh_transfer tool rather than slash-command syntax so direction, paths, overwrite behaviour, and recursion remain explicit.

configuration

Settings live in src/ssh_tools/config.py as an SSHConfig dataclass:

setting default description
default_port 22 SSH port for new machines
default_user current local user SSH user for new machines
connect_timeout 5s SSH handshake timeout
command_timeout 30s command execution timeout
max_output_chars 50,000 output truncation threshold
audit_log_mode redacted redacted, metadata, or off
idle_check_interval 60s seconds between idle checks
idle_timeout_minutes 30m auto-kill after this idle time
closed_prune_hours 24h remove closed sessions after this
strict_host_key_checking yes SSH host key verification

ssh_transfer has a 300-second default timeout and a 3,600-second maximum supplied through its tool schema.

architecture

src/ssh_tools/
├── __init__.py          # plugin registration and Hermes hooks
├── approval.py          # Hermes dangerous-command approval bridge
├── config.py            # SSHConfig
├── manager.py           # machine, command, and session state
├── models.py            # Machine and Session dataclasses
├── schemas.py           # LLM-facing tool schemas
├── storage.py           # encrypted machine registry
├── transfers/           # SFTP policy, transport, staging, and audit
│   ├── __init__.py      # validated transfer entry point
│   ├── models.py        # transfer request and result models
│   ├── policy.py        # local and remote path safety
│   ├── service.py       # transfer orchestration and finalisation
│   └── transport.py     # OpenSSH SFTP argv and batch construction
├── utils.py             # handler response helpers
├── py.typed             # PEP 561 marker
└── handlers/
    ├── terminal.py      # ssh_terminal
    ├── transfer.py      # ssh_transfer
    ├── machines.py      # ssh_machines
    ├── sessions.py      # ssh_sessions
    └── slash.py         # /ssh

Key design decisions:

  • SSHManager owns the machine registry, command execution, and session state.
  • Tool handlers are thin closures that validate parameters and dispatch work.
  • Transfers use argv plus SFTP batch input, never shell=True or interpolated local shell commands.
  • Upload/download payloads are staged before their final rename.
  • Machine records are encrypted at rest.
  • JSON files use atomic writes for crash safety.
  • Data directories use 0o700; audit and output files use 0o600.
  • Machine names and transfer paths are validated before reaching OpenSSH.
  • Connections are reused through ControlMaster with a five-minute persist window.

security

See SECURITY.md for the full boundary.

Defaults and limitations worth knowing:

  • StrictHostKeyChecking=yes is the default. Add a verified host key to OpenSSH known_hosts before registering a machine. accept-new remains available only for an explicit compatibility override.
  • Machine credentials are encrypted at rest under ~/.hermes/ssh-tools/.
  • Audit logs redact common inline command credentials by default. Metadata mode stores hashes and lengths instead of transfer paths.
  • Commands and transfers run with the registered remote user's permissions.
  • Transfer path blocks reduce accidental credential movement but are not a sandbox for untrusted prompts.
  • Use dedicated non-root accounts and expose Hermes only to trusted operators.

shared inventory

The machine registry is a shared inventory: all data lives in ~/.hermes/ssh-tools/ (the data_dir in src/ssh_tools/config.py) and is global across Hermes profiles. A machine registered in one profile is visible to every other profile on the same host, and the desktop UI labels it "shared inventory" for exactly this reason. There is no per-profile scoping today.

Two consequences worth knowing:

  • Names must be unique across profiles. If two profiles register the same host+user under different names, the registry guard (see ssh_machines) warns on the second add.
  • The registry is per-machine, not per-user. It follows the filesystem user that runs Hermes (~/.hermes/ssh-tools/ expands for that user). The encrypted store's key and 0o700 permissions live alongside the data, so other OS users cannot read it.

requirements

  • Python 3.11+
  • OpenSSH clients named ssh and sftp
  • Hermes Agent

troubleshooting

Plugin installed but tools are absent

Enable the plugin and reset Hermes:

hermes plugins enable hermes-ssh --no-allow-tool-override
hermes plugins list --enabled --plain

Permission denied (publickey)

The stored user, key path, or remote authorisation is wrong. Verify the same account with OpenSSH outside Hermes.

Transfer says sftp is unavailable

Install the OpenSSH client package in the environment or container that runs Hermes and verify sftp is on that process's PATH.

Remote path rejected

Remote transfer paths must be absolute or begin with ~/, name an explicit file or directory, and contain no wildcard or traversal segments.

Destination already exists

Regular files require overwrite=true for replacement. Directory replacement and merging are intentionally unsupported.

Command timeout

Increase timeout or use background=true for terminal commands. Transfers accept timeouts from 1 to 3,600 seconds.

Output looks truncated

Large terminal output is retained under the restricted output directory. Use read_output or read_file on the returned path.

development

git clone https://github.com/TheEpTic/hermes-plugins.git
cd hermes-plugins/hermes-ssh
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'

black --check src/ssh_tools/ tests/
mypy src/ssh_tools/
pytest

CI runs those gates on Python 3.11, 3.12, and 3.13.

license

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hermes_ssh-0.4.4.tar.gz (106.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hermes_ssh-0.4.4-py3-none-any.whl (46.2 kB view details)

Uploaded Python 3

File details

Details for the file hermes_ssh-0.4.4.tar.gz.

File metadata

  • Download URL: hermes_ssh-0.4.4.tar.gz
  • Upload date:
  • Size: 106.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for hermes_ssh-0.4.4.tar.gz
Algorithm Hash digest
SHA256 bab8cb3e63364f30cdd3d46ee75898fbb59d1b1dfd57c364701efac6da2173d6
MD5 ed675c682b26a6b050f56890d0672ed1
BLAKE2b-256 50222c581981c13f5135d20e846002c450aaf3f33d3ee98a3af2e1b7d5d64541

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_ssh-0.4.4.tar.gz:

Publisher: pypi-publish-ssh.yml on TheEpTic/hermes-plugins

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hermes_ssh-0.4.4-py3-none-any.whl.

File metadata

  • Download URL: hermes_ssh-0.4.4-py3-none-any.whl
  • Upload date:
  • Size: 46.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for hermes_ssh-0.4.4-py3-none-any.whl
Algorithm Hash digest
SHA256 399e2ab3e06d3b194f79c8fb030c8503d2044461f0dd7c3ced749a6f36aec116
MD5 3b3d1e31a350b5d38fa5ae8a32ad92a0
BLAKE2b-256 65bb7513c1472c751dacaad72678a15d2978ffdd2a92ad8278babb5760fcc6ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_ssh-0.4.4-py3-none-any.whl:

Publisher: pypi-publish-ssh.yml on TheEpTic/hermes-plugins

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.8

2 files

0.4.7

2 files

This release

0.4.4 This release

2 files

0.4.3

2 files

0.4.2

2 files

0.4.0

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page