systemprompt-models 0.1.18

Shared data models and types for systemprompt.io OS
Documentation
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathsConfig {
    pub system: String,
    pub services: String,
    pub bin: String,

    #[serde(default)]
    pub web_path: Option<String>,

    #[serde(default)]
    pub storage: Option<String>,

    #[serde(default)]
    pub geoip_database: Option<String>,
}

impl PathsConfig {
    pub fn resolve_relative_to(&mut self, base: &Path) {
        self.system = resolve_path(base, &self.system);
        self.services = resolve_path(base, &self.services);
        self.bin = resolve_path(base, &self.bin);
        self.storage = self.storage.as_ref().map(|p| resolve_path(base, p));
        self.geoip_database = self.geoip_database.as_ref().map(|p| resolve_path(base, p));
        self.web_path = self.web_path.as_ref().map(|p| resolve_path(base, p));
    }

    pub fn skills(&self) -> String {
        format!("{}/skills", self.services)
    }

    pub fn config(&self) -> String {
        format!("{}/config/config.yaml", self.services)
    }

    pub fn ai_config(&self) -> String {
        format!("{}/ai/config.yaml", self.services)
    }

    pub fn content_config(&self) -> String {
        format!("{}/content/config.yaml", self.services)
    }

    pub fn web_config(&self) -> String {
        format!("{}/web/config.yaml", self.services)
    }

    pub fn web_metadata(&self) -> String {
        format!("{}/web/metadata.yaml", self.services)
    }

    pub fn plugins(&self) -> String {
        format!("{}/plugins", self.services)
    }

    pub fn hooks(&self) -> String {
        format!("{}/hooks", self.services)
    }

    pub fn agents(&self) -> String {
        format!("{}/agents", self.services)
    }

    pub fn web_path_resolved(&self) -> String {
        self.web_path
            .clone()
            .unwrap_or_else(|| format!("{}/web", self.system))
    }

    pub fn storage_resolved(&self) -> Option<&str> {
        self.storage.as_deref()
    }

    pub fn geoip_database_resolved(&self) -> Option<&str> {
        self.geoip_database.as_deref()
    }
}

pub fn resolve_path(base: &Path, path: &str) -> String {
    let p = Path::new(path);
    if p.is_absolute() {
        path.to_string()
    } else {
        let resolved = base.join(p);
        resolved.canonicalize().map_or_else(
            |_| resolved.to_string_lossy().to_string(),
            |canonical| canonical.to_string_lossy().to_string(),
        )
    }
}

pub fn expand_home(path_str: &str) -> PathBuf {
    path_str.strip_prefix("~/").map_or_else(
        || PathBuf::from(path_str),
        |stripped| {
            let home = std::env::var("HOME")
                .or_else(|_| std::env::var("USERPROFILE"))
                .unwrap_or_else(|_| {
                    tracing::warn!(
                        path = %path_str,
                        "Cannot expand ~/ path: neither HOME nor USERPROFILE is set"
                    );
                    String::new()
                });
            PathBuf::from(home).join(stripped)
        },
    )
}

pub fn resolve_with_home(base: &Path, path_str: &str) -> PathBuf {
    let path = expand_home(path_str);

    if path.is_absolute() {
        path
    } else {
        base.join(path)
    }
}