use crate::{
Configurable, CredentialManager, claude_cli, cli,
credentials::{CredentialStore, get_api_key_interactively},
settings::{ClaudeSettings, format_settings_comparison, format_settings_for_display},
snapshots::{self, SnapshotScope, SnapshotStore},
templates::{Template, TemplateType, get_template_instance_with_input, get_template_type},
utils::{
backup_settings, confirm_action, get_credentials_dir, get_settings_path, get_snapshots_dir,
},
};
use anyhow::{Result, anyhow};
use console::style;
use std::path::PathBuf;
pub fn run_command(args: &crate::Cli) -> Result<()> {
match &args.command {
cli::Commands::List => {
list_command()?;
}
cli::Commands::Apply {
target,
scope,
model,
settings_path,
backup,
yes,
} => apply_command(target, scope, model, settings_path, *backup, *yes)?,
cli::Commands::Credentials { command } => match command {
cli::CredentialCommands::List => credentials_list_command()?,
cli::CredentialCommands::Clear { yes } => credentials_clear_command(*yes)?,
},
}
Ok(())
}
pub fn list_command() -> Result<()> {
println!("📸 Snapshot Browser");
println!();
let mut selector = crate::selectors::snapshot::SnapshotSelector::new()?;
match selector.run_management() {
Ok(()) => {
println!("\n👋 Goodbye!");
}
Err(e) => {
let error_str = e.to_string();
if error_str.contains("User cancelled selection") {
println!("\n👋 Cancelled. See you next time!");
} else {
println!("\n❌ Error: {}", e);
}
}
}
Ok(())
}
pub fn snap_command(
name: &str,
scope: &SnapshotScope,
settings_path: &Option<PathBuf>,
description: &Option<String>,
overwrite: bool,
) -> Result<()> {
let settings_path = get_settings_path(settings_path.clone());
let settings = ClaudeSettings::from_file(&settings_path)?;
let mut snapshot_settings = settings;
if matches!(scope, SnapshotScope::All | SnapshotScope::Env) {
snapshot_settings.env = Some(ClaudeSettings::capture_environment());
}
let snapshots_dir = crate::utils::get_snapshots_dir();
let store = SnapshotStore::new(snapshots_dir);
if store.exists_by_name(name)
&& !overwrite
&& !confirm_action(
&format!("Snapshot '{}' already exists. Overwrite?", name),
false,
)?
{
return Ok(());
}
let snapshot = snapshots::Snapshot::new(
name.to_string(),
snapshot_settings,
scope.clone(),
description.clone(),
);
store.save(&snapshot)?;
println!(
"{} Snapshot '{}' created successfully!",
style("✓").green().bold(),
name
);
Ok(())
}
pub fn apply_command(
target: &str,
scope: &SnapshotScope,
model: &Option<String>,
settings_path: &Option<PathBuf>,
backup: bool,
yes: bool,
) -> Result<()> {
let settings_path = get_settings_path(settings_path.clone());
if let Ok(template_type) = get_template_type(target) {
return apply_template_command(
&template_type,
target,
scope,
model,
&settings_path,
backup,
yes,
);
}
apply_snapshot_command(target, scope, model, &settings_path, backup, yes)
}
fn apply_template_command(
template_type: &TemplateType,
target: &str,
scope: &SnapshotScope,
model: &Option<String>,
settings_path: &PathBuf,
backup: bool,
yes: bool,
) -> Result<()> {
let initial_template = get_template_instance_with_input(template_type, target);
let template_instance = if initial_template.has_variants()
&& ((target == "kat-coder" || target == "katcoder" || target == "kat")
|| (target == "kimi")
|| (target == "zai" || target == "glm" || target == "zhipu")
|| (target == "anyrouter" || target == "anyr" || target == "ar"))
{
match template_type {
crate::templates::TemplateType::KatCoder => {
let kat_coder_template =
crate::templates::kat_coder::KatCoderTemplate::create_interactively()?;
Box::new(kat_coder_template) as Box<dyn Template>
}
crate::templates::TemplateType::Kimi => {
let kimi_template = crate::templates::kimi::KimiTemplate::create_interactively()?;
Box::new(kimi_template) as Box<dyn Template>
}
crate::templates::TemplateType::Zai => {
let zai_template = crate::templates::zai::ZaiTemplate::create_interactively()?;
Box::new(zai_template) as Box<dyn Template>
}
crate::templates::TemplateType::AnyRouter => {
let anyrouter_template =
crate::templates::anyrouter::AnyRouterTemplate::create_interactively()?;
Box::new(anyrouter_template) as Box<dyn Template>
}
_ => initial_template,
}
} else {
initial_template
};
let api_key = {
let env_var_names = template_instance.env_var_names();
let mut env_vars_with_keys = Vec::new();
for env_var_name in &env_var_names {
if let Some(api_key) = std::env::var(env_var_name)
.ok()
.filter(|key| !key.trim().is_empty())
{
env_vars_with_keys.push((env_var_name, api_key));
}
}
if !env_vars_with_keys.is_empty() {
use inquire::Select;
let mut options = Vec::new();
for (env_var_name, _) in &env_vars_with_keys {
options.push(format!(
"Use API key from environment variable {}",
env_var_name
));
}
options.push("Enter a custom API key".to_string());
let choice = Select::new("API key source:", options)
.prompt()
.map_err(|e| anyhow!("Failed to get API key source selection: {}", e))?;
let mut selected_api_key: Option<String> = None;
for (env_var_name, api_key) in &env_vars_with_keys {
if choice.contains(&format!(
"Use API key from environment variable {}",
env_var_name
)) {
println!("✓ Using API key from environment variable {}", env_var_name);
selected_api_key = Some(api_key.clone());
break;
}
}
if let Some(api_key) = selected_api_key {
api_key
} else {
get_api_key_interactively(template_type.clone())?
}
} else {
get_api_key_interactively(template_type.clone())?
}
};
let mut settings = template_instance.create_settings(&api_key, scope);
if let Some(model_name) = model {
settings.model = Some(model_name.clone());
}
let existing_settings = ClaudeSettings::from_file(settings_path)?;
if backup {
backup_settings(settings_path)?;
}
if !yes {
let existing_masked = existing_settings.clone().mask_sensitive_data();
let new_masked = settings.clone().mask_sensitive_data();
let comparison = format_settings_comparison(&existing_masked, &new_masked);
if comparison == "Settings are identical." {
println!(
"{}",
style("Settings are already configured as requested.").green()
);
settings.to_file(settings_path)?;
return Ok(());
}
println!("Changes to be applied:");
println!("{}", comparison);
if !confirm_action("Apply these changes?", false)? {
return Ok(());
}
}
settings.to_file(settings_path)?;
if let Some(api_host) = template_instance.api_host() {
match claude_cli::patch_claude_cli_with_host(api_host, false) {
Ok(true) => {
println!(
"{} Patched Claude CLI to use host: {}",
style("✓").green().bold(),
style(api_host).cyan()
);
}
Ok(false) => {
}
Err(e) => {
eprintln!(
"{} Failed to patch Claude CLI: {}",
style("⚠").yellow().bold(),
e
);
}
}
}
println!(
"{} Applied template '{}' successfully!",
style("✓").green().bold(),
template_type
);
Ok(())
}
fn apply_snapshot_command(
snapshot_name: &str,
scope: &SnapshotScope,
model: &Option<String>,
settings_path: &PathBuf,
backup: bool,
yes: bool,
) -> Result<()> {
let snapshots_dir = get_snapshots_dir();
let store = SnapshotStore::new(snapshots_dir);
let mut snapshot = store.load_by_name(snapshot_name)?;
snapshot.settings = snapshot.settings.filter_by_scope(scope);
if let Some(model_name) = model {
snapshot.settings.model = Some(model_name.clone());
}
let existing_settings = ClaudeSettings::from_file(settings_path)?;
if backup {
backup_settings(settings_path)?;
}
if !yes {
let existing_masked = existing_settings.clone().mask_sensitive_data();
let snapshot_masked = snapshot.settings.clone().mask_sensitive_data();
println!("Current settings:");
println!("{}", format_settings_for_display(&existing_masked, false));
println!("\nSnapshot settings:");
println!("{}", format_settings_for_display(&snapshot_masked, false));
if !confirm_action("Apply these settings?", false)? {
return Ok(());
}
}
snapshot.settings.to_file(settings_path)?;
println!(
"{} Applied snapshot '{}' successfully!",
style("✓").green().bold(),
snapshot_name
);
Ok(())
}
pub fn credentials_list_command() -> Result<()> {
println!("🔐 Credential Browser");
println!();
let mut selector = crate::selectors::credential::CredentialSelector::new_all()?;
match selector.run_management() {
Ok(()) => {
println!("\n👋 Goodbye!");
}
Err(e) => {
let error_str = e.to_string();
if error_str.contains("User cancelled selection") {
println!("\n👋 Cancelled. See you next time!");
} else {
println!("\n❌ Error: {}", e);
}
}
}
Ok(())
}
pub fn credentials_clear_command(yes: bool) -> Result<()> {
if !yes && !confirm_action("Clear all saved credentials?", false)? {
return Ok(());
}
let _credentials_dir = get_credentials_dir();
let credential_store = CredentialStore::new()?;
credential_store.clear_credentials()?;
println!("{} Cleared all credentials!", style("✓").green().bold());
Ok(())
}