coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! Shell command execution tools for CoderLib
//!
//! This module provides tools for executing shell commands safely.

use async_trait::async_trait;
use std::process::Stdio;
use tokio::process::Command;

use crate::integration::HostIntegration;
use crate::tools::{Tool, ToolResponse, ToolError, Permission, validation};

/// Tool for executing shell commands
pub struct ShellCommandTool;

impl ShellCommandTool {
    pub fn new() -> Self {
        Self
    }
}

#[async_trait]
impl Tool for ShellCommandTool {
    async fn execute(
        &self,
        parameters: serde_json::Value,
        _host: &dyn HostIntegration,
    ) -> Result<ToolResponse, ToolError> {
        let command = validation::require_string(&parameters, "command")?;
        let working_dir = validation::optional_path(&parameters, "working_dir");
        
        // Basic security check - prevent dangerous commands
        let dangerous_commands = [
            "rm -rf", "del /f", "format", "fdisk", "mkfs",
            "shutdown", "reboot", "halt", "poweroff",
            "sudo rm", "sudo del", "sudo format",
        ];
        
        for dangerous in &dangerous_commands {
            if command.to_lowercase().contains(dangerous) {
                return Err(ToolError::SecurityViolation(
                    format!("Dangerous command detected: {}", dangerous)
                ));
            }
        }
        
        // Determine shell based on platform
        let (shell, shell_arg) = if cfg!(target_os = "windows") {
            ("cmd", "/C")
        } else {
            ("sh", "-c")
        };
        
        let mut cmd = Command::new(shell);
        cmd.arg(shell_arg)
           .arg(&command)
           .stdout(Stdio::piped())
           .stderr(Stdio::piped());
        
        if let Some(dir) = working_dir {
            cmd.current_dir(dir);
        }
        
        let output = cmd.output().await
            .map_err(|e| ToolError::ExecutionFailed(format!("Failed to execute command: {}", e)))?;
        
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);
        
        let mut result = String::new();
        
        if !stdout.is_empty() {
            result.push_str("STDOUT:\n");
            result.push_str(&stdout);
        }
        
        if !stderr.is_empty() {
            if !result.is_empty() {
                result.push_str("\n\n");
            }
            result.push_str("STDERR:\n");
            result.push_str(&stderr);
        }
        
        if result.is_empty() {
            result = "Command executed successfully (no output)".to_string();
        }
        
        let metadata = serde_json::json!({
            "exit_code": output.status.code(),
            "success": output.status.success(),
            "command": command,
        });
        
        Ok(ToolResponse::with_metadata(result, metadata))
    }
    
    fn requires_permission(&self) -> Permission {
        Permission::ExecuteShell
    }
    
    fn description(&self) -> &str {
        "Execute a shell command"
    }
    
    fn name(&self) -> &str {
        "shell_command"
    }
    
    fn parameter_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "The shell command to execute"
                },
                "working_dir": {
                    "type": "string",
                    "description": "Optional working directory for the command"
                }
            },
            "required": ["command"]
        })
    }
    
    fn clone_box(&self) -> Box<dyn Tool> {
        Box::new(ShellCommandTool::new())
    }
}