claude_code_toolkit/cli/commands/
configure.rs1use crate::{ config::manager::ConfigurationManager, error::*, providers::github::GitHubManager };
2use console::{ Term, style };
3use std::io::{ self, Write };
4
5pub async fn handle_configure() -> Result<()> {
6  let term = Term::stdout();
7  term.clear_screen().ok();
8
9  println!("{}", style("🛠️  Claude Code Configuration Wizard").bold().cyan());
10  println!("{}", style("═".repeat(50)).dim());
11  println!();
12  println!("{}", style("This wizard will help you set up claude-code for automatic").dim());
13  println!("{}", style("Claude credential synchronization to GitHub.").dim());
14  println!();
15
16  let config_manager = ConfigurationManager::new()?;
17  let github_manager = GitHubManager::new();
18
19  println!("{}", style("🔍 Checking prerequisites...").bold());
21
22  if !github_manager.check_gh_cli().await? {
23    println!("{}", style("❌ GitHub CLI (gh) not found. Please install it first:").red());
24    println!("{}", style("   https://cli.github.com/").dim());
25    return Ok(());
26  }
27
28  if !github_manager.check_authentication().await? {
29    println!("{}", style("❌ GitHub CLI not authenticated. Please run:").red());
30    println!("{}", style("   gh auth login").dim());
31    return Ok(());
32  }
33
34  println!("{}", style("✅ GitHub CLI is available and authenticated").green());
35
36  let credentials_manager = crate::config::credentials::CredentialsManager::new()?;
38  match credentials_manager.read_credentials().await {
39    Ok(_) => println!("{}", style("✅ Claude credentials found").green()),
40    Err(_) => {
41      println!(
42        "{}",
43        style(
44          "❌ Claude credentials not found. Please ensure Claude Code is installed and you're logged in."
45        ).red()
46      );
47      return Ok(());
48    }
49  }
50
51  println!();
52
53  let mut config = config_manager.load_config().await.unwrap_or_default();
55
56  println!("{}", style("🏢 Organization Configuration").bold().cyan());
60
61  let available_orgs = github_manager.list_organizations().await?;
62  if !available_orgs.is_empty() {
63    println!("{}", style("Available organizations:").dim());
64    for (i, org) in available_orgs.iter().enumerate() {
65      println!("{}", style(format!("  {}. {}", i + 1, org)).dim());
66    }
67    println!();
68
69    if prompt_yes_no("Would you like to add organizations for credential sync?")? {
70      loop {
71        let org_name = prompt("Organization name (or 'done' to finish)")?;
72        if org_name.to_lowercase() == "done" {
73          break;
74        }
75
76        if available_orgs.contains(&org_name) {
77          config_manager.add_organization(org_name.clone()).await?;
78          println!("{}", style(format!("✅ Added organization: {}", org_name)).green());
79        } else {
80          println!(
81            "{}",
82            style(
83              format!("⚠️  Organization '{}' not found in your available organizations", org_name)
84            ).yellow()
85          );
86          if prompt_yes_no("Add it anyway?")? {
87            config_manager.add_organization(org_name.clone()).await?;
88            println!("{}", style(format!("✅ Added organization: {}", org_name)).green());
89          }
90        }
91      }
92    }
93  }
94
95  println!();
96
97  println!("{}", style("📁 Repository Configuration").bold().cyan());
99
100  if prompt_yes_no("Would you like to add repositories for credential sync?")? {
101    loop {
102      let repo = prompt("Repository (owner/repo format, or 'done' to finish)")?;
103      if repo.to_lowercase() == "done" {
104        break;
105      }
106
107      if repo.contains('/') {
108        config_manager.add_repository(repo.clone()).await?;
109        println!("{}", style(format!("✅ Added repository: {}", repo)).green());
110      } else {
111        println!(
112          "{}",
113          style("❌ Invalid format. Use owner/repo format (e.g., microsoft/vscode)").red()
114        );
115      }
116    }
117  }
118
119  println!();
120
121  println!("{}", style("🔐 Secrets Configuration").bold().cyan());
123  println!();
124  println!(
125    "{}",
126    style("Secret mappings define which credential fields sync to which GitHub secrets.").dim()
127  );
128  println!("{}", style("You can configure these now or edit config.yml manually later.").dim());
129  println!();
130
131  if prompt_yes_no("Configure secret mappings now?")? {
132    let mut mappings = std::collections::HashMap::new();
133
134    println!("{}", style("Add credential field mappings:").bold());
135    println!(
136      "{}",
137      style("Common fields: accessToken, refreshToken, expiresAt, subscriptionType, scopes").dim()
138    );
139    println!();
140
141    loop {
142      let field = prompt("Credential field name (or 'done' to finish)")?;
143      if field.to_lowercase() == "done" {
144        break;
145      }
146      let secret = prompt(&format!("GitHub secret name for '{}'", field))?;
147      mappings.insert(field.clone(), secret.clone());
148      println!("{}", style(format!("✅ Added mapping: {} → {}", field, secret)).green());
149    }
150
151    config.credentials.field_mappings = mappings;
152
153    if !config.credentials.field_mappings.is_empty() {
154      println!();
155      println!("{}", style("✅ Secret mappings configured:").green());
156      for (field, secret) in &config.credentials.field_mappings {
157        println!("  {} → {}", style(field).cyan(), style(secret).yellow());
158      }
159    }
160  } else {
161    println!("{}", style("⚠️  No secret mappings configured.").yellow());
162    println!(
163      "{}",
164      style("   Edit ~/.goodiebag/claude-code/config.yml to add mappings manually.").dim()
165    );
166    println!("{}", style("   See config-template.yml for examples.").dim());
167  }
168
169  println!();
170
171  config_manager.save_config(&config).await?;
173
174  println!("{}", style("🤖 Daemon Configuration").bold().cyan());
176
177  if prompt_yes_no("Would you like to install and start the sync daemon?")? {
178    crate::cli::commands::service::handle_install().await?;
180  }
181
182  println!();
183  println!("{}", style("🎉 Configuration Complete!").bold().green());
184  println!();
185  println!("{}", style("Your claude-code is now configured. Here's what you can do next:").dim());
186  println!();
187  println!("{}", style("• View status:").bold());
188  println!("{}", style("  claude-code status").dim());
189  println!();
190  println!("{}", style("• Manual sync:").bold());
191  println!("{}", style("  claude-code sync now").dim());
192  println!();
193  println!("{}", style("• Real-time timer:").bold());
194  println!("{}", style("  claude-code timer").dim());
195  println!();
196  println!("{}", style("• Configuration file:").bold());
197  println!("{}", style(format!("  {}", config_manager.config_path().display())).dim());
198
199  Ok(())
200}
201
202fn prompt(message: &str) -> Result<String> {
203  print!("{}: ", style(message).bold());
204  io::stdout().flush().unwrap();
205
206  let mut input = String::new();
207  io
208    ::stdin()
209    .read_line(&mut input)
210    .map_err(|e| crate::error::ClaudeCodeError::Generic(e.to_string()))?;
211
212  Ok(input.trim().to_string())
213}
214
215fn prompt_yes_no(message: &str) -> Result<bool> {
216  loop {
217    print!("{} [y/N]: ", style(message).bold());
218    io::stdout().flush().unwrap();
219
220    let mut input = String::new();
221    io
222      ::stdin()
223      .read_line(&mut input)
224      .map_err(|e| crate::error::ClaudeCodeError::Generic(e.to_string()))?;
225
226    match input.trim().to_lowercase().as_str() {
227      "y" | "yes" => {
228        return Ok(true);
229      }
230      "n" | "no" | "" => {
231        return Ok(false);
232      }
233      _ => println!("{}", style("Please enter 'y' for yes or 'n' for no").yellow()),
234    }
235  }
236}