> ## Documentation Index
> Fetch the complete documentation index at: https://celesto.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SSHClient

> SSHClient reference: run commands on Celesto guest VMs through persistent paramiko SSH connections, eliminating the per-call ssh process overhead.

## Overview

The `SSHClient` class provides efficient command execution on guest VMs using persistent SSH connections. It uses [paramiko](https://www.paramiko.org/) to maintain a single TCP connection that is reused for all commands, eliminating the \~170ms overhead of forking a new `ssh` process per call.

The connection is established lazily on first use and automatically reconnects if the connection is lost.

## Constructor

```python theme={null} theme={null}
SSHClient(
    host: str,
    user: str = "root",
    port: int = 22,
    key_path: str | None = None,
    password: str | None = None,
    connect_timeout: int = 10,
    shell_kind: Literal["sh", "powershell", "cmd"] = "sh",
)
```

**host** `str` *required*

Guest IP address or hostname.

**user** `str` (default: root)

SSH username for authentication.

**port** `int` (default: 22)

SSH port on the guest.

**key\_path** `str | None` (default: None)

Optional path to an SSH private key file.

**password** `str | None` (default: None)

Optional password for authentication.

**connect\_timeout** `int` (default: 10)

Seconds to wait for the TCP connection.

**shell\_kind** `Literal["sh", "powershell", "cmd"]` (default: sh)

Login-shell flavor used to wrap commands when `run(..., shell="login")` is called:

* `"sh"` (default) — POSIX guests. Wraps with `$SHELL -lc <quoted>`.
* `"powershell"` — Windows guests. Wraps with `powershell.exe -NoProfile -EncodedCommand <base64>` so the command bytes survive Windows OpenSSH's `cmd.exe` layer unchanged.
* `"cmd"` — Windows guests where you want `cmd.exe /c "<command>"` semantics instead. You are responsible for `cmd.exe` quoting.

`shell="raw"` on `run(...)` bypasses the wrap entirely regardless of `shell_kind`.

```python theme={null} theme={null}
from celesto import SSHClient

# Basic usage with IP
client = SSHClient(host="172.16.0.2")

# With custom user and port
client = SSHClient(host="172.16.0.2", user="ubuntu", port=22)

# With SSH key
client = SSHClient(
    host="172.16.0.2",
    key_path="/home/user/.ssh/id_rsa"
)

# With password authentication
client = SSHClient(
    host="172.16.0.2",
    password="secret"
)

# Windows guest (PowerShell wrap + password auth)
client = SSHClient(
    host="172.16.0.2",
    user="celesto",
    password="celesto",
    shell_kind="powershell",
)
```

## Properties

### connected

```python theme={null} theme={null}
@property
def connected() -> bool
```

Check if the SSH connection is currently alive.

**returns** `bool`

True if the connection is active, False otherwise.

```python theme={null} theme={null}
client = SSHClient(host="172.16.0.2")
if client.connected:
    print("Already connected")
else:
    print("Not connected yet")
```

## Methods

### run

```python theme={null} theme={null}
def run(
    command: str,
    timeout: int = 30,
    shell: Literal["login", "raw"] = "login"
) -> CommandResult
```

Execute a command on the guest VM using the persistent SSH connection.

If the connection is not yet established or has been lost, it is (re)created transparently.

**command** `str` *required*

Shell command to execute.

**timeout** `int` (default: 30)

Maximum seconds to wait for the command to complete.

**shell** `Literal['login', 'raw']` (default: login)

Command execution mode:

* `"login"` (default): Run via guest login shell with environment variables loaded
* `"raw"`: Execute command directly with no shell wrapping

**returns** `CommandResult`

Result object with:

* `exit_code` (int): Exit code of the command (0 = success)
* `stdout` (str): Standard output captured from the command
* `stderr` (str): Standard error captured from the command
* `ok` (bool): Whether the command succeeded (exit\_code == 0)
* `output` (str): Convenience alias for stripped stdout

**raises**

* `ValueError`: If command is empty
* `OperationTimeoutError`: If the command exceeds timeout
* `CelestoError`: If the SSH connection cannot be established

```python theme={null} theme={null}
from celesto import SSHClient

client = SSHClient(host="172.16.0.2")

# Execute a simple command
result = client.run("echo 'Hello from VM'")
print(result.stdout.strip())  # "Hello from VM"
print(result.exit_code)       # 0

# Check if command succeeded
result = client.run("test -f /etc/passwd")
if result.ok:
    print("File exists")

# Handle errors
result = client.run("ls /nonexistent")
if not result.ok:
    print(f"Error: {result.stderr}")

# Use raw shell mode
result = client.run("whoami", shell="raw")
print(result.output)  # "root"

# Custom timeout
result = client.run("sleep 5", timeout=10)
```

### wait\_for\_ssh

```python theme={null} theme={null}
def wait_for_ssh(
    timeout: float = 60.0,
    interval: float = 0.1
) -> None
```

Wait for the SSH daemon to become reachable on the guest.

Uses a two-phase approach for fast detection:

1. **TCP probe** - Lightweight `socket.connect()` calls (\~1ms each) to detect when sshd is listening
2. **Paramiko connect** - Full SSH handshake + auth. The resulting connection is kept open for subsequent `run()` calls

**timeout** `float` (default: 60.0)

Maximum seconds to wait for SSH to become available.

**interval** `float` (default: 0.1)

Seconds between TCP probe attempts.

**raises** `OperationTimeoutError`

If SSH does not become available within timeout seconds.

```python theme={null} theme={null}
from celesto import SSHClient

client = SSHClient(host="172.16.0.2")

# Wait for SSH to be ready
try:
    client.wait_for_ssh(timeout=30.0)
    print("SSH is ready!")
except OperationTimeoutError:
    print("SSH failed to start")

# Now run commands
result = client.run("hostname")
print(result.output)
```

### close

```python theme={null} theme={null}
def close() -> None
```

Close the SSH connection and release resources.

It's recommended to use the client as a context manager instead of calling this manually.

```python theme={null} theme={null}
client = SSHClient(host="172.16.0.2")
try:
    result = client.run("echo 'hello'")
    print(result.output)
finally:
    client.close()
```

## Usage Patterns

### Basic Command Execution

```python theme={null} theme={null}
from celesto import SSHClient

client = SSHClient(host="172.16.0.2")

# Wait for SSH to be ready
client.wait_for_ssh()

# Run commands
result = client.run("uname -a")
print(f"Kernel: {result.output}")

result = client.run("df -h")
print(f"Disk usage:\n{result.stdout}")

client.close()
```

### Error Handling

```python theme={null} theme={null}
from celesto import SSHClient, OperationTimeoutError, CelestoError

client = SSHClient(host="172.16.0.2")

try:
    # Wait for SSH
    client.wait_for_ssh(timeout=30.0)
    
    # Run command
    result = client.run("apt-get update", timeout=120)
    
    if not result.ok:
        print(f"Command failed: {result.stderr}")
        
except OperationTimeoutError as e:
    print(f"Timeout: {e}")
except CelestoError as e:
    print(f"SSH error: {e}")
finally:
    client.close()
```

### Using with Celesto

```python theme={null} theme={null}
from celesto import Celesto, SSHClient

with Celesto() as vm:
    # Get the VM's IP
    ip = vm.get_ip()
    
    # Create SSH client
    client = SSHClient(host=ip)
    client.wait_for_ssh()
    
    # Run commands
    result = client.run("python3 --version")
    print(result.output)
    
    result = client.run("pip install requests")
    if result.ok:
        print("Package installed successfully")
    
    client.close()
```

### Persistent Connection Benefits

```python theme={null} theme={null}
from celesto import SSHClient
import time

client = SSHClient(host="172.16.0.2")
client.wait_for_ssh()

# First command establishes connection
start = time.time()
result = client.run("echo 'test'")
print(f"First command: {time.time() - start:.3f}s")

# Subsequent commands reuse connection (much faster)
start = time.time()
result = client.run("echo 'test'")
print(f"Second command: {time.time() - start:.3f}s")

# Still fast on third command
start = time.time()
result = client.run("echo 'test'")
print(f"Third command: {time.time() - start:.3f}s")

client.close()
```

### Shell Modes

```python theme={null} theme={null}
from celesto import SSHClient

client = SSHClient(host="172.16.0.2")
client.wait_for_ssh()

# Login shell mode (default) - loads environment variables
result = client.run("echo $PATH", shell="login")
print(f"PATH: {result.output}")

# Raw mode - no shell wrapping
result = client.run("whoami", shell="raw")
print(f"User: {result.output}")

client.close()
```

## Performance

The persistent SSH connection provides significant performance benefits:

* **First command**: \~100-200ms (establishes connection)
* **Subsequent commands**: \~10-50ms (reuses connection)
* **Without persistence**: \~170ms per command (fork + handshake overhead)

For workloads that execute many commands, this can reduce total execution time by 80-90%.

## Related

* [CommandResult](https://celesto.ai/docs/celesto/api/commandresult.md) - Result object returned by `run()`
* [Celesto](https://celesto.ai/docs/celesto/api/celesto.md) - High-level VM facade that uses SSHClient internally


## Related topics

- [Use the Celesto HTTP API and TypeScript SDK](https://celesto.ai/docs/celesto/guides/http-api-typescript-sdk.md)
- [Sandbox an OpenAI agent with Celesto](https://celesto.ai/docs/cloud/openai-agents.md)
- [CLI overview](https://celesto.ai/docs/celesto/cli/overview.md)
- [Give your AI assistant a cloud computer](https://celesto.ai/docs/cloud/mcp-server.md)
- [Authenticate the Celesto SDK and CLI](https://celesto.ai/docs/cloud/authentication.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.