microclaw_engine/internal/tool_runtime/
command_runner.rs1use std::path::Path;
2
3pub struct CommandSpec {
4 pub program: String,
5 pub args: Vec<String>,
6}
7
8pub fn shell_command(command: &str) -> CommandSpec {
9 if cfg!(target_os = "windows") {
10 CommandSpec {
11 program: "powershell".to_string(),
12 args: vec![
13 "-NoProfile".to_string(),
14 "-NonInteractive".to_string(),
15 "-Command".to_string(),
16 command.to_string(),
17 ],
18 }
19 } else {
20 let shell = std::env::var("SHELL")
21 .ok()
22 .filter(|s| !s.trim().is_empty())
23 .unwrap_or_else(|| "/bin/sh".to_string());
24 CommandSpec {
25 program: shell,
26 args: vec!["-c".to_string(), command.to_string()],
27 }
28 }
29}
30
31pub fn agent_browser_program() -> String {
32 if cfg!(target_os = "windows") {
33 "agent-browser.cmd".to_string()
34 } else {
35 "agent-browser".to_string()
36 }
37}
38
39pub fn build_command(spec: &CommandSpec, working_dir: Option<&Path>) -> tokio::process::Command {
40 let mut cmd = tokio::process::Command::new(&spec.program);
41 cmd.args(&spec.args);
42 if let Some(dir) = working_dir {
43 cmd.current_dir(dir);
44 }
45 cmd
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn test_shell_command_shape() {
54 let spec = shell_command("echo hello");
55 assert!(!spec.program.is_empty());
56 assert!(!spec.args.is_empty());
57 }
58
59 #[test]
60 fn test_agent_browser_program_not_empty() {
61 let p = agent_browser_program();
62 assert!(!p.trim().is_empty());
63 }
64}