npcrs 0.1.11

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)
}