Skip to main content
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.

Create a PTY session

You can use sandbox.pty.create() to start an interactive bash shell.
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)
The PTY launches an interactive bash shell with TERM=xterm-256color, so ANSI colors and escape sequences work as expected.

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.
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
})

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().
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')
)

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.
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,
})

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.
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()

Kill the PTY

You can use kill() to terminate the PTY session.
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()

Wait for PTY to exit

You can use wait() to block until the terminal session ends, for example when the user types exit.
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)

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.
Last modified on June 4, 2026