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