Skip to main content

claude_rust_tools/infrastructure/
bash_tool.rs

1use claude_rust_errors::{AppError, AppResult};
2use claude_rust_types::{InterruptBehavior, PermissionLevel, Tool};
3use serde_json::{Value, json};
4use tokio::process::Command;
5
6pub struct BashTool;
7
8#[async_trait::async_trait]
9impl Tool for BashTool {
10    fn name(&self) -> &str {
11        "bash"
12    }
13
14    fn description(&self) -> &str {
15        "Execute a bash command and return its output."
16    }
17
18    fn input_schema(&self) -> Value {
19        json!({
20            "type": "object",
21            "properties": {
22                "command": {
23                    "type": "string",
24                    "description": "The bash command to execute"
25                }
26            },
27            "required": ["command"]
28        })
29    }
30
31    fn permission_level(&self) -> PermissionLevel {
32        PermissionLevel::Dangerous
33    }
34
35    fn interrupt_behavior(&self) -> InterruptBehavior {
36        InterruptBehavior::Cancel
37    }
38
39    async fn execute(&self, input: Value) -> AppResult<String> {
40        let command = input
41            .get("command")
42            .and_then(|v| v.as_str())
43            .ok_or_else(|| AppError::Tool("missing 'command' field".into()))?;
44
45        tracing::info!(command, "executing bash");
46
47        let output = Command::new("bash")
48            .arg("-c")
49            .arg(command)
50            .output()
51            .await
52            .map_err(|e| AppError::Tool(format!("failed to spawn bash: {e}")))?;
53
54        let stdout = String::from_utf8_lossy(&output.stdout);
55        let stderr = String::from_utf8_lossy(&output.stderr);
56
57        let mut result = String::new();
58        if !stdout.is_empty() {
59            result.push_str(&stdout);
60        }
61        if !stderr.is_empty() {
62            if !result.is_empty() {
63                result.push('\n');
64            }
65            result.push_str("STDERR:\n");
66            result.push_str(&stderr);
67        }
68        if result.is_empty() {
69            result.push_str("(no output)");
70        }
71
72        if result.len() > 100_000 {
73            result.truncate(100_000);
74            result.push_str("\n... (truncated)");
75        }
76
77        Ok(result)
78    }
79}