1use crate::core::version::Version;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
6#[serde(rename_all = "lowercase")]
7pub enum InstallScope {
8 Global,
10 #[default]
12 Local,
13}
14
15impl std::fmt::Display for InstallScope {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 InstallScope::Global => write!(f, "global"),
19 InstallScope::Local => write!(f, "local"),
20 }
21 }
22}
23
24#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
25#[serde(rename_all = "lowercase")]
26pub enum PackType {
27 Skill,
28 Command,
29 Mcp,
30}
31
32impl std::fmt::Display for PackType {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 PackType::Skill => write!(f, "skill"),
36 PackType::Command => write!(f, "command"),
37 PackType::Mcp => write!(f, "mcp"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PackMetadata {
44 #[serde(default)]
45 pub description: Option<String>,
46 #[serde(default)]
47 pub source_url: Option<String>,
48 #[serde(default)]
49 pub author: Option<String>,
50 #[serde(default)]
51 pub license: Option<String>,
52}
53
54impl PackMetadata {
55 pub fn new() -> Self {
56 Self {
57 description: None,
58 source_url: None,
59 author: None,
60 license: None,
61 }
62 }
63
64 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
65 self.description = Some(desc.into());
66 self
67 }
68
69 pub fn with_source_url(mut self, url: impl Into<String>) -> Self {
70 self.source_url = Some(url.into());
71 self
72 }
73
74 #[allow(dead_code)]
75 pub fn with_author(mut self, author: impl Into<String>) -> Self {
76 self.author = Some(author.into());
77 self
78 }
79
80 #[allow(dead_code)]
81 pub fn with_license(mut self, license: impl Into<String>) -> Self {
82 self.license = Some(license.into());
83 self
84 }
85}
86
87impl Default for PackMetadata {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Pack {
95 pub name: String,
96 #[serde(rename = "type")]
97 pub pack_type: PackType,
98 pub version: Version,
99 #[serde(default)]
100 pub tool: Option<String>,
101 #[serde(default)]
102 pub metadata: PackMetadata,
103 #[serde(default)]
104 pub scope: InstallScope,
105}
106
107impl Pack {
108 pub fn new(name: impl Into<String>, pack_type: PackType, version: Version) -> Self {
109 Self {
110 name: name.into(),
111 pack_type,
112 version,
113 tool: None,
114 metadata: PackMetadata::new(),
115 scope: InstallScope::default(),
116 }
117 }
118
119 pub fn with_tool(mut self, tool: impl Into<String>) -> Self {
120 self.tool = Some(tool.into());
121 self
122 }
123
124 pub fn with_metadata(mut self, metadata: PackMetadata) -> Self {
125 self.metadata = metadata;
126 self
127 }
128
129 pub fn with_scope(mut self, scope: InstallScope) -> Self {
130 self.scope = scope;
131 self
132 }
133
134 pub fn id(&self) -> String {
135 format!(
136 "{}/{}/{}@{}",
137 self.scope,
138 self.tool.as_deref().unwrap_or("*"),
139 self.name,
140 self.version
141 )
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_pack_id() {
151 let pack = Pack::new("aif", PackType::Skill, Version::new(1, 0, 0));
152 assert_eq!(pack.id(), "local/*/aif@1.0.0");
153
154 let pack_with_tool = pack.clone().with_tool("qwen");
155 assert_eq!(pack_with_tool.id(), "local/qwen/aif@1.0.0");
156
157 let pack_global = Pack::new("aif", PackType::Skill, Version::new(1, 0, 0))
158 .with_tool("qwen")
159 .with_scope(InstallScope::Global);
160 assert_eq!(pack_global.id(), "global/qwen/aif@1.0.0");
161 }
162
163 #[test]
164 fn test_pack_metadata() {
165 let metadata = PackMetadata::new()
166 .with_description("A test pack")
167 .with_source_url("https://github.com/example/pack")
168 .with_author("Test Author")
169 .with_license("MIT");
170
171 assert_eq!(metadata.description, Some("A test pack".to_string()));
172 assert_eq!(
173 metadata.source_url,
174 Some("https://github.com/example/pack".to_string())
175 );
176 assert_eq!(metadata.author, Some("Test Author".to_string()));
177 assert_eq!(metadata.license, Some("MIT".to_string()));
178 }
179
180 #[test]
181 fn test_install_scope_default() {
182 let scope = InstallScope::default();
183 assert_eq!(scope, InstallScope::Local);
184 }
185
186 #[test]
187 fn test_install_scope_display() {
188 assert_eq!(InstallScope::Local.to_string(), "local");
189 assert_eq!(InstallScope::Global.to_string(), "global");
190 }
191}