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

# Interactive Terminals

> Connect to running sandboxes with interactive terminal sessions

## Overview

Access running sandboxes through interactive terminal sessions with full PTY support. Use the CLI for command-line access or integrate terminals directly into your web application.

## Prerequisites

Before using interactive terminals, ensure you have:

* Installed the Sandbox SDK ([see Quickstart](/quickstart))
* Created and started a sandbox instance
* Set your `SANDBOX_API_KEY` environment variable
* Basic familiarity with shell commands and WebSockets

## CLI usage

### Connect to a sandbox

Connect to a running sandbox from your terminal:

```bash theme={null}
export SANDBOX_API_KEY=sk_live_...
sandboxcli connect <sandbox-id>
```

### Select a shell

Choose your preferred shell (bash, zsh, fish, or sh):

```bash theme={null}
# Use zsh
sandboxcli connect sbx_abc123 --shell zsh

# Use fish
sandboxcli connect sbx_abc123 --shell fish

# Default is bash
sandboxcli connect sbx_abc123
```

### Features

When connected via CLI:

* **Full PTY support**: Colors, cursor control, vim, tmux, nano work perfectly
* **Terminal resize**: Automatically detects and syncs terminal size changes
* **Interactive input**: Real-time input forwarding with proper buffering
* **Graceful disconnect**: Press Ctrl+C to disconnect cleanly

## Browser integration

### Creating a terminal via SDK

```typescript theme={null}
const { terminal, wsUrl } = await sandbox.terminals.create({
  shell: "bash",  // Optional: bash, zsh, fish, sh
  cwd: "/app",    // Optional: working directory
  cols: 80,       // Optional: terminal width
  rows: 24        // Optional: terminal height
});

// terminal = { id, sandboxId, shell, status, createdAt }
```

<Accordion title="Using raw API directly">
  ```typescript theme={null}
  const response = await fetch(
    `/api/v1/sandboxes/${sandboxId}/terminals`,
    {
      method: "POST",
      credentials: "include",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        shell: "bash",
        cwd: "/app",
        cols: 80,
        rows: 24
      })
    }
  );

  const { terminal, wsUrl } = await response.json();
  ```
</Accordion>

### Connecting via WebSocket

Connect to the terminal using the WebSocket URL:

```typescript theme={null}
const ws = new WebSocket(wsUrl);

ws.onopen = () => {
  console.log("Terminal connected");
};

// Receive terminal output
ws.onmessage = (event) => {
  const data = event.data;

  // Check if it's a control message (JSON)
  if (typeof data === "string" && data.startsWith("{")) {
    const message = JSON.parse(data);

    if (message.type === "ready") {
      console.log("Terminal ready:", message);
    } else if (message.type === "exit") {
      console.log("Process exited:", message.exitCode);
    }
  } else {
    // Raw terminal output - display in your terminal UI
    console.log(data);
  }
};

// Send input to terminal
ws.send("ls -la\n");

// Send resize event
ws.send(JSON.stringify({
  type: "resize",
  cols: 120,
  rows: 40
}));
```

### Using xterm.js

Example React component using xterm.js:

```typescript theme={null}
import { useEffect, useRef } from "react";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import "xterm/css/xterm.css";

export function SandboxTerminal({ sandboxId }: { sandboxId: string }) {
  const terminalRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!terminalRef.current) return;

    // 1. Initialize xterm.js
    const term = new Terminal({
      cursorBlink: true,
      fontSize: 14,
      fontFamily: 'Menlo, Monaco, "Courier New", monospace',
      theme: {
        background: "#1e1e1e",
        foreground: "#d4d4d4",
      },
    });

    const fitAddon = new FitAddon();
    term.loadAddon(fitAddon);
    term.open(terminalRef.current);
    fitAddon.fit();

    // 2. Create terminal via SDK
    sandbox.terminals.create({ shell: "bash" })
      .then(({ wsUrl }) => {
        // 3. Connect WebSocket
        const ws = new WebSocket(wsUrl);

        ws.onmessage = (event) => {
          term.write(event.data);
        };

        // 4. Forward input to WebSocket
        term.onData((data) => {
          if (ws.readyState === WebSocket.OPEN) {
            ws.send(data);
          }
        });

        // 5. Handle resize
        term.onResize(({ cols, rows }) => {
          if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: "resize", cols, rows }));
          }
        });

        // Cleanup
        return () => {
          ws.close();
          term.dispose();
        };
      });
  }, [sandboxId]);

  return <div ref={terminalRef} style={{ width: "100%", height: "100%" }} />;
}
```

## Multiple terminals

### Create multiple terminals per sandbox

Each sandbox can have multiple independent terminal sessions:

```typescript theme={null}
// Create terminal 1
const term1 = await sandbox.terminals.create({
  shell: "bash",
  cwd: "/app"
});

// Create terminal 2
const term2 = await sandbox.terminals.create({
  shell: "zsh",
  cwd: "/home"
});

// Each has its own terminal ID and WebSocket URL
console.log(term1.terminal.id);  // "123"
console.log(term2.terminal.id);  // "456"
```

### List terminals

Get all active terminals for a sandbox:

```typescript theme={null}
const response = await fetch(
  `/api/v1/sandboxes/${sandboxId}/terminals`,
  {
    credentials: "include"
  }
);

const { terminals } = await response.json();

terminals.forEach(terminal => {
  console.log(`Terminal ${terminal.id}: ${terminal.shell} (${terminal.status})`);
});
```

### Close a terminal

Delete a specific terminal session:

```typescript theme={null}
await fetch(
  `/api/v1/sandboxes/${sandboxId}/terminals/${terminalId}`,
  {
    method: "DELETE",
    credentials: "include"
  }
);
```

## Multiple clients (screen sharing)

Multiple WebSocket clients can connect to the same terminal for collaborative sessions:

```typescript theme={null}
// Client 1 connects
const ws1 = new WebSocket(wsUrl);

// Client 2 connects to same terminal
const ws2 = new WebSocket(wsUrl);

// Both clients receive all output
// Input from either client goes to the terminal
// Perfect for pair programming or debugging together
```

All connected clients receive the same terminal output in real-time. Any client can send input to the terminal.

## WebSocket protocol

### Control messages

Control messages are JSON objects sent from client to server or server to client:

**Resize terminal (client → server):**

```json theme={null}
{
  "type": "resize",
  "cols": 120,
  "rows": 40
}
```

**Ready message (server → client):**

```json theme={null}
{
  "type": "ready",
  "pid": 123,
  "shell": "bash"
}
```

**Exit notification (server → client):**

```json theme={null}
{
  "type": "exit",
  "exitCode": 0,
  "signal": null
}
```

### Raw terminal data

All other messages are raw terminal input/output:

* **Client → Server**: Raw bytes of keyboard input
* **Server → Client**: Raw bytes of terminal output (including ANSI escape codes)

## Terminal lifecycle

### Auto-cleanup

Terminals are automatically cleaned up after 30 minutes of inactivity when no clients are connected:

```typescript theme={null}
// Create terminal
const { wsUrl } = await createTerminal(sandboxId);

// Connect client
const ws = new WebSocket(wsUrl);

// Terminal stays alive while client is connected

ws.close();
// Terminal enters idle state

// After 30 minutes with no clients, terminal is automatically deleted
```

<Note>
  Terminals with at least one connected client are never automatically cleaned up, regardless of idle time.
</Note>

### Manual cleanup

Close terminals explicitly when done:

```typescript theme={null}
// Close specific terminal
await fetch(`/api/v1/sandboxes/${sandboxId}/terminals/${terminalId}`, {
  method: "DELETE"
});

// Or just disconnect WebSocket and let auto-cleanup handle it
ws.close();
```

### Sandbox termination

When a sandbox is deleted or killed, all its terminals are automatically closed:

```typescript theme={null}
await sandbox.kill();
// All terminals for this sandbox are immediately terminated
```

## Common patterns

### Interactive debugging session

Start a terminal for debugging a running application:

```bash theme={null}
# Connect to sandbox
sandboxcli connect sbx_abc123

# Check running processes
ps aux

# Inspect logs
tail -f /tmp/app.log

# Test network connectivity
curl localhost:3000/health

# Debug environment
env | grep NODE
```

### Remote shell for CI/CD

Use terminals in CI pipelines for interactive debugging:

```typescript theme={null}
// In your CI script
import { Sandbox } from "@simplesandbox/sdk";

const client = Sandbox({
  apiKey: process.env.SANDBOX_API_KEY
});

const sandbox = await client.sandboxes.create({
  image: "node:20"
});

// Run tests
const testResult = await sandbox.exec("npm test");

if (!testResult.success) {
  console.log("Tests failed. Connect for debugging:");
  console.log(`sandboxcli connect ${sandbox.id}`);

  // Keep sandbox alive for 10 minutes
  await sandbox.updateTimeout({ timeoutMs: 10 * 60 * 1000 });

  // Exit with failure
  process.exit(1);
}
```

### Collaborative debugging

Multiple developers can connect to the same terminal:

```typescript theme={null}
// Developer 1 creates terminal
const { terminal, wsUrl } = await createTerminal(sandboxId);

// Share terminal ID with Developer 2
console.log("Connect via:", terminal.id);

// Developer 2 connects to same terminal
const reconnectUrl = `wss://host/api/v1/sandboxes/${sandboxId}/terminals/${terminal.id}/ws`;
const ws2 = new WebSocket(reconnectUrl);

// Both developers see the same output and can type commands
```

### Running interactive tools

Use terminals for tools that require TTY:

```bash theme={null}
# Connect to sandbox
sandboxcli connect sbx_abc123

# Use vim
vim /app/config.json

# Use tmux for multiple panes
tmux

# Use htop for process monitoring
htop

# Use interactive Python REPL
python3
```

## Troubleshooting

### WebSocket connection fails

If the WebSocket connection fails immediately:

* **Check authentication**: Ensure your API key or session is valid
* **Check sandbox state**: Sandbox must be in "started" state
* **Check terminal exists**: Verify the terminal ID is valid
* **Check network**: Ensure WebSocket traffic is not blocked by firewall

### Terminal output appears garbled

If terminal output looks corrupted:

* **Check terminal size**: Send a resize event with your terminal dimensions
* **Check xterm.js version**: Ensure you're using a compatible version
* **Clear and reset**: Try sending Ctrl+L or running `reset` command

### Input not working

If keyboard input is not reaching the terminal:

* **Check WebSocket state**: Verify `ws.readyState === WebSocket.OPEN`
* **Check raw mode**: For CLI, ensure terminal is in raw mode
* **Check focus**: For browser, ensure terminal element has focus

### Terminal exits immediately

If terminals close right after creation:

* **Check shell**: Some shells exit without input (try `bash` instead of `sh`)
* **Check working directory**: Ensure `cwd` parameter points to existing directory
* **Check logs**: Look at terminal output before it closed

### Performance issues

If terminal feels slow or laggy:

* **Check network latency**: High latency affects responsiveness
* **Reduce output**: Avoid commands that produce huge amounts of output
* **Use pagination**: For large output, use `less`, `more`, or `head`/`tail`

## Best practices

* **Choose the right shell**: Use bash for compatibility, zsh/fish for features
* **Clean up terminals**: Delete terminals when done to free resources
* **Handle disconnections**: Implement reconnection logic in your application
* **Set proper dimensions**: Send accurate terminal size for best display
* **Use for debugging**: Keep terminals as a debugging tool, not primary interface
* **Secure access**: Always authenticate WebSocket connections
* **Monitor activity**: Track terminal usage for billing and security

## Limitations

* **Max idle time**: Terminals auto-cleanup after 30 minutes without clients
* **No session persistence**: Terminals don't survive sandbox restarts
* **No buffer history**: Reconnecting clients don't receive previous output
* **PTY only**: Requires PTY library (`@lydell/node-pty` in production)
* **Shell availability**: Only supports bash, zsh, fish, sh (must be installed in image)
* **Single PTY per terminal**: Each terminal is one PTY process

## API endpoints

### Create terminal

```http theme={null}
POST /api/v1/sandboxes/{sandboxId}/terminals
Content-Type: application/json

{
  "shell": "bash",
  "cwd": "/app",
  "cols": 80,
  "rows": 24
}
```

**Response:**

```json theme={null}
{
  "terminal": {
    "id": "123",
    "sandboxId": "sbx_abc",
    "shell": "bash",
    "status": "active",
    "createdAt": "2025-11-07T12:00:00Z"
  },
  "wsUrl": "wss://host/api/v1/sandboxes/sbx_abc/terminals/123/ws"
}
```

### List terminals

```http theme={null}
GET /api/v1/sandboxes/{sandboxId}/terminals
```

**Response:**

```json theme={null}
{
  "terminals": [
    {
      "id": "123",
      "shell": "bash",
      "status": "active",
      "clientCount": 2
    }
  ]
}
```

### Get terminal info

```http theme={null}
GET /api/v1/sandboxes/{sandboxId}/terminals/{terminalId}
```

**Response:**

```json theme={null}
{
  "id": "123",
  "sandboxId": "sbx_abc",
  "shell": "bash",
  "status": "active",
  "clientCount": 1,
  "createdAt": "2025-11-07T12:00:00Z"
}
```

### Delete terminal

```http theme={null}
DELETE /api/v1/sandboxes/{sandboxId}/terminals/{terminalId}
```

**Response:** `204 No Content`

### Connect WebSocket

```http theme={null}
GET /api/v1/sandboxes/{sandboxId}/terminals/{terminalId}/ws
Upgrade: websocket
Connection: Upgrade
```

Upgrades to WebSocket connection for bidirectional terminal communication.
