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

# Sandbox Terminal

> Open interactive terminal sessions inside a sandbox

Sandboxes have PTY session support for interactive terminal sessions.
SDK is available for Node.js and Python to build your own terminal client.

This page documents how to build a terminal client, to start a terminal session, see [CLI Terminal](/sandboxes/cli#open-an-interactive-terminal).

## Create and Attach to a Terminal

Create a terminal and attach to its websocket stream:

<CodeGroup>
  ```typescript Node.js theme={null}
  const terminal = await sandbox.terminal.create({
    command: "bash",
    args: ["-l"],
    rows: 24,
    cols: 80,
  });

  const connection = await terminal.attach();

  for await (const event of connection.events()) {
    if (event.type === "output") {
      process.stdout.write(event.data);
      continue;
    }

    console.log("Exit code:", event.status.exitCode);
    break;
  }
  ```

  ```python Python theme={null}
  from hyperbrowser.models import SandboxTerminalCreateParams

  terminal = sandbox.terminal.create(
      SandboxTerminalCreateParams(
          command="bash",
          args=["-l"],
          rows=24,
          cols=80,
      )
  )

  connection = terminal.attach()

  for event in connection.events():
      if event.type == "output":
          print(event.data, end="")
          continue

      print(f"Exit code: {event.status.exit_code}")
      break
  ```
</CodeGroup>

## Write Input and Resize the PTY

Send input through the attached connection and resize the terminal:

<CodeGroup>
  ```typescript Node.js theme={null}
  const terminal = await sandbox.terminal.create({
    command: "bash",
    args: ["-l"],
  });

  const connection = await terminal.attach();

  await connection.resize(32, 110);
  await connection.write("pwd\n");
  await connection.write("echo terminal-ok\n");
  await connection.write("exit\n");

  await connection.close();
  ```

  ```python Python theme={null}
  from hyperbrowser.models import SandboxTerminalCreateParams

  terminal = sandbox.terminal.create(
      SandboxTerminalCreateParams(
          command="bash",
          args=["-l"],
      )
  )

  connection = terminal.attach()

  connection.resize(32, 110)
  connection.write("pwd\n")
  connection.write("echo terminal-ok\n")
  connection.write("exit\n")

  connection.close()
  ```
</CodeGroup>

## Get, Refresh, and Wait

Fetch an existing terminal or wait for it to complete:

<CodeGroup>
  ```typescript Node.js theme={null}
  const terminal = await sandbox.terminal.create({
    command: "bash",
    args: ["-lc", "echo hello-from-terminal"],
  });

  const fetched = await sandbox.terminal.get(terminal.id, true);
  console.log(fetched.current.output?.map((chunk) => chunk.data).join(""));

  const status = await terminal.wait({
    timeoutMs: 2_000,
    includeOutput: true,
  });

  console.log(status.running, status.exitCode);
  ```

  ```python Python theme={null}
  from hyperbrowser.models import SandboxTerminalCreateParams

  terminal = sandbox.terminal.create(
      SandboxTerminalCreateParams(
          command="bash",
          args=["-lc", "echo hello-from-terminal"],
      )
  )

  fetched = sandbox.terminal.get(terminal.id, include_output=True)
  print("".join(chunk.data for chunk in fetched.current.output or []))

  status = terminal.wait(
      timeout_ms=2000,
      include_output=True,
  )

  print(status.running, status.exit_code)
  ```
</CodeGroup>

## Signal and Kill

You can signal or kill a terminal-backed process:

<CodeGroup>
  ```typescript Node.js theme={null}
  const terminal = await sandbox.terminal.create({
    command: "bash",
    args: ["-lc", "sleep 30"],
  });

  await terminal.signal("TERM");
  const status = await terminal.wait({ timeoutMs: 5_000 });
  console.log("Still running:", status.running);
  console.log("Exit code:", status.exitCode);
  ```

  ```python Python theme={null}
  from hyperbrowser.models import SandboxTerminalCreateParams

  terminal = sandbox.terminal.create(
      SandboxTerminalCreateParams(
          command="bash",
          args=["-lc", "sleep 30"],
      )
  )

  terminal.signal("TERM")
  status = terminal.wait(timeout_ms=5000)
  print("Still running:", status.running)
  print("Exit code:", status.exit_code)
  ```
</CodeGroup>

`status.running` tells you whether the terminal session is still alive after the signal.
After `signal("TERM")` and a successful `wait(...)`, you would usually expect
`status.running` to be `false`.

## Common Terminal Parameters

<ParamField path="command" type="string" required>
  Command to launch inside the terminal session.
</ParamField>

<ParamField path="args" type="string[]">
  Optional command arguments.
</ParamField>

<ParamField path="cwd" type="string">
  Working directory for the terminal process.
</ParamField>

<ParamField path="env" type="object">
  Environment variables to set for the terminal process.
</ParamField>

<ParamField path="rows" type="number">
  Initial terminal row count.
</ParamField>

<ParamField path="cols" type="number">
  Initial terminal column count.
</ParamField>

<ParamField path="timeoutMs" type="number">
  Optional PTY runtime limit in milliseconds.
</ParamField>

<Tip>
  `sandbox.pty` is an alias for `sandbox.terminal`.
</Tip>

## Create Interactive Terminal

Run an interactive sandbox shell from your backend process and bridge local terminal input/output to the sandbox PTY stream.

<CodeGroup>
  ```typescript Node.js theme={null}
  const terminal = await sandbox.terminal.create({
    command: "bash",
    args: ["-l"],
    rows: process.stdout.rows ?? 24,
    cols: process.stdout.columns ?? 80,
  });

  console.log("Sandbox PTY ID:", terminal.id);

  const connection = await terminal.attach();

  const onStdinData = (chunk: Buffer) => {
    void connection.write(chunk);
  };

  const onResize = () => {
    void connection.resize(process.stdout.rows ?? 24, process.stdout.columns ?? 80);
  };

  if (process.stdin.isTTY) {
    process.stdin.setRawMode(true);
  }
  process.stdin.resume();
  process.stdin.on("data", onStdinData);

  if (process.stdout.isTTY) {
    process.stdout.on("resize", onResize);
  }

  try {
    for await (const event of connection.events()) {
      if (event.type === "output") {
        process.stdout.write(event.data);
        continue;
      }

      process.stdout.write(`\n[remote exited: ${event.status.exitCode ?? "unknown"}]\n`);
      break;
    }
  } finally {
    process.stdin.off("data", onStdinData);
    process.stdin.pause();
    process.stdout.off("resize", onResize);
    if (process.stdin.isTTY) {
      process.stdin.setRawMode(false);
    }
    await connection.close();
    await sandbox.stop();
  }
  ```

  ```python Python theme={null}
  import os
  import sys
  import termios
  import threading
  import tty

  from hyperbrowser.models import SandboxTerminalCreateParams

  terminal = sandbox.terminal.create(
      SandboxTerminalCreateParams(
          command="bash",
          args=["-l"],
          rows=os.get_terminal_size().lines if sys.stdout.isatty() else 24,
          cols=os.get_terminal_size().columns if sys.stdout.isatty() else 80,
      )
  )

  print(f"Sandbox PTY ID: {terminal.id}")

  connection = terminal.attach()

  stop_input = threading.Event()


  def stdin_pump():
      while not stop_input.is_set():
          chunk = os.read(sys.stdin.fileno(), 1024)
          if not chunk:
              break
          connection.write(chunk)


  input_thread = threading.Thread(target=stdin_pump, daemon=True)
  input_thread.start()

  original_tty = None
  if sys.stdin.isatty():
      original_tty = termios.tcgetattr(sys.stdin.fileno())
      tty.setraw(sys.stdin.fileno())

  try:
      for event in connection.events():
          if event.type == "output":
              sys.stdout.write(event.data)
              sys.stdout.flush()
              continue

          print(f"\n[remote exited: {event.status.exit_code}]")
          break
  finally:
      stop_input.set()
      connection.close()
      if original_tty is not None:
          termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, original_tty)
      sandbox.stop()
  ```
</CodeGroup>

<Tip>
  The Python raw-mode bridge uses `termios` and `tty`, so it is intended for POSIX terminals (Linux/macOS).
</Tip>
