Skip to main content

clickup_cli/commands/
field.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 FieldCommands {
11    /// List custom fields
12    List {
13        /// List ID
14        #[arg(long)]
15        list: Option<String>,
16        /// Folder ID
17        #[arg(long)]
18        folder: Option<String>,
19        /// Space ID
20        #[arg(long)]
21        space: Option<String>,
22        /// Workspace-level fields
23        #[arg(long = "workspace-level")]
24        workspace_level: bool,
25    },
26    /// Set a custom field value on a task
27    Set {
28        /// Task ID
29        task_id: String,
30        /// Field ID
31        field_id: String,
32        /// Field value (string, number, or JSON)
33        #[arg(long)]
34        value: String,
35    },
36    /// Unset (clear) a custom field value on a task
37    Unset {
38        /// Task ID
39        task_id: String,
40        /// Field ID
41        field_id: String,
42    },
43}
44
45const FIELD_FIELDS: &[&str] = &["id", "name", "type", "required"];
46
47pub async fn execute(command: FieldCommands, cli: &Cli) -> Result<(), CliError> {
48    let token = resolve_token(cli)?;
49    let client = ClickUpClient::new(&token, cli.timeout)?;
50    let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
51
52    match command {
53        FieldCommands::List {
54            list,
55            folder,
56            space,
57            workspace_level,
58        } => {
59            let path = if let Some(list_id) = list {
60                format!("/v2/list/{}/field", list_id)
61            } else if let Some(folder_id) = folder {
62                format!("/v2/folder/{}/field", folder_id)
63            } else if let Some(space_id) = space {
64                format!("/v2/space/{}/field", space_id)
65            } else if workspace_level {
66                let ws_id = resolve_workspace(cli)?;
67                format!("/v2/team/{}/field", ws_id)
68            } else {
69                return Err(CliError::ClientError {
70                    message: "Specify --list, --folder, --space, or --workspace-level".into(),
71                    status: 0,
72                });
73            };
74
75            let resp = client.get(&path).await?;
76            let fields = resp
77                .get("fields")
78                .and_then(|f| f.as_array())
79                .cloned()
80                .unwrap_or_default();
81            output.print_items(&fields, FIELD_FIELDS, "id");
82            Ok(())
83        }
84        FieldCommands::Set {
85            task_id,
86            field_id,
87            value,
88        } => {
89            // Try to parse value as JSON first, fallback to string
90            let parsed_value: serde_json::Value = serde_json::from_str(&value)
91                .unwrap_or_else(|_| serde_json::Value::String(value));
92            let body = serde_json::json!({ "value": parsed_value });
93            let resp = client
94                .post(&format!("/v2/task/{}/field/{}", task_id, field_id), &body)
95                .await?;
96            output.print_single(&resp, FIELD_FIELDS, "id");
97            Ok(())
98        }
99        FieldCommands::Unset { task_id, field_id } => {
100            client
101                .delete(&format!("/v2/task/{}/field/{}", task_id, field_id))
102                .await?;
103            output.print_message(&format!(
104                "Field {} cleared on task {}",
105                field_id, task_id
106            ));
107            Ok(())
108        }
109    }
110}