dev-terminal

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dev 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
Ready
message before running scripts.
在后台终端启动服务器:
bash
cd dev-terminal && npm install && ./server.sh &
在运行脚本前,请等待
Ready
消息出现。

Headed 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
dev-terminal/
directory:
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
dev-terminal/
目录下使用here-doc方式运行内嵌脚本:
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();
EOF

Shell Defaults

Shell默认配置

By default, terminals use:
  • Shell: User's default shell (
    $SHELL
    env var, e.g., zsh on macOS, bash on Linux)
  • Mode: Login shell (
    -l
    flag) - loads full profile (
    ~/.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(
    $SHELL
    环境变量,例如macOS上的zsh、Linux上的bash)
  • 模式:登录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:
OptionTypeDescription
host
stringRemote hostname or IP (required)
port
numberSSH port (default: 22)
username
stringSSH username (required)
password
stringPassword authentication
privateKey
stringPrivate key content (not path)
passphrase
stringPassphrase for encrypted keys
agent
stringPath to SSH agent socket
Notes:
  • SSH terminals don't have a
    pid
    (it's
    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配置选项:
选项名类型描述
host
string远程主机名或IP(必填)
port
numberSSH端口(默认:22)
username
stringSSH用户名(必填)
password
string密码认证方式
privateKey
string私钥内容(而非文件路径)
passphrase
string加密密钥的密码
agent
stringSSH代理套接字的路径
注意事项:
  • SSH终端没有
    pid
    属性(值为
    undefined
  • 所有终端方法的使用方式保持一致(write、key、snapshot等)
  • 远程Shell由服务器决定,不受本地设置影响

Key Principles

核心原则

  1. Small scripts: Each script does ONE thing (start app, check screen, send key)
  2. Observe output: Always check
    snapshot()
    to see current state
  3. Descriptive names: Use
    "claude-monitor"
    ,
    "vim-edit"
    , not
    "term1"
  4. Terminals persist:
    disconnect()
    leaves terminals running for next script
  1. 脚本轻量化:每个脚本仅完成一项任务(启动应用、检查屏幕、发送按键)
  2. 观察输出:始终通过
    snapshot()
    检查当前状态
  3. 命名规范化:使用
    "claude-monitor"
    "vim-edit"
    这类有意义的名称,而非
    "term1"
  4. 终端持久化:调用
    disconnect()
    后终端仍会保持运行,供后续脚本使用

Workflow Loop

工作流循环

  1. Write a script to perform one action
  2. Run it and observe the screen output
  3. Evaluate - what's displayed? Did it work?
  4. Decide - send more input or task complete?
  5. Repeat until done
  1. 编写脚本:实现单个操作
  2. 运行脚本:观察屏幕输出
  3. 评估结果:当前显示内容是什么?操作是否成功?
  4. 决策下一步:继续发送输入还是结束任务?
  5. 重复执行:直至任务完成

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
term.key()
with these names:
CategoryKeys
Arrows
up
,
down
,
left
,
right
Control
enter
,
tab
,
escape
,
backspace
,
delete
Ctrl+X
ctrl+c
,
ctrl+d
,
ctrl+z
,
ctrl+l
,
ctrl+a
,
ctrl+e
,
ctrl+k
,
ctrl+u
,
ctrl+w
,
ctrl+r
Function
f1
-
f12
Navigation
home
,
end
,
pageup
,
pagedown
,
insert
使用
term.key()
方法时可传入以下按键名称:
分类按键列表
方向键
up
,
down
,
left
,
right
控制键
enter
,
tab
,
escape
,
backspace
,
delete
Ctrl组合键
ctrl+c
,
ctrl+d
,
ctrl+z
,
ctrl+l
,
ctrl+a
,
ctrl+e
,
ctrl+k
,
ctrl+u
,
ctrl+w
,
ctrl+r
功能键
f1
-
f12
导航键
home
,
end
,
pageup
,
pagedown
,
insert

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();
EOF
bash
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();
EOF

Example: Interactive Session

示例:交互式会话

bash
undefined
bash
undefined

Script 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
undefined
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
undefined

Error 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();
EOF

Tips

实用技巧

  • TUI apps need time: Use
    sleep()
    after starting to let them render
  • Check alive status: TUI might crash - check
    snap.alive
  • Clear for fresh state: Use
    term.clear()
    before important snapshots
  • Large output:
    snap.lines
    gives the last ~120 lines (3x terminal height)
  • TUI应用需要启动时间:启动后使用
    sleep()
    等待应用完成渲染
  • 检查进程活跃状态:TUI应用可能崩溃,需检查
    snap.alive
  • 清理获取干净状态:在重要的快照操作前使用
    term.clear()
  • 大输出场景
    snap.lines
    会返回最近约120行内容(终端高度的3倍)