--- title: Pseudo Terminal (PTY) description: Interact with pseudo terminal sessions in your sandboxes. --- 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 an interactive terminal session that can execute commands and handle user input. ```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) ) ``` ```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() ``` ```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}" ``` ```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) ``` ```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(); ``` ```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 }' ``` ## Connect to PTY session Establish a connection to an existing PTY session. ```python pty_handle = sandbox.process.connect_pty_session("my-session") ``` ```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() ``` ```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}" ``` ```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) ``` ```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(); ``` ```bash curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}/connect' ``` ## List PTY sessions List PTY sessions currently registered in the sandbox. ```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}") ``` ```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('---') } ``` ```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 ``` ```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("---") } ``` ```java import io.daytona.toolbox.client.model.PtySessionInfo; import java.util.List; List 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("---"); } ``` ```bash curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty' ``` ## Get PTY session info Get details about a specific PTY session. ```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}") ``` ```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}`) ``` ```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}" ``` ```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) ``` ```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()); ``` ```bash curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}' ``` ## Kill PTY session Kill a PTY session, terminating the shell process and removing the session from the sandbox. ```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}") ``` ```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') } ``` ```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 ``` ```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) } ``` ```java import io.daytona.toolbox.client.model.PtySessionInfo; import java.util.List; sandbox.process.killPtySession("my-session"); List sessions = sandbox.process.listPtySessions(); for (PtySessionInfo session : sessions) { System.out.println("PTY session: " + session.getId()); } ``` ```bash curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}' \ --request DELETE ``` ## Resize PTY session Resize a PTY session, allowing you to change the terminal dimensions of an active PTY session. ```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) ``` ```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 ``` ```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}" ``` ```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) ``` ```java sandbox.process.resizePtySession("my-session", 150, 40); // Or resize using the handle (after createPty or connectPty) handle.resize(150, 40); ``` ```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 }' ``` ## Interactive commands Handle interactive commands with PTY sessions, allowing you to handle interactive commands that require user input and can be resized during execution. ```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}") ``` ```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}`) ``` ```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}" ``` ```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) ``` ```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()); ``` ## Long-running processes Manage long-running processes with PTY sessions, allowing you to manage long-running processes that need to be monitored or terminated. ```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}") ``` ```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}`) } ``` ```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 ``` ```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) } ``` ```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()); } ``` ## Resource management Manage resource leaks with PTY sessions, allowing you to always clean up PTY sessions to prevent resource leaks. ```python # Python: Use try/finally 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() ``` ```typescript // TypeScript: Use try/finally let ptyHandle try { ptyHandle = await sandbox.process.createPty({ id: 'session', cols: 120, rows: 30, }) // Do work... } finally { if (ptyHandle) await ptyHandle.kill() } ``` ```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 ``` ```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) ``` ```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(); } } ``` ## 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 to a PTY session, allowing you to send input data (keystrokes or commands) to the PTY session. ```python # Send a command pty_handle.send_input("ls -la\n") # Send user input pty_handle.send_input("y\n") ``` ```typescript // Send a command await ptyHandle.sendInput('ls -la\n') // Send raw bytes await ptyHandle.sendInput(new Uint8Array([3])) // Ctrl+C ``` ```ruby # Send a command pty_handle.send_input("ls -la\n") # Send user input pty_handle.send_input("y\n") ``` ```go // Send a command handle.SendInput([]byte("ls -la\n")) // Send Ctrl+C handle.SendInput([]byte{0x03}) ``` ```java // Send a command ptyHandle.sendInput("ls -la\n"); // Send raw bytes (Ctrl+C) ptyHandle.sendInput(new byte[] { 0x03 }); ``` ### Wait for completion Wait for a PTY process to exit and return the result. ```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}") ``` ```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}`) } } ``` ```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 ``` ```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) } } ``` ```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()); } } ``` ### Wait for connection Wait for the WebSocket connection to be established before sending input. ```python # Python handles connection internally during creation # No explicit wait needed ``` ```typescript // Wait for connection to be established await ptyHandle.waitForConnection() // Now safe to send input await ptyHandle.sendInput('echo "Connected!"\n') ``` ```ruby pty_handle.wait_for_connection pty_handle.send_input("echo 'Connected!'\n") ``` ```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")) ``` ```java // Wait for connection (timeout in seconds) ptyHandle.waitForConnection(30); ptyHandle.sendInput("echo \"Connected!\"\n"); ``` ### Kill PTY process Kill a PTY process and terminate the session from the handle. ```python pty_handle.kill() ``` ```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}`) ``` ```ruby # Kill a long-running process pty_handle.kill puts "Process terminated with exit code: #{pty_handle.exit_code}" ``` ```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) ``` ```java import io.daytona.sdk.PtyResult; ptyHandle.kill(); PtyResult result = ptyHandle.waitForExit(); System.out.println("Process terminated with exit code: " + result.getExitCode()); ``` ### Resize from handle Resize the PTY terminal dimensions directly from the handle. ```python from daytona.common.pty import PtySize pty_handle.resize(PtySize(cols=120, rows=30)) ``` ```typescript // Resize to 120x30 await ptyHandle.resize(120, 30) ``` ```ruby # Resize to 120x30 pty_handle.resize(Daytona::PtySize.new(cols: 120, rows: 30)) ``` ```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) ``` ```java // Resize to 120x30 ptyHandle.resize(120, 30); ``` ### Disconnect Disconnect from a PTY session and clean up resources without killing the process. ```python # Python: Use kill() to terminate, or let the handle go out of scope ``` ```typescript // Always clean up when done try { // ... use PTY session } finally { await ptyHandle.disconnect() } ``` ```ruby begin # ... use PTY session ensure pty_handle.disconnect end ``` ```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 ``` ```java try { // ... use PTY session } finally { ptyHandle.disconnect(); } ``` ### Check connection status Check if a PTY session is still connected. ```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}") ``` ```typescript if (ptyHandle.isConnected()) { console.log('PTY session is active') } ``` ```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 ``` ```go if handle.IsConnected() { fmt.Println("PTY session is active") } ``` ```java if (ptyHandle.isConnected()) { System.out.println("PTY session is active"); } ``` ### Exit code and error Access the exit code and error message after a PTY process terminates. ```python # After iteration or wait completes print(f"Exit code: {pty_handle.exit_code}") if pty_handle.error: print(f"Error: {pty_handle.error}") ``` ```typescript // Access via getters after process terminates console.log(`Exit code: ${ptyHandle.exitCode}`) if (ptyHandle.error) { console.log(`Error: ${ptyHandle.error}`) } ``` ```ruby # Access after process terminates puts "Exit code: #{pty_handle.exit_code}" puts "Error: #{pty_handle.error}" if pty_handle.error ``` ```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) } ``` ```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()); } ``` ### Iterate over output (Python) Iterate over a PTY handle to receive output data. ```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}") ``` ```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) }, }) ``` ```ruby # Iterate over PTY output pty_handle.each do |data| print data end puts "Session ended with exit code: #{pty_handle.exit_code}" ``` ```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()) ``` ```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)))); ``` ## Error handling Monitor exit codes and handle errors appropriately with PTY sessions. ```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}") ``` ```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}`) } ``` ```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 ``` ```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) } } ``` ```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()); } } ```