control-cli について
対話型 CLI を再現可能なローカル環境でテストするためのスキルです。開発者が不具合を再現し、ターミナルの動作を確認して、プロファイルやデモの記録を取得できます。
主な機能
- 01決定的な CLI 入力
- 02キー操作とリサイズのテスト
- 03操作記録の取得
- 04起動時・実行時のプロファイリング
- 05ターミナルデモの記録
活用シーン
- 01TUI の不具合再現
- 02割り込みやリサイズ動作の確認
- 03修正前後の CLI 出力比較
- 04起動遅延やハングの調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ cursor/ plugins --skill control-cliインストール後、Claude Code で/control-cliを入力して読み込まれたか確認します。
詳細
Control CLI
手動で試行錯誤する代わりに、再現可能なローカルハーネスを使って対話型 CLI を検証します。まず、リポジトリに独自のテスト/デモ用ハーネスがあれば再利用し、なければ標準的なローカルツールで一時的なハーネスを組み立てます。
用途
- 決定論的な入力で CLI/TUI のバグを再現します。
- キーボード操作のフロー、プロンプト、中断、リサイズ時の動作、ターミナルのレイアウトを検証します。
- バグ修正の前後のトランスクリプトを記録します。
- 起動時間、遅い処理、ハング、メモリ増加をプロファイリングします。
- 説明するより出力を見せるほうが分かりやすい場合に、短いターミナルデモを記録します。
ハーネスの手順
- 検証対象のコマンドと、再現に必要な最小のワークスペースを特定します。
- 既存のローカルハーネスを探します。パッケージスクリプト、e2e テスト、デモ記録ツール、expect スクリプト、PTY ヘルパーなどが対象です。
- ハーネスがなければ、決定論的な環境変数を設定して、隔離されたターミナルセッションで CLI を起動します。
- 操作する前に現在の画面を記録します。
- テキスト、Enter、矢印キー、Escape、Ctrl-C、リサイズなどの操作を一つずつ送信します。
- 次の操作に進む前に、具体的な画面パターンまたはプロンプトを待ちます。
- トランスクリプトとプロファイル関連の成果物を保存します。
- セッションを正常に終了します。
ハーネスの選択肢
- リポジトリ標準のハーネス:アプリの起動方法、環境変数、プロンプトを把握しているため、チェックイン済みのスクリプトを優先します。
tmux:セッションの管理、capture-pane、send-keys、アタッチ/デタッチに使用します。- PTY プローブ:
tmuxが使えない場合は、短い Python、Node、または Expect スクリプトを使用します。 - ランタイムインスペクター:CPU プロファイル、ヒープスナップショット、ライブ評価には Node または Bun のインスペクターを使用します。
- ターミナルレコーダー:ユーザーがデモを求めている場合は、リポジトリ内のデモツールまたは asciinema 互換ツールを使用します。
最小限の tmux ハーネス
SESSION="cli-harness-$(date +%s)"
tmux new-session -d -s "$SESSION" -- <command-under-test>
tmux capture-pane -pt "$SESSION"
tmux send-keys -t "$SESSION" "help" Enter
tmux capture-pane -pt "$SESSION"
tmux kill-session -t "$SESSION"
Node CLI の場合:
NODE_OPTIONS="--inspect=127.0.0.1:0" tmux new-session -d -s "$SESSION" -- <node-cli-command>
ターミナル出力からインスペクターの URL を確認し、プロファイリングが必要な場合は Chrome DevTools 互換ツールを使用します。
最小限の PTY ハーネス
tmux もデモ用ハーネスもないリポジトリで、決定論的な待機が必要な場合は、PTY スクリプトを使用します。ユーザーから再利用可能なテストの追加を求められていない限り、一時的なものにしてください。
import os
import pty
import select
import subprocess
import time
master_fd, slave_fd = pty.openpty()
proc = subprocess.Popen(
["<command>", "<arg>"],
stdin=slave_fd,
stdout=slave_fd,
stderr=slave_fd,
close_fds=True,
)
os.close(slave_fd)
deadline = time.time() + 30
buffer = b""
while time.time() < deadline:
ready, _, _ = select.select([master_fd], [], [], 0.25)
if not ready:
continue
chunk = os.read(master_fd, 4096)
buffer += chunk
if b"<ready text>" in buffer:
os.write(master_fd, b"help\n")
break
print(buffer.decode(errors="replace"))
proc.terminate()
os.close(master_fd)
CLI により高度なターミナル制御が必要な場合は、pty.fork() または既存の PTY ライブラリを使用します。
プロファイリングの手順
- 起動時の性能低下:同じマシン、環境、コマンドで、変更前と変更後の起動時間を計測します。
- 遅い処理:CPU プロファイルを開始し、処理を実行してからプロファイルを停止し、自己時間の長い関数を比較します。
- メモリリーク:利用可能な場合は GC を強制し、ヒープスナップショットを取得して処理を繰り返し実行し、再度 GC を強制してから別のスナップショットを取得します。
- ハング:中断する前に、画面、アクティブなハンドル/リソース、スタック/CPU サンプルを記録します。
注意事項
- スリープよりも決定論的な待機を優先します。スリープが必要な場合は、その理由を説明してください。
- 管理対象のセッションに認証情報や破壊的なコマンドを送信しないでください。
- リポジトリに既存のテスト/デモ用ハーネスがない限り、ハーネスは
/tmpに置いてください。 - 別のリポジトリのパスをハードコードしないでください。現在のリポジトリのスクリプトとランタイムに合わせてコマンドを調整してください。
- ユーザーから保持を求められていない限り、
tmuxセッション、一時ディレクトリ、インスペクタープロセス、デモ成果物を削除してください。
コメント
Markdown 対応