rsclaw 0.0.1-alpha.1

rsclaw: High-performance AI agent (BETA). Optimized for M4 Max and 2GB VPS. 100% compatible with openclaw
Documentation
use anyhow::{Context, Result};
use std::path::Path;
use std::process::Stdio;
use std::sync::Arc;
use tokio::process::Command;
use tokio::time::{timeout, Duration};

/// Skill execution result.
#[derive(Debug, Clone)]
pub struct SkillResult {
    pub success: bool,
    pub stdout: Arc<str>,
    pub stderr: Arc<str>,
    pub exit_code: Option<i32>,
}

/// Shell skill runner for executing bash skills.
pub struct ShellRunner {
    timeout_secs: u64,
}

impl ShellRunner {
    /// Create a new shell runner.
    pub fn new(timeout_secs: u64) -> Self {
        Self { timeout_secs }
    }

    /// Execute a shell skill.
    pub async fn run(&self, skill_path: &Path, args: &[String]) -> Result<SkillResult> {
        if !skill_path.exists() {
            anyhow::bail!("Skill file not found: {:?}", skill_path);
        }

        let mut cmd = Command::new("bash");
        cmd.arg(skill_path);
        cmd.args(args);
        cmd.stdout(Stdio::piped());
        cmd.stderr(Stdio::piped());

        let child = cmd.spawn().context("Failed to spawn skill process")?;

        let timeout_duration = Duration::from_secs(self.timeout_secs);

        match timeout(timeout_duration, child.wait_with_output()).await {
            Ok(Ok(output)) => {
                let stdout = Arc::from(String::from_utf8_lossy(&output.stdout).as_ref());
                let stderr = Arc::from(String::from_utf8_lossy(&output.stderr).as_ref());

                Ok(SkillResult {
                    success: output.status.success(),
                    stdout,
                    stderr,
                    exit_code: output.status.code(),
                })
            }
            Ok(Err(e)) => {
                anyhow::bail!("Failed to execute skill: {}", e);
            }
            Err(_) => {
                anyhow::bail!("Skill execution timed out after {} seconds", self.timeout_secs);
            }
        }
    }
}

impl Default for ShellRunner {
    fn default() -> Self {
        Self::new(30)
    }
}