Skip to main content

systemprompt_models/profile/
paths.rs

1//! Profile `paths:` block resolving system/services locations.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use serde::{Deserialize, Serialize};
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
10#[serde(deny_unknown_fields)]
11pub struct PathsConfig {
12    pub system: String,
13    pub services: String,
14    pub bin: String,
15
16    #[serde(default)]
17    pub web_path: Option<String>,
18
19    #[serde(default)]
20    pub storage: Option<String>,
21
22    #[serde(default)]
23    pub geoip_database: Option<String>,
24}
25
26impl PathsConfig {
27    pub fn resolve_relative_to(&mut self, base: &Path) {
28        self.system = resolve_path(base, &self.system);
29        self.services = resolve_path(base, &self.services);
30        self.bin = resolve_path(base, &self.bin);
31        self.storage = self.storage.as_ref().map(|p| resolve_path(base, p));
32        self.geoip_database = self.geoip_database.as_ref().map(|p| resolve_path(base, p));
33        self.web_path = self.web_path.as_ref().map(|p| resolve_path(base, p));
34    }
35
36    #[must_use]
37    pub fn with_services_root(&self, root: &Path) -> Self {
38        Self {
39            services: root.to_string_lossy().into_owned(),
40            ..self.clone()
41        }
42    }
43
44    pub fn skills(&self) -> String {
45        format!("{}/skills", self.services)
46    }
47
48    pub fn config(&self) -> String {
49        format!("{}/config/config.yaml", self.services)
50    }
51
52    pub fn ai_config(&self) -> String {
53        format!("{}/ai/config.yaml", self.services)
54    }
55
56    pub fn content_config(&self) -> String {
57        format!("{}/content/config.yaml", self.services)
58    }
59
60    pub fn web_config(&self) -> String {
61        format!("{}/web/config.yaml", self.services)
62    }
63
64    pub fn web_metadata(&self) -> String {
65        format!("{}/web/metadata.yaml", self.services)
66    }
67
68    pub fn plugins(&self) -> String {
69        format!("{}/plugins", self.services)
70    }
71
72    pub fn marketplaces(&self) -> String {
73        format!("{}/marketplaces", self.services)
74    }
75
76    pub fn hooks(&self) -> String {
77        format!("{}/hooks", self.services)
78    }
79
80    pub fn agents(&self) -> String {
81        format!("{}/agents", self.services)
82    }
83
84    pub fn logs(&self) -> String {
85        format!("{}/logs", self.system)
86    }
87
88    pub fn web_path_resolved(&self) -> String {
89        self.web_path
90            .clone()
91            .unwrap_or_else(|| format!("{}/web", self.system))
92    }
93
94    pub fn storage_resolved(&self) -> Option<&str> {
95        self.storage.as_deref()
96    }
97
98    pub fn geoip_database_resolved(&self) -> Option<&str> {
99        self.geoip_database.as_deref()
100    }
101}
102
103pub fn resolve_path(base: &Path, path: &str) -> String {
104    let p = Path::new(path);
105    if p.is_absolute() {
106        path.to_owned()
107    } else {
108        let resolved = base.join(p);
109        resolved.canonicalize().map_or_else(
110            |_| resolved.to_string_lossy().to_string(),
111            |canonical| canonical.to_string_lossy().to_string(),
112        )
113    }
114}
115
116pub fn expand_home(path_str: &str) -> PathBuf {
117    path_str.strip_prefix("~/").map_or_else(
118        || PathBuf::from(path_str),
119        |stripped| {
120            let home = std::env::var("HOME")
121                .or_else(|_| std::env::var("USERPROFILE"))
122                .unwrap_or_else(|_| {
123                    tracing::warn!(
124                        path = %path_str,
125                        "Cannot expand ~/ path: neither HOME nor USERPROFILE is set"
126                    );
127                    String::new()
128                });
129            PathBuf::from(home).join(stripped)
130        },
131    )
132}
133
134pub fn resolve_with_home(base: &Path, path_str: &str) -> PathBuf {
135    let path = expand_home(path_str);
136
137    if path.is_absolute() {
138        path
139    } else {
140        base.join(path)
141    }
142}