clickup_cli/commands/
setup.rs1use crate::client::ClickUpClient;
2use crate::config::{AuthConfig, Config, DefaultsConfig};
3use crate::error::CliError;
4use crate::Cli;
5use clap::Args;
6use std::io::{self, Write};
7
8#[derive(Args)]
9pub struct SetupArgs {
10 #[arg(long)]
12 pub token: Option<String>,
13}
14
15pub async fn execute(args: SetupArgs, cli: &Cli) -> Result<(), CliError> {
16 let token = match args.token.or_else(|| cli.token.clone()) {
17 Some(t) => t,
18 None => prompt_token()?,
19 };
20
21 let client = ClickUpClient::new(&token, cli.timeout)?;
23 let user_resp = client.get("/v2/user").await?;
24 let username = user_resp
25 .get("user")
26 .and_then(|u| u.get("username"))
27 .and_then(|u| u.as_str())
28 .unwrap_or("Unknown");
29 eprintln!("Validating... ✓ Authenticated as {}", username);
30
31 let teams_resp = client.get("/v2/team").await?;
33 let teams = teams_resp
34 .get("teams")
35 .and_then(|t| t.as_array())
36 .cloned()
37 .unwrap_or_default();
38
39 let workspace_id = match teams.len() {
40 0 => {
41 return Err(CliError::ClientError {
42 message: "No workspaces found for this token".into(),
43 status: 0,
44 });
45 }
46 1 => {
47 let ws = &teams[0];
48 let id = ws.get("id").and_then(|v| v.as_str()).unwrap_or("");
49 let name = ws.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown");
50 eprintln!("\nOnly one workspace found — setting as default.");
51 eprintln!(" {} (ID: {})", name, id);
52 id.to_string()
53 }
54 _ => {
55 eprintln!("\nFetching workspaces...");
56 for (i, ws) in teams.iter().enumerate() {
57 let id = ws.get("id").and_then(|v| v.as_str()).unwrap_or("");
58 let name = ws.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown");
59 eprintln!(" [{}] {} (ID: {})", i + 1, name, id);
60 }
61 let choice = prompt_choice(teams.len())?;
62 let ws = &teams[choice - 1];
63 ws.get("id")
64 .and_then(|v| v.as_str())
65 .unwrap_or("")
66 .to_string()
67 }
68 };
69
70 let config = Config {
71 auth: AuthConfig {
72 token: token.clone(),
73 },
74 defaults: DefaultsConfig {
75 workspace_id: Some(workspace_id),
76 output: None,
77 },
78 git: Default::default(),
79 };
80 config.save()?;
81
82 let path = Config::config_path()?;
83 eprintln!("Config saved to {}", path.display());
84 Ok(())
85}
86
87fn prompt_token() -> Result<String, CliError> {
88 eprint!("API Token (get one at Settings > Apps): ");
89 io::stderr().flush()?;
90 let mut token = String::new();
91 io::stdin().read_line(&mut token)?;
92 let token = token.trim().to_string();
93 if token.is_empty() {
94 return Err(CliError::ConfigError("No token provided".into()));
95 }
96 Ok(token)
97}
98
99fn prompt_choice(max: usize) -> Result<usize, CliError> {
100 eprint!("\nSelect workspace [1-{}]: ", max);
101 io::stderr().flush()?;
102 let mut input = String::new();
103 io::stdin().read_line(&mut input)?;
104 let choice: usize = input
105 .trim()
106 .parse()
107 .map_err(|_| CliError::ConfigError("Invalid selection".into()))?;
108 if choice < 1 || choice > max {
109 return Err(CliError::ConfigError(format!(
110 "Selection must be between 1 and {}",
111 max
112 )));
113 }
114 Ok(choice)
115}