use super::types::{BlockedPane, TmuxPane};
use chrono::Utc;
use std::process::Command;
pub fn is_tmux_available() -> bool {
Command::new("tmux")
.arg("-V")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
pub fn is_inside_tmux() -> bool {
std::env::var("TMUX").is_ok()
}
pub fn list_tmux_panes() -> Vec<TmuxPane> {
let output = match Command::new("tmux")
.args([
"list-panes",
"-a",
"-F",
"#{pane_id}|#{session_name}|#{window_index}|#{pane_active}",
])
.output()
{
Ok(output) if output.status.success() => output,
_ => return Vec::new(),
};
let stdout = String::from_utf8_lossy(&output.stdout);
stdout
.lines()
.filter_map(|line| {
let parts: Vec<&str> = line.split('|').collect();
if parts.len() == 4 {
Some(TmuxPane {
id: parts[0].to_string(),
session: parts[1].to_string(),
window_index: parts[2].parse().unwrap_or(0),
active: parts[3] == "1",
})
} else {
None
}
})
.collect()
}
pub fn capture_pane_content(pane_id: &str, lines: usize) -> String {
let output = match Command::new("tmux")
.args([
"capture-pane",
"-p",
"-t",
pane_id,
"-S",
&format!("-{}", lines),
])
.output()
{
Ok(output) if output.status.success() => output,
_ => return String::new(),
};
String::from_utf8_lossy(&output.stdout).to_string()
}
pub fn scan_for_blocked_panes(lines: usize) -> Vec<BlockedPane> {
let panes = list_tmux_panes();
let mut blocked = Vec::new();
let blocking_patterns = [
"Rate limit reached",
"Please wait",
"Usage limit",
"429",
"Too many requests",
"rate_limit_error",
"You've hit a rate limit",
];
for pane in panes {
let content = capture_pane_content(&pane.id, lines);
let content_lower = content.to_lowercase();
let is_blocked = blocking_patterns
.iter()
.any(|pattern| content_lower.contains(&pattern.to_lowercase()));
if is_blocked {
blocked.push(BlockedPane {
id: pane.id.clone(),
session: pane.session.clone(),
window_index: pane.window_index,
first_detected_at: Utc::now(),
resume_attempted: false,
resume_successful: false,
});
}
}
blocked
}
pub fn send_resume_sequence(pane_id: &str) -> bool {
let result = Command::new("tmux")
.args(["send-keys", "-t", pane_id, "Enter"])
.status();
result.map(|status| status.success()).unwrap_or(false)
}
pub fn send_keys_to_pane(pane_id: &str, keys: &str) -> bool {
let result = Command::new("tmux")
.args(["send-keys", "-t", pane_id, keys])
.status();
result.map(|status| status.success()).unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tmux_available() {
let _ = is_tmux_available();
}
#[test]
fn test_inside_tmux() {
let _ = is_inside_tmux();
}
}