npcrs 0.1.0

Rust core for the NPC system — agent kernel, jinx executor, LLM client
Documentation

use crate::error::{NpcError, Result};
use std::collections::HashMap;
use std::path::Path;

pub fn capture_screenshot(full: bool) -> Result<HashMap<String, String>> {
    let dir = shellexpand::tilde("~/.npcsh/screenshots").to_string();
    std::fs::create_dir_all(&dir).map_err(|e| NpcError::FileLoad { path: dir.clone(), source: e })?;
    let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
    let filename = format!("screenshot_{}.png", timestamp);
    let file_path = format!("{}/{}", dir, filename);
    let os = std::env::consts::OS;
    if full {
        match os {
            "macos" => { let _ = std::process::Command::new("screencapture").arg(&file_path).output(); }
            "linux" => {
                let tools: &[(&str, &[&str])] = &[("grim", &[]), ("scrot", &[]), ("import", &["-window", "root"]), ("gnome-screenshot", &["-f"])];
                let mut took = false;
                for (cmd, extra) in tools {
                    if std::process::Command::new("which").arg(cmd).output().map(|o| o.status.success()).unwrap_or(false) {
                        let mut args: Vec<&str> = extra.to_vec(); args.push(&file_path);
                        let _ = std::process::Command::new(cmd).args(&args).output();
                        if Path::new(&file_path).exists() { took = true; break; }
                    }
                }
                if !took { return Err(NpcError::Shell("No screenshot tool found".into())); }
            }
            _ => return Err(NpcError::Shell(format!("Unsupported OS: {}", os))),
        }
    } else {
        match os {
            "macos" => { let _ = std::process::Command::new("screencapture").args(["-i", &file_path]).output(); }
            "linux" => {
                if std::process::Command::new("which").arg("scrot").output().map(|o| o.status.success()).unwrap_or(false) {
                    let _ = std::process::Command::new("scrot").args(["-s", &file_path]).output();
                } else { return Err(NpcError::Shell("No interactive screenshot tool".into())); }
            }
            _ => return Err(NpcError::Shell(format!("Unsupported OS: {}", os))),
        }
    }
    if Path::new(&file_path).exists() {
        let mut r = HashMap::new(); r.insert("filename".into(), filename); r.insert("file_path".into(), file_path); Ok(r)
    } else { Err(NpcError::Shell("Screenshot failed".into())) }
}

pub fn compress_image(image_bytes: &[u8], max_width: u32, max_height: u32) -> Result<Vec<u8>> {
    let tmp_in = std::env::temp_dir().join(format!("npc_ci_{}.png", std::process::id()));
    let tmp_out = std::env::temp_dir().join(format!("npc_co_{}.jpg", std::process::id()));
    std::fs::write(&tmp_in, image_bytes).map_err(|e| NpcError::FileLoad { path: tmp_in.display().to_string(), source: e })?;
    let resize = format!("{}x{}>", max_width, max_height);
    let result = std::process::Command::new("convert").args([tmp_in.to_str().unwrap(), "-resize", &resize, "-quality", "95", tmp_out.to_str().unwrap()]).output();
    let out = match result {
        Ok(o) if o.status.success() && tmp_out.exists() => std::fs::read(&tmp_out).unwrap_or_else(|_| image_bytes.to_vec()),
        _ => image_bytes.to_vec(),
    };
    let _ = std::fs::remove_file(&tmp_in); let _ = std::fs::remove_file(&tmp_out);
    Ok(out)
}