feat:(first commit)created repository and complete 0.1.0

This commit is contained in:
2026-01-30 14:18:32 +08:00
commit 5d4156e5e0
36 changed files with 8686 additions and 0 deletions

270
src/commands/init.rs Normal file
View File

@@ -0,0 +1,270 @@
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 QuicCommit...".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!("{}", "✅ QuicCommit 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<SshConfig> {
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<GpgConfig> {
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,
})
}
}