Skip to content

Commit c1df0cc

Browse files
authored
Add PTY module documentation (#96)
* Add PTY module documentation Add documentation for the sandbox PTY (pseudo-terminal) module that enables interactive terminal sessions with real-time bidirectional communication.
1 parent 5755c96 commit c1df0cc

2 files changed

Lines changed: 345 additions & 0 deletions

File tree

docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"docs/sandbox/list",
9090
"docs/sandbox/connect",
9191
"docs/sandbox/internet-access",
92+
"docs/sandbox/pty",
9293
"docs/sandbox/connect-bucket",
9394
"docs/sandbox/rate-limits",
9495
"docs/sandbox/secured-access",

docs/sandbox/pty.mdx

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
---
2+
title: "Interactive terminal (PTY)"
3+
sidebarTitle: Interactive terminal
4+
---
5+
6+
The PTY (pseudo-terminal) module allows you to create interactive terminal sessions in the sandbox with real-time, bidirectional communication.
7+
8+
Unlike `commands.run()` which executes a command and returns output after completion, PTY provides:
9+
- **Real-time streaming** - Output is streamed as it happens via callbacks
10+
- **Bidirectional input** - Send input while the terminal is running
11+
- **Interactive shell** - Full terminal support with ANSI colors and escape sequences
12+
- **Session persistence** - Disconnect and reconnect to running sessions
13+
14+
## Create a PTY session
15+
16+
Use `sandbox.pty.create()` to start an interactive bash shell.
17+
18+
<CodeGroup>
19+
```js JavaScript & TypeScript
20+
import { Sandbox } from '@e2b/code-interpreter'
21+
22+
const sandbox = await Sandbox.create()
23+
24+
const terminal = await sandbox.pty.create({
25+
cols: 80, // Terminal width in characters
26+
rows: 24, // Terminal height in characters
27+
onData: (data) => {
28+
// Called whenever terminal outputs data
29+
process.stdout.write(data)
30+
},
31+
envs: { MY_VAR: 'hello' }, // Optional environment variables
32+
cwd: '/home/user', // Optional working directory
33+
user: 'root', // Optional user to run as
34+
})
35+
36+
// terminal.pid contains the process ID
37+
console.log('Terminal PID:', terminal.pid)
38+
```
39+
40+
```python Python
41+
from e2b_code_interpreter import Sandbox
42+
43+
sandbox = Sandbox()
44+
45+
terminal = sandbox.pty.create(
46+
cols=80, # Terminal width in characters
47+
rows=24, # Terminal height in characters
48+
on_data=lambda data: print(data.decode(), end=''), # end='' prevents print from adding extra newline
49+
envs={'MY_VAR': 'hello'}, # Optional environment variables
50+
cwd='/home/user', # Optional working directory
51+
user='root', # Optional user to run as
52+
)
53+
54+
# terminal.pid contains the process ID
55+
print('Terminal PID:', terminal.pid)
56+
```
57+
</CodeGroup>
58+
59+
<Note>
60+
The PTY runs an interactive bash shell with `TERM=xterm-256color`, which supports ANSI colors and escape sequences.
61+
</Note>
62+
63+
## Timeout
64+
65+
By default, PTY sessions have a **60-second timeout** which limits the total duration of the session. When the timeout is reached, the connection to the PTY session will be closed regardless of activity.
66+
67+
For long-running sessions, set `timeoutMs: 0` (JavaScript) or `timeout=0` (Python) to disable the timeout.
68+
69+
<CodeGroup>
70+
```js JavaScript & TypeScript
71+
import { Sandbox } from '@e2b/code-interpreter'
72+
73+
const sandbox = await Sandbox.create()
74+
75+
const terminal = await sandbox.pty.create({
76+
cols: 80,
77+
rows: 24,
78+
onData: (data) => process.stdout.write(data),
79+
timeoutMs: 0, // No timeout for long-running sessions
80+
})
81+
```
82+
83+
```python Python
84+
from e2b_code_interpreter import Sandbox
85+
86+
sandbox = Sandbox()
87+
88+
# end='' prevents print() from adding an extra newline
89+
# (PTY output already contains newlines)
90+
terminal = sandbox.pty.create(
91+
cols=80,
92+
rows=24,
93+
on_data=lambda data: print(data.decode(), end=''),
94+
timeout=0, # No timeout for long-running sessions
95+
)
96+
```
97+
</CodeGroup>
98+
99+
## Send input to PTY
100+
101+
Use `sendInput()` in JavaScript or `send_stdin()` in Python to send data to the terminal. These methods return a Promise (JavaScript) or complete synchronously (Python) - the actual output will be delivered to your `onData` callback.
102+
103+
<CodeGroup>
104+
```js JavaScript & TypeScript
105+
import { Sandbox } from '@e2b/code-interpreter'
106+
107+
const sandbox = await Sandbox.create()
108+
109+
const terminal = await sandbox.pty.create({
110+
cols: 80,
111+
rows: 24,
112+
onData: (data) => process.stdout.write(data),
113+
})
114+
115+
// Send a command (don't forget the newline!)
116+
await sandbox.pty.sendInput(
117+
terminal.pid,
118+
new TextEncoder().encode('echo "Hello from PTY"\n')
119+
)
120+
```
121+
122+
```python Python
123+
from e2b_code_interpreter import Sandbox
124+
125+
sandbox = Sandbox()
126+
127+
terminal = sandbox.pty.create(
128+
cols=80,
129+
rows=24,
130+
on_data=lambda data: print(data.decode(), end=''), # end='' prevents extra newline
131+
)
132+
133+
# Send a command as bytes (b'...' is Python's byte string syntax)
134+
# Don't forget the newline!
135+
sandbox.pty.send_stdin(terminal.pid, b'echo "Hello from PTY"\n')
136+
```
137+
</CodeGroup>
138+
139+
## Resize the terminal
140+
141+
When the user's terminal window changes size, notify the PTY with `resize()`. The `cols` and `rows` parameters are measured in characters, not pixels.
142+
143+
<CodeGroup>
144+
```js JavaScript & TypeScript
145+
import { Sandbox } from '@e2b/code-interpreter'
146+
147+
const sandbox = await Sandbox.create()
148+
149+
const terminal = await sandbox.pty.create({
150+
cols: 80,
151+
rows: 24,
152+
onData: (data) => process.stdout.write(data),
153+
})
154+
155+
// Resize to new dimensions (in characters)
156+
await sandbox.pty.resize(terminal.pid, {
157+
cols: 120,
158+
rows: 40,
159+
})
160+
```
161+
162+
```python Python
163+
from e2b_code_interpreter import Sandbox
164+
165+
sandbox = Sandbox()
166+
167+
terminal = sandbox.pty.create(
168+
cols=80,
169+
rows=24,
170+
on_data=lambda data: print(data.decode(), end=''), # end='' prevents extra newline
171+
)
172+
173+
# Resize to new dimensions (in characters)
174+
sandbox.pty.resize(terminal.pid, cols=120, rows=40)
175+
```
176+
</CodeGroup>
177+
178+
## Disconnect and reconnect
179+
180+
You can disconnect from a PTY session while keeping it running, then reconnect later with a new data handler. This is useful for:
181+
- Resuming terminal sessions after network interruptions
182+
- Sharing terminal access between multiple clients
183+
- Implementing terminal session persistence
184+
185+
<CodeGroup>
186+
```js JavaScript & TypeScript
187+
import { Sandbox } from '@e2b/code-interpreter'
188+
189+
const sandbox = await Sandbox.create()
190+
191+
// Create a PTY session
192+
const terminal = await sandbox.pty.create({
193+
cols: 80,
194+
rows: 24,
195+
onData: (data) => console.log('Handler 1:', new TextDecoder().decode(data)),
196+
})
197+
198+
const pid = terminal.pid
199+
200+
// Send a command
201+
await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo hello\n'))
202+
203+
// Disconnect - PTY keeps running in the background
204+
await terminal.disconnect()
205+
206+
// Later: reconnect with a new data handler
207+
const reconnected = await sandbox.pty.connect(pid, {
208+
onData: (data) => console.log('Handler 2:', new TextDecoder().decode(data)),
209+
})
210+
211+
// Continue using the session
212+
await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo world\n'))
213+
214+
// Wait for the terminal to exit
215+
await reconnected.wait()
216+
```
217+
218+
```python Python
219+
import time
220+
from e2b_code_interpreter import Sandbox
221+
222+
sandbox = Sandbox()
223+
224+
# Create a PTY session
225+
terminal = sandbox.pty.create(
226+
cols=80,
227+
rows=24,
228+
on_data=lambda data: print('Handler 1:', data.decode()),
229+
)
230+
231+
pid = terminal.pid
232+
233+
# Send a command
234+
sandbox.pty.send_stdin(pid, b'echo hello\n')
235+
time.sleep(0.5)
236+
237+
# Disconnect - PTY keeps running in the background
238+
terminal.disconnect()
239+
240+
# Later: reconnect with a new data handler
241+
reconnected = sandbox.pty.connect(
242+
pid,
243+
on_data=lambda data: print('Handler 2:', data.decode()),
244+
)
245+
246+
# Continue using the session
247+
sandbox.pty.send_stdin(pid, b'echo world\n')
248+
249+
# Wait for the terminal to exit
250+
reconnected.wait()
251+
```
252+
</CodeGroup>
253+
254+
## Kill the PTY
255+
256+
Terminate the PTY session with `kill()`.
257+
258+
<CodeGroup>
259+
```js JavaScript & TypeScript
260+
import { Sandbox } from '@e2b/code-interpreter'
261+
262+
const sandbox = await Sandbox.create()
263+
264+
const terminal = await sandbox.pty.create({
265+
cols: 80,
266+
rows: 24,
267+
onData: (data) => process.stdout.write(data),
268+
})
269+
270+
// Kill the PTY
271+
const killed = await sandbox.pty.kill(terminal.pid)
272+
console.log('Killed:', killed) // true if successful
273+
274+
// Or use the handle method
275+
// await terminal.kill()
276+
```
277+
278+
```python Python
279+
from e2b_code_interpreter import Sandbox
280+
281+
sandbox = Sandbox()
282+
283+
terminal = sandbox.pty.create(
284+
cols=80,
285+
rows=24,
286+
on_data=lambda data: print(data.decode(), end=''), # end='' prevents extra newline
287+
)
288+
289+
# Kill the PTY
290+
killed = sandbox.pty.kill(terminal.pid)
291+
print('Killed:', killed) # True if successful
292+
293+
# Or use the handle method
294+
# terminal.kill()
295+
```
296+
</CodeGroup>
297+
298+
## Wait for PTY to exit
299+
300+
Use `wait()` to wait for the terminal session to end (e.g., when the user types `exit`).
301+
302+
<CodeGroup>
303+
```js JavaScript & TypeScript
304+
import { Sandbox } from '@e2b/code-interpreter'
305+
306+
const sandbox = await Sandbox.create()
307+
308+
const terminal = await sandbox.pty.create({
309+
cols: 80,
310+
rows: 24,
311+
onData: (data) => process.stdout.write(data),
312+
})
313+
314+
// Send exit command
315+
await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n'))
316+
317+
// Wait for the terminal to exit
318+
const result = await terminal.wait()
319+
console.log('Exit code:', result.exitCode)
320+
```
321+
322+
```python Python
323+
from e2b_code_interpreter import Sandbox
324+
325+
sandbox = Sandbox()
326+
327+
terminal = sandbox.pty.create(
328+
cols=80,
329+
rows=24,
330+
on_data=lambda data: print(data.decode(), end=''), # end='' prevents extra newline
331+
)
332+
333+
# Send exit command
334+
sandbox.pty.send_stdin(terminal.pid, b'exit\n')
335+
336+
# Wait for the terminal to exit
337+
result = terminal.wait()
338+
print('Exit code:', result.exit_code)
339+
```
340+
</CodeGroup>
341+
342+
## Interactive terminal (SSH-like)
343+
344+
Building a fully interactive terminal (like SSH) requires handling raw mode, stdin forwarding, and terminal resize events. For a production implementation, see the [E2B CLI source code](https://github.com/e2b-dev/E2B/blob/main/packages/cli/src/terminal.ts) which uses the same `sandbox.pty` API documented above.

0 commit comments

Comments
 (0)