creamtop 0.1.0

MCP server that manages a GitHub-backed shared skill library for Claude Code.
//! Filesystem cache for skills. Layout: `<cache_dir>/<name>/SKILL.md`.

use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};

use crate::skills::{parse_skill_meta, SkillMeta, Source};

pub fn cache_dir() -> PathBuf {
    if let Ok(dir) = std::env::var("SKILL_CACHE_DIR") {
        return PathBuf::from(dir);
    }
    dirs::cache_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join("skill-library")
}

pub fn list_cached_skills() -> Result<Vec<SkillMeta>> {
    let dir = cache_dir();
    if !dir.exists() {
        return Ok(Vec::new());
    }
    let mut skills = Vec::new();
    for entry in fs::read_dir(&dir).with_context(|| format!("read_dir {}", dir.display()))? {
        let entry = entry?;
        if !entry.file_type()?.is_dir() {
            continue;
        }
        let skill_file = entry.path().join("SKILL.md");
        if !skill_file.exists() {
            continue;
        }
        let Some(name) = entry.file_name().to_str().map(str::to_string) else {
            continue;
        };
        let content = fs::read_to_string(&skill_file)
            .with_context(|| format!("read {}", skill_file.display()))?;
        skills.push(parse_skill_meta(&name, &content, Source::Local));
    }
    skills.sort_by(|a, b| a.name.cmp(&b.name));
    Ok(skills)
}

pub fn read_cached_skill(name: &str) -> Result<Option<String>> {
    let path = cache_dir().join(name).join("SKILL.md");
    if !path.exists() {
        return Ok(None);
    }
    Ok(Some(
        fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?,
    ))
}

pub fn write_cached_skill(name: &str, content: &str) -> Result<()> {
    let dir = cache_dir().join(name);
    fs::create_dir_all(&dir).with_context(|| format!("mkdir {}", dir.display()))?;
    let path = dir.join("SKILL.md");
    fs::write(&path, content).with_context(|| format!("write {}", path.display()))?;
    Ok(())
}