toast_api/
shared_utils.rs1use std::fs;
2use std::process::{Command, Stdio};
3use std::time::Duration;
4use std::thread;
5use std::sync::mpsc;
6use anyhow::Result;
7
8pub fn execute_command(command: &str) -> Result<String> {
10 let timeout_duration = Duration::from_secs(120);
12
13 let child = Command::new("sh")
15 .arg("-c")
16 .arg(command)
17 .stdout(Stdio::piped())
18 .stderr(Stdio::piped())
19 .spawn()?;
20
21 let (tx, rx) = mpsc::channel();
23
24 thread::spawn(move || {
26 let output = child.wait_with_output();
27 let _ = tx.send(output);
28 });
29
30 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
61pub 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
80pub 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}