chore: 升级版本至 0.1.10 并更新密钥环与加密相关描述

This commit is contained in:
2026-03-19 16:34:45 +08:00
parent e2d43315e3
commit 0289dd4684
5 changed files with 445 additions and 12 deletions

View File

@@ -1,12 +1,13 @@
use anyhow::{bail, Result};
use clap::{Parser, Subcommand};
use colored::Colorize;
use dialoguer::{Confirm, Input, Select};
use dialoguer::{Confirm, Input, Select, Password};
use std::path::PathBuf;
use crate::config::{Language, manager::ConfigManager};
use crate::config::CommitFormat;
use crate::utils::keyring::{get_supported_providers, get_default_model, get_default_base_url, provider_needs_api_key};
use crate::utils::crypto::{encrypt, decrypt};
/// Mask API key with asterisks for security
fn mask_api_key(key: Option<&str>) -> String {
@@ -130,6 +131,10 @@ enum ConfigSubcommand {
/// Output file (defaults to stdout)
#[arg(short, long)]
output: Option<String>,
/// Password for encryption (will prompt if not provided)
#[arg(short = 'p', long)]
password: Option<String>,
},
/// Import configuration
@@ -137,6 +142,10 @@ enum ConfigSubcommand {
/// Input file
#[arg(short, long)]
file: String,
/// Password for decryption (will prompt if file is encrypted)
#[arg(short = 'p', long)]
password: Option<String>,
},
/// List available LLM models
@@ -172,8 +181,8 @@ impl ConfigCommand {
Some(ConfigSubcommand::SetKeepTypesEnglish { keep }) => self.set_keep_types_english(*keep, &config_path).await,
Some(ConfigSubcommand::SetKeepChangelogTypesEnglish { keep }) => self.set_keep_changelog_types_english(*keep, &config_path).await,
Some(ConfigSubcommand::Reset { force }) => self.reset(*force, &config_path).await,
Some(ConfigSubcommand::Export { output }) => self.export_config(output.as_deref(), &config_path).await,
Some(ConfigSubcommand::Import { file }) => self.import_config(file, &config_path).await,
Some(ConfigSubcommand::Export { output, password }) => self.export_config(output.as_deref(), password.as_deref(), &config_path).await,
Some(ConfigSubcommand::Import { file, password }) => self.import_config(file, password.as_deref(), &config_path).await,
Some(ConfigSubcommand::ListModels) => self.list_models(&config_path).await,
Some(ConfigSubcommand::TestLlm) => self.test_llm(&config_path).await,
Some(ConfigSubcommand::Path) => self.show_path(&config_path).await,
@@ -737,28 +746,90 @@ impl ConfigCommand {
Ok(())
}
async fn export_config(&self, output: Option<&str>, config_path: &Option<PathBuf>) -> Result<()> {
async fn export_config(&self, output: Option<&str>, password: Option<&str>, config_path: &Option<PathBuf>) -> Result<()> {
let manager = self.get_manager(config_path)?;
let toml = manager.export()?;
let export_content = if let Some(path) = output {
let pwd = if let Some(p) = password {
p.to_string()
} else {
let confirm = Confirm::new()
.with_prompt("Encrypt the exported configuration?")
.default(true)
.interact()?;
if confirm {
let pwd1 = Password::new()
.with_prompt("Enter encryption password")
.interact()?;
let pwd2 = Password::new()
.with_prompt("Confirm encryption password")
.interact()?;
if pwd1 != pwd2 {
bail!("Passwords do not match");
}
pwd1
} else {
String::new()
}
};
if pwd.is_empty() {
toml
} else {
let encrypted = encrypt(toml.as_bytes(), &pwd)?;
format!("ENCRYPTED:{}", encrypted)
}
} else {
toml
};
match output {
Some(path) => {
std::fs::write(path, &toml)?;
println!("{} Configuration exported to {}", "".green(), path);
std::fs::write(path, &export_content)?;
if export_content.starts_with("ENCRYPTED:") {
println!("{} Configuration encrypted and exported to {}", "".green(), path);
} else {
println!("{} Configuration exported to {}", "".green(), path);
}
}
None => {
println!("{}", toml);
println!("{}", export_content);
}
}
Ok(())
}
async fn import_config(&self, file: &str, config_path: &Option<PathBuf>) -> Result<()> {
async fn import_config(&self, file: &str, password: Option<&str>, config_path: &Option<PathBuf>) -> Result<()> {
let content = std::fs::read_to_string(file)?;
let config_content = if content.starts_with("ENCRYPTED:") {
let encrypted_data = content.strip_prefix("ENCRYPTED:").unwrap();
let pwd = if let Some(p) = password {
p.to_string()
} else {
Password::new()
.with_prompt("Enter decryption password")
.interact()?
};
match decrypt(encrypted_data, &pwd) {
Ok(decrypted) => String::from_utf8(decrypted)
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in decrypted content: {}", e))?,
Err(e) => {
bail!("Failed to decrypt configuration: {}. Please check your password.", e);
}
}
} else {
content
};
let mut manager = self.get_manager(config_path)?;
manager.import(&content)?;
manager.import(&config_content)?;
manager.save()?;
println!("{} Configuration imported from {}", "".green(), file);