decision_cockpit 0.1.0

Layer — product decision memory with MCP tools and an embedded review dashboard
Documentation
use std::env;
use std::path::PathBuf;

const DEFAULT_DATABASE_URL: &str = "postgres://cockpit:cockpit@localhost:5432/decision_cockpit";

/// Runtime configuration loaded from environment variables.
#[derive(Debug, Clone)]
pub struct Config {
    pub database_url: String,
    pub api_bind_addr: String,
    pub cors_allowed_origins: Vec<String>,
    /// When true, the MCP process hosts the HTTP API + dashboard.
    pub serve_dashboard: bool,
    /// When true, run `docker compose up -d --wait` before connecting.
    pub auto_start_postgres: bool,
    /// Optional override: serve dashboard from disk instead of the embedded bundle.
    pub frontend_dist_override: Option<PathBuf>,
}

impl Config {
    /// Load configuration from the environment, applying `.env` if present.
    pub fn from_env() -> anyhow::Result<Self> {
        let _ = dotenvy::dotenv();

        let database_url =
            env::var("DATABASE_URL").unwrap_or_else(|_| DEFAULT_DATABASE_URL.to_string());

        let api_bind_addr =
            env::var("API_BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:3000".to_string());

        let cors_allowed_origins = env::var("CORS_ALLOWED_ORIGINS")
            .unwrap_or_else(|_| "http://localhost:5173".to_string())
            .split(',')
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect();

        let frontend_dist_override = env::var("COCKPIT_FRONTEND_DIST")
            .ok()
            .map(PathBuf::from);

        Ok(Self {
            database_url,
            api_bind_addr,
            cors_allowed_origins,
            serve_dashboard: env_bool("COCKPIT_SERVE_DASHBOARD", true),
            auto_start_postgres: env_bool("COCKPIT_AUTO_DOCKER", true),
            frontend_dist_override,
        })
    }

    /// Browser URL for the dashboard (always `localhost`, regardless of bind addr).
    pub fn dashboard_url(&self) -> String {
        let port = self
            .api_bind_addr
            .rsplit(':')
            .next()
            .unwrap_or("3000");
        format!("http://localhost:{port}")
    }
}

fn env_bool(key: &str, default: bool) -> bool {
    match env::var(key) {
        Ok(v) => matches!(v.to_lowercase().as_str(), "1" | "true" | "yes"),
        Err(_) => default,
    }
}