# 交互式终端（PTY）
<subtitle>在沙箱中创建交互式终端会话，支持实时输出、输入、调整窗口大小和重连。</subtitle>

PTY（pseudo-terminal，伪终端）模块允许您在沙箱中创建交互式终端会话，并进行实时的双向通信。

与 `commands.run()` 执行命令并在结束后返回输出不同，PTY 提供：

- **实时流式输出**：终端输出会通过回调实时返回。
- **双向输入**：终端运行期间可以继续发送输入。
- **交互式 Shell**：支持完整终端行为，包括 ANSI 颜色和转义序列。
- **会话持久性**：可以断开连接，并稍后重新连接到仍在运行的会话。

## 创建 PTY 会话

使用 `sandbox.pty.create()` 启动一个交互式 bash shell。下面的示例会执行 `echo 'hello world'`，随后退出并打印终端输出。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(
    size=PtySize(rows=24, cols=80),  # 终端字符尺寸
    envs={"MY_VAR": "hello"},        # 可选环境变量
    cwd="/home/user",                # 可选工作目录
    user="root",                     # 可选运行用户
)

# terminal.pid 是终端进程 ID
print("Terminal PID:", terminal.pid)

# 向 PTY 发送输入。数据必须是 bytes，末尾的换行相当于按下 Enter。
sandbox.pty.send_stdin(terminal.pid, b"echo 'hello world'\n")

# PTY 运行的是交互式登录 shell，不会自行退出，因此需要显式发送 exit。
# 否则下面的 wait() 会一直阻塞。
sandbox.pty.send_stdin(terminal.pid, b"exit\n")

# 通过 wait() 的 on_pty 回调流式接收输出。
# end='' 可以避免 print 额外添加换行。
terminal.wait(on_pty=lambda data: print(data.decode(), end=""))
```

> PTY 会运行 `TERM=xterm-256color` 的交互式 bash shell，支持 ANSI 颜色和转义序列。

## 超时

PTY 会话支持配置超时时间，用于控制会话持续时间。默认超时时间为 60 秒。对于交互式或长时间运行的会话，可以设置 `timeout=0`，让会话保持打开。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(
    PtySize(rows=24, cols=80),
    timeout=0,  # 保持会话一直打开
)

# end='' 可以避免 print() 额外添加换行
# PTY 输出本身通常已经包含换行
terminal.wait(on_pty=lambda data: print(data.decode(), end=""))
```

## 向 PTY 发送输入

使用 `send_stdin()` 向终端发送数据。该方法会同步完成发送，实际输出会通过传给 `wait()` 的 `on_pty` 回调返回。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(PtySize(rows=24, cols=80))

# 发送命令。数据需要是 bytes；不要忘记结尾换行。
sandbox.pty.send_stdin(terminal.pid, b'echo "Hello from PTY"\n')

# 通过 wait() 的 on_pty 回调接收输出。
# end='' 可以避免 print 额外添加换行。
terminal.wait(on_pty=lambda data: print(data.decode(), end=""))
```

## 调整终端尺寸

当用户的终端窗口大小变化时，可以调用 `resize()` 通知 PTY。`cols` 和 `rows` 表示字符数量，不是像素。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(PtySize(rows=24, cols=80))

# 调整为新的终端尺寸，单位是字符
sandbox.pty.resize(terminal.pid, PtySize(rows=40, cols=120))
```

## 断开并重新连接

您可以断开与 PTY 会话的连接，同时让会话继续在后台运行；之后再使用新的数据处理逻辑重新连接。

这适用于：

- 网络中断后恢复终端会话。
- 多个客户端共享终端访问。
- 实现终端会话持久化。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

# 创建 PTY 会话
terminal = sandbox.pty.create(PtySize(rows=24, cols=80))

pid = terminal.pid

# 发送命令
sandbox.pty.send_stdin(pid, b"echo hello\n")

# 断开连接，PTY 会继续在后台运行
terminal.disconnect()

# 稍后重新连接到同一个会话
reconnected = sandbox.pty.connect(pid)

# 继续使用该会话，然后退出
sandbox.pty.send_stdin(pid, b"echo world\nexit\n")

# 等待终端退出，并通过 on_pty 流式接收输出
reconnected.wait(on_pty=lambda data: print("Handler:", data.decode()))
```

## 终止 PTY

使用 `kill()` 终止 PTY 会话。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(PtySize(rows=24, cols=80))

# 终止 PTY
killed = sandbox.pty.kill(terminal.pid)
print("Killed:", killed)  # 成功时为 True

# 也可以使用句柄方法
# terminal.kill()
```

## 等待 PTY 退出

使用 `wait()` 等待终端会话结束，例如用户输入 `exit` 后。

```python
from ucloud_sandbox import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(PtySize(rows=24, cols=80))

# 发送 exit 命令
sandbox.pty.send_stdin(terminal.pid, b"exit\n")

# 等待终端退出，并通过 on_pty 流式接收输出
result = terminal.wait(on_pty=lambda data: print(data.decode(), end=""))
print("Exit code:", result.exit_code)
```

## 交互式终端实现

构建类似 SSH 的完整交互式终端，需要处理 raw mode、标准输入转发和终端窗口大小变化事件。实际实现时，可以基于本文介绍的 `sandbox.pty` API 封装自己的终端前端或 CLI 交互逻辑。
