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