| name | interactive-cli |
| description | Control interactive CLI processes across multiple turns using tmux. Use when a command requires interactive input (y/n prompts, confirmations, menu selection, pdb, REPL, etc.) — any program that reads from stdin and cannot be run non-interactively. If a command fails with EOFError or hangs waiting for input, use this skill. |
| argument-hint | <command> |
| allowed-tools | ["Bash(tmux:*)"] |
Interactive CLI Process Control
Control interactive CLI processes across multiple bash tool calls using tmux sessions.
Session Management
Use a named tmux session with a descriptive cli_ prefix that captures the full context of what the session is doing:
tmux new-session -d -s cli_ssh_prod_deploying_v2 -x 120 -y 40 "<command>"
tmux send-keys -t cli_ssh_prod_deploying_v2 $'<input>\n'
tmux send-keys -t cli_ssh_prod_deploying_v2 $'cmd1\ncmd2\n'
tmux capture-pane -t cli_ssh_prod_deploying_v2 -p
tmux capture-pane -t cli_ssh_prod_deploying_v2 -p -S -100
tmux kill-session -t cli_ssh_prod_deploying_v2
Workflow
- Start: Launch the interactive process in a detached tmux session
- Interact: Send commands with
send-keys, read output with capture-pane
- Iterate: Continue sending commands and reading output as needed
- Cleanup: Kill the session when done
Examples
Python Debugger (pdb)
tmux new-session -d -s cli_pdb_debug_config_loading -x 120 -y 40 "python3 -m pdb script.py"
sleep 2 && tmux capture-pane -t cli_pdb_debug_config_loading -p
tmux send-keys -t cli_pdb_debug_config_loading $'n\n'
sleep 0.5 && tmux capture-pane -t cli_pdb_debug_config_loading -p
tmux send-keys -t cli_pdb_debug_config_loading $'print(my_var)\n'
sleep 0.5 && tmux capture-pane -t cli_pdb_debug_config_loading -p
tmux send-keys -t cli_pdb_debug_config_loading $'b 42\nc\n'
sleep 1 && tmux capture-pane -t cli_pdb_debug_config_loading -p
tmux send-keys -t cli_pdb_debug_config_loading $'q\n' && tmux kill-session -t cli_pdb_debug_config_loading
Python REPL
tmux new-session -d -s cli_pyrepl_testing_math_functions -x 120 -y 40 "python3"
sleep 1 && tmux send-keys -t cli_pyrepl_testing_math_functions $'import math\nmath.pi\n'
sleep 0.5 && tmux capture-pane -t cli_pyrepl_testing_math_functions -p
Node.js REPL
tmux new-session -d -s cli_node_repl_array_transforms -x 120 -y 40 "node"
sleep 1 && tmux send-keys -t cli_node_repl_array_transforms $'const x = [1,2,3]\nx.map(n => n * 2)\n'
sleep 0.5 && tmux capture-pane -t cli_node_repl_array_transforms -p
Async Code & Playwright Debugging
To debug async code (e.g., Playwright scripts) interactively, use nest_asyncio to enable nested event loops.
Install if needed: pip install nest_asyncio
Interactive Async Debugging
At any pdb breakpoint where async objects (like Playwright's page) are in scope:
tmux new-session -d -s cli_pdb_playwright_animation_bug -x 120 -y 40 "python3 -m pdb my_playwright_script.py"
sleep 2
tmux send-keys -t cli_pdb_playwright_animation_bug $'b 50\nc\n'
sleep 10
tmux send-keys -t cli_pdb_playwright_animation_bug $'import nest_asyncio; nest_asyncio.apply(); import asyncio\n'
tmux send-keys -t cli_pdb_playwright_animation_bug $'asyncio.run(page.evaluate("() => document.title"))\n'
sleep 1 && tmux capture-pane -t cli_pdb_playwright_animation_bug -p
tmux send-keys -t cli_pdb_playwright_animation_bug $'asyncio.run(page.evaluate("() => document.querySelectorAll(\'button\').length"))\n'
tmux send-keys -t cli_pdb_playwright_animation_bug $'asyncio.run(page.click("button"))\n'
tmux send-keys -t cli_pdb_playwright_animation_bug $'asyncio.run(page.screenshot(path="debug.png"))\n'
Key Points
nest_asyncio.apply() patches the event loop to allow asyncio.run() inside an already-running loop
- Use
asyncio.run(coroutine) to execute any async code at pdb prompts
- Works with Playwright's
page.evaluate(), page.click(), page.screenshot(), etc.
- Can call your own async functions to inspect state
- Useful for debugging animation waits, DOM state, network timing issues
Example: Debugging Animation State
(Pdb) asyncio.run(page.evaluate('() => document.querySelectorAll("circle").length'))
11
(Pdb) asyncio.run(page.click('circle'))
(Pdb) import time; time.sleep(2)
(Pdb) asyncio.run(page.evaluate('() => document.querySelectorAll("circle").length'))
21
Sending Longer Scripts
Always pass multiline commands with HEREDOC:
tmux send-keys -t cli_pyrepl_testing_math_functions "$(cat <<'EOF'
def greet(name):
return f"Hello, {name}!"
greet("world")
EOF
)"
HEREDOC preserves formatting and avoids escaping issues. Use <<'EOF' (quoted) to prevent variable expansion.
Tips
- Always add
sleep after starting or sending commands to allow output to render
- Use
capture-pane -p -S -100 to get scrollback history if output scrolled off
- Chain commands with
&& when they don't need intermediate reads
- For long-running processes, check status with
tmux has-session -t cli_ssh_prod_deploying_v2
- Multiple sessions: use different descriptive names for each context
Handling User Arguments
If the user provides a command as $ARGUMENTS, derive a descriptive session name from the command context:
tmux new-session -d -s cli_<descriptive_name_from_context> -x 120 -y 40 "$ARGUMENTS"