Skip to main content

systemprompt_cli/commands/admin/config/
paths.rs

1//! `admin config paths` command: show and validate the configured filesystem
2//! paths.
3//!
4//! [`PathsCommands`] reports the system, services, bin, web, storage, and
5//! `GeoIP` paths from the active profile and checks whether each required path
6//! exists.
7
8use anyhow::Result;
9use clap::Subcommand;
10use std::path::Path;
11use systemprompt_config::ProfileBootstrap;
12use systemprompt_logging::CliService;
13use systemprompt_models::Profile;
14
15use super::types::{PathInfo, PathValidation, PathsConfigOutput, PathsValidateOutput};
16use crate::CliConfig;
17use crate::cli_settings::OutputFormat;
18use crate::shared::{CommandOutput, render_result};
19
20#[derive(Debug, Clone, Copy, Subcommand)]
21pub enum PathsCommands {
22    #[command(about = "Show paths configuration", alias = "list")]
23    Show,
24
25    #[command(about = "Validate that all configured paths exist")]
26    Validate,
27}
28
29pub fn execute(command: PathsCommands, config: &CliConfig) -> Result<()> {
30    match command {
31        PathsCommands::Show => execute_show(config),
32        PathsCommands::Validate => execute_validate(config),
33    }
34}
35
36pub(super) fn execute_show(config: &CliConfig) -> Result<()> {
37    let profile = ProfileBootstrap::get()?;
38
39    let output = PathsConfigOutput {
40        system: PathInfo {
41            path: profile.paths.system.clone(),
42            exists: Path::new(&profile.paths.system).exists(),
43        },
44        services: PathInfo {
45            path: profile.paths.services.clone(),
46            exists: Path::new(&profile.paths.services).exists(),
47        },
48        bin: PathInfo {
49            path: profile.paths.bin.clone(),
50            exists: Path::new(&profile.paths.bin).exists(),
51        },
52        web_path: profile.paths.web_path.as_ref().map(|p| PathInfo {
53            path: p.clone(),
54            exists: Path::new(p).exists(),
55        }),
56        storage: profile.paths.storage.as_ref().map(|p| PathInfo {
57            path: p.clone(),
58            exists: Path::new(p).exists(),
59        }),
60        geoip_database: profile.paths.geoip_database.as_ref().map(|p| PathInfo {
61            path: p.clone(),
62            exists: Path::new(p).exists(),
63        }),
64    };
65
66    render_result(
67        &CommandOutput::card_value("Paths Configuration", &output),
68        config,
69    );
70
71    Ok(())
72}
73
74pub(super) fn execute_validate(config: &CliConfig) -> Result<()> {
75    let profile = ProfileBootstrap::get()?;
76
77    let validations = collect_path_validations(profile);
78    let valid = validations.iter().filter(|v| v.required).all(|v| v.exists);
79
80    let output = PathsValidateOutput {
81        valid,
82        paths: validations,
83    };
84
85    render_result(
86        &CommandOutput::table_of(vec!["name", "path", "exists", "required"], &output.paths)
87            .with_title("Paths Validation"),
88        config,
89    );
90
91    if config.output_format() == OutputFormat::Table {
92        if valid {
93            CliService::success("All required paths exist");
94        } else {
95            CliService::error("Some required paths are missing");
96        }
97    }
98
99    Ok(())
100}
101
102fn collect_path_validations(profile: &Profile) -> Vec<PathValidation> {
103    let mut validations = vec![
104        path_validation("system", &profile.paths.system, true),
105        path_validation("services", &profile.paths.services, true),
106        path_validation("bin", &profile.paths.bin, true),
107    ];
108
109    if let Some(web_path) = &profile.paths.web_path {
110        validations.push(path_validation("web_path", web_path, false));
111    }
112
113    if let Some(storage) = &profile.paths.storage {
114        validations.push(path_validation("storage", storage, false));
115    }
116
117    if let Some(geoip) = &profile.paths.geoip_database {
118        validations.push(path_validation("geoip_database", geoip, false));
119    }
120
121    validations.push(path_validation("config", &profile.paths.config(), false));
122    validations.push(path_validation(
123        "ai_config",
124        &profile.paths.ai_config(),
125        false,
126    ));
127    validations.push(path_validation(
128        "content_config",
129        &profile.paths.content_config(),
130        false,
131    ));
132
133    validations
134}
135
136fn path_validation(name: &str, path: &str, required: bool) -> PathValidation {
137    PathValidation {
138        name: name.to_owned(),
139        path: path.to_owned(),
140        exists: Path::new(path).exists(),
141        required,
142    }
143}