opencode-ralph-loop-cli 0.1.0

Scaffolder CLI for OpenCode Ralph Loop plugin — one command setup
Documentation
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::error::CliError;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TelemetryConfig {
    #[serde(default)]
    pub enabled: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
    pub default_output: Option<String>,
    pub default_force: Option<bool>,
    pub plugin_version: Option<String>,
    pub template_version: Option<String>,
    #[serde(default)]
    pub telemetry: TelemetryConfig,
}

impl Config {
    pub fn load(config_file: Option<&PathBuf>) -> Self {
        if let Some(path) = config_file {
            return Self::load_from_path(path).unwrap_or_else(|e| {
                tracing::warn!("failed to load config from {}: {e}", path.display());
                Self::default()
            });
        }

        if let Some(path) = Self::default_config_path() {
            if path.exists() {
                return Self::load_from_path(&path).unwrap_or_else(|e| {
                    tracing::warn!("failed to load default config: {e}");
                    Self::default()
                });
            }
        }

        Self::default()
    }

    fn load_from_path(path: &PathBuf) -> Result<Self, CliError> {
        let content = std::fs::read_to_string(path)
            .map_err(|e| CliError::io(path.to_string_lossy().into_owned(), e))?;
        toml::from_str(&content)
            .map_err(|e| CliError::ConfigParse(format!("invalid configuration: {e}")))
    }

    pub fn default_config_path() -> Option<PathBuf> {
        directories::ProjectDirs::from("", "", "opencode-ralph-loop-cli")
            .map(|d| d.config_dir().join("config.toml"))
    }

    pub fn config_path_display() -> String {
        Self::default_config_path()
            .map(|p| p.display().to_string())
            .unwrap_or_else(|| "unavailable".to_string())
    }
}