systemprompt-cli 0.2.2

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use std::path::Path;

use systemprompt_cloud::ProjectContext;
use systemprompt_cloud::constants::{container, profile as consts};
use systemprompt_identifiers::TenantId;
use systemprompt_loader::ExtensionLoader;
use systemprompt_models::auth::JwtAudience;
use systemprompt_models::profile::{SecretsConfig, SecretsSource, SecretsValidationMode};
use systemprompt_models::{
    CloudConfig, CloudValidationMode, ContentNegotiationConfig, Environment, ExtensionsConfig,
    LogLevel, OutputFormat, PathsConfig, Profile, ProfileDatabaseConfig, ProfileType,
    RateLimitsConfig, RuntimeConfig, SecurityConfig, SecurityHeadersConfig, ServerConfig,
    SiteConfig,
};

use super::templates::generate_display_name;

pub struct LocalProfileBuilder {
    name: String,
    tenant_id: Option<TenantId>,
    secrets_path: String,
    services_path: String,
}

impl LocalProfileBuilder {
    pub fn new(
        name: impl Into<String>,
        secrets_path: impl AsRef<Path>,
        services_path: impl AsRef<Path>,
    ) -> Self {
        Self {
            name: name.into(),
            tenant_id: None,
            secrets_path: secrets_path.as_ref().to_string_lossy().to_string(),
            services_path: services_path.as_ref().to_string_lossy().to_string(),
        }
    }

    pub fn with_tenant_id(mut self, tenant_id: TenantId) -> Self {
        self.tenant_id = Some(tenant_id);
        self
    }

    pub fn build(self) -> Profile {
        let ctx = ProjectContext::discover();
        let root = ctx.root();
        let system_path = root.to_string_lossy().to_string();
        let display_name = generate_display_name(&self.name);
        let local_url = format!("http://localhost:{}", consts::DEFAULT_PORT);

        Profile {
            name: self.name,
            display_name,
            target: ProfileType::Local,
            site: SiteConfig {
                name: "systemprompt.io".to_string(),
                github_link: None,
            },
            database: ProfileDatabaseConfig {
                db_type: consts::DEFAULT_DB_TYPE.to_string(),
                external_db_access: false,
            },
            server: ServerConfig {
                host: consts::LOCAL_HOST.to_string(),
                port: consts::DEFAULT_PORT,
                api_server_url: local_url.clone(),
                api_internal_url: local_url.clone(),
                api_external_url: local_url.clone(),
                use_https: false,
                cors_allowed_origins: vec![local_url, "http://localhost:5173".to_string()],
                content_negotiation: ContentNegotiationConfig::default(),
                security_headers: SecurityHeadersConfig::default(),
            },
            paths: PathsConfig {
                system: system_path,
                services: self.services_path,
                bin: ExtensionLoader::resolve_bin_directory(root, None)
                    .to_string_lossy()
                    .to_string(),
                storage: Some(ctx.storage_dir().to_string_lossy().to_string()),
                geoip_database: None,
                web_path: None,
            },
            security: SecurityConfig {
                issuer: consts::LOCAL_ISSUER.to_string(),
                access_token_expiration: consts::ACCESS_TOKEN_EXPIRATION,
                refresh_token_expiration: consts::REFRESH_TOKEN_EXPIRATION,
                audiences: vec![
                    JwtAudience::Web,
                    JwtAudience::Api,
                    JwtAudience::A2a,
                    JwtAudience::Mcp,
                ],
                allow_registration: true,
            },
            rate_limits: RateLimitsConfig {
                disabled: true,
                ..Default::default()
            },
            runtime: RuntimeConfig {
                environment: Environment::Development,
                log_level: LogLevel::Verbose,
                output_format: OutputFormat::Text,
                no_color: false,
                non_interactive: false,
            },
            cloud: Some(CloudConfig {
                tenant_id: self.tenant_id.map(|id| id.to_string()),
                validation: CloudValidationMode::Warn,
            }),
            secrets: Some(SecretsConfig {
                secrets_path: self.secrets_path,
                validation: SecretsValidationMode::Warn,
                source: SecretsSource::File,
            }),
            extensions: ExtensionsConfig::default(),
        }
    }
}

pub struct CloudProfileBuilder {
    name: String,
    tenant_id: Option<TenantId>,
    external_url: Option<String>,
    external_db_access: bool,
    secrets_path: Option<String>,
}

impl CloudProfileBuilder {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            tenant_id: None,
            external_url: None,
            external_db_access: false,
            secrets_path: None,
        }
    }

    pub fn with_tenant_id(mut self, tenant_id: TenantId) -> Self {
        self.tenant_id = Some(tenant_id);
        self
    }

    pub fn with_external_url(mut self, url: impl Into<String>) -> Self {
        self.external_url = Some(url.into());
        self
    }

    pub const fn with_external_db_access(mut self, enabled: bool) -> Self {
        self.external_db_access = enabled;
        self
    }

    pub fn with_secrets_path(mut self, path: impl Into<String>) -> Self {
        self.secrets_path = Some(path.into());
        self
    }

    pub fn build(self) -> Profile {
        let display_name = generate_display_name(&self.name);
        let external = self
            .external_url
            .unwrap_or_else(|| consts::DEFAULT_CLOUD_URL.to_string());
        let internal_url = format!("http://localhost:{}", consts::DEFAULT_PORT);

        Profile {
            name: self.name,
            display_name,
            target: ProfileType::Cloud,
            site: SiteConfig {
                name: "systemprompt.io".to_string(),
                github_link: None,
            },
            database: ProfileDatabaseConfig {
                db_type: consts::DEFAULT_DB_TYPE.to_string(),
                external_db_access: self.external_db_access,
            },
            server: ServerConfig {
                host: consts::CLOUD_HOST.to_string(),
                port: consts::DEFAULT_PORT,
                api_server_url: external.clone(),
                api_internal_url: internal_url,
                api_external_url: external.clone(),
                use_https: true,
                cors_allowed_origins: vec![external],
                content_negotiation: ContentNegotiationConfig::default(),
                security_headers: SecurityHeadersConfig::default(),
            },
            paths: PathsConfig {
                system: container::APP.to_string(),
                services: container::SERVICES.to_string(),
                bin: container::BIN.to_string(),
                storage: Some(container::STORAGE.to_string()),
                geoip_database: None,
                web_path: Some(container::WEB.to_string()),
            },
            security: SecurityConfig {
                issuer: consts::CLOUD_ISSUER.to_string(),
                access_token_expiration: consts::ACCESS_TOKEN_EXPIRATION,
                refresh_token_expiration: consts::REFRESH_TOKEN_EXPIRATION,
                audiences: vec![
                    JwtAudience::Web,
                    JwtAudience::Api,
                    JwtAudience::A2a,
                    JwtAudience::Mcp,
                ],
                allow_registration: true,
            },
            rate_limits: RateLimitsConfig::default(),
            runtime: RuntimeConfig {
                environment: Environment::Production,
                log_level: LogLevel::Normal,
                output_format: OutputFormat::Json,
                no_color: true,
                non_interactive: true,
            },
            cloud: Some(CloudConfig {
                tenant_id: self.tenant_id.map(|id| id.to_string()),
                validation: CloudValidationMode::Strict,
            }),
            secrets: Some(SecretsConfig {
                secrets_path: self.secrets_path.unwrap_or_else(String::new),
                validation: SecretsValidationMode::Strict,
                source: SecretsSource::Env,
            }),
            extensions: ExtensionsConfig::default(),
        }
    }
}