Skip to main content

clickup_cli/
config.rs

1use crate::error::CliError;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Serialize, Deserialize, Default)]
6pub struct Config {
7    pub auth: AuthConfig,
8    #[serde(default)]
9    pub defaults: DefaultsConfig,
10}
11
12#[derive(Debug, Serialize, Deserialize, Default)]
13pub struct AuthConfig {
14    pub token: String,
15}
16
17#[derive(Debug, Serialize, Deserialize, Default)]
18pub struct DefaultsConfig {
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub workspace_id: Option<String>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub output: Option<String>,
23}
24
25impl Config {
26    pub fn config_path() -> Result<PathBuf, CliError> {
27        let config_dir = dirs::config_dir()
28            .ok_or_else(|| CliError::ConfigError("Could not determine config directory".into()))?;
29        Ok(config_dir.join("clickup-cli").join("config.toml"))
30    }
31
32    /// Load config: project-level .clickup.toml first, then global config
33    pub fn load() -> Result<Self, CliError> {
34        // 1. Project-level config (.clickup.toml in current directory)
35        let project_path = PathBuf::from(".clickup.toml");
36        if project_path.exists() {
37            let project_config = Self::load_from(&project_path)?;
38            if !project_config.auth.token.is_empty() {
39                return Ok(project_config);
40            }
41        }
42        // 2. Global config
43        let path = Self::config_path()?;
44        Self::load_from(&path)
45    }
46
47    pub fn load_from(path: &std::path::Path) -> Result<Self, CliError> {
48        if !path.exists() {
49            return Err(CliError::ConfigError("Not configured".into()));
50        }
51        let contents = std::fs::read_to_string(path)?;
52        toml::from_str(&contents)
53            .map_err(|e| CliError::ConfigError(format!("Invalid config file: {}", e)))
54    }
55
56    pub fn save(&self) -> Result<(), CliError> {
57        let path = Self::config_path()?;
58        self.save_to(&path)
59    }
60
61    pub fn save_to(&self, path: &std::path::Path) -> Result<(), CliError> {
62        if let Some(parent) = path.parent() {
63            std::fs::create_dir_all(parent)?;
64        }
65        let contents = toml::to_string_pretty(self)
66            .map_err(|e| CliError::ConfigError(format!("Failed to serialize config: {}", e)))?;
67        std::fs::write(path, contents)?;
68        Ok(())
69    }
70}