use clap::{Parser, Subcommand};
use colored::*;
use anyhow::{Result, Context};
use serde_json::{json, Value};
use uuid::Uuid;
use chrono::Utc;
mod models;
mod config;
mod providers;
#[cfg(test)]
mod integration_test;
use models::*;
use config::*;
use providers::*;
fn get_provider_display_name(provider_type: &str, with_color: bool) -> colored::ColoredString {
match provider_type {
"zhipu" => {
if with_color {
"智普LLM (Zhipu)".bright_yellow()
} else {
"智普LLM (Zhipu)".normal()
}
}
"minimax" => {
if with_color {
"MiniMax".bright_cyan()
} else {
"MiniMax".normal()
}
}
"kimi" => {
if with_color {
"Kimi".bright_magenta()
} else {
"Kimi".normal()
}
}
"claude" => {
if with_color {
"Claude (Official)".white()
} else {
"Claude (Official)".normal()
}
}
_ => "Unknown Provider".normal(),
}
}
#[derive(Parser)]
#[command(
name = "cckit",
version = "0.1.0",
about = "Code Kit Written by rust for Claude model Switch",
long_about = "Support 智普LLM, MiniMax, Kimi 提供的 Claude model\n\nManage and switch between different Claude model providers and their configurations.",
author = "Claude Code Kit"
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List,
Switch {
provider: String,
},
Current,
Configure {
provider: String,
#[arg(long)]
api_key: Option<String>,
#[arg(long)]
base_url: Option<String>,
#[arg(long)]
model: Option<String>,
},
Show {
provider: String,
},
Test {
provider: String,
},
Export {
#[arg(short, long)]
output: Option<String>,
},
Import {
file: String,
},
Reset,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let mut config_manager = ConfigManager::new()?;
config_manager.initialize()?;
match cli.command {
Commands::List => {
list_providers(&config_manager)?;
}
Commands::Switch { provider } => {
switch_provider(&mut config_manager, &provider)?;
}
Commands::Current => {
show_current_provider(&config_manager)?;
}
Commands::Configure { provider, api_key, base_url, model } => {
configure_provider(&mut config_manager, &provider, api_key, base_url, model)?;
}
Commands::Show { provider } => {
show_provider_config(&config_manager, &provider)?;
}
Commands::Test { provider } => {
test_provider(&config_manager, &provider).await?;
}
Commands::Export { output } => {
export_config(&config_manager, output)?;
}
Commands::Import { file } => {
import_config(&mut config_manager, &file)?;
}
Commands::Reset => {
reset_to_default(&mut config_manager)?;
}
}
Ok(())
}
fn list_providers(config_manager: &ConfigManager) -> Result<()> {
println!("{}", "Available Model Providers:".bold().blue());
println!();
let providers = config_manager.list_providers()?;
let current_provider = config_manager.get_current_provider()?;
for provider in &providers {
let is_current = current_provider
.as_ref()
.map(|current| current.id == provider.id)
.unwrap_or(false);
let status = if is_current {
"ACTIVE".green().bold()
} else {
"INACTIVE".dimmed()
};
let provider_name = get_provider_display_name(&provider.provider_type, true);
println!(" {} {}", status, provider_name.bold());
println!(" ID: {}", provider.id.dimmed());
if let Some(model) = &provider.model {
println!(" Model: {}", model.dimmed());
}
if !provider.api_key.is_empty() {
println!(" API Key: {}***", &provider.api_key[..provider.api_key.len().min(8)].dimmed());
}
println!();
}
Ok(())
}
fn switch_provider(config_manager: &mut ConfigManager, provider_id: &str) -> Result<()> {
let providers = config_manager.list_providers()?;
let provider = providers.iter()
.find(|p| p.id == provider_id || p.name == provider_id || p.provider_type == provider_id)
.context(format!("Provider '{}' not found", provider_id))?;
println!("{}", "Switching provider...".yellow());
config_manager.switch_provider(&provider.id)?;
let provider_name = get_provider_display_name(&provider.provider_type, false);
println!("{} {}", "✓ Successfully switched to:".green(), provider_name.bold());
println!("{}", "Please restart Claude Code to apply the changes.".dimmed());
Ok(())
}
fn show_current_provider(config_manager: &ConfigManager) -> Result<()> {
if let Some(current_provider) = config_manager.get_current_provider()? {
println!("{}", "Current Active Provider:".bold().blue());
println!();
let provider_name = get_provider_display_name(¤t_provider.provider_type, true);
println!(" {}", provider_name.bold());
println!(" ID: {}", current_provider.id.dimmed());
println!(" Type: {}", current_provider.provider_type.dimmed());
if let Some(model) = ¤t_provider.model {
println!(" Model: {}", model.dimmed());
}
if let Some(base_url) = ¤t_provider.base_url {
println!(" Base URL: {}", base_url.dimmed());
}
if !current_provider.api_key.is_empty() {
println!(" API Key: {}***", ¤t_provider.api_key[..current_provider.api_key.len().min(8)].dimmed());
}
} else {
println!("{}", "No active provider found. Use 'cckit switch <provider>' to set one.".yellow());
}
Ok(())
}
fn configure_provider(
config_manager: &mut ConfigManager,
provider_id: &str,
api_key: Option<String>,
base_url: Option<String>,
model: Option<String>,
) -> Result<()> {
let providers = config_manager.list_providers()?;
let provider_index = providers.iter()
.position(|p| p.id == provider_id || p.name == provider_id || p.provider_type == provider_id);
let mut provider = if let Some(index) = provider_index {
providers[index].clone()
} else {
let (provider_type, name) = match provider_id {
"zhipu" | "智普" => ("zhipu", "智普LLM"),
"minimax" => ("minimax", "MiniMax"),
"kimi" => ("kimi", "Kimi"),
"claude" => ("claude", "Claude"),
_ => return Err(anyhow::anyhow!("Unknown provider: {}", provider_id)),
};
ModelProvider {
id: Uuid::new_v4().to_string(),
name: name.to_string(),
provider_type: provider_type.to_string(),
api_key: String::new(),
base_url: get_default_base_url(provider_type),
model: get_default_model(provider_type),
created_at: Utc::now(),
updated_at: Utc::now(),
}
};
if let Some(key) = api_key {
provider.api_key = key;
}
if let Some(url) = base_url {
provider.base_url = Some(url);
}
if let Some(m) = model {
provider.model = Some(m);
}
provider.updated_at = Utc::now();
config_manager.save_provider(&provider)?;
let provider_name = get_provider_display_name(&provider.provider_type, false);
println!("{} {}", "✓ Successfully configured:".green(), provider_name.bold());
if !provider.api_key.is_empty() {
println!(" API Key: {}***", &provider.api_key[..provider.api_key.len().min(8)].dimmed());
}
if let Some(base_url) = &provider.base_url {
println!(" Base URL: {}", base_url);
}
if let Some(model) = &provider.model {
println!(" Model: {}", model);
}
Ok(())
}
fn show_provider_config(config_manager: &ConfigManager, provider_id: &str) -> Result<()> {
let providers = config_manager.list_providers()?;
let provider = providers.iter()
.find(|p| p.id == provider_id || p.name == provider_id || p.provider_type == provider_id)
.context(format!("Provider '{}' not found", provider_id))?;
let provider_name = get_provider_display_name(&provider.provider_type, false);
println!("{}", format!("Configuration for {}:", provider_name).bold().blue());
println!();
println!(" ID: {}", provider.id);
println!(" Name: {}", provider.name);
println!(" Type: {}", provider.provider_type);
println!(" Created: {}", provider.created_at.format("%Y-%m-%d %H:%M:%S UTC"));
println!(" Updated: {}", provider.updated_at.format("%Y-%m-%d %H:%M:%S UTC"));
if !provider.api_key.is_empty() {
println!(" API Key: {}***", &provider.api_key[..provider.api_key.len().min(8)]);
} else {
println!(" API Key: {}", "Not configured".red());
}
if let Some(base_url) = &provider.base_url {
println!(" Base URL: {}", base_url);
}
if let Some(model) = &provider.model {
println!(" Model: {}", model);
}
Ok(())
}
async fn test_provider(config_manager: &ConfigManager, provider_id: &str) -> Result<()> {
let providers = config_manager.list_providers()?;
let provider = providers.iter()
.find(|p| p.id == provider_id || p.name == provider_id || p.provider_type == provider_id)
.context(format!("Provider '{}' not found", provider_id))?;
println!("{}", format!("Testing connection to {}...", provider.name).yellow());
if provider.api_key.is_empty() {
return Err(anyhow::anyhow!("Provider not configured. Please set API key first."));
}
match provider.provider_type.as_str() {
"zhipu" => test_zhipu_provider(provider).await,
"minimax" => test_minimax_provider(provider).await,
"kimi" => test_kimi_provider(provider).await,
"claude" => test_claude_provider(provider).await,
_ => Err(anyhow::anyhow!("Unsupported provider type: {}", provider.provider_type)),
}
}
fn export_config(config_manager: &ConfigManager, output: Option<String>) -> Result<()> {
let providers = config_manager.list_providers()?;
let current_provider = config_manager.get_current_provider()?;
let export_data = json!({
"providers": providers,
"current_provider": current_provider,
"exported_at": Utc::now().to_rfc3339(),
"version": "0.1.0"
});
let json_str = serde_json::to_string_pretty(&export_data)?;
if let Some(output_path) = output {
std::fs::write(&output_path, json_str)?;
println!("{} {}", "✓ Configuration exported to:".green(), output_path.bold());
} else {
println!("{}", "Configuration:".bold().blue());
println!("{}", json_str);
}
Ok(())
}
fn import_config(config_manager: &mut ConfigManager, file: &str) -> Result<()> {
let content = std::fs::read_to_string(file)?;
let import_data: Value = serde_json::from_str(&content)?;
if let Some(providers_array) = import_data.get("providers").and_then(|p| p.as_array()) {
for provider_value in providers_array {
if let Ok(provider) = serde_json::from_value::<ModelProvider>(provider_value.clone()) {
config_manager.save_provider(&provider)?;
}
}
}
if let Some(current_provider) = import_data.get("current_provider") {
if let Ok(provider) = serde_json::from_value::<ModelProvider>(current_provider.clone()) {
config_manager.switch_provider(&provider.id)?;
}
}
println!("{} {}", "✓ Configuration imported from:".green(), file.bold());
Ok(())
}
fn reset_to_default(config_manager: &mut ConfigManager) -> Result<()> {
println!("{}", "Resetting to default Claude configuration...".yellow());
config_manager.reset_to_claude_default()?;
println!("{}", "✓ Reset to default Claude configuration".green());
println!("{}", "Please restart Claude Code to apply the changes.".dimmed());
Ok(())
}