Skip to main content

clickup_cli/commands/
group.rs

1use clap::Subcommand;
2use crate::client::ClickUpClient;
3use crate::commands::auth::resolve_token;
4use crate::commands::workspace::resolve_workspace;
5use crate::error::CliError;
6use crate::output::OutputConfig;
7use crate::Cli;
8
9#[derive(Subcommand)]
10pub enum GroupCommands {
11    /// List groups in the workspace
12    List,
13    /// Create a group
14    Create {
15        /// Group name
16        #[arg(long)]
17        name: String,
18        /// Member user IDs (repeat for multiple)
19        #[arg(long = "member")]
20        members: Vec<String>,
21    },
22    /// Update a group
23    Update {
24        /// Group ID
25        id: String,
26        /// New group name
27        #[arg(long)]
28        name: Option<String>,
29        /// User IDs to add (repeat for multiple)
30        #[arg(long = "add-member")]
31        add_members: Vec<String>,
32        /// User IDs to remove (repeat for multiple)
33        #[arg(long = "rem-member")]
34        rem_members: Vec<String>,
35    },
36    /// Delete a group
37    Delete {
38        /// Group ID
39        id: String,
40    },
41}
42
43const GROUP_FIELDS: &[&str] = &["id", "name", "members"];
44
45pub async fn execute(command: GroupCommands, cli: &Cli) -> Result<(), CliError> {
46    let token = resolve_token(cli)?;
47    let client = ClickUpClient::new(&token, cli.timeout)?;
48    let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
49
50    match command {
51        GroupCommands::List => {
52            let resp = client.get("/v2/group").await?;
53            let groups = resp
54                .get("groups")
55                .and_then(|g| g.as_array())
56                .cloned()
57                .unwrap_or_default();
58            output.print_items(&groups, GROUP_FIELDS, "id");
59            Ok(())
60        }
61        GroupCommands::Create { name, members } => {
62            let team_id = resolve_workspace(cli)?;
63            let body = serde_json::json!({
64                "name": name,
65                "member_ids": members,
66            });
67            let resp = client
68                .post(&format!("/v2/team/{}/group", team_id), &body)
69                .await?;
70            let group = resp.get("group").cloned().unwrap_or(resp);
71            output.print_single(&group, GROUP_FIELDS, "id");
72            Ok(())
73        }
74        GroupCommands::Update {
75            id,
76            name,
77            add_members,
78            rem_members,
79        } => {
80            let mut body = serde_json::Map::new();
81            if let Some(n) = name {
82                body.insert("name".into(), serde_json::Value::String(n));
83            }
84            if !add_members.is_empty() || !rem_members.is_empty() {
85                let add: Vec<serde_json::Value> = add_members
86                    .into_iter()
87                    .map(serde_json::Value::String)
88                    .collect();
89                let rem: Vec<serde_json::Value> = rem_members
90                    .into_iter()
91                    .map(serde_json::Value::String)
92                    .collect();
93                body.insert(
94                    "members".into(),
95                    serde_json::json!({ "add": add, "rem": rem }),
96                );
97            }
98            let resp = client
99                .put(
100                    &format!("/v2/group/{}", id),
101                    &serde_json::Value::Object(body),
102                )
103                .await?;
104            let group = resp.get("group").cloned().unwrap_or(resp);
105            output.print_single(&group, GROUP_FIELDS, "id");
106            Ok(())
107        }
108        GroupCommands::Delete { id } => {
109            client.delete(&format!("/v2/group/{}", id)).await?;
110            output.print_message(&format!("Group {} deleted", id));
111            Ok(())
112        }
113    }
114}