Skip to main content

clickup_cli/commands/
tag.rs

1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::error::CliError;
4use crate::output::OutputConfig;
5use crate::Cli;
6use clap::Subcommand;
7
8#[derive(Subcommand)]
9pub enum TagCommands {
10    /// List tags in a space
11    List {
12        /// Space ID
13        #[arg(long)]
14        space: String,
15    },
16    /// Create a tag in a space
17    Create {
18        /// Space ID
19        #[arg(long)]
20        space: String,
21        /// Tag name
22        #[arg(long)]
23        name: String,
24        /// Foreground color (hex)
25        #[arg(long)]
26        fg_color: Option<String>,
27        /// Background color (hex)
28        #[arg(long)]
29        bg_color: Option<String>,
30    },
31    /// Update a tag in a space
32    Update {
33        /// Space ID
34        #[arg(long)]
35        space: String,
36        /// Tag name (current)
37        #[arg(long)]
38        tag: String,
39        /// New tag name
40        #[arg(long)]
41        name: Option<String>,
42        /// New foreground color (hex)
43        #[arg(long)]
44        fg_color: Option<String>,
45        /// New background color (hex)
46        #[arg(long)]
47        bg_color: Option<String>,
48    },
49    /// Delete a tag from a space
50    Delete {
51        /// Space ID
52        #[arg(long)]
53        space: String,
54        /// Tag name
55        #[arg(long)]
56        tag: String,
57    },
58}
59
60const TAG_FIELDS: &[&str] = &["name", "tag_fg", "tag_bg"];
61
62pub async fn execute(command: TagCommands, cli: &Cli) -> Result<(), CliError> {
63    let token = resolve_token(cli)?;
64    let client = ClickUpClient::new(&token, cli.timeout)?;
65    let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
66
67    match command {
68        TagCommands::List { space } => {
69            let resp = client.get(&format!("/v2/space/{}/tag", space)).await?;
70            let tags = resp
71                .get("tags")
72                .and_then(|t| t.as_array())
73                .cloned()
74                .unwrap_or_default();
75            output.print_items(&tags, TAG_FIELDS, "name");
76            Ok(())
77        }
78        TagCommands::Create {
79            space,
80            name,
81            fg_color,
82            bg_color,
83        } => {
84            let mut tag_obj = serde_json::json!({ "name": name });
85            if let Some(fg) = fg_color {
86                tag_obj["tag_fg"] = serde_json::Value::String(fg);
87            }
88            if let Some(bg) = bg_color {
89                tag_obj["tag_bg"] = serde_json::Value::String(bg);
90            }
91            let body = serde_json::json!({ "tag": tag_obj });
92            let resp = client
93                .post(&format!("/v2/space/{}/tag", space), &body)
94                .await?;
95            output.print_single(&resp, TAG_FIELDS, "name");
96            Ok(())
97        }
98        TagCommands::Update {
99            space,
100            tag,
101            name,
102            fg_color,
103            bg_color,
104        } => {
105            let mut tag_obj = serde_json::Map::new();
106            if let Some(n) = name {
107                tag_obj.insert("name".into(), serde_json::Value::String(n));
108            }
109            // NOTE: Update uses fg_color/bg_color (not tag_fg/tag_bg like Create)
110            if let Some(fg) = fg_color {
111                tag_obj.insert("fg_color".into(), serde_json::Value::String(fg));
112            }
113            if let Some(bg) = bg_color {
114                tag_obj.insert("bg_color".into(), serde_json::Value::String(bg));
115            }
116            let body = serde_json::json!({ "tag": serde_json::Value::Object(tag_obj) });
117            let resp = client
118                .put(&format!("/v2/space/{}/tag/{}", space, tag), &body)
119                .await?;
120            output.print_single(&resp, TAG_FIELDS, "name");
121            Ok(())
122        }
123        TagCommands::Delete { space, tag } => {
124            client
125                .delete(&format!("/v2/space/{}/tag/{}", space, tag))
126                .await?;
127            output.print_message(&format!("Tag '{}' deleted from space {}", tag, space));
128            Ok(())
129        }
130    }
131}