use crate::error::{GdeltError, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use super::manifest::SkillManifest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillStatus {
pub installed: bool,
pub location: Option<String>,
pub version: Option<String>,
pub is_project_local: bool,
}
fn get_global_skills_dir() -> Result<PathBuf> {
let home = dirs::home_dir()
.ok_or_else(|| GdeltError::Config("Could not determine home directory".into()))?;
Ok(home.join(".claude").join("skills"))
}
fn get_project_skills_dir() -> PathBuf {
PathBuf::from(".claude").join("skills")
}
fn get_skill_path(project_local: bool) -> Result<PathBuf> {
let base = if project_local {
get_project_skills_dir()
} else {
get_global_skills_dir()?
};
Ok(base.join("gdelt"))
}
pub fn install_skill(project_local: bool, force: bool) -> Result<SkillStatus> {
let skill_dir = get_skill_path(project_local)?;
if skill_dir.exists() && !force {
return Err(GdeltError::Config(format!(
"Skill already installed at {}. Use --force to overwrite.",
skill_dir.display()
)));
}
std::fs::create_dir_all(&skill_dir)?;
let manifest = SkillManifest::default();
let claude_md = manifest.generate_claude_md();
let claude_md_path = skill_dir.join("CLAUDE.md");
std::fs::write(&claude_md_path, claude_md)?;
let manifest_path = skill_dir.join("manifest.json");
let manifest_json = serde_json::to_string_pretty(&manifest)?;
std::fs::write(&manifest_path, manifest_json)?;
Ok(SkillStatus {
installed: true,
location: Some(skill_dir.to_string_lossy().to_string()),
version: Some(manifest.version),
is_project_local: project_local,
})
}
pub fn uninstall_skill() -> Result<SkillStatus> {
let project_path = get_skill_path(true)?;
if project_path.exists() {
std::fs::remove_dir_all(&project_path)?;
return Ok(SkillStatus {
installed: false,
location: Some(project_path.to_string_lossy().to_string()),
version: None,
is_project_local: true,
});
}
let global_path = get_skill_path(false)?;
if global_path.exists() {
std::fs::remove_dir_all(&global_path)?;
return Ok(SkillStatus {
installed: false,
location: Some(global_path.to_string_lossy().to_string()),
version: None,
is_project_local: false,
});
}
Err(GdeltError::NotFound("GDELT skill is not installed".into()))
}
pub fn update_skill() -> Result<SkillStatus> {
let (skill_dir, is_project_local) = {
let project_path = get_skill_path(true)?;
if project_path.exists() {
(project_path, true)
} else {
let global_path = get_skill_path(false)?;
if global_path.exists() {
(global_path, false)
} else {
return Err(GdeltError::NotFound(
"GDELT skill is not installed. Run 'gdelt skill install' first.".into(),
));
}
}
};
let manifest = SkillManifest::default();
let claude_md = manifest.generate_claude_md();
let claude_md_path = skill_dir.join("CLAUDE.md");
std::fs::write(&claude_md_path, claude_md)?;
let manifest_path = skill_dir.join("manifest.json");
let manifest_json = serde_json::to_string_pretty(&manifest)?;
std::fs::write(&manifest_path, manifest_json)?;
Ok(SkillStatus {
installed: true,
location: Some(skill_dir.to_string_lossy().to_string()),
version: Some(manifest.version),
is_project_local,
})
}
pub fn get_skill_status() -> Result<SkillStatus> {
let project_path = get_skill_path(true)?;
if project_path.exists() {
let version = read_installed_version(&project_path);
return Ok(SkillStatus {
installed: true,
location: Some(project_path.to_string_lossy().to_string()),
version,
is_project_local: true,
});
}
let global_path = get_skill_path(false)?;
if global_path.exists() {
let version = read_installed_version(&global_path);
return Ok(SkillStatus {
installed: true,
location: Some(global_path.to_string_lossy().to_string()),
version,
is_project_local: false,
});
}
Ok(SkillStatus {
installed: false,
location: None,
version: None,
is_project_local: false,
})
}
fn read_installed_version(skill_dir: &PathBuf) -> Option<String> {
let manifest_path = skill_dir.join("manifest.json");
if let Ok(content) = std::fs::read_to_string(manifest_path) {
if let Ok(manifest) = serde_json::from_str::<SkillManifest>(&content) {
return Some(manifest.version);
}
}
None
}