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