clau_runtime/
process.rs

1use clau_core::{Error, Result, Config, StreamFormat};
2use tokio::process::Command;
3use tokio::time::{timeout, Duration};
4use tracing::debug;
5
6/// Execute a one-shot Claude command with timeout
7pub async fn execute_claude(config: &Config, query: &str) -> Result<String> {
8    
9    let claude_binary = which::which("claude").map_err(|_| Error::BinaryNotFound)?;
10    
11    let mut cmd = Command::new(claude_binary);
12    
13    // Always use non-interactive mode for SDK
14    cmd.arg("-p");
15    
16    // Add format flag
17    match config.stream_format {
18        StreamFormat::Json => {
19            cmd.arg("--output-format").arg("json");
20        }
21        StreamFormat::StreamJson => {
22            cmd.arg("--output-format").arg("stream-json");
23            // stream-json requires verbose flag
24            cmd.arg("--verbose");
25        }
26        StreamFormat::Text => {
27            // Text is default, no need to specify
28        }
29    }
30    
31    // Add verbose flag if configured (and not already added for stream-json)
32    if config.verbose && config.stream_format != StreamFormat::StreamJson {
33        cmd.arg("--verbose");
34    }
35    
36    // Add optional flags
37    if let Some(system_prompt) = &config.system_prompt {
38        cmd.arg("--system-prompt").arg(system_prompt);
39    }
40    
41    if let Some(model) = &config.model {
42        cmd.arg("--model").arg(model);
43    }
44    
45    if let Some(mcp_config_path) = &config.mcp_config_path {
46        cmd.arg("--mcp-config").arg(mcp_config_path);
47    }
48    
49    if let Some(allowed_tools) = &config.allowed_tools {
50        for tool in allowed_tools {
51            cmd.arg("--allowedTools").arg(tool);
52        }
53    }
54    
55    if let Some(max_tokens) = &config.max_tokens {
56        cmd.arg("--max-tokens").arg(max_tokens.to_string());
57    }
58    
59    // Add the query as the last argument
60    cmd.arg(query);
61    
62    debug!("Executing Claude command: {:?}", cmd);
63    
64    // Execute the command with timeout
65    let timeout_duration = Duration::from_secs(config.timeout_secs.unwrap_or(30));
66    let output = timeout(timeout_duration, cmd.output())
67        .await
68        .map_err(|_| Error::Timeout(config.timeout_secs.unwrap_or(30)))??;
69    
70    if !output.status.success() {
71        let stderr = String::from_utf8_lossy(&output.stderr);
72        return Err(Error::ProcessError(format!("Claude command failed: {}", stderr)));
73    }
74    
75    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
76    Ok(stdout)
77}