1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ApiSearchResponse {
11 pub results: Vec<ApiSearchResult>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ApiSearchResult {
17 pub score: f64,
18 pub slug: String,
19 #[serde(rename = "displayName")]
20 pub display_name: String,
21 pub summary: String,
22 #[serde(default)]
23 pub version: Option<String>,
24 #[serde(default, rename = "updatedAt")]
25 pub updated_at: i64,
26}
27
28impl From<ApiSearchResult> for SearchResult {
29 fn from(item: ApiSearchResult) -> Self {
30 Self {
31 slug: item.slug,
32 name: item.display_name,
33 description: item.summary,
34 install_count: 0, virustotal: None,
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SearchResponse {
43 pub items: Vec<SearchItem>,
44 #[serde(rename = "nextCursor")]
45 pub next_cursor: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SearchItem {
51 pub slug: String,
52 #[serde(rename = "displayName")]
53 pub display_name: String,
54 pub summary: String,
55 pub tags: HashMap<String, String>,
56 pub stats: SkillStats,
57 #[serde(rename = "createdAt")]
58 pub created_at: i64,
59 #[serde(rename = "updatedAt")]
60 pub updated_at: i64,
61 #[serde(rename = "latestVersion")]
62 pub latest_version: Option<VersionItem>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SkillStats {
68 #[serde(default)]
69 pub comments: i32,
70 #[serde(default)]
71 pub downloads: i32,
72 #[serde(default, rename = "installsAllTime", alias = "installs")]
73 pub installs_all_time: i32,
74 #[serde(default, rename = "installsCurrent")]
75 pub installs_current: i32,
76 #[serde(default)]
77 pub stars: i32,
78 #[serde(default)]
79 pub versions: i32,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct VersionItem {
85 pub version: String,
86 #[serde(rename = "createdAt")]
87 pub created_at: i64,
88 pub changelog: String,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct GetSkillResponse {
94 pub skill: SkillItem,
95 #[serde(rename = "latestVersion")]
96 pub latest_version: VersionItem,
97 pub owner: Owner,
98 pub moderation: Option<serde_json::Value>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct SkillItem {
104 pub slug: String,
105 #[serde(rename = "displayName")]
106 pub display_name: String,
107 pub summary: String,
108 pub tags: HashMap<String, String>,
109 pub stats: SkillStats,
110 #[serde(rename = "createdAt")]
111 pub created_at: i64,
112 #[serde(rename = "updatedAt")]
113 pub updated_at: i64,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct Owner {
119 pub handle: String,
120 #[serde(rename = "userId")]
121 pub user_id: String,
122 #[serde(rename = "displayName")]
123 pub display_name: String,
124 pub image: String,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct LockFile {
134 pub version: u32,
135 pub skills: HashMap<String, LockEntry>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct LockEntry {
140 pub slug: String,
141 #[serde(rename = "installedVersion")]
142 pub installed_version: String,
143 #[serde(rename = "installedAt")]
144 pub installed_at: String,
145 #[serde(rename = "contentHash")]
146 pub content_hash: String,
147 #[serde(rename = "treeHash", default, skip_serializing_if = "Option::is_none")]
152 pub tree_hash: Option<String>,
153 #[serde(rename = "localPath")]
154 pub local_path: String,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct SkillMeta {
160 pub slug: String,
161 pub name: String,
162 pub description: String,
163 pub versions: Vec<SkillVersion>,
164 #[serde(default)]
165 pub virustotal: Option<VirusTotal>,
166 #[serde(default)]
167 pub metadata: SkillMetadata,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct SkillVersion {
172 pub version: String,
173 #[serde(default)]
174 pub latest: bool,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct VirusTotal {
179 #[serde(rename = "reportCount")]
180 pub report_count: i32,
181 #[serde(rename = "pendingScan")]
182 pub pending_scan: bool,
183 pub status: String,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, Default)]
187pub struct SkillMetadata {
188 #[serde(default)]
189 pub openclaw: Option<OpenClawMeta>,
190 #[serde(default)]
191 pub clawdbot: Option<ClawdbotMeta>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, Default)]
195pub struct OpenClawMeta {
196 #[serde(default)]
197 pub requires: Option<Requires>,
198 #[serde(default)]
199 pub os: Vec<String>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, Default)]
203pub struct ClawdbotMeta {
204 #[serde(default)]
205 pub requires: Option<Requires>,
206 #[serde(default)]
207 pub os: Vec<String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, Default)]
211pub struct Requires {
212 #[serde(default)]
213 pub bins: Vec<String>,
214 #[serde(default)]
215 pub env: Vec<String>,
216 #[serde(default, rename = "anyBins")]
217 pub any_bins: Vec<String>,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct SearchResult {
223 pub slug: String,
224 pub name: String,
225 pub description: String,
226 #[serde(rename = "installCount")]
227 pub install_count: i32,
228 #[serde(default)]
229 pub virustotal: Option<VirusTotal>,
230}
231
232impl From<SearchItem> for SearchResult {
233 fn from(item: SearchItem) -> Self {
234 Self {
235 slug: item.slug,
236 name: item.display_name,
237 description: item.summary,
238 install_count: item.stats.installs_current,
239 virustotal: None, }
241 }
242}
243
244impl From<GetSkillResponse> for SkillMeta {
245 fn from(resp: GetSkillResponse) -> Self {
246 Self {
247 slug: resp.skill.slug,
248 name: resp.skill.display_name,
249 description: resp.skill.summary,
250 versions: vec![SkillVersion {
251 version: resp.latest_version.version,
252 latest: true,
253 }],
254 virustotal: None, metadata: SkillMetadata::default(),
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_lockfile_serde() {
266 let lock = LockFile {
267 version: 1,
268 skills: std::collections::HashMap::new(),
269 };
270 let json = serde_json::to_string(&lock).unwrap();
271 assert!(json.contains(r#""version":1"#));
272 }
273
274 #[test]
275 fn test_skill_meta_deserialize() {
276 let json = r#"{
277 "slug": "test-skill",
278 "name": "Test Skill",
279 "description": "A test skill",
280 "versions": [{"version": "1.0.0", "latest": true}]
281 }"#;
282 let _meta: SkillMeta = serde_json::from_str(json).unwrap();
283 }
284}