use std::path::Path; use std::process::Command; use std::sync::mpsc::Sender; use anyhow::{Context, Result}; use crate::app::AppEvent; use crate::model::{CommandSummary, SessionEntry, SessionSource, SessionStream}; use crate::repo; pub fn run_shell_commands( repo_root: &Path, commands: &[String], event_tx: &Sender, title: &str, tag: Option, ) -> Result { let run_id = repo::next_run_id(); let mut output = Vec::new(); let mut passed = true; for command in commands { let _ = event_tx.send(AppEvent::Session(SessionEntry { source: SessionSource::Verifier, stream: SessionStream::Status, title: title.to_string(), tag: tag.clone(), body: command.clone(), run_id, })); let result = Command::new("zsh") .arg("-lc") .arg(command) .current_dir(repo_root) .output() .with_context(|| format!("failed to execute shell command: {command}"))?; let stdout = String::from_utf8_lossy(&result.stdout).trim().to_string(); if !stdout.is_empty() { let _ = event_tx.send(AppEvent::Session(SessionEntry { source: SessionSource::Verifier, stream: SessionStream::Stdout, title: title.to_string(), tag: tag.clone(), body: stdout.clone(), run_id, })); output.push(stdout); } let stderr = String::from_utf8_lossy(&result.stderr).trim().to_string(); if !stderr.is_empty() { let _ = event_tx.send(AppEvent::Session(SessionEntry { source: SessionSource::Verifier, stream: SessionStream::Stderr, title: title.to_string(), tag: tag.clone(), body: stderr.clone(), run_id, })); output.push(stderr); } if !result.status.success() { passed = false; break; } } Ok(CommandSummary { passed, summary: if commands.is_empty() { "No commands requested".to_string() } else if passed { "All commands passed".to_string() } else { "One or more commands failed".to_string() }, commands: commands.to_vec(), output, }) }