systemprompt-cli 0.14.3

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use systemprompt_cloud::ProjectContext;
use systemprompt_identifiers::TenantId;

pub(in crate::commands::cloud::tenant) const SHARED_CONTAINER_NAME: &str =
    "systemprompt-postgres-shared";
pub(in crate::commands::cloud) const SHARED_ADMIN_USER: &str = "systemprompt_admin";
pub(in crate::commands::cloud) const SHARED_VOLUME_NAME: &str = "systemprompt-postgres-shared-data";
pub(in crate::commands::cloud) const SHARED_PORT: u16 = 5432;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(in crate::commands::cloud) struct SharedContainerConfig {
    pub admin_password: String,
    pub port: u16,
    pub created_at: DateTime<Utc>,
    pub tenant_databases: Vec<TenantDatabaseMapping>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(in crate::commands::cloud::tenant) struct TenantDatabaseMapping {
    pub tenant_id: TenantId,
    pub database_name: String,
}

impl SharedContainerConfig {
    pub(in crate::commands::cloud::tenant) fn new(admin_password: String, port: u16) -> Self {
        Self {
            admin_password,
            port,
            created_at: Utc::now(),
            tenant_databases: Vec::new(),
        }
    }

    pub(in crate::commands::cloud::tenant) fn add_tenant(
        &mut self,
        tenant: TenantId,
        database_name: String,
    ) {
        self.tenant_databases.push(TenantDatabaseMapping {
            tenant_id: tenant,
            database_name,
        });
    }

    pub(in crate::commands::cloud::tenant) fn remove_tenant(
        &mut self,
        tenant: &str,
    ) -> Option<TenantDatabaseMapping> {
        self.tenant_databases
            .iter()
            .position(|t| t.tenant_id == tenant)
            .map(|pos| self.tenant_databases.remove(pos))
    }
}

pub(in crate::commands::cloud::tenant) fn shared_config_path() -> PathBuf {
    let ctx = ProjectContext::discover();
    ctx.docker_dir().join("shared_config.json")
}

pub(in crate::commands::cloud) fn load_shared_config() -> Result<Option<SharedContainerConfig>> {
    let path = shared_config_path();
    if !path.exists() {
        return Ok(None);
    }
    let content =
        fs::read_to_string(&path).with_context(|| format!("Failed to read {}", path.display()))?;
    let config: SharedContainerConfig = serde_json::from_str(&content)
        .with_context(|| format!("Failed to parse {}", path.display()))?;
    Ok(Some(config))
}

pub(in crate::commands::cloud) fn save_shared_config(config: &SharedContainerConfig) -> Result<()> {
    let path = shared_config_path();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let content = serde_json::to_string_pretty(config)?;
    fs::write(&path, content)?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&path)?.permissions();
        perms.set_mode(0o600);
        fs::set_permissions(&path, perms)?;
    }

    Ok(())
}