Skip to main content

SSH SDK

InternSSH is a small, opinionated wrapper around Paramiko for talking to a Dev Edition Intern from your workstation. It exposes synchronous run, file transfer via SFTP, and a few convenience helpers for one-off use.

Source: ssh/ssh_sdk.py in intern-developer-sdk.

Install

pip install paramiko

No platform-specific deps — Paramiko ships pure-Python.

Quick example

from ssh_sdk import InternSSH

with InternSSH("172.168.20.145", "system", password="12345") as ssh:
result = ssh.run("systemctl status openclaw --no-pager")
print(result.exit_code, result.duration_s)
print(result.stdout)

Constructor

InternSSH(
host: str,
user: str,
password: str | None = None,
key_path: str | None = None,
port: int = 22,
connect_timeout: float = 10.0,
)
ArgDefaultNotes
hostrequiredIP address of the device (find it via Preflight)
userrequiredUsually system
passwordNoneProvide this or key_path
key_pathNonePath to a private key (e.g. ~/.ssh/id_ed25519)
port22Override for non-default SSH ports
connect_timeout10.0Seconds before the connect call gives up

Connection lifecycle

InternSSH is a context manager:

with InternSSH("172.168.20.145", "system", key_path="~/.ssh/id_ed25519") as ssh:
...

Or manage the connection by hand:

ssh = InternSSH("172.168.20.145", "system", password="...")
ssh.connect()
try:
...
finally:
ssh.close()

Running commands

result = ssh.run(
"df -h /",
sudo=False, # prepend `sudo`
timeout=None, # per-command timeout in seconds
check=False, # raise CommandError on non-zero exit
)

CommandResult is a dataclass:

FieldType
stdoutstr
stderrstr
exit_codeint
duration_sfloat

Run with sudo when you need to touch /root/:

ssh.run("jq . /root/config/config.json", sudo=True)

check=True raises CommandError on any non-zero exit, which is useful in scripts:

ssh.run("test -f /usr/local/bin/intern-server", check=True)

File transfer

ssh.put("./local/build/intern-server", "/tmp/intern-server")
ssh.get("/var/log/intern.log", "./logs/intern.log")

ssh.put_dir("./audio", "/home/system/sdk/audio")
ssh.get_dir("/root/.openclaw", "./backup/openclaw")

put / get are single files; put_dir / get_dir recurse.

Filesystem helpers

ssh.exists("/usr/local/bin/intern-server") # → bool
ssh.read_text("/root/config/config.json") # → str
ssh.write_text("/tmp/note.txt", "hello")
ssh.listdir("/root/.openclaw") # → list[str]

These wrap SFTP and avoid the round-trip of spawning cat or ls over a shell.

One-shot helpers

For scripts that don't want to manage a context, the module exposes:

from ssh_sdk import run_once, scp_to, scp_from

result = run_once(
"172.168.20.145", "system",
"uptime",
password="...",
)

scp_to(
"172.168.20.145", "system",
local="./build/intern-server",
remote="/tmp/intern-server",
key_path="~/.ssh/id_ed25519",
)

scp_from(
"172.168.20.145", "system",
remote="/var/log/intern.log",
local="./intern.log",
password="...",
)

Each helper opens a connection, performs one action, and closes — fine for ad-hoc work, not for loops (you'd open a connection per call).

Errors

ExceptionWhen
SSHErrorBase class for all SDK errors
ConnectionErrorTCP, auth, or handshake failure
CommandErrorrun(check=True) saw a non-zero exit
TransferErrorSFTP put/get failed mid-transfer
from ssh_sdk import InternSSH, ConnectionError, CommandError

try:
with InternSSH(...) as ssh:
ssh.run("false", check=True)
except CommandError as e:
print(f"command failed: exit {e.exit_code}")
except ConnectionError as e:
print(f"could not connect: {e}")

When to use SSH vs the HTTP API

intern-server exposes a small HTTP API on :5000 (proxied to :80 by nginx). For pairing, channel changes, and factory reset, prefer the HTTP API — it's idempotent and doesn't require credentials. For service inspection, log tailing, and arbitrary maintenance, SSH is the only option.