Skip to main content

clickup_cli/commands/
tag.rs

1use clap::Subcommand;
2use crate::client::ClickUpClient;
3use crate::commands::auth::resolve_token;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
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
70                .get(&format!("/v2/space/{}/tag", space))
71                .await?;
72            let tags = resp
73                .get("tags")
74                .and_then(|t| t.as_array())
75                .cloned()
76                .unwrap_or_default();
77            output.print_items(&tags, TAG_FIELDS, "name");
78            Ok(())
79        }
80        TagCommands::Create {
81            space,
82            name,
83            fg_color,
84            bg_color,
85        } => {
86            let mut tag_obj = serde_json::json!({ "name": name });
87            if let Some(fg) = fg_color {
88                tag_obj["tag_fg"] = serde_json::Value::String(fg);
89            }
90            if let Some(bg) = bg_color {
91                tag_obj["tag_bg"] = serde_json::Value::String(bg);
92            }
93            let body = serde_json::json!({ "tag": tag_obj });
94            let resp = client
95                .post(&format!("/v2/space/{}/tag", space), &body)
96                .await?;
97            output.print_single(&resp, TAG_FIELDS, "name");
98            Ok(())
99        }
100        TagCommands::Update {
101            space,
102            tag,
103            name,
104            fg_color,
105            bg_color,
106        } => {
107            let mut tag_obj = serde_json::Map::new();
108            if let Some(n) = name {
109                tag_obj.insert("name".into(), serde_json::Value::String(n));
110            }
111            // NOTE: Update uses fg_color/bg_color (not tag_fg/tag_bg like Create)
112            if let Some(fg) = fg_color {
113                tag_obj.insert("fg_color".into(), serde_json::Value::String(fg));
114            }
115            if let Some(bg) = bg_color {
116                tag_obj.insert("bg_color".into(), serde_json::Value::String(bg));
117            }
118            let body = serde_json::json!({ "tag": serde_json::Value::Object(tag_obj) });
119            let resp = client
120                .put(&format!("/v2/space/{}/tag/{}", space, tag), &body)
121                .await?;
122            output.print_single(&resp, TAG_FIELDS, "name");
123            Ok(())
124        }
125        TagCommands::Delete { space, tag } => {
126            client
127                .delete(&format!("/v2/space/{}/tag/{}", space, tag))
128                .await?;
129            output.print_message(&format!("Tag '{}' deleted from space {}", tag, space));
130            Ok(())
131        }
132    }
133}