| name | description |
|---|---|
control-interactive-tool |
This skill should be used when the user asks to "debug with lldb", "attach gdb", "run an interactive CLI", "control lldb/gdb", or needs to drive an interactive terminal tool (debugger, REPL, etc.) programmatically from Claude Code. |
Drive interactive CLI tools (lldb, gdb, python, bash, etc.) via a background PTY server over a Unix socket.
A Ruby PTY server spawns the tool in a pseudo-terminal and exposes it over a Unix domain socket with a JSON request/response protocol. This lets Claude send commands and read output across multiple conversation turns without the process dying.
- Server:
references/pty_server.rb— background process holding the PTY, listening on/tmp/<name>-<pid>.sock - Client:
PTYClientclass in the same file — fire-and-forget JSON requests - Output detection: Silence heuristic (
settleparam) — output is "done" when the process stops writing for N seconds. No prompt matching needed.
-> {"cmd": "run", "input": "bt", "settle": 0.3, "timeout": 5}
<- {"output": "...", "alive": true}
-> {"cmd": "drain", "settle": 0.3, "timeout": 5}
<- {"output": "...", "alive": true}
-> {"cmd": "quit"}
<- {"output": "...", "alive": false}
ruby /path/to/pty_server.rb lldb /path/to/binary &The first line of stdout is the socket path (e.g. /tmp/lldb-12345.sock).
require "/path/to/pty_server.rb"
c = PTYClient.new("/tmp/lldb-12345.sock")
r = c.run("b iseq_to_hir")
puts strip_ansi(r["output"])
r = c.run("run", settle: 3, timeout: 15)
puts strip_ansi(r["output"])
r = c.run("bt", settle: 0.5)
puts strip_ansi(r["output"])
c.run("kill")
c.quitEach server gets a unique socket (/tmp/<name>-<pid>.sock), so you can run many in parallel:
sock1 = IO.popen(["ruby", "pty_server.rb", "lldb", bin]).gets.chomp
sock2 = IO.popen(["ruby", "pty_server.rb", "gdb", "-q", bin]).gets.chomp
c1 = PTYClient.new(sock1)
c2 = PTYClient.new(sock2)| Parameter | Default | Purpose |
|---|---|---|
settle |
0.3s (run), 0.5s (init) | How long to wait for silence before returning output |
timeout |
5s (run), 10s (init) | Max wait time |
- Use higher
settle(1-3s) for commands that produce output in bursts (e.g.runin lldb) - Use higher
timeoutfor slow startup tools
# Start server
ruby pty_server.rb lldb /path/to/build-dev/ruby &
# Client
c = PTYClient.new(sock_path)
c.run("settings set -- target.run-args --zjit --zjit-call-threshold=1 script.rb")
c.run("b iseq_to_hir")
c.run("run", settle: 3, timeout: 15) # hits breakpoint
c.run("bt") # backtrace
c.run("frame variable") # inspect locals
c.run("kill")
c.quitRuby stdlib only: pty, io/wait, socket, json. No gems needed.