// 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 { 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 { let preview: String = msg.chars().take(100).collect(); info!("SEND [{pane}]: {preview}..."); 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 (literal mode so spaces aren't key separators) let ok = Command::new("tmux") .args(["send-keys", "-t", pane, "-l", msg]) .output() .is_ok(); if !ok { return false; } thread::sleep(Duration::from_millis(500)); // Submit send(&["Enter"]) }