librefang 2026.3.31-beta4

Official Rust client for LibreFang Agent OS
Documentation
use crate::{extract_error, Result};
use reqwest::Client;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct Skill {
    pub name: String,
    pub description: String,
    pub author: Option<String>,
    pub version: Option<String>,
    pub enabled: bool,
    #[serde(rename = "has_prompt_context")]
    pub has_prompt_context: bool,
    #[serde(rename = "tools_count")]
    pub tools_count: u32,
}

#[derive(Debug, Deserialize)]
pub struct SkillsResponse {
    pub skills: Vec<Skill>,
}

#[derive(Debug, Serialize)]
pub struct InstallSkillRequest {
    pub name: String,
}

pub struct Skills {
    base_url: String,
    client: Client,
}

impl std::fmt::Debug for Skills {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Skills").finish()
    }
}

impl Skills {
    pub fn new(base_url: String, client: Client) -> Self {
        Self { base_url, client }
    }

    pub async fn list(&self) -> Result<SkillsResponse> {
        let url = format!("{}/api/skills", self.base_url);
        let response = self.client.get(&url).send().await?;
        let status = response.status();
        let body = response.text().await?;

        if status.is_success() {
            Ok(serde_json::from_str(&body)?)
        } else {
            Err(extract_error(status, &body))
        }
    }

    pub async fn install(&self, name: &str) -> Result<serde_json::Value> {
        let url = format!("{}/api/skills/install", self.base_url);
        let request = InstallSkillRequest {
            name: name.to_string(),
        };
        let response = self.client.post(&url).json(&request).send().await?;
        let status = response.status();
        let body = response.text().await?;

        if status.is_success() {
            Ok(serde_json::from_str(&body)?)
        } else {
            Err(extract_error(status, &body))
        }
    }

    pub async fn uninstall(&self, name: &str) -> Result<serde_json::Value> {
        let url = format!("{}/api/skills/uninstall", self.base_url);
        let request = InstallSkillRequest {
            name: name.to_string(),
        };
        let response = self.client.post(&url).json(&request).send().await?;
        let status = response.status();
        let body = response.text().await?;

        if status.is_success() {
            Ok(serde_json::from_str(&body)?)
        } else {
            Err(extract_error(status, &body))
        }
    }
}