dev-terminal について
dev-terminalは、ローカルおよびSSHのターミナルセッションを自動化するスキルです。短いTypeScriptスクリプトで、対話型TUIアプリを実行して状態を確認できます。
主な機能
- 01名前付きPTYセッションの維持
- 02テキストと特殊キーの送信
- 03ターミナル画面のスナップショット取得
- 04SSH経由のリモートターミナル接続
- 05ブラウザーでの任意のリアルタイム表示
活用シーン
- 01TUIアプリの操作自動化
- 02スクリプト実行間の端末出力確認
- 03リモートシェルセッションのデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ parkerhancock/ dev-terminal --skill dev-terminalインストール後、Claude Code で/dev-terminalを入力して読み込まれたか確認します。
詳細
Dev Terminal Skill
ターミナル自動化により、スクリプトの実行をまたいで PTY セッションを維持します。TUI アプリケーションの実行、キー入力の送信、画面出力の取得、ターミナルアプリのデバッグを、すべて状態を保持したまま行えます。
セットアップ
バックグラウンドターミナルでサーバーを起動します。
cd dev-terminal && npm install && ./server.sh &
スクリプトを実行する前に、Ready メッセージが表示されるまで待ってください。
ヘッド付きモード(任意)
視覚的にデバッグするには、ブラウザー UI を使って起動します。
cd dev-terminal && ./server.sh --headed &
ブラウザーウィンドウが開き、すべてのターミナルがリアルタイムで表示されます。次の用途に便利です。
- AI の操作をリアルタイムで確認する
- 必要に応じて手動で介入する
- TUI の操作をデバッグする
スクリプトの作成
dev-terminal/ ディレクトリから heredoc を使って、スクリプトをインラインで実行します。
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
シェルのデフォルト設定
デフォルトでは、ターミナルは次の設定を使用します。
- シェル:ユーザーのデフォルトシェル(
$SHELL環境変数。例:macOS では zsh、Linux では bash) - モード:ログインシェル(
-lフラグ)— プロファイル全体(~/.zprofile、~/.bash_profile)を読み込みます
そのため、シェルのエイリアス、PATH、環境変数を利用できます。
シェルを上書きする:
// 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"],
});
SSH リモートターミナル
SSH 経由でリモートサーバーに接続します。API はローカルターミナルと同じです。
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 オプション:
| オプション | 型 | 説明 |
|---|---|---|
host | string | リモートホスト名または IP(必須) |
port | number | SSH ポート(デフォルト:22) |
username | string | SSH ユーザー名(必須) |
password | string | パスワード認証 |
privateKey | string | 秘密鍵の内容(パスではありません) |
passphrase | string | 暗号化された鍵のパスフレーズ |
agent | string | SSH エージェントソケットのパス |
注意事項:
- SSH ターミナルには
pidがありません(値はundefinedです) - すべての Terminal メソッドは同じように動作します(write、key、snapshot など)
- リモートシェルはローカル設定ではなく、サーバーによって決まります
重要な原則
- 小さなスクリプト:各スクリプトは 1 つの処理だけを行います(アプリの起動、画面の確認、キーの送信など)
- 出力を確認する:現在の状態を把握するため、必ず
snapshot()を確認します - わかりやすい名前:「
term1」ではなく、「claude-monitor」や「vim-edit」を使います - ターミナルは維持される:
disconnect()を呼び出すと、ターミナルは次のスクリプトでも実行されたままになります
ワークフローのループ
- 1 つの操作を実行するスクリプトを作成する
- 実行して、画面出力を確認する
- 評価する — 何が表示されていますか?うまく動作しましたか?
- 判断する — さらに入力を送信しますか、それともタスクは完了ですか?
- 完了するまで繰り返す
クライアント API
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();
ターミナルメソッド
// 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 });
特殊キー
次の名前を指定して term.key() を使います。
| カテゴリ | キー |
|---|---|
| 矢印キー | up、down、left、right |
| コントロール | 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 |
| ファンクション | f1 - f12 |
| ナビゲーション | home、end、pageup、pagedown、insert |
例:TUI アプリのデバッグ
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
例:対話型セッション
# Script 1: Start app
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!)
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
# Script 3: Quit
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
エラーからの復旧
問題が発生した場合は、ターミナルの状態を確認します。
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
ヒント
- TUI アプリには時間が必要です:起動後に
sleep()を使い、画面が描画されるのを待ちます - 起動状態を確認する:TUI がクラッシュしている可能性があります。
snap.aliveを確認します - 新しい状態で確認する:重要なスナップショットを取得する前に
term.clear()を使います - 大量の出力:
snap.linesには直近約 120 行(ターミナルの高さの 3 倍)が含まれます
コメント
Markdown 対応