clickup-cli 0.9.1

CLI for the ClickUp API, optimized for AI agents
Documentation
use thiserror::Error;

#[derive(Error, Debug)]
pub enum CliError {
    #[error("{message}")]
    ClientError { message: String, status: u16 },
    #[error("{message}")]
    AuthError { message: String },
    #[error("{message}")]
    Forbidden { message: String },
    #[error("{message}")]
    NotFound {
        message: String,
        resource_id: String,
    },
    #[error("{message}")]
    RateLimited {
        message: String,
        retry_after: Option<u64>,
    },
    #[error("{message}")]
    ServerError { message: String },
    #[error("{0}")]
    ConfigError(String),
    #[error("{message}")]
    BranchDetect { message: String, hint: String },
    #[error("{0}")]
    IoError(#[from] std::io::Error),
}

impl CliError {
    pub fn exit_code(&self) -> i32 {
        match self {
            CliError::ClientError { .. } => 1,
            CliError::AuthError { .. } => 2,
            CliError::Forbidden { .. } => 2,
            CliError::NotFound { .. } => 3,
            CliError::RateLimited { .. } => 4,
            CliError::ServerError { .. } => 5,
            CliError::ConfigError(_) => 1,
            CliError::BranchDetect { .. } => 1,
            CliError::IoError(_) => 1,
        }
    }

    pub fn print(&self, output_mode: &str) {
        if output_mode == "json" {
            let json = serde_json::json!({
                "error": true,
                "message": self.to_string(),
                "exit_code": self.exit_code(),
                "hint": self.hint(),
            });
            eprintln!("{}", serde_json::to_string_pretty(&json).unwrap());
        } else {
            eprintln!("Error: {}", self);
            if let Some(status) = self.status() {
                eprintln!("  Status:  {}", status);
            }
            if let Some(hint) = self.hint() {
                eprintln!("  Hint:    {}", hint);
            }
        }
    }

    pub fn status(&self) -> Option<u16> {
        match self {
            CliError::ClientError { status, .. } => Some(*status),
            CliError::AuthError { .. } => Some(401),
            CliError::Forbidden { .. } => Some(403),
            CliError::NotFound { .. } => Some(404),
            CliError::RateLimited { .. } => Some(429),
            CliError::ServerError { .. } => Some(500),
            _ => None,
        }
    }

    pub fn hint(&self) -> Option<String> {
        match self {
            CliError::AuthError { .. } => {
                Some("Check your API token, or run 'clickup setup' to reconfigure".into())
            }
            CliError::Forbidden { .. } => Some(
                "This feature may require a higher ClickUp plan (Business+, Enterprise)".into(),
            ),
            CliError::NotFound { resource_id, .. } => Some(format!(
                "Check the ID '{}', or use --custom-task-id if using a custom task ID",
                resource_id
            )),
            CliError::RateLimited { retry_after, .. } => {
                retry_after.map(|s| format!("Rate limited. Retry after {} seconds", s))
            }
            CliError::ServerError { .. } => {
                Some("ClickUp server error. Try again in a few seconds.".into())
            }
            CliError::ConfigError(_) => {
                Some("Run 'clickup setup' to configure your API token".into())
            }
            CliError::BranchDetect { hint, .. } => Some(hint.clone()),
            _ => None,
        }
    }
}