Skip to main content

SSH remote execution plugin for Hermes Agent

Project description

hermes-ssh

CI License: MIT Python 3.11+

SSH remote execution plugin for Hermes Agent.

Run commands on remote servers, track sessions, reuse connections — all from inside Hermes.

/ssh web1 uptime
ssh_machines add name=web1 host=192.168.1.50
ssh_sessions list

Quick Start

Requires Python 3.11+ and an OpenSSH client (ssh) on the host system.

Option 1: Deploy script (recommended)

git clone https://github.com/TheEpTic/hermes-plugins.git
cd hermes-plugins/hermes-ssh
./deploy.sh

Then restart Hermes with /reset.

Option 2: Manual symlink

git clone https://github.com/TheEpTic/hermes-plugins.git
ln -s "$(pwd)/hermes-plugins/hermes-ssh/src/ssh_tools" ~/.hermes/plugins/hermes-ssh

Then /reset in Hermes. The symlink points at the source tree, but Python modules are imported once, so code changes still require /reset or a Hermes process restart before they load.

Option 3: As a Python package

pip install git+https://github.com/TheEpTic/hermes-plugins.git#subdirectory=hermes-ssh

Then enable it and restart Hermes:

hermes plugins enable hermes-ssh

Features

ssh_terminal — Run Commands

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

# Synchronous (waits for completion)
ssh_terminal machine=web1 command="df -h"

# Background (returns immediately)
ssh_terminal machine=web1 command="tail -f /var/log/syslog" background=true

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

Output truncation: 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 file path is returned. The LLM can then use read_file to access the complete output.

Background commands: Long-running commands can be backgrounded. The plugin tracks the process and lets you poll for status or retrieve output later.

# Check if still running
ssh_terminal poll=<session_id>

# Read output from completed command
ssh_terminal read_output=<session_id>

# Or via ssh_sessions
ssh_sessions action=poll session_id=<session_id>
ssh_sessions action=read_output session_id=<session_id>

ssh_machines — Machine Registry

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

# Add a server
ssh_machines action=add name=web1 host=192.168.1.50 user=deploy key=~/.ssh/id_ed25519

# Add with aliases and tags
ssh_machines action=add name=prod-web host=10.0.0.1 aliases=web1,tags=production,web

# List all machines
ssh_machines action=list

# Test connectivity
ssh_machines action=test name=web1

# Get full details
ssh_machines action=inspect name=web1

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

ssh_sessions — Session Tracking

Every command creates a session. Sessions track the PID, machine, command count, and idle time.

# List active sessions
ssh_sessions action=list

# Kill a session (terminates the SSH process)
ssh_sessions action=kill session_id=<session_id>

# Clean up all idle sessions (>30 min)
ssh_sessions action=cleanup

# Remove old closed sessions (>24 hours)
ssh_sessions action=prune

Idle sessions are automatically killed by a background checker after 30 minutes. Closed sessions are pruned after 24 hours.

/ssh Slash Command

Quick access from chat without remembering tool names:

/ssh                     # List machines and sessions
/ssh web1                # Inspect a machine
/ssh web1 uptime         # Run a command
/ssh web1 docker ps      # Run a command
/ssh test                # Test connectivity to all machines
/ssh cleanup             # Kill all idle sessions
/ssh help                # Show help

Configuration

All 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 root 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
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 accept-new SSH host key verification

Architecture

src/ssh_tools/
├── __init__.py          # Plugin registration + Hermes hooks
├── config.py            # SSHConfig (immutable dataclass)
├── manager.py           # SSHManager — all state and operations
├── models.py            # Machine, Session dataclasses
├── schemas.py           # Tool schemas (LLM-facing)
├── utils.py             # ok(), err(), require() helpers
├── py.typed             # PEP 561 marker
└── handlers/
    ├── terminal.py      # ssh_terminal (execute, poll, read_output)
    ├── machines.py      # ssh_machines (add/list/remove/test/inspect)
    ├── sessions.py      # ssh_sessions (list/kill/cleanup/prune/poll/read_output)
    └── slash.py         # /ssh slash command

Key design decisions:

  • SSHManager owns all state. Thread-safe. No module-level mutable state.
  • Handlers are thin closures — validate params, dispatch to manager, return JSON.
  • JSON files use atomic writes (temp file + os.replace) for crash safety.
  • Data directory has restricted permissions (0o700). Audit log and output files use 0o600.
  • Machine names are validated to prevent path traversal and glob injection.
  • Connection reuse via ControlMaster with 5-minute persist window.

Security

See SECURITY.md for the full picture.

Defaults you should know about:

  • StrictHostKeyChecking=accept-new — accepts first-seen host keys but rejects changed keys. Set to yes for strict production hosts.
  • Machine credentials are encrypted at rest in ~/.hermes/ssh-tools/machines.json. Data directory is 0o700.
  • All commands execute with the permissions of the Hermes agent process.

Hardening applied:

  • Machine names validated against ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$
  • Output files written with 0o600 permissions
  • Audit log created with 0o600 permissions
  • Atomic JSON writes prevent corruption on crash
  • Orphaned temp files cleaned on startup

Requirements

Troubleshooting

Connection refused The remote host may not be listening on the expected port, or a firewall is blocking the connection. Verify with ssh -v user@host outside of Hermes.

Permission denied (publickey) The SSH key path stored in the machine registry may be incorrect, or the remote host doesn't have the corresponding public key in ~/.ssh/authorized_keys. Verify with ssh -i /path/to/key user@host.

Command timeout Commands exceeding command_timeout (default 30s) are killed. Increase the timeout or use background=true for long-running work.

Output looks truncated This is intentional — large outputs are saved under the restricted plugin output directory and a summary is returned. Use read_output or read_file on the returned path for the full output.

Session stuck as "active" after process died If the agent restarted, background process references are lost. Use ssh_sessions action=cleanup to kill stale sessions, or ssh_sessions action=prune to remove old closed ones.

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]'

# Run checks
black --check src/ssh_tools/ tests/
mypy src/ssh_tools/
pytest

See CONTRIBUTING.md for guidelines.

License

MIT — see LICENSE.

Project details


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.3.2.tar.gz (66.2 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.3.2-py3-none-any.whl (28.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: hermes_ssh-0.3.2.tar.gz
  • Upload date:
  • Size: 66.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hermes_ssh-0.3.2.tar.gz
Algorithm Hash digest
SHA256 41c05e4b8ccc2600c85f3dfcb717a8ea46e685806913488c870c55a7f3011a33
MD5 e8a24a28a3276e840dde741b24ef1990
BLAKE2b-256 0e3f7c77c94c865d59d276e2f52af1bff0a4f94be695c26b671332bfbfe5fc46

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_ssh-0.3.2.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.3.2-py3-none-any.whl.

File metadata

  • Download URL: hermes_ssh-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 28.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hermes_ssh-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f332a9fb5fd7fa01e5008d79eec3c8f0e912c1946d56cbfc92c864b3f7a632a1
MD5 258303d6a4f9e43a5d0a453dd67812a6
BLAKE2b-256 7a2805ce6d9e4c472507f816b14376caed33a7fc278a3034d7e7c1a8263b267e

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_ssh-0.3.2-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.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page