systemprompt_cli/commands/admin/config/
config_section.rs1use std::path::PathBuf;
10
11use anyhow::{Context, Result};
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use systemprompt_config::ProfileBootstrap;
15
16use super::rate_limit_types::ResetChange;
17
18#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
19pub struct ValidateOutput {
20 pub valid: bool,
21 pub errors: Vec<String>,
22 pub warnings: Vec<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
26pub struct ExportOutput {
27 pub format: String,
28 pub path: String,
29 pub message: String,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
33pub struct ImportOutput {
34 pub path: String,
35 pub changes: Vec<ResetChange>,
36 pub message: String,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
40pub struct DiffOutput {
41 pub source: String,
42 pub differences: Vec<DiffEntry>,
43 pub identical: bool,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
47pub struct DiffEntry {
48 pub field: String,
49 pub current: String,
50 pub other: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
54pub struct ConfigFileInfo {
55 pub path: String,
56 pub section: String,
57 pub exists: bool,
58 pub valid: bool,
59 pub error: Option<String>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
63pub struct ConfigListOutput {
64 pub total: usize,
65 pub valid: usize,
66 pub invalid: usize,
67 pub files: Vec<ConfigFileInfo>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
71pub struct ConfigValidateOutput {
72 pub files: Vec<ConfigFileInfo>,
73 pub all_valid: bool,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum ConfigSection {
78 Ai,
79 Content,
80 Web,
81 Scheduler,
82 Agents,
83 Mcp,
84 Skills,
85 Profile,
86 Services,
87}
88
89impl ConfigSection {
90 pub const fn all() -> &'static [Self] {
91 &[
92 Self::Profile,
93 Self::Services,
94 Self::Ai,
95 Self::Content,
96 Self::Web,
97 Self::Scheduler,
98 Self::Agents,
99 Self::Mcp,
100 Self::Skills,
101 ]
102 }
103
104 pub fn file_path(self) -> Result<PathBuf> {
105 let profile = ProfileBootstrap::get()?;
106 match self {
107 Self::Ai => Ok(PathBuf::from(&profile.paths.services).join("ai/config.yaml")),
108 Self::Content => Ok(PathBuf::from(&profile.paths.services).join("content/config.yaml")),
109 Self::Web => Ok(PathBuf::from(&profile.paths.services).join("web/config.yaml")),
110 Self::Scheduler => {
111 Ok(PathBuf::from(&profile.paths.services).join("scheduler/config.yaml"))
112 },
113 Self::Agents => Ok(PathBuf::from(&profile.paths.services).join("agents/config.yaml")),
114 Self::Mcp => Ok(PathBuf::from(&profile.paths.services).join("mcp/config.yaml")),
115 Self::Skills => Ok(PathBuf::from(&profile.paths.services).join("skills/config.yaml")),
116 Self::Profile => Ok(PathBuf::from(ProfileBootstrap::get_path()?)),
117 Self::Services => Ok(PathBuf::from(&profile.paths.services).join("config/config.yaml")),
118 }
119 }
120
121 pub fn all_files(self) -> Result<Vec<PathBuf>> {
122 let profile = ProfileBootstrap::get()?;
123 let services_path = PathBuf::from(&profile.paths.services);
124
125 match self {
126 Self::Profile => Ok(vec![PathBuf::from(ProfileBootstrap::get_path()?)]),
127 Self::Services => Ok(vec![services_path.join("config/config.yaml")]),
128 Self::Ai => Self::collect_yaml_files(&services_path.join("ai")),
129 Self::Content => Self::collect_yaml_files(&services_path.join("content")),
130 Self::Web => Self::collect_yaml_files(&services_path.join("web")),
131 Self::Scheduler => Self::collect_yaml_files(&services_path.join("scheduler")),
132 Self::Agents => Self::collect_yaml_files(&services_path.join("agents")),
133 Self::Mcp => Self::collect_yaml_files(&services_path.join("mcp")),
134 Self::Skills => Self::collect_yaml_files(&services_path.join("skills")),
135 }
136 }
137
138 fn collect_yaml_files(dir: &PathBuf) -> Result<Vec<PathBuf>> {
139 let mut files = Vec::new();
140 if dir.exists() {
141 Self::collect_yaml_recursive(dir, &mut files)?;
142 }
143 Ok(files)
144 }
145
146 fn collect_yaml_recursive(dir: &PathBuf, files: &mut Vec<PathBuf>) -> Result<()> {
147 if !dir.is_dir() {
148 return Ok(());
149 }
150
151 for entry in std::fs::read_dir(dir)? {
152 let entry = entry?;
153 let path = entry.path();
154
155 if path.is_dir() {
156 Self::collect_yaml_recursive(&path, files)?;
157 } else if let Some(ext) = path.extension() {
158 if ext == "yaml" || ext == "yml" {
159 files.push(path);
160 }
161 }
162 }
163 Ok(())
164 }
165}
166
167impl std::fmt::Display for ConfigSection {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 match self {
170 Self::Ai => write!(f, "ai"),
171 Self::Content => write!(f, "content"),
172 Self::Web => write!(f, "web"),
173 Self::Scheduler => write!(f, "scheduler"),
174 Self::Agents => write!(f, "agents"),
175 Self::Mcp => write!(f, "mcp"),
176 Self::Skills => write!(f, "skills"),
177 Self::Profile => write!(f, "profile"),
178 Self::Services => write!(f, "services"),
179 }
180 }
181}
182
183impl std::str::FromStr for ConfigSection {
184 type Err = anyhow::Error;
185
186 fn from_str(s: &str) -> Result<Self> {
187 match s.to_lowercase().as_str() {
188 "ai" => Ok(Self::Ai),
189 "content" => Ok(Self::Content),
190 "web" => Ok(Self::Web),
191 "scheduler" => Ok(Self::Scheduler),
192 "agents" => Ok(Self::Agents),
193 "mcp" => Ok(Self::Mcp),
194 "skills" => Ok(Self::Skills),
195 "profile" => Ok(Self::Profile),
196 "services" => Ok(Self::Services),
197 _ => Err(anyhow::anyhow!("Unknown config section: {}", s)),
198 }
199 }
200}
201
202pub fn read_yaml_file(path: &std::path::Path) -> Result<serde_yaml::Value> {
203 let content = std::fs::read_to_string(path)
204 .with_context(|| format!("Failed to read file: {}", path.display()))?;
205 serde_yaml::from_str(&content)
206 .with_context(|| format!("Failed to parse YAML from: {}", path.display()))
207}
208
209pub fn write_yaml_file(path: &std::path::Path, content: &serde_yaml::Value) -> Result<()> {
210 let yaml_str = serde_yaml::to_string(content).with_context(|| "Failed to serialize YAML")?;
211 std::fs::write(path, yaml_str)
212 .with_context(|| format!("Failed to write file: {}", path.display()))
213}