Skip to main content

claude_rust_tools/infrastructure/
powershell_tool.rs

1use claude_rust_errors::AppResult;
2use claude_rust_types::{InterruptBehavior, PermissionLevel, Tool};
3use serde_json::{Value, json};
4use tokio::process::Command;
5
6/// Tool to execute PowerShell commands.
7pub struct PowerShellTool;
8
9impl PowerShellTool {
10    pub fn new() -> Self {
11        Self
12    }
13}
14
15#[async_trait::async_trait]
16impl Tool for PowerShellTool {
17    fn name(&self) -> &str {
18        "powershell"
19    }
20
21    fn description(&self) -> &str {
22        "Execute a PowerShell command and return its output."
23    }
24
25    fn input_schema(&self) -> Value {
26        json!({
27            "type": "object",
28            "properties": {
29                "command": {
30                    "type": "string",
31                    "description": "The PowerShell command to execute"
32                },
33                "timeout": {
34                    "type": "integer",
35                    "description": "Optional timeout in seconds"
36                }
37            },
38            "required": ["command"]
39        })
40    }
41
42    fn permission_level(&self) -> PermissionLevel {
43        PermissionLevel::Dangerous
44    }
45
46    fn interrupt_behavior(&self) -> InterruptBehavior {
47        InterruptBehavior::Cancel
48    }
49
50    async fn execute(&self, input: Value) -> AppResult<String> {
51        let command = input
52            .get("command")
53            .and_then(|v| v.as_str())
54            .ok_or_else(|| claude_rust_errors::AppError::Tool("missing 'command' field".into()))?;
55
56        let _timeout = input
57            .get("timeout")
58            .and_then(|v| v.as_u64());
59
60        tracing::info!(command, "executing PowerShell");
61
62        // Use pwsh (cross-platform) or powershell.exe on Windows
63        let program = if cfg!(target_os = "windows") {
64            "powershell.exe"
65        } else {
66            "pwsh"
67        };
68
69        let output = Command::new(program)
70            .arg("-Command")
71            .arg(command)
72            .output()
73            .await
74            .map_err(|e| claude_rust_errors::AppError::Tool(
75                format!("failed to spawn {program}: {e}")
76            ))?;
77
78        let stdout = String::from_utf8_lossy(&output.stdout);
79        let stderr = String::from_utf8_lossy(&output.stderr);
80
81        let mut result = String::new();
82        if !stdout.is_empty() {
83            result.push_str(&stdout);
84        }
85        if !stderr.is_empty() {
86            if !result.is_empty() {
87                result.push('\n');
88            }
89            result.push_str("STDERR:\n");
90            result.push_str(&stderr);
91        }
92        if result.is_empty() {
93            result.push_str("(no output)");
94        }
95
96        if result.len() > 100_000 {
97            result.truncate(100_000);
98            result.push_str("\n... (truncated)");
99        }
100
101        Ok(result)
102    }
103}