use anyhow::{Context, Result}; use clap::Parser; use colored::Colorize; use dialoguer::{Confirm, Input, Select}; use crate::config::{GitProfile}; use crate::config::manager::ConfigManager; use crate::config::profile::{GpgConfig, SshConfig}; use crate::utils::validators::validate_email; /// Initialize quicommit configuration #[derive(Parser)] pub struct InitCommand { /// Skip interactive setup #[arg(short, long)] yes: bool, /// Reset existing configuration #[arg(long)] reset: bool, } impl InitCommand { pub async fn execute(&self) -> Result<()> { println!("{}", "🚀 Initializing QuiCommit...".bold().cyan()); let config_path = crate::config::AppConfig::default_path()?; // Check if config already exists if config_path.exists() && !self.reset { if !self.yes { let overwrite = Confirm::new() .with_prompt("Configuration already exists. Overwrite?") .default(false) .interact()?; if !overwrite { println!("{}", "Initialization cancelled.".yellow()); return Ok(()); } } } let mut manager = if self.reset { ConfigManager::new()? } else { ConfigManager::new().or_else(|_| Ok::<_, anyhow::Error>(ConfigManager::default()))? }; if self.yes { // Quick setup with defaults self.quick_setup(&mut manager).await?; } else { // Interactive setup self.interactive_setup(&mut manager).await?; } manager.save()?; println!("{}", "✅ QuiCommit initialized successfully!".bold().green()); println!("\nConfig file: {}", config_path.display()); println!("\nNext steps:"); println!(" 1. Create a profile: {}", "quicommit profile add".cyan()); println!(" 2. Configure LLM: {}", "quicommit config set-llm".cyan()); println!(" 3. Start committing: {}", "quicommit commit".cyan()); Ok(()) } async fn quick_setup(&self, manager: &mut ConfigManager) -> Result<()> { // Try to get git user info let git_config = git2::Config::open_default()?; let user_name = git_config.get_string("user.name").unwrap_or_else(|_| "User".to_string()); let user_email = git_config.get_string("user.email").unwrap_or_else(|_| "user@example.com".to_string()); let profile = GitProfile::new( "default".to_string(), user_name, user_email, ); manager.add_profile("default".to_string(), profile)?; manager.set_default_profile(Some("default".to_string()))?; // Set default LLM to Ollama manager.set_llm_provider("ollama".to_string()); Ok(()) } async fn interactive_setup(&self, manager: &mut ConfigManager) -> Result<()> { println!("\n{}", "Let's set up your first profile:".bold()); // Profile name let profile_name: String = Input::new() .with_prompt("Profile name") .default("personal".to_string()) .interact_text()?; // User info let git_config = git2::Config::open_default().ok(); let default_name = git_config.as_ref() .and_then(|c| c.get_string("user.name").ok()) .unwrap_or_default(); let default_email = git_config.as_ref() .and_then(|c| c.get_string("user.email").ok()) .unwrap_or_default(); let user_name: String = Input::new() .with_prompt("Git user name") .default(default_name) .interact_text()?; let user_email: String = Input::new() .with_prompt("Git user email") .default(default_email) .validate_with(|input: &String| { validate_email(input).map_err(|e| e.to_string()) }) .interact_text()?; let description: String = Input::new() .with_prompt("Profile description (optional)") .allow_empty(true) .interact_text()?; let is_work = Confirm::new() .with_prompt("Is this a work profile?") .default(false) .interact()?; let organization = if is_work { Some(Input::new() .with_prompt("Organization/Company name") .interact_text()?) } else { None }; // SSH configuration let setup_ssh = Confirm::new() .with_prompt("Configure SSH key?") .default(false) .interact()?; let ssh_config = if setup_ssh { Some(self.setup_ssh_interactive().await?) } else { None }; // GPG configuration let setup_gpg = Confirm::new() .with_prompt("Configure GPG signing?") .default(false) .interact()?; let gpg_config = if setup_gpg { Some(self.setup_gpg_interactive().await?) } else { None }; // Create profile let mut profile = GitProfile::new( profile_name.clone(), user_name, user_email, ); if !description.is_empty() { profile.description = Some(description); } profile.is_work = is_work; profile.organization = organization; profile.ssh = ssh_config; profile.gpg = gpg_config; manager.add_profile(profile_name.clone(), profile)?; manager.set_default_profile(Some(profile_name))?; // LLM provider selection println!("\n{}", "Select your preferred LLM provider:".bold()); let providers = vec!["Ollama (local)", "OpenAI", "Anthropic Claude"]; let provider_idx = Select::new() .items(&providers) .default(0) .interact()?; let provider = match provider_idx { 0 => "ollama", 1 => "openai", 2 => "anthropic", _ => "ollama", }; manager.set_llm_provider(provider.to_string()); // Configure API key if needed if provider == "openai" { let api_key: String = Input::new() .with_prompt("OpenAI API key") .interact_text()?; manager.set_openai_api_key(api_key); } else if provider == "anthropic" { let api_key: String = Input::new() .with_prompt("Anthropic API key") .interact_text()?; manager.set_anthropic_api_key(api_key); } Ok(()) } async fn setup_ssh_interactive(&self) -> Result { use std::path::PathBuf; let ssh_dir = dirs::home_dir() .map(|h| h.join(".ssh")) .unwrap_or_else(|| PathBuf::from("~/.ssh")); let key_path: String = Input::new() .with_prompt("SSH private key path") .default(ssh_dir.join("id_rsa").display().to_string()) .interact_text()?; let has_passphrase = Confirm::new() .with_prompt("Does this key have a passphrase?") .default(false) .interact()?; let passphrase = if has_passphrase { Some(crate::utils::password_input("SSH key passphrase")?) } else { None }; Ok(SshConfig { private_key_path: Some(PathBuf::from(key_path)), public_key_path: None, passphrase, agent_forwarding: false, ssh_command: None, known_hosts_file: None, }) } async fn setup_gpg_interactive(&self) -> Result { let key_id: String = Input::new() .with_prompt("GPG key ID") .interact_text()?; let use_agent = Confirm::new() .with_prompt("Use GPG agent?") .default(true) .interact()?; Ok(GpgConfig { key_id, program: "gpg".to_string(), home_dir: None, passphrase: None, use_agent, }) } }