toast_api/
shared_utils.rs

1use std::fs;
2use std::process::{Command, Stdio};
3use std::time::Duration;
4use std::thread;
5use std::sync::mpsc;
6use anyhow::Result;
7
8/// Execute a shell command and capture its output with timeout
9pub fn execute_command(command: &str) -> Result<String> {
10    // Default timeout of 30 seconds for most commands
11    let timeout_duration = Duration::from_secs(120);
12    
13    // Spawn the command
14    let child = Command::new("sh")
15        .arg("-c")
16        .arg(command)
17        .stdout(Stdio::piped())
18        .stderr(Stdio::piped())
19        .spawn()?;
20    
21    // Create a channel to communicate between threads
22    let (tx, rx) = mpsc::channel();
23    
24    // Spawn a thread to wait for the process
25    thread::spawn(move || {
26        let output = child.wait_with_output();
27        let _ = tx.send(output);
28    });
29    
30    // Wait for either completion or timeout
31    let output = match rx.recv_timeout(timeout_duration) {
32        Ok(Ok(output)) => output,
33        Ok(Err(e)) => return Err(anyhow::anyhow!("Command failed: {}", e)),
34        Err(_) => {
35            return Err(anyhow::anyhow!("Command timed out after {} seconds. Consider using shorter commands or adding explicit timeouts.", timeout_duration.as_secs()));
36        }
37    };
38
39    let mut msg = String::new();
40
41    if !output.stdout.is_empty() {
42        msg.push_str("=== STDOUT ===\n");
43        msg.push_str(&String::from_utf8_lossy(&output.stdout));
44        msg.push('\n');
45    }
46
47    if !output.stderr.is_empty() {
48        msg.push_str("=== STDERR ===\n");
49        msg.push_str(&String::from_utf8_lossy(&output.stderr));
50        msg.push('\n');
51    }
52
53    msg.push_str(&format!(
54        "Exit code: {}",
55        output.status.code().unwrap_or(-1)
56    ));
57
58    Ok(msg)
59}
60
61/// Process file reads from a message
62pub fn process_file_reads(files: &[String]) -> String {
63    let mut result = String::new();
64    
65    for file_path in files {
66        let trimmed_path = file_path.trim();
67        match fs::read_to_string(trimmed_path) {
68            Ok(content) => {
69                result.push_str(&format!("=== {trimmed_path} ===\n{content}\n"));
70            }
71            Err(e) => {
72                result.push_str(&format!("Error reading {file_path}: {e}\n"));
73            }
74        }
75    }
76    
77    result
78}
79
80/// Check if a command is an exit command
81pub fn is_exit_command(input: &str) -> bool {
82    let lower = input.trim().to_lowercase();
83    matches!(
84        lower.as_str(),
85        "exit" | "quit" | "bye" | "goodbye" | "/exit" | "/quit"
86    )
87}