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

# Interactive terminal (PTY)

export const SandboxConfigHint = () => {
  if (typeof document === "undefined") {
    return null;
  } else {
    return <Note>Before running the example code in this document, please ensure you have properly configured environment variables. For details, please refer to <a href="/guides/sandbox-your-first-agent-sandbox#configure-environment-variables">Configure Environment Variables</a>.</Note>;
  }
};

The PTY, or pseudo-terminal, module enables interactive terminal sessions inside the sandbox with real-time, bidirectional communication.

A PTY session supports **real-time streaming**, delivering terminal output continuously through callbacks as it is produced, and provides **bidirectional input**, allowing data to be sent while the session is still running. It also offers an **interactive shell** experience with full terminal behavior, including ANSI colors and escape sequences, and supports **session persistence**, so a running session can be detached and reconnected later.

<SandboxConfigHint />

## Create a PTY session

You can use `sandbox.pty.create()` to start an interactive bash shell.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const terminal = await sandbox.pty.create({
    cols: 80,              // Terminal width in characters
    rows: 24,              // Terminal height in characters
    onData: (data) => {
      // Called whenever terminal outputs data
      process.stdout.write(data)
    },
    envs: { MY_VAR: 'hello' },  // Optional environment variables
    cwd: '/home/user',          // Optional working directory
    user: 'root',               // Optional user to run as
  })

  // terminal.pid contains the process ID
  console.log('Terminal PID:', terminal.pid)
  ```

  ```python Python icon="python" theme={"system"}
  import threading

  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  terminal = sandbox.pty.create(
      size=PtySize(rows=24, cols=80),  # PtySize is (rows, cols)
      envs={'MY_VAR': 'hello'},        # Optional environment variables
      cwd='/home/user',                # Optional working directory
      user='root',                     # Optional user to run as
  )

  # terminal.pid contains the process ID
  print('Terminal PID:', terminal.pid)

  # The Python SDK has no on_data param. Output is streamed via wait(on_pty=...),
  # which blocks, so run it in a background thread.
  threading.Thread(
      target=lambda: terminal.wait(on_pty=lambda data: print(data.decode(), end='')),
      daemon=True,
  ).start()
  ```
</CodeGroup>

<Note>
  The PTY launches an interactive bash shell with `TERM=xterm-256color`, so ANSI colors and escape sequences work as expected.
</Note>

## Timeout

The timeout setting is configurable and determines how long the PTY session remains active. You can keep a PTY session open indefinitely by setting `timeoutMs: 0` in JavaScript or `timeout=0` in Python. The session uses a 60-second timeout by default.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const terminal = await sandbox.pty.create({
    cols: 80,
    rows: 24,
    onData: (data) => process.stdout.write(data),
    timeoutMs: 0,  // Keep the session open indefinitely
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  terminal = sandbox.pty.create(
      size=PtySize(rows=24, cols=80),
      timeout=0,  # Keep the session open indefinitely
  )
  ```
</CodeGroup>

## Send input to PTY

You can use `sendInput()` in JavaScript or `send_stdin()` in Python to send data to the terminal.

In JavaScript, `sendInput()` returns a Promise, and any terminal output is delivered through the `onData` callback rather than returned directly.
In Python, `send_stdin()` completes synchronously, and any terminal output is delivered through the `on_pty` callback passed to `wait()`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const terminal = await sandbox.pty.create({
    cols: 80,
    rows: 24,
    onData: (data) => process.stdout.write(data),
  })

  // Send a command (don't forget the newline!)
  await sandbox.pty.sendInput(
    terminal.pid,
    new TextEncoder().encode('echo "Hello from PTY"\n')
  )
  ```

  ```python Python icon="python" theme={"system"}
  import threading

  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

  # Stream output in a background thread (Python uses wait(on_pty=...))
  threading.Thread(
      target=lambda: terminal.wait(on_pty=lambda data: print(data.decode(), end='')),
      daemon=True,
  ).start()

  # Send a command as bytes (b'...' is Python's byte string syntax)
  # Don't forget the newline!
  sandbox.pty.send_stdin(terminal.pid, b'echo "Hello from PTY"\n')
  ```
</CodeGroup>

## Resize the terminal

You can use `resize()` to notify the PTY when the user changes the terminal window size.
The cols and rows values represent the terminal dimensions in characters rather than pixels.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const terminal = await sandbox.pty.create({
    cols: 80,
    rows: 24,
    onData: (data) => process.stdout.write(data),
  })

  // Resize to new dimensions (in characters)
  await sandbox.pty.resize(terminal.pid, {
    cols: 120,
    rows: 40,
  })
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

  # Resize to new dimensions (in characters)
  sandbox.pty.resize(terminal.pid, PtySize(rows=40, cols=120))
  ```
</CodeGroup>

## Disconnect and reconnect

A PTY session can remain active even after the client disconnects. You can detach from the session and reconnect to it again later with a new data handler.

This can be used to recover from network interruptions, support terminal access from multiple clients, and preserve session state across reconnects.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  // Create a PTY session
  const terminal = await sandbox.pty.create({
    cols: 80,
    rows: 24,
    onData: (data) => console.log('Handler 1:', new TextDecoder().decode(data)),
  })

  const pid = terminal.pid

  // Send a command
  await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo hello\n'))

  // Disconnect - PTY keeps running in the background
  await terminal.disconnect()

  // Later: reconnect with a new data handler
  const reconnected = await sandbox.pty.connect(pid, {
    onData: (data) => console.log('Handler 2:', new TextDecoder().decode(data)),
  })

  // Continue using the session
  await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo world\n'))

  // Wait for the terminal to exit
  await reconnected.wait()
  ```

  ```python Python icon="python" theme={"system"}
  import threading
  import time

  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  # Create a PTY session
  terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))
  pid = terminal.pid

  # Send a command
  sandbox.pty.send_stdin(pid, b'echo hello\n')
  time.sleep(0.5)

  # Disconnect - PTY keeps running in the background.
  # Don't disconnect while a wait() is iterating the same handle.
  terminal.disconnect()

  # Later: reconnect with a new handle and stream its output
  reconnected = sandbox.pty.connect(pid)
  threading.Thread(
      target=lambda: reconnected.wait(on_pty=lambda data: print('Handler 2:', data.decode())),
      daemon=True,
  ).start()

  # Continue using the session
  sandbox.pty.send_stdin(pid, b'echo world\n')
  time.sleep(1.5)
  ```
</CodeGroup>

## Kill the PTY

You can use `kill()` to terminate the PTY session.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const terminal = await sandbox.pty.create({
    cols: 80,
    rows: 24,
    onData: (data) => process.stdout.write(data),
  })

  // Kill the PTY
  const killed = await sandbox.pty.kill(terminal.pid)
  console.log('Killed:', killed)  // true if successful

  // Or use the handle method
  // await terminal.kill()
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

  # Kill the PTY
  killed = sandbox.pty.kill(terminal.pid)
  print('Killed:', killed)  # True if successful

  # Or use the handle method
  # terminal.kill()
  ```
</CodeGroup>

## Wait for PTY to exit

You can use `wait()` to block until the terminal session ends, for example when the user types `exit`.

<CodeGroup>
  ```js JavaScript & TypeScript icon="js" theme={"system"}
  import { Sandbox } from 'novita-sandbox/code-interpreter'

  const sandbox = await Sandbox.create()

  const terminal = await sandbox.pty.create({
    cols: 80,
    rows: 24,
    onData: (data) => process.stdout.write(data),
  })

  // Send exit command
  await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n'))

  // Wait for the terminal to exit
  const result = await terminal.wait()
  console.log('Exit code:', result.exitCode)
  ```

  ```python Python icon="python" theme={"system"}
  from novita_sandbox.code_interpreter import Sandbox, PtySize

  sandbox = Sandbox.create()

  terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

  # Send exit command
  sandbox.pty.send_stdin(terminal.pid, b'exit\n')

  # wait() blocks until the terminal exits; pass on_pty to stream output
  result = terminal.wait(on_pty=lambda data: print(data.decode(), end=''))
  print('Exit code:', result.exit_code)
  ```
</CodeGroup>

## Interactive terminal (SSH-like)

You can use the same `sandbox.pty` API described above to create a fully interactive terminal such as SSH by handling raw mode, stdin forwarding, and terminal resize events.
