dev-terminal
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDev Terminal Skill
Dev Terminal 技能
Terminal automation that maintains PTY sessions across script executions. Run TUI applications, send keystrokes, capture screen output, and debug terminal apps - all with persistent state.
可在脚本执行期间保持PTY会话的终端自动化工具。运行TUI应用、发送按键指令、捕获屏幕输出、调试终端应用——所有操作均支持持久化状态。
Setup
设置步骤
Start the server in a background terminal:
bash
cd dev-terminal && npm install && ./server.sh &Wait for the message before running scripts.
Ready在后台终端启动服务器:
bash
cd dev-terminal && npm install && ./server.sh &在运行脚本前,请等待消息出现。
ReadyHeaded Mode (Optional)
带界面模式(可选)
For visual debugging, start with browser UI:
bash
cd dev-terminal && ./server.sh --headed &This opens a browser window showing all terminals in real-time. Useful for:
- Watching AI actions as they happen
- Manual intervention if needed
- Debugging TUI interactions
如需可视化调试,可通过浏览器UI启动:
bash
cd dev-terminal && ./server.sh --headed &这会打开一个浏览器窗口,实时显示所有终端内容。适用于:
- 实时查看AI操作过程
- 必要时进行人工干预
- 调试TUI交互逻辑
Writing Scripts
编写脚本
Run scripts inline using heredocs from the directory:
dev-terminal/bash
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
// Create or get a named terminal
const term = await client.terminal("my-app", {
command: "python",
args: ["-m", "my_module"],
cols: 120,
rows: 40,
});
// Wait for app to start
await sleep(1000);
// Get screen snapshot
const snap = await term.snapshot();
console.log("=== SCREEN ===");
console.log(snap.text);
client.disconnect();
EOF在目录下使用here-doc方式运行内嵌脚本:
dev-terminal/bash
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
// 创建或获取指定名称的终端
const term = await client.terminal("my-app", {
command: "python",
args: ["-m", "my_module"],
cols: 120,
rows: 40,
});
// 等待应用启动
await sleep(1000);
// 获取屏幕快照
const snap = await term.snapshot();
console.log("=== SCREEN ===");
console.log(snap.text);
client.disconnect();
EOFShell Defaults
Shell默认配置
By default, terminals use:
- Shell: User's default shell (env var, e.g., zsh on macOS, bash on Linux)
$SHELL - Mode: Login shell (flag) - loads full profile (
-l,~/.zprofile)~/.bash_profile
This means your shell aliases, PATH, and environment are available.
Override the shell:
typescript
// Use a specific shell
const term = await client.terminal("my-term", {
command: "bash",
args: ["-l"], // keep as login shell
});
// Non-login shell (only loads ~/.bashrc, not ~/.bash_profile)
const term = await client.terminal("my-term", {
command: "bash",
args: [], // no -l flag
});
// Run a command directly (not a shell)
const term = await client.terminal("my-term", {
command: "python",
args: ["-m", "my_app"],
});默认情况下,终端使用:
- Shell:用户默认Shell(环境变量,例如macOS上的zsh、Linux上的bash)
$SHELL - 模式:登录Shell(参数)——加载完整配置文件(
-l、~/.zprofile)~/.bash_profile
这意味着你的Shell别名、PATH和环境变量都可以正常使用。
自定义Shell:
typescript
// 使用指定Shell
const term = await client.terminal("my-term", {
command: "bash",
args: ["-l"], // 保持登录Shell模式
});
// 非登录Shell(仅加载~/.bashrc,不加载~/.bash_profile)
const term = await client.terminal("my-term", {
command: "bash",
args: [], // 不使用-l参数
});
// 直接运行命令(不启动Shell)
const term = await client.terminal("my-term", {
command: "python",
args: ["-m", "my_app"],
});SSH Remote Terminals
SSH远程终端
Connect to remote servers via SSH. The API is identical to local terminals.
typescript
import { connect } from "./src/client.js";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
const client = await connect();
// SSH with private key
const term = await client.terminal("remote-server", {
ssh: {
host: "192.168.1.100",
username: "deploy",
privateKey: fs.readFileSync(path.join(os.homedir(), ".ssh/id_rsa"), "utf8"),
},
});
// SSH with password
const term = await client.terminal("remote-server", {
ssh: {
host: "example.com",
username: "admin",
password: "secret",
},
});
// SSH with agent (uses SSH_AUTH_SOCK)
const term = await client.terminal("remote-server", {
ssh: {
host: "example.com",
username: "admin",
agent: process.env.SSH_AUTH_SOCK,
},
});
// SSH with custom port and encrypted key
const term = await client.terminal("remote-server", {
ssh: {
host: "example.com",
port: 2222,
username: "admin",
privateKey: fs.readFileSync("/path/to/key", "utf8"),
passphrase: "key-passphrase",
},
});SSH Options:
| Option | Type | Description |
|---|---|---|
| string | Remote hostname or IP (required) |
| number | SSH port (default: 22) |
| string | SSH username (required) |
| string | Password authentication |
| string | Private key content (not path) |
| string | Passphrase for encrypted keys |
| string | Path to SSH agent socket |
Notes:
- SSH terminals don't have a (it's
pid)undefined - All Terminal methods work the same (write, key, snapshot, etc.)
- The remote shell is determined by the server, not local settings
通过SSH连接到远程服务器。API使用方式与本地终端完全一致。
typescript
import { connect } from "./src/client.js";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
const client = await connect();
// 使用私钥进行SSH连接
const term = await client.terminal("remote-server", {
ssh: {
host: "192.168.1.100",
username: "deploy",
privateKey: fs.readFileSync(path.join(os.homedir(), ".ssh/id_rsa"), "utf8"),
},
});
// 使用密码进行SSH连接
const term = await client.terminal("remote-server", {
ssh: {
host: "example.com",
username: "admin",
password: "secret",
},
});
// 使用SSH代理连接(依赖SSH_AUTH_SOCK)
const term = await client.terminal("remote-server", {
ssh: {
host: "example.com",
username: "admin",
agent: process.env.SSH_AUTH_SOCK,
},
});
// 使用自定义端口和加密密钥进行SSH连接
const term = await client.terminal("remote-server", {
ssh: {
host: "example.com",
port: 2222,
username: "admin",
privateKey: fs.readFileSync("/path/to/key", "utf8"),
passphrase: "key-passphrase",
},
});SSH配置选项:
| 选项名 | 类型 | 描述 |
|---|---|---|
| string | 远程主机名或IP(必填) |
| number | SSH端口(默认:22) |
| string | SSH用户名(必填) |
| string | 密码认证方式 |
| string | 私钥内容(而非文件路径) |
| string | 加密密钥的密码 |
| string | SSH代理套接字的路径 |
注意事项:
- SSH终端没有属性(值为
pid)undefined - 所有终端方法的使用方式保持一致(write、key、snapshot等)
- 远程Shell由服务器决定,不受本地设置影响
Key Principles
核心原则
- Small scripts: Each script does ONE thing (start app, check screen, send key)
- Observe output: Always check to see current state
snapshot() - Descriptive names: Use ,
"claude-monitor", not"vim-edit""term1" - Terminals persist: leaves terminals running for next script
disconnect()
- 脚本轻量化:每个脚本仅完成一项任务(启动应用、检查屏幕、发送按键)
- 观察输出:始终通过检查当前状态
snapshot() - 命名规范化:使用、
"claude-monitor"这类有意义的名称,而非"vim-edit""term1" - 终端持久化:调用后终端仍会保持运行,供后续脚本使用
disconnect()
Workflow Loop
工作流循环
- Write a script to perform one action
- Run it and observe the screen output
- Evaluate - what's displayed? Did it work?
- Decide - send more input or task complete?
- Repeat until done
- 编写脚本:实现单个操作
- 运行脚本:观察屏幕输出
- 评估结果:当前显示内容是什么?操作是否成功?
- 决策下一步:继续发送输入还是结束任务?
- 重复执行:直至任务完成
Client API
客户端API
typescript
const client = await connect();
// Get or create named terminal (uses default shell as login shell)
const term = await client.terminal("name");
// With options
const term = await client.terminal("name", {
command: "python", // override shell/command
args: ["-m", "my_app"], // override args (clears default -l flag)
cols: 120,
rows: 40,
cwd: "/path/to/dir",
env: { MY_VAR: "value" },
});
// List all terminal names
const names = await client.list();
// Close/kill a terminal
await client.close("name");
// Disconnect (terminals persist)
client.disconnect();typescript
const client = await connect();
// 获取或创建指定名称的终端(默认使用登录Shell)
const term = await client.terminal("name");
// 带配置选项的方式
const term = await client.terminal("name", {
command: "python", // 自定义Shell/命令
args: ["-m", "my_app"], // 自定义参数(会清除默认的-l参数)
cols: 120,
rows: 40,
cwd: "/path/to/dir",
env: { MY_VAR: "value" },
});
// 列出所有终端名称
const names = await client.list();
// 关闭/终止指定终端
await client.close("name");
// 断开连接(终端仍保持运行)
client.disconnect();Terminal Methods
终端方法
typescript
// Send raw input
await term.write("hello");
// Send special keys
await term.key("enter");
await term.key("up");
await term.key("ctrl+c");
// Send a line (adds Enter)
await term.writeLine("ls -la");
// Get screen state
const snap = await term.snapshot();
console.log(snap.text); // Plain text (no ANSI codes)
console.log(snap.lines); // Array of lines
console.log(snap.alive); // Process still running?
// Get SVG rendering (for visual analysis)
const svgSnap = await term.snapshot({ format: "svg" });
console.log(svgSnap.svg); // SVG string
// Resize
await term.resize(80, 24);
// Clear buffer
await term.clear();
// Wait for text to appear
const found = await term.waitForText("Ready", { timeout: 5000 });
// Wait for process to exit
const exitCode = await term.waitForExit({ timeout: 10000 });typescript
// 发送原始输入
await term.write("hello");
// 发送特殊按键
await term.key("enter");
await term.key("up");
await term.key("ctrl+c");
// 发送一行内容(自动追加回车)
await term.writeLine("ls -la");
// 获取屏幕状态
const snap = await term.snapshot();
console.log(snap.text); // 纯文本内容(无ANSI控制码)
console.log(snap.lines); // 按行拆分的数组
console.log(snap.alive); // 进程是否仍在运行?
// 获取SVG格式的渲染结果(用于可视化分析)
const svgSnap = await term.snapshot({ format: "svg" });
console.log(svgSnap.svg); // SVG字符串
// 调整终端尺寸
await term.resize(80, 24);
// 清除缓冲区
await term.clear();
// 等待指定文本出现
const found = await term.waitForText("Ready", { timeout: 5000 });
// 等待进程退出
const exitCode = await term.waitForExit({ timeout: 10000 });Special Keys
特殊按键列表
Use with these names:
term.key()| Category | Keys |
|---|---|
| Arrows | |
| Control | |
| Ctrl+X | |
| Function | |
| Navigation | |
使用方法时可传入以下按键名称:
term.key()| 分类 | 按键列表 |
|---|---|
| 方向键 | |
| 控制键 | |
| Ctrl组合键 | |
| 功能键 | |
| 导航键 | |
Example: Debug a TUI App
示例:调试TUI应用
bash
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
// Start the TUI
const term = await client.terminal("claude-monitor", {
command: "../.venv/bin/python",
args: ["-m", "claude_monitor"],
cols: 120,
rows: 40,
cwd: "..",
});
// Wait for it to render
await sleep(2000);
// Capture screen
const snap = await term.snapshot();
console.log("=== SCREEN OUTPUT ===");
console.log(snap.text);
console.log("=== ALIVE:", snap.alive, "===");
client.disconnect();
EOFbash
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
// 启动TUI应用
const term = await client.terminal("claude-monitor", {
command: "../.venv/bin/python",
args: ["-m", "claude_monitor"],
cols: 120,
rows: 40,
cwd: "..",
});
// 等待应用渲染完成
await sleep(2000);
// 捕获屏幕内容
const snap = await term.snapshot();
console.log("=== 屏幕输出 ===");
console.log(snap.text);
console.log("=== 进程状态:", snap.alive, "===");
client.disconnect();
EOFExample: Interactive Session
示例:交互式会话
bash
undefinedbash
undefinedScript 1: Start app
脚本1:启动应用
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui", {
command: "htop",
});
await sleep(1000);
const snap = await term.snapshot();
console.log(snap.text);
client.disconnect();
EOF
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui", {
command: "htop",
});
await sleep(1000);
const snap = await term.snapshot();
console.log(snap.text);
client.disconnect();
EOF
Script 2: Send keys (terminal persists!)
脚本2:发送按键指令(终端会保持运行!)
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui"); // Reconnect to existing
await term.key("down");
await term.key("down");
await sleep(500);
const snap = await term.snapshot();
console.log(snap.text);
client.disconnect();
EOF
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui"); // 重新连接到已存在的终端
await term.key("down");
await term.key("down");
await sleep(500);
const snap = await term.snapshot();
console.log(snap.text);
client.disconnect();
EOF
Script 3: Quit
脚本3:退出应用
cd dev-terminal && npx tsx <<'EOF'
import { connect } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui");
await term.write("q");
client.disconnect();
EOF
undefinedcd dev-terminal && npx tsx <<'EOF'
import { connect } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui");
await term.write("q");
client.disconnect();
EOF
undefinedError Recovery
错误恢复
If something goes wrong, check the terminal state:
bash
cd dev-terminal && npx tsx <<'EOF'
import { connect } from "./src/client.js";
const client = await connect();
// List all terminals
const terminals = await client.list();
console.log("Active terminals:", terminals);
// Check specific terminal
if (terminals.includes("my-app")) {
const term = await client.terminal("my-app");
const snap = await term.snapshot();
console.log("Alive:", snap.alive);
console.log("Exit code:", snap.exitCode);
console.log("Last output:", snap.lines.slice(-20).join("\n"));
}
client.disconnect();
EOF如果出现异常,可检查终端状态:
bash
cd dev-terminal && npx tsx <<'EOF'
import { connect } from "./src/client.js";
const client = await connect();
// 列出所有终端
const terminals = await client.list();
console.log("活跃终端:", terminals);
// 检查指定终端
if (terminals.includes("my-app")) {
const term = await client.terminal("my-app");
const snap = await term.snapshot();
console.log("进程是否活跃:", snap.alive);
console.log("退出码:", snap.exitCode);
console.log("最新输出:", snap.lines.slice(-20).join("\n"));
}
client.disconnect();
EOFTips
实用技巧
- TUI apps need time: Use after starting to let them render
sleep() - Check alive status: TUI might crash - check
snap.alive - Clear for fresh state: Use before important snapshots
term.clear() - Large output: gives the last ~120 lines (3x terminal height)
snap.lines
- TUI应用需要启动时间:启动后使用等待应用完成渲染
sleep() - 检查进程活跃状态:TUI应用可能崩溃,需检查
snap.alive - 清理获取干净状态:在重要的快照操作前使用
term.clear() - 大输出场景:会返回最近约120行内容(终端高度的3倍)
snap.lines