use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum ResourceType {
Agent,
#[default]
Snippet,
Command,
#[serde(rename = "mcp-server")]
McpServer,
Script,
Hook,
Skill,
}
impl ResourceType {
pub const fn all() -> &'static [Self] {
&[
Self::Agent,
Self::Snippet,
Self::Command,
Self::McpServer,
Self::Script,
Self::Hook,
Self::Skill,
]
}
pub const fn to_plural(&self) -> &'static str {
match self {
Self::Agent => "agents",
Self::Snippet => "snippets",
Self::Command => "commands",
Self::Script => "scripts",
Self::Hook => "hooks",
Self::McpServer => "mcp-servers",
Self::Skill => "skills",
}
}
#[must_use]
pub const fn default_directory(&self) -> Option<&str> {
match self {
Self::Agent => Some(".claude/agents"),
Self::Snippet => Some(".claude/snippets"),
Self::Command => Some(".claude/commands"),
Self::McpServer => None, Self::Script => Some(".claude/scripts"),
Self::Hook => None, Self::Skill => Some(".claude/skills"),
}
}
pub fn from_frontmatter_str(s: &str) -> Option<Self> {
match s {
"agents" | "agent" => Some(ResourceType::Agent),
"snippets" | "snippet" => Some(ResourceType::Snippet),
"commands" | "command" => Some(ResourceType::Command),
"hooks" | "hook" => Some(ResourceType::Hook),
"scripts" | "script" => Some(ResourceType::Script),
"mcp-servers" | "mcp-server" => Some(ResourceType::McpServer),
"skills" | "skill" => Some(ResourceType::Skill),
_ => None,
}
}
#[must_use]
pub const fn default_tool(&self) -> &'static str {
match self {
Self::Snippet => "agpm", _ => "claude-code", }
}
}
impl std::fmt::Display for ResourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Agent => write!(f, "agent"),
Self::Snippet => write!(f, "snippet"),
Self::Command => write!(f, "command"),
Self::McpServer => write!(f, "mcp-server"),
Self::Script => write!(f, "script"),
Self::Hook => write!(f, "hook"),
Self::Skill => write!(f, "skill"),
}
}
}
impl std::str::FromStr for ResourceType {
type Err = crate::core::AgpmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"agent" | "agents" => Ok(Self::Agent),
"snippet" | "snippets" => Ok(Self::Snippet),
"command" | "commands" => Ok(Self::Command),
"mcp-server" | "mcp-servers" | "mcpserver" | "mcp" => Ok(Self::McpServer),
"script" | "scripts" => Ok(Self::Script),
"hook" | "hooks" => Ok(Self::Hook),
"skill" | "skills" => Ok(Self::Skill),
_ => Err(crate::core::AgpmError::InvalidResourceType {
resource_type: s.to_string(),
}),
}
}
}
pub trait Resource {
fn name(&self) -> &str;
fn resource_type(&self) -> ResourceType;
fn description(&self) -> Option<&str>;
fn validate(&self) -> Result<()>;
fn install(&self, target: &Path, profile: Option<&str>) -> Result<()>;
fn install_path(&self, base: &Path) -> std::path::PathBuf;
fn metadata(&self) -> Result<serde_json::Value>;
fn as_any(&self) -> &dyn std::any::Any;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_type_default_directory() {
assert_eq!(ResourceType::Agent.default_directory(), Some(".claude/agents"));
assert_eq!(ResourceType::Snippet.default_directory(), Some(".claude/snippets"));
assert_eq!(ResourceType::Command.default_directory(), Some(".claude/commands"));
assert_eq!(ResourceType::McpServer.default_directory(), None);
assert_eq!(ResourceType::Script.default_directory(), Some(".claude/scripts"));
assert_eq!(ResourceType::Hook.default_directory(), None);
assert_eq!(ResourceType::Skill.default_directory(), Some(".claude/skills"));
}
#[test]
fn test_resource_type_display() {
assert_eq!(ResourceType::Agent.to_string(), "agent");
assert_eq!(ResourceType::Snippet.to_string(), "snippet");
assert_eq!(ResourceType::Command.to_string(), "command");
assert_eq!(ResourceType::McpServer.to_string(), "mcp-server");
assert_eq!(ResourceType::Script.to_string(), "script");
assert_eq!(ResourceType::Hook.to_string(), "hook");
assert_eq!(ResourceType::Skill.to_string(), "skill");
}
#[test]
fn test_resource_type_from_str() {
use std::str::FromStr;
assert_eq!(ResourceType::from_str("agent").unwrap(), ResourceType::Agent);
assert_eq!(ResourceType::from_str("snippet").unwrap(), ResourceType::Snippet);
assert_eq!(ResourceType::from_str("AGENT").unwrap(), ResourceType::Agent);
assert_eq!(ResourceType::from_str("Snippet").unwrap(), ResourceType::Snippet);
assert_eq!(ResourceType::from_str("command").unwrap(), ResourceType::Command);
assert_eq!(ResourceType::from_str("COMMAND").unwrap(), ResourceType::Command);
assert_eq!(ResourceType::from_str("mcp-server").unwrap(), ResourceType::McpServer);
assert_eq!(ResourceType::from_str("MCP").unwrap(), ResourceType::McpServer);
assert_eq!(ResourceType::from_str("script").unwrap(), ResourceType::Script);
assert_eq!(ResourceType::from_str("SCRIPT").unwrap(), ResourceType::Script);
assert_eq!(ResourceType::from_str("hook").unwrap(), ResourceType::Hook);
assert_eq!(ResourceType::from_str("HOOK").unwrap(), ResourceType::Hook);
assert_eq!(ResourceType::from_str("skill").unwrap(), ResourceType::Skill);
assert_eq!(ResourceType::from_str("SKILL").unwrap(), ResourceType::Skill);
assert!(ResourceType::from_str("invalid").is_err());
}
#[test]
fn test_resource_type_serialization() {
let agent = ResourceType::Agent;
let json = serde_json::to_string(&agent).unwrap();
assert_eq!(json, "\"agent\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Agent);
let command = ResourceType::Command;
let json = serde_json::to_string(&command).unwrap();
assert_eq!(json, "\"command\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Command);
let mcp_server = ResourceType::McpServer;
let json = serde_json::to_string(&mcp_server).unwrap();
assert_eq!(json, "\"mcp-server\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::McpServer);
let script = ResourceType::Script;
let json = serde_json::to_string(&script).unwrap();
assert_eq!(json, "\"script\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Script);
let hook = ResourceType::Hook;
let json = serde_json::to_string(&hook).unwrap();
assert_eq!(json, "\"hook\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Hook);
let skill = ResourceType::Skill;
let json = serde_json::to_string(&skill).unwrap();
assert_eq!(json, "\"skill\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Skill);
}
#[test]
fn test_resource_type_equality() {
assert_eq!(ResourceType::Command, ResourceType::Command);
assert_ne!(ResourceType::Command, ResourceType::Agent);
assert_ne!(ResourceType::Command, ResourceType::Snippet);
assert_eq!(ResourceType::McpServer, ResourceType::McpServer);
assert_ne!(ResourceType::McpServer, ResourceType::Agent);
assert_eq!(ResourceType::Script, ResourceType::Script);
assert_ne!(ResourceType::Script, ResourceType::Hook);
assert_eq!(ResourceType::Hook, ResourceType::Hook);
assert_ne!(ResourceType::Hook, ResourceType::Agent);
assert_eq!(ResourceType::Skill, ResourceType::Skill);
assert_ne!(ResourceType::Skill, ResourceType::Agent);
}
#[test]
fn test_resource_type_copy() {
let command = ResourceType::Command;
let copied = command; assert_eq!(command, copied);
}
#[test]
fn test_resource_type_all() {
let all_types = ResourceType::all();
assert_eq!(all_types.len(), 7);
assert_eq!(all_types[0], ResourceType::Agent);
assert_eq!(all_types[1], ResourceType::Snippet);
assert_eq!(all_types[2], ResourceType::Command);
assert_eq!(all_types[3], ResourceType::McpServer);
assert_eq!(all_types[4], ResourceType::Script);
assert_eq!(all_types[5], ResourceType::Hook);
assert_eq!(all_types[6], ResourceType::Skill);
let mut count = 0;
for _ in ResourceType::all() {
count += 1;
}
assert_eq!(count, 7);
}
#[test]
fn test_resource_type_from_frontmatter_str() {
assert_eq!(ResourceType::from_frontmatter_str("agent"), Some(ResourceType::Agent));
assert_eq!(ResourceType::from_frontmatter_str("snippet"), Some(ResourceType::Snippet));
assert_eq!(ResourceType::from_frontmatter_str("command"), Some(ResourceType::Command));
assert_eq!(ResourceType::from_frontmatter_str("hook"), Some(ResourceType::Hook));
assert_eq!(ResourceType::from_frontmatter_str("script"), Some(ResourceType::Script));
assert_eq!(ResourceType::from_frontmatter_str("mcp-server"), Some(ResourceType::McpServer));
assert_eq!(ResourceType::from_frontmatter_str("skill"), Some(ResourceType::Skill));
assert_eq!(ResourceType::from_frontmatter_str("agents"), Some(ResourceType::Agent));
assert_eq!(ResourceType::from_frontmatter_str("snippets"), Some(ResourceType::Snippet));
assert_eq!(ResourceType::from_frontmatter_str("commands"), Some(ResourceType::Command));
assert_eq!(ResourceType::from_frontmatter_str("hooks"), Some(ResourceType::Hook));
assert_eq!(ResourceType::from_frontmatter_str("scripts"), Some(ResourceType::Script));
assert_eq!(
ResourceType::from_frontmatter_str("mcp-servers"),
Some(ResourceType::McpServer)
);
assert_eq!(ResourceType::from_frontmatter_str("skills"), Some(ResourceType::Skill));
assert_eq!(ResourceType::from_frontmatter_str("unknown"), None);
assert_eq!(ResourceType::from_frontmatter_str("invalid"), None);
assert_eq!(ResourceType::from_frontmatter_str(""), None);
assert_eq!(ResourceType::from_frontmatter_str("Agent"), None);
assert_eq!(ResourceType::from_frontmatter_str("AGENTS"), None);
assert_eq!(ResourceType::from_frontmatter_str("Snippet"), None);
}
}