consciousness/src/bin/poc-daemon/tmux.rs
ProofOfConcept 95baba54c0 tmux: use send-keys -l for literal text input
Without -l, tmux send-keys treats spaces as key-name separators,
so multi-word messages like "This is your time" get split into
individual unrecognized key names instead of being typed as text.
This caused idle nudges to arrive as blank messages.
2026-03-08 18:39:47 -04:00

66 lines
1.7 KiB
Rust

// 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 {
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"])
}