use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiSearchResponse {
pub results: Vec<ApiSearchResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiSearchResult {
pub score: f64,
pub slug: String,
#[serde(rename = "displayName")]
pub display_name: String,
pub summary: String,
#[serde(default)]
pub version: Option<String>,
#[serde(default, rename = "updatedAt")]
pub updated_at: i64,
}
impl From<ApiSearchResult> for SearchResult {
fn from(item: ApiSearchResult) -> Self {
Self {
slug: item.slug,
name: item.display_name,
description: item.summary,
install_count: 0, virustotal: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResponse {
pub items: Vec<SearchItem>,
#[serde(rename = "nextCursor")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchItem {
pub slug: String,
#[serde(rename = "displayName")]
pub display_name: String,
pub summary: String,
pub tags: HashMap<String, String>,
pub stats: SkillStats,
#[serde(rename = "createdAt")]
pub created_at: i64,
#[serde(rename = "updatedAt")]
pub updated_at: i64,
#[serde(rename = "latestVersion")]
pub latest_version: Option<VersionItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillStats {
#[serde(default)]
pub comments: i32,
#[serde(default)]
pub downloads: i32,
#[serde(default, rename = "installsAllTime", alias = "installs")]
pub installs_all_time: i32,
#[serde(default, rename = "installsCurrent")]
pub installs_current: i32,
#[serde(default)]
pub stars: i32,
#[serde(default)]
pub versions: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionItem {
pub version: String,
#[serde(rename = "createdAt")]
pub created_at: i64,
pub changelog: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetSkillResponse {
pub skill: SkillItem,
#[serde(rename = "latestVersion")]
pub latest_version: VersionItem,
pub owner: Owner,
pub moderation: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillItem {
pub slug: String,
#[serde(rename = "displayName")]
pub display_name: String,
pub summary: String,
pub tags: HashMap<String, String>,
pub stats: SkillStats,
#[serde(rename = "createdAt")]
pub created_at: i64,
#[serde(rename = "updatedAt")]
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Owner {
pub handle: String,
#[serde(rename = "userId")]
pub user_id: String,
#[serde(rename = "displayName")]
pub display_name: String,
pub image: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockFile {
pub version: u32,
pub skills: HashMap<String, LockEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockEntry {
pub slug: String,
#[serde(rename = "installedVersion")]
pub installed_version: String,
#[serde(rename = "installedAt")]
pub installed_at: String,
#[serde(rename = "contentHash")]
pub content_hash: String,
#[serde(rename = "treeHash", default, skip_serializing_if = "Option::is_none")]
pub tree_hash: Option<String>,
#[serde(rename = "localPath")]
pub local_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillMeta {
pub slug: String,
pub name: String,
pub description: String,
pub versions: Vec<SkillVersion>,
#[serde(default)]
pub virustotal: Option<VirusTotal>,
#[serde(default)]
pub metadata: SkillMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillVersion {
pub version: String,
#[serde(default)]
pub latest: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VirusTotal {
#[serde(rename = "reportCount")]
pub report_count: i32,
#[serde(rename = "pendingScan")]
pub pending_scan: bool,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SkillMetadata {
#[serde(default)]
pub openclaw: Option<OpenClawMeta>,
#[serde(default)]
pub clawdbot: Option<ClawdbotMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OpenClawMeta {
#[serde(default)]
pub requires: Option<Requires>,
#[serde(default)]
pub os: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ClawdbotMeta {
#[serde(default)]
pub requires: Option<Requires>,
#[serde(default)]
pub os: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Requires {
#[serde(default)]
pub bins: Vec<String>,
#[serde(default)]
pub env: Vec<String>,
#[serde(default, rename = "anyBins")]
pub any_bins: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub slug: String,
pub name: String,
pub description: String,
#[serde(rename = "installCount")]
pub install_count: i32,
#[serde(default)]
pub virustotal: Option<VirusTotal>,
}
impl From<SearchItem> for SearchResult {
fn from(item: SearchItem) -> Self {
Self {
slug: item.slug,
name: item.display_name,
description: item.summary,
install_count: item.stats.installs_current,
virustotal: None, }
}
}
impl From<GetSkillResponse> for SkillMeta {
fn from(resp: GetSkillResponse) -> Self {
Self {
slug: resp.skill.slug,
name: resp.skill.display_name,
description: resp.skill.summary,
versions: vec![SkillVersion {
version: resp.latest_version.version,
latest: true,
}],
virustotal: None, metadata: SkillMetadata::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lockfile_serde() {
let lock = LockFile {
version: 1,
skills: std::collections::HashMap::new(),
};
let json = serde_json::to_string(&lock).unwrap();
assert!(json.contains(r#""version":1"#));
}
#[test]
fn test_skill_meta_deserialize() {
let json = r#"{
"slug": "test-skill",
"name": "Test Skill",
"description": "A test skill",
"versions": [{"version": "1.0.0", "latest": true}]
}"#;
let _meta: SkillMeta = serde_json::from_str(json).unwrap();
}
}