Skip to content

SSH Fabric

Remote host management, command execution, health monitoring, and Home Assistant integration.

Host Configuration

Hosts are defined in ~/.claude-superpowers/hosts.yaml:

hosts:
  - alias: proxmox
    hostname: 192.168.30.10
    port: 22
    username: root
    auth: key
    key_file: ~/.ssh/id_ed25519
    groups:
      - servers
      - hypervisors
    tags:
      role: hypervisor

  - alias: truenas
    hostname: 192.168.13.69
    username: ray
    auth: password
    groups:
      - storage

  - alias: pihole
    hostname: 192.168.30.53
    auth: agent
    groups:
      - dns

Fields

Field Required Default Description
alias yes — Short name used in commands
hostname yes — IP address or DNS name
port no 22 SSH port
username no root SSH login user
auth no key Authentication method: key, password, agent
key_file no "" Path to SSH private key (for key auth)
groups no ["all"] Groups this host belongs to; all is auto-appended
tags no {} Arbitrary key-value metadata

Authentication Methods

key (default)

Uses SSH key-based authentication. If key_file is set, that key is used explicitly. Otherwise paramiko uses the default key discovery.

If the key has a passphrase, store it in the vault:

claw vault set ssh:proxmox:passphrase "my-passphrase"

password

Requires the encrypted vault. Store the password first:

claw vault set ssh:truenas:password "hunter2"

The connection pool looks up ssh:<alias>:password at connect time.

agent

Delegates authentication to the running SSH agent (ssh-agent). No key file or password needed.

Vault Integration

The SSH fabric uses the same age-encrypted vault as the rest of claude-superpowers (~/.claude-superpowers/vault.enc). Vault keys used by SSH:

Vault Key Purpose
ssh:<alias>:password Password for password auth
ssh:<alias>:passphrase Key passphrase for key auth

Manage with claw vault:

claw vault set ssh:truenas:password "s3cret"
claw vault get ssh:truenas:password
claw vault list

CLI Commands

Run commands on hosts

# Single host
claw ssh proxmox "uptime"

# By group
claw ssh servers "df -h"

# All hosts
claw ssh all "hostname"

Host management

# List configured hosts
claw ssh hosts

# Show groups
claw ssh groups

# Health check
claw ssh health
claw ssh health --json

Connection Pool

The ConnectionPool manages cached paramiko SSH clients:

  • Lazy connection: clients are created on first use
  • Caching: subsequent calls to the same host reuse the existing connection
  • Staleness detection: if the transport is no longer active, the client reconnects
  • Max age: connections older than max_age seconds (default 300) are recycled
  • Context manager: with ConnectionPool(hosts) as pool: automatically closes all connections on exit

SSH Executor

The SSHExecutor resolves targets (aliases or groups) and runs commands across matched hosts:

from superpowers.ssh_fabric import ConnectionPool, HostRegistry, SSHExecutor

hosts = HostRegistry()
with ConnectionPool(hosts) as pool:
    executor = SSHExecutor(pool, hosts)
    results = executor.run("all", "uptime")
    for r in results:
        print(f"{r.host}: {'OK' if r.ok else 'FAIL'} — {r.stdout.strip()}")

Key behaviors:

  • A connection failure on one host does not abort execution on others
  • Each result includes stdout, stderr, exit_code, and an ok flag
  • ok is True only when exit_code == 0 and no error occurred

Health Checker

The HealthChecker runs ICMP ping and SSH probes against all configured hosts:

from superpowers.ssh_fabric import ConnectionPool, HostRegistry, SSHExecutor
from superpowers.ssh_fabric.health import HealthChecker

hosts = HostRegistry()
with ConnectionPool(hosts) as pool:
    executor = SSHExecutor(pool, hosts)
    checker = HealthChecker(hosts, executor)
    report = checker.check_all()

    for h in report.hosts:
        print(f"{h.alias}: ping={'OK' if h.ping_ok else 'FAIL'} ssh={'OK' if h.ssh_ok else 'FAIL'}")

    # Write to ~/.claude-superpowers/ssh/health.json
    checker.write_json(report)

Health report JSON

Written to ~/.claude-superpowers/ssh/health.json via atomic replace:

{
  "timestamp": 1700000000.0,
  "all_ok": false,
  "hosts": [
    {
      "alias": "proxmox",
      "hostname": "192.168.30.10",
      "ping_ok": true,
      "ssh_ok": true,
      "uptime": "up 5 days, load average: 0.15, 0.20, 0.18",
      "load_avg": "0.15, 0.20, 0.18",
      "latency_ms": 1.2,
      "error": ""
    }
  ]
}

Home Assistant Integration

Control Home Assistant devices via the REST API:

from superpowers.ssh_fabric.homeassistant import HomeAssistantClient

ha = HomeAssistantClient(
    url="http://192.168.30.50:8123",
    token="your-long-lived-access-token",
)

# Get entity state
state = ha.get_state("light.office")
print(state["state"])  # "on" or "off"

# Turn devices on/off
ha.turn_on("light.office")
ha.turn_off("switch.fan")

# Set thermostat
ha.set_temperature("climate.living_room", 72.0)

# Call any service
ha.call_service("light", "turn_on", "light.office", {"brightness": 200})

# List all states
states = ha.get_states()

Setup

Store the HA token in the vault:

claw vault set ha:token "your-long-lived-access-token"

The token is a long-lived access token generated in the Home Assistant UI under Profile > Long-Lived Access Tokens.

Error Handling

All errors are raised as SSHError with descriptive messages:

  • HTTP 401: invalid or expired token
  • HTTP 404: entity not found
  • Connection refused: HA instance unreachable