clickup_cli/commands/
setup.rs1use clap::Args;
2use crate::client::ClickUpClient;
3use crate::config::{AuthConfig, Config, DefaultsConfig};
4use crate::error::CliError;
5use crate::Cli;
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 };
79 config.save()?;
80
81 let path = Config::config_path()?;
82 eprintln!("Config saved to {}", path.display());
83 Ok(())
84}
85
86fn prompt_token() -> Result<String, CliError> {
87 eprint!("API Token (get one at Settings > Apps): ");
88 io::stderr().flush()?;
89 let mut token = String::new();
90 io::stdin().read_line(&mut token)?;
91 let token = token.trim().to_string();
92 if token.is_empty() {
93 return Err(CliError::ConfigError("No token provided".into()));
94 }
95 Ok(token)
96}
97
98fn prompt_choice(max: usize) -> Result<usize, CliError> {
99 eprint!("\nSelect workspace [1-{}]: ", max);
100 io::stderr().flush()?;
101 let mut input = String::new();
102 io::stdin().read_line(&mut input)?;
103 let choice: usize = input
104 .trim()
105 .parse()
106 .map_err(|_| CliError::ConfigError("Invalid selection".into()))?;
107 if choice < 1 || choice > max {
108 return Err(CliError::ConfigError(format!("Selection must be between 1 and {}", max)));
109 }
110 Ok(choice)
111}