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

Uploaded Python 3

File details

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

File metadata

  • Download URL: hermes_ssh-0.3.1.tar.gz
  • Upload date:
  • Size: 66.1 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.1.tar.gz
Algorithm Hash digest
SHA256 a12a4c394197c270ecb4ebae835ffb760711dfd890cf9c97908db4a2e7c78a40
MD5 d7783bb073d9e763ea7eb3732bde1358
BLAKE2b-256 14a26e7486aa21bf112243428aaba9ef1c3c7bb5353572c35a43cef9fadb60e4

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: hermes_ssh-0.3.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a164540efbc3c330caececa5930f94b7f3419357feb3103878406d6ad849d8bb
MD5 017839e5b808ae90ec99a1fd6d35866e
BLAKE2b-256 8a5f3ecc359f1c2d30ceacb57d86e0d3d39402909f93ef1e7b1cc33830ec5657

See more details on using hashes here.

Provenance

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