# Pseudo Terminal (PTY) (/docs/pty)

Daytona provides powerful pseudo terminal (PTY) capabilities through the `process` module in sandboxes. PTY sessions allow you to create interactive terminal sessions that can execute commands, handle user input, and manage terminal operations.

A PTY (Pseudo Terminal) is a virtual terminal interface that allows programs to interact with a shell as if they were connected to a real terminal. PTY sessions in Daytona enable:

- **Interactive Development**: REPLs, debuggers, and development tools
- **Build Processes**: Running and monitoring compilation, testing, or deployment
- **System Administration**: Remote server management and configuration
- **User Interfaces**: Terminal-based applications requiring user interaction

## Create PTY session [#create-pty-session]

Create an interactive terminal session that can execute commands and handle user input.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona.common.pty import PtySize

pty_handle = sandbox.process.create_pty_session(
    id="my-session",
    cwd="/workspace",
    envs={"TERM": "xterm-256color"},
    pty_size=PtySize(cols=120, rows=30)
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Create a PTY session with custom configuration
const ptyHandle = await sandbox.process.createPty({
  id: 'my-interactive-session',
  cwd: '/workspace',
  envs: { TERM: 'xterm-256color', LANG: 'en_US.UTF-8' },
  cols: 120,
  rows: 30,
  onData: (data) => {
    // Handle terminal output
    const text = new TextDecoder().decode(data)
    process.stdout.write(text)
  },
})

// Wait for connection to be established
await ptyHandle.waitForConnection()

// Send commands to the terminal
await ptyHandle.sendInput('ls -la\n')
await ptyHandle.sendInput('echo "Hello, PTY!"\n')
await ptyHandle.sendInput('exit\n')

// Wait for completion and get result
const result = await ptyHandle.wait()
console.log(`PTY session completed with exit code: ${result.exitCode}`)

// Clean up
await ptyHandle.disconnect()
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
pty_size = Daytona::PtySize.new(rows: 30, cols: 120)
pty_handle = sandbox.process.create_pty_session(
  id: 'my-interactive-session',
  cwd: '/workspace',
  envs: { 'TERM' => 'xterm-256color' },
  pty_size: pty_size
)

# Use the PTY session
pty_handle.send_input("ls -la\n")
pty_handle.send_input("echo 'Hello, PTY!'\n")
pty_handle.send_input("exit\n")

# Handle output
pty_handle.each { |data| print data }

puts "PTY session completed with exit code: #{pty_handle.exit_code}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Create a PTY session with custom configuration
handle, err := sandbox.Process.CreatePty(ctx, "my-interactive-session",
	options.WithCreatePtySize(types.PtySize{Cols: 120, Rows: 30}),
	options.WithCreatePtyEnv(map[string]string{"TERM": "xterm-256color"}),
)
if err != nil {
	log.Fatal(err)
}
defer handle.Disconnect()

// Wait for connection to be established
if err := handle.WaitForConnection(ctx); err != nil {
	log.Fatal(err)
}

// Send commands to the terminal
handle.SendInput([]byte("ls -la\n"))
handle.SendInput([]byte("echo 'Hello, PTY!'\n"))
handle.SendInput([]byte("exit\n"))

// Read output from channel
for data := range handle.DataChan() {
	fmt.Print(string(data))
}

// Wait for completion and get result
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("PTY session completed with exit code: %d\n", *result.ExitCode)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyCreateOptions;
import io.daytona.sdk.PtyHandle;
import io.daytona.sdk.PtyResult;
import java.nio.charset.StandardCharsets;

PtyCreateOptions options = new PtyCreateOptions()
    .setId("my-interactive-session")
    .setCols(120)
    .setRows(30)
    .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));

PtyHandle ptyHandle = sandbox.process.createPty(options);
ptyHandle.waitForConnection(30);

ptyHandle.sendInput("ls -la\n");
ptyHandle.sendInput("echo \"Hello, PTY!\"\n");
ptyHandle.sendInput("exit\n");

PtyResult result = ptyHandle.waitForExit();
System.out.println("PTY session completed with exit code: " + result.getExitCode());

ptyHandle.disconnect();
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty' \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "id": "my-session",
  "cwd": "/workspace",
  "cols": 120,
  "rows": 30,
  "envs": {
    "TERM": "xterm-256color"
  },
  "lazyStart": false
}'
```

</TabItem>

</Tabs>

## Connect to PTY session [#connect-to-pty-session]

Establish a connection to an existing PTY session.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
pty_handle = sandbox.process.connect_pty_session("my-session")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Connect to an existing PTY session
const handle = await sandbox.process.connectPty('my-session', {
  onData: (data) => {
    // Handle terminal output
    const text = new TextDecoder().decode(data)
    process.stdout.write(text)
  },
})

// Wait for connection to be established
await handle.waitForConnection()

// Send commands to the existing session
await handle.sendInput('pwd\n')
await handle.sendInput('ls -la\n')
await handle.sendInput('exit\n')

// Wait for completion
const result = await handle.wait()
console.log(`Session exited with code: ${result.exitCode}`)

// Clean up
await handle.disconnect()
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Connect to an existing PTY session
pty_handle = sandbox.process.connect_pty_session('my-session')
pty_handle.send_input("echo 'Hello World'\n")
pty_handle.send_input("exit\n")

# Handle output
pty_handle.each { |data| print data }

puts "Session exited with code: #{pty_handle.exit_code}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Connect to an existing PTY session
handle, err := sandbox.Process.ConnectPty(ctx, "my-session")
if err != nil {
	log.Fatal(err)
}
defer handle.Disconnect()

// Wait for connection to be established
if err := handle.WaitForConnection(ctx); err != nil {
	log.Fatal(err)
}

// Send commands to the existing session
handle.SendInput([]byte("pwd\n"))
handle.SendInput([]byte("ls -la\n"))
handle.SendInput([]byte("exit\n"))

// Read output
for data := range handle.DataChan() {
	fmt.Print(string(data))
}

// Wait for completion
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Session exited with code: %d\n", *result.ExitCode)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyCreateOptions;
import io.daytona.sdk.PtyHandle;
import io.daytona.sdk.PtyResult;
import java.nio.charset.StandardCharsets;

PtyCreateOptions options = new PtyCreateOptions()
    .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));
PtyHandle handle = sandbox.process.connectPty("my-session", options);
handle.waitForConnection(30);

handle.sendInput("pwd\n");
handle.sendInput("ls -la\n");
handle.sendInput("exit\n");

PtyResult result = handle.waitForExit();
System.out.println("Session exited with code: " + result.getExitCode());
handle.disconnect();
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}/connect'
```

</TabItem>
</Tabs>

## List PTY sessions [#list-pty-sessions]

List PTY sessions currently registered in the sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# List all PTY sessions
sessions = sandbox.process.list_pty_sessions()

for session in sessions:
    print(f"Session ID: {session.id}")
    print(f"Active: {session.active}")
    print(f"Lazy start: {session.lazy_start}")
    print(f"Created: {session.created_at}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// List all PTY sessions
const sessions = await sandbox.process.listPtySessions()

for (const session of sessions) {
  console.log(`Session ID: ${session.id}`)
  console.log(`Active: ${session.active}`)
  console.log(`Lazy start: ${session.lazyStart}`)
  console.log(`Created: ${session.createdAt}`)
  console.log('---')
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# List all PTY sessions
sessions = sandbox.process.list_pty_sessions

sessions.each do |session|
  puts "Session ID: #{session.id}"
  puts "Active: #{session.active}"
  puts "Terminal Size: #{session.cols}x#{session.rows}"
  puts '---'
end
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// List all PTY sessions
sessions, err := sandbox.Process.ListPtySessions(ctx)
if err != nil {
	log.Fatal(err)
}

for _, session := range sessions {
	fmt.Printf("Session ID: %s\n", session.ID)
	fmt.Printf("Active: %t\n", session.Active)
	fmt.Printf("Terminal Size: %dx%d\n", session.Cols, session.Rows)
	fmt.Println("---")
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.toolbox.client.model.PtySessionInfo;
import java.util.List;

List<PtySessionInfo> sessions = sandbox.process.listPtySessions();
for (PtySessionInfo session : sessions) {
    System.out.println("Session ID: " + session.getId());
    System.out.println("Active: " + session.getActive());
    System.out.println("Lazy start: " + session.getLazyStart());
    System.out.println("Created: " + session.getCreatedAt());
    System.out.println("---");
}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty'
```

</TabItem>
</Tabs>

## Get PTY session info [#get-pty-session-info]

Get details about a specific PTY session.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Get details about a specific PTY session
session_info = sandbox.process.get_pty_session_info("my-session")

print(f"Session ID: {session_info.id}")
print(f"Active: {session_info.active}")
print(f"Lazy start: {session_info.lazy_start}")
print(f"Working Directory: {session_info.cwd}")
print(f"Terminal Size: {session_info.cols}x{session_info.rows}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Get details about a specific PTY session
const session = await sandbox.process.getPtySessionInfo('my-session')

console.log(`Session ID: ${session.id}`)
console.log(`Active: ${session.active}`)
console.log(`Lazy start: ${session.lazyStart}`)
console.log(`Working Directory: ${session.cwd}`)
console.log(`Terminal Size: ${session.cols}x${session.rows}`)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Get details about a specific PTY session
session_info = sandbox.process.get_pty_session_info('my-session')

puts "Session ID: #{session_info.id}"
puts "Active: #{session_info.active}"
puts "Lazy start: #{session_info.lazy_start}"
puts "Working Directory: #{session_info.cwd}"
puts "Terminal Size: #{session_info.cols}x#{session_info.rows}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Get details about a specific PTY session
session, err := sandbox.Process.GetPtySessionInfo(ctx, "my-session")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Session ID: %s\n", session.ID)
fmt.Printf("Active: %t\n", session.Active)
fmt.Printf("Working Directory: %s\n", session.CWD)
fmt.Printf("Terminal Size: %dx%d\n", session.Cols, session.Rows)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.toolbox.client.model.PtySessionInfo;

PtySessionInfo session = sandbox.process.getPtySessionInfo("my-session");
System.out.println("Session ID: " + session.getId());
System.out.println("Active: " + session.getActive());
System.out.println("Lazy start: " + session.getLazyStart());
System.out.println("Working Directory: " + session.getCwd());
System.out.println("Terminal Size: " + session.getCols() + "x" + session.getRows());
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}'
```

</TabItem>
</Tabs>

## Kill PTY session [#kill-pty-session]

Kill a PTY session, terminating the shell process and removing the session from the sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Kill a specific PTY session
sandbox.process.kill_pty_session("my-session")

# Verify the session no longer exists
pty_sessions = sandbox.process.list_pty_sessions()
for pty_session in pty_sessions:
    print(f"PTY session: {pty_session.id}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Kill a specific PTY session
await sandbox.process.killPtySession('my-session')

// Verify the session is no longer active
try {
  const info = await sandbox.process.getPtySessionInfo('my-session')
  console.log(`Session still exists but active: ${info.active}`)
} catch (error) {
  console.log('Session has been completely removed')
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Delete a specific PTY session
sandbox.process.delete_pty_session('my-session')

# Verify the session no longer exists
sessions = sandbox.process.list_pty_sessions
sessions.each do |session|
  puts "PTY session: #{session.id}"
end
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Kill a specific PTY session
err := sandbox.Process.KillPtySession(ctx, "my-session")
if err != nil {
	log.Fatal(err)
}

// Verify the session is no longer active
sessions, err := sandbox.Process.ListPtySessions(ctx)
if err != nil {
	log.Fatal(err)
}

for _, session := range sessions {
	fmt.Printf("PTY session: %s\n", session.ID)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.toolbox.client.model.PtySessionInfo;
import java.util.List;

sandbox.process.killPtySession("my-session");

List<PtySessionInfo> sessions = sandbox.process.listPtySessions();
for (PtySessionInfo session : sessions) {
    System.out.println("PTY session: " + session.getId());
}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}' \
  --request DELETE
```

</TabItem>
</Tabs>

## Resize PTY session [#resize-pty-session]

Resize a PTY session, allowing you to change the terminal dimensions of an active PTY session.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona.common.pty import PtySize

# Resize a PTY session to a larger terminal
new_size = PtySize(rows=40, cols=150)
updated_info = sandbox.process.resize_pty_session("my-session", new_size)

print(f"Terminal resized to {updated_info.cols}x{updated_info.rows}")

# You can also use the PtyHandle's resize method
pty_handle.resize(new_size)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Resize a PTY session to a larger terminal
const updatedInfo = await sandbox.process.resizePtySession('my-session', 150, 40)
console.log(`Terminal resized to ${updatedInfo.cols}x${updatedInfo.rows}`)

// You can also use the PtyHandle's resize method
await ptyHandle.resize(150, 40) // cols, rows
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Resize a PTY session to a larger terminal
pty_size = Daytona::PtySize.new(rows: 40, cols: 150)
session_info = sandbox.process.resize_pty_session('my-session', pty_size)

puts "Terminal resized to #{session_info.cols}x#{session_info.rows}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Resize a PTY session to a larger terminal
updatedInfo, err := sandbox.Process.ResizePtySession(ctx, "my-session", types.PtySize{
	Cols: 150,
	Rows: 40,
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Terminal resized to %dx%d\n", updatedInfo.Cols, updatedInfo.Rows)

// You can also use the PtyHandle's Resize method
info, err := handle.Resize(ctx, 150, 40)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Terminal resized to %dx%d\n", info.Cols, info.Rows)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
sandbox.process.resizePtySession("my-session", 150, 40);

// Or resize using the handle (after createPty or connectPty)
handle.resize(150, 40);
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}/resize' \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "cols": 150,
  "rows": 40
}'
```

</TabItem>
</Tabs>

## Interactive commands [#interactive-commands]

Handle interactive commands with PTY sessions, allowing you to handle interactive commands that require user input and can be resized during execution.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import time
import threading
from daytona.common.pty import PtySize

def handle_pty_data(data: bytes):
    text = data.decode("utf-8", errors="replace")
    print(text, end="")

# Create PTY session
pty_handle = sandbox.process.create_pty_session(
    id="interactive-session",
    pty_size=PtySize(cols=300, rows=100)
)

# Handle output while sending interactive input
thread = threading.Thread(target=pty_handle.wait, args=(handle_pty_data,))
thread.start()

# Send interactive command
pty_handle.send_input('printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi\n')
time.sleep(1)
pty_handle.send_input("y\n")

# Resize terminal
pty_session_info = pty_handle.resize(PtySize(cols=210, rows=110))
print(f"PTY session resized to {pty_session_info.cols}x{pty_session_info.rows}")

# Exit the session
pty_handle.send_input('exit\n')
thread.join()

print(f"Session completed with exit code: {pty_handle.exit_code}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Sandbox } from '@daytona/sdk'

// Create PTY session
const ptyHandle = await sandbox.process.createPty({
  id: 'interactive-session',
  cols: 300,
  rows: 100,
  onData: data => {
    const text = new TextDecoder().decode(data)
    process.stdout.write(text)
  },
})

await ptyHandle.waitForConnection()

// Send interactive command
await ptyHandle.sendInput(
  'printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi\n'
)
await new Promise(resolve => setTimeout(resolve, 1000))
await ptyHandle.sendInput('y\n')

// Resize terminal
const ptySessionInfo = await sandbox.process.resizePtySession(
  'interactive-session',
  210,
  110
)
console.log(
  `\nPTY session resized to ${ptySessionInfo.cols}x${ptySessionInfo.rows}`
)

// Exit the session
await ptyHandle.sendInput('exit\n')

// Wait for completion
const result = await ptyHandle.wait()
console.log(`Session completed with exit code: ${result.exitCode}`)
```

</TabItem>

<TabItem label="Ruby" icon="seti:ruby">
```ruby
require 'daytona'

# Create PTY session
pty_handle = sandbox.process.create_pty_session(
  id: 'interactive-session',
  pty_size: Daytona::PtySize.new(cols: 300, rows: 100)
)

# Handle output in a separate thread
thread = Thread.new do
  pty_handle.each { |data| print data }
end

# Send interactive command
pty_handle.send_input('printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi' + "\n")
sleep(1)
pty_handle.send_input("y\n")

# Resize terminal
pty_handle.resize(Daytona::PtySize.new(cols: 210, rows: 110))
puts "\nPTY session resized"

# Exit the session
pty_handle.send_input("exit\n")

# Wait for the thread to finish
thread.join

puts "Session completed with exit code: #{pty_handle.exit_code}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Create PTY session
handle, err := sandbox.Process.CreatePty(ctx, "interactive-session",
	options.WithCreatePtySize(types.PtySize{Cols: 300, Rows: 100}),
)
if err != nil {
	log.Fatal(err)
}
defer handle.Disconnect()

if err := handle.WaitForConnection(ctx); err != nil {
	log.Fatal(err)
}

// Handle output in a goroutine
go func() {
	for data := range handle.DataChan() {
		fmt.Print(string(data))
	}
}()

// Send interactive command
handle.SendInput([]byte(`printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi` + "\n"))
time.Sleep(1 * time.Second)
handle.SendInput([]byte("y\n"))

// Resize terminal
info, err := handle.Resize(ctx, 210, 110)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("\nPTY session resized to %dx%d\n", info.Cols, info.Rows)

// Exit the session
handle.SendInput([]byte("exit\n"))

// Wait for completion
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Session completed with exit code: %d\n", *result.ExitCode)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyCreateOptions;
import io.daytona.sdk.PtyHandle;
import io.daytona.sdk.PtyResult;
import java.nio.charset.StandardCharsets;

PtyCreateOptions options = new PtyCreateOptions()
    .setId("interactive-session")
    .setCols(300)
    .setRows(100)
    .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));

PtyHandle handle = sandbox.process.createPty(options);
handle.waitForConnection(30);

handle.sendInput(
    "printf \"Are you accepting the terms and conditions? (y/n): \" && read confirm && if [ \"$confirm\" = \"y\" ]; then echo \"You accepted\"; else echo \"You did not accept\"; fi\n");
Thread.sleep(1000);
handle.sendInput("y\n");

sandbox.process.resizePtySession("interactive-session", 210, 110);
System.out.println("\nPTY session resized");

handle.sendInput("exit\n");

PtyResult result = handle.waitForExit();
System.out.println("Session completed with exit code: " + result.getExitCode());
```

</TabItem>
</Tabs>

## Long-running processes [#long-running-processes]

Manage long-running processes with PTY sessions, allowing you to manage long-running processes that need to be monitored or terminated.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import time
import threading
from daytona import Daytona, Sandbox
from daytona.common.pty import PtySize

def handle_pty_data(data: bytes):
    text = data.decode("utf-8", errors="replace")
    print(text, end="")

# Create PTY session
pty_handle = sandbox.process.create_pty_session(
    id="long-running-session",
    pty_size=PtySize(cols=120, rows=30)
)

# Start a long-running process
pty_handle.send_input('while true; do echo "Running... $(date)"; sleep 1; done\n')

# Using thread and wait() method to handle PTY output
thread = threading.Thread(target=pty_handle.wait, args=(handle_pty_data, 10))
thread.start()

time.sleep(3)  # Let it run for a bit

print("Killing long-running process...")
pty_handle.kill()

thread.join()

print(f"\nProcess terminated with exit code: {pty_handle.exit_code}")
if pty_handle.error:
    print(f"Termination reason: {pty_handle.error}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Sandbox } from '@daytona/sdk'

// Create PTY session
const ptyHandle = await sandbox.process.createPty({
  id: 'long-running-session',
  cols: 120,
  rows: 30,
  onData: (data) => {
    const text = new TextDecoder().decode(data)
    process.stdout.write(text)
  },
})

await ptyHandle.waitForConnection()

// Start a long-running process
await ptyHandle.sendInput('while true; do echo "Running... $(date)"; sleep 1; done\n')
await new Promise(resolve => setTimeout(resolve, 3000)) // Let it run for a bit

console.log('Killing long-running process...')
await ptyHandle.kill()

// Wait for termination
const result = await ptyHandle.wait()
console.log(`\nProcess terminated with exit code: ${result.exitCode}`)
if (result.error) {
    console.log(`Termination reason: ${result.error}`)
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

# Create PTY session
pty_handle = sandbox.process.create_pty_session(
  id: 'long-running-session',
  pty_size: Daytona::PtySize.new(cols: 120, rows: 30)
)

# Handle output in a separate thread
thread = Thread.new do
  pty_handle.each { |data| print data }
end

# Start a long-running process
pty_handle.send_input("while true; do echo \"Running... $(date)\"; sleep 1; done\n")
sleep(3) # Let it run for a bit

puts "Killing long-running process..."
pty_handle.kill

thread.join

puts "\nProcess terminated with exit code: #{pty_handle.exit_code}"
puts "Termination reason: #{pty_handle.error}" if pty_handle.error
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Create PTY session
handle, err := sandbox.Process.CreatePty(ctx, "long-running-session",
	options.WithCreatePtySize(types.PtySize{Cols: 120, Rows: 30}),
)
if err != nil {
	log.Fatal(err)
}
defer handle.Disconnect()

if err := handle.WaitForConnection(ctx); err != nil {
	log.Fatal(err)
}

// Handle output in a goroutine
go func() {
	for data := range handle.DataChan() {
		fmt.Print(string(data))
	}
}()

// Start a long-running process
handle.SendInput([]byte(`while true; do echo "Running... $(date)"; sleep 1; done` + "\n"))
time.Sleep(3 * time.Second) // Let it run for a bit

fmt.Println("Killing long-running process...")
if err := handle.Kill(ctx); err != nil {
	log.Fatal(err)
}

// Wait for termination
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("\nProcess terminated with exit code: %d\n", *result.ExitCode)
if result.Error != nil {
	fmt.Printf("Termination reason: %s\n", *result.Error)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyCreateOptions;
import io.daytona.sdk.PtyHandle;
import io.daytona.sdk.PtyResult;
import java.nio.charset.StandardCharsets;

PtyCreateOptions options = new PtyCreateOptions()
    .setId("long-running-session")
    .setCols(120)
    .setRows(30)
    .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));

PtyHandle handle = sandbox.process.createPty(options);
handle.waitForConnection(30);

handle.sendInput("while true; do echo \"Running... $(date)\"; sleep 1; done\n");
Thread.sleep(3000);

System.out.println("Killing long-running process...");
handle.kill();

PtyResult result = handle.waitForExit();
System.out.println("\nProcess terminated with exit code: " + result.getExitCode());
if (result.getError() != null) {
    System.out.println("Termination reason: " + result.getError());
}
```

</TabItem>
</Tabs>

## Resource management [#resource-management]

Manage resource leaks with PTY sessions, allowing you to always clean up PTY sessions to prevent resource leaks.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">
```python
# Python: Use try/finally
from daytona import PtySize

pty_handle = None
try:
    pty_handle = sandbox.process.create_pty_session(id="session", pty_size=PtySize(cols=120, rows=30))
    # Do work...
finally:
    if pty_handle:
        pty_handle.kill()
```
</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">
```typescript
// TypeScript: Use try/finally
let ptyHandle
try {
  ptyHandle = await sandbox.process.createPty({
    id: 'session',
    cols: 120,
    rows: 30,
    onData: (data) => {
      process.stdout.write(new TextDecoder().decode(data))
    },
  })
  // Do work...
} finally {
  if (ptyHandle) await ptyHandle.kill()
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Ruby: Use begin/ensure
pty_handle = nil
begin
  pty_handle = sandbox.process.create_pty_session(
    id: 'session',
    pty_size: Daytona::PtySize.new(cols: 120, rows: 30)
  )
  # Do work...
ensure
  pty_handle&.disconnect
end
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Go: defer Disconnect to close the WebSocket without killing the shell
handle, err := sandbox.Process.CreatePty(ctx, "session",
	options.WithCreatePtySize(types.PtySize{Cols: 120, Rows: 30}),
)
if err != nil {
	log.Fatal(err)
}
defer handle.Disconnect()

// Do work...

// Call Kill to terminate the shell process when needed
// handle.Kill(ctx)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyCreateOptions;
import io.daytona.sdk.PtyHandle;

PtyHandle handle = null;
try {
    handle = sandbox.process.createPty(
        new PtyCreateOptions().setId("session").setCols(120).setRows(30));
    handle.waitForConnection(30);
    // Do work...
} finally {
    if (handle != null) {
        handle.kill();
        handle.disconnect();
    }
}
```

</TabItem>
</Tabs>

## PtyHandle methods [#ptyhandle-methods]

Daytona provides methods to interact with PTY sessions, allowing you to send input, resize the terminal, wait for completion, and manage the WebSocket connection to a PTY session.

### Send input [#send-input]

Send input to a PTY session, allowing you to send input data (keystrokes or commands) to the PTY session.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Send a command
pty_handle.send_input("ls -la\n")

# Send user input
pty_handle.send_input("y\n")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Send a command
await ptyHandle.sendInput('ls -la\n')

// Send raw bytes
await ptyHandle.sendInput(new Uint8Array([3])) // Ctrl+C
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Send a command
pty_handle.send_input("ls -la\n")

# Send user input
pty_handle.send_input("y\n")
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Send a command
handle.SendInput([]byte("ls -la\n"))

// Send Ctrl+C
handle.SendInput([]byte{0x03})
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
// Send a command
ptyHandle.sendInput("ls -la\n");

// Send raw bytes (Ctrl+C)
ptyHandle.sendInput(new byte[] { 0x03 });
```

</TabItem>
</Tabs>

### Wait for completion [#wait-for-completion]

Wait for a PTY process to exit and return the result.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Wait with a callback for output data
def handle_data(data: bytes):
    print(data.decode("utf-8", errors="replace"), end="")

result = pty_handle.wait(on_data=handle_data, timeout=30)
print(f"Exit code: {result.exit_code}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Wait for process to complete
const result = await ptyHandle.wait()

if (result.exitCode === 0) {
  console.log('Process completed successfully')
} else {
  console.log(`Process failed with code: ${result.exitCode}`)
  if (result.error) {
    console.log(`Error: ${result.error}`)
  }
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Wait by iterating over output (blocks until PTY session ends)
pty_handle.each { |data| print data }

if pty_handle.exit_code == 0
  puts 'Process completed successfully'
else
  puts "Process failed with code: #{pty_handle.exit_code}"
  puts "Error: #{pty_handle.error}" if pty_handle.error
end
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Wait for process to complete
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}

if result.ExitCode != nil && *result.ExitCode == 0 {
	fmt.Println("Process completed successfully")
} else {
	fmt.Printf("Process failed with code: %d\n", *result.ExitCode)
	if result.Error != nil {
		fmt.Printf("Error: %s\n", *result.Error)
	}
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyResult;

PtyResult result = ptyHandle.waitForExit();

if (result.getExitCode() == 0) {
    System.out.println("Process completed successfully");
} else {
    System.out.println("Process failed with code: " + result.getExitCode());
    if (result.getError() != null) {
        System.out.println("Error: " + result.getError());
    }
}
```

</TabItem>
</Tabs>

### Wait for connection [#wait-for-connection]

Wait for the WebSocket connection to be established before sending input.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Python handles connection internally during creation
# No explicit wait needed
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Wait for connection to be established
await ptyHandle.waitForConnection()

// Now safe to send input
await ptyHandle.sendInput('echo "Connected!"\n')
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
pty_handle.wait_for_connection
pty_handle.send_input("echo 'Connected!'\n")
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Wait for connection to be established
if err := handle.WaitForConnection(ctx); err != nil {
	log.Fatal(err)
}

// Now safe to send input
handle.SendInput([]byte("echo 'Connected!'\n"))
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
// Wait for connection (timeout in seconds)
ptyHandle.waitForConnection(30);

ptyHandle.sendInput("echo \"Connected!\"\n");
```

</TabItem>
</Tabs>

### Kill PTY process [#kill-pty-process]

Kill a PTY process and terminate the session from the handle.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
pty_handle.kill()
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Kill a long-running process
await ptyHandle.kill()

// Wait to confirm termination
const result = await ptyHandle.wait()
console.log(`Process terminated with exit code: ${result.exitCode}`)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Kill a long-running process
pty_handle.kill

puts "Process terminated with exit code: #{pty_handle.exit_code}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Kill a long-running process
if err := handle.Kill(ctx); err != nil {
	log.Fatal(err)
}

// Wait to confirm termination
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Process terminated with exit code: %d\n", *result.ExitCode)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyResult;

ptyHandle.kill();

PtyResult result = ptyHandle.waitForExit();
System.out.println("Process terminated with exit code: " + result.getExitCode());
```

</TabItem>
</Tabs>

### Resize from handle [#resize-from-handle]

Resize the PTY terminal dimensions directly from the handle.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona.common.pty import PtySize

pty_handle.resize(PtySize(cols=120, rows=30))
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Resize to 120x30
await ptyHandle.resize(120, 30)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Resize to 120x30
pty_handle.resize(Daytona::PtySize.new(cols: 120, rows: 30))
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Resize to 120x30
info, err := handle.Resize(ctx, 120, 30)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Resized to %dx%d\n", info.Cols, info.Rows)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
// Resize to 120x30
ptyHandle.resize(120, 30);
```

</TabItem>
</Tabs>

### Disconnect [#disconnect]

Disconnect from a PTY session and clean up resources without killing the process.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Python: Use kill() to terminate, or let the handle go out of scope
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Always clean up when done
try {
  // ... use PTY session
} finally {
  await ptyHandle.disconnect()
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
begin
  # ... use PTY session
ensure
  pty_handle.disconnect
end
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Always clean up when done using defer
handle, err := sandbox.Process.CreatePty(ctx, "session")
if err != nil {
	log.Fatal(err)
}
defer handle.Disconnect()

// ... use PTY session
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
try {
    // ... use PTY session
} finally {
    ptyHandle.disconnect();
}
```

</TabItem>
</Tabs>

### Check connection status [#check-connection-status]

Check if a PTY session is still connected.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Python: Check by attempting operations or using session info
session_info = sandbox.process.get_pty_session_info("my-session")
print(f"Session active: {session_info.active}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
if (ptyHandle.isConnected()) {
  console.log('PTY session is active')
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Ruby: Check by using session info
session_info = sandbox.process.get_pty_session_info('my-session')
puts 'PTY session is active' if session_info.active
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
if handle.IsConnected() {
	fmt.Println("PTY session is active")
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
if (ptyHandle.isConnected()) {
    System.out.println("PTY session is active");
}
```

</TabItem>
</Tabs>

### Exit code and error [#exit-code-and-error]

Access the exit code and error message after a PTY process terminates.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# After iteration or wait completes
print(f"Exit code: {pty_handle.exit_code}")
if pty_handle.error:
    print(f"Error: {pty_handle.error}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// Access via getters after process terminates
console.log(`Exit code: ${ptyHandle.exitCode}`)
if (ptyHandle.error) {
  console.log(`Error: ${ptyHandle.error}`)
}
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Access after process terminates
puts "Exit code: #{pty_handle.exit_code}"
puts "Error: #{pty_handle.error}" if pty_handle.error
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Access via methods after process terminates
if exitCode := handle.ExitCode(); exitCode != nil {
	fmt.Printf("Exit code: %d\n", *exitCode)
}
if errMsg := handle.Error(); errMsg != nil {
	fmt.Printf("Error: %s\n", *errMsg)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyResult;

PtyResult result = ptyHandle.waitForExit();
System.out.println("Exit code: " + result.getExitCode());
if (result.getError() != null) {
    System.out.println("Error: " + result.getError());
}
```

</TabItem>
</Tabs>

### Iterate over output (Python) [#iterate-over-output-python]

Iterate over a PTY handle to receive output data.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Iterate over PTY output
for data in pty_handle:
    text = data.decode("utf-8", errors="replace")
    print(text, end="")

print(f"Session ended with exit code: {pty_handle.exit_code}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
// TypeScript uses the onData callback instead
const ptyHandle = await sandbox.process.createPty({
  id: 'my-session',
  onData: (data) => {
    const text = new TextDecoder().decode(data)
    process.stdout.write(text)
  },
})
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
# Iterate over PTY output
pty_handle.each do |data|
  print data
end

puts "Session ended with exit code: #{pty_handle.exit_code}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Go uses a channel to receive output data
for data := range handle.DataChan() {
	fmt.Print(string(data))
}

// Or use as io.Reader
io.Copy(os.Stdout, handle)

fmt.Printf("Session ended with exit code: %d\n", *handle.ExitCode())
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyCreateOptions;
import java.nio.charset.StandardCharsets;

PtyHandle ptyHandle = sandbox.process.createPty(
    new PtyCreateOptions()
        .setId("my-session")
        .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8))));
```

</TabItem>
</Tabs>

## Error handling [#error-handling]

Monitor exit codes and handle errors appropriately with PTY sessions.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
# Python: Check exit codes
result = pty_handle.wait()
if result.exit_code != 0:
    print(f"Command failed: {result.exit_code}")
    print(f"Error: {result.error}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">
```typescript
// TypeScript: Check exit codes
const result = await ptyHandle.wait()
if (result.exitCode !== 0) {
  console.log(`Command failed: ${result.exitCode}`)
  console.log(`Error: ${result.error}`)
}
```
</TabItem>

<TabItem label="Ruby" icon="seti:ruby">
```ruby
# Ruby: Check exit codes
# The handle blocks until the PTY session completes
pty_handle.each { |data| print data }

if pty_handle.exit_code != 0
  puts "Command failed: #{pty_handle.exit_code}"
  puts "Error: #{pty_handle.error}"
end
```
</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Go: Check exit codes
result, err := handle.Wait(ctx)
if err != nil {
	log.Fatal(err)
}

if result.ExitCode != nil && *result.ExitCode != 0 {
	fmt.Printf("Command failed: %d\n", *result.ExitCode)
	if result.Error != nil {
		fmt.Printf("Error: %s\n", *result.Error)
	}
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.PtyResult;

PtyResult result = ptyHandle.waitForExit();
if (result.getExitCode() != 0) {
    System.out.println("Command failed: " + result.getExitCode());
    if (result.getError() != null) {
        System.out.println("Error: " + result.getError());
    }
}
```

</TabItem>
</Tabs>
