2026-03-05 19:17:22 -05:00
|
|
|
// Tmux interaction: pane detection and prompt injection.
|
|
|
|
|
|
|
|
|
|
use std::process::Command;
|
|
|
|
|
use std::thread;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
use tracing::info;
|
|
|
|
|
|
|
|
|
|
/// Find Claude Code's tmux pane by scanning for the "claude" process.
|
|
|
|
|
pub fn find_claude_pane() -> Option<String> {
|
|
|
|
|
let out = Command::new("tmux")
|
|
|
|
|
.args([
|
|
|
|
|
"list-panes",
|
|
|
|
|
"-a",
|
|
|
|
|
"-F",
|
|
|
|
|
"#{session_name}:#{window_index}.#{pane_index}\t#{pane_current_command}",
|
|
|
|
|
])
|
|
|
|
|
.output()
|
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
|
|
|
for line in stdout.lines() {
|
|
|
|
|
if let Some((pane, cmd)) = line.split_once('\t') {
|
|
|
|
|
if cmd == "claude" {
|
|
|
|
|
return Some(pane.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Send a prompt to a tmux pane. Returns true on success.
|
|
|
|
|
///
|
|
|
|
|
/// Sequence: Escape q C-c C-u (clear input), wait, type message, Enter.
|
|
|
|
|
pub fn send_prompt(pane: &str, msg: &str) -> bool {
|
2026-03-05 21:15:40 -05:00
|
|
|
let preview: String = msg.chars().take(100).collect();
|
|
|
|
|
info!("SEND [{pane}]: {preview}...");
|
2026-03-05 19:17:22 -05:00
|
|
|
|
|
|
|
|
let send = |keys: &[&str]| {
|
|
|
|
|
Command::new("tmux")
|
|
|
|
|
.arg("send-keys")
|
|
|
|
|
.arg("-t")
|
|
|
|
|
.arg(pane)
|
|
|
|
|
.args(keys)
|
|
|
|
|
.output()
|
|
|
|
|
.is_ok()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Clear any partial input
|
|
|
|
|
if !send(&["Escape", "q", "C-c", "C-u"]) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
thread::sleep(Duration::from_secs(1));
|
|
|
|
|
|
|
|
|
|
// Type the message
|
|
|
|
|
if !send(&[msg]) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
thread::sleep(Duration::from_millis(500));
|
|
|
|
|
|
|
|
|
|
// Submit
|
|
|
|
|
send(&["Enter"])
|
|
|
|
|
}
|