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}