mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
refactor(commands): improve terminal launch code structure and fix env vars
重构 open_provider_terminal 相关代码,提升可维护性和可读性。 主要改进: - 将 launch_terminal_with_env 拆分为多个职责单一的小函数 * write_claude_config: 写入配置文件 * escape_shell_path: 转义 shell 路径 * generate_wrapper_script: 生成包装脚本 * launch_macos_terminal / launch_linux_terminal / launch_windows_terminal: 平台特定启动逻辑 - 使用 let Some else 提前返回模式,减少嵌套 - 修复 Gemini 环境变量名为 GEMINI_API_KEY(而非 GOOGLE_API_KEY) - 完善临时文件清理逻辑: * macOS/Linux: 使用 trap EXIT 自动清理 * Windows: 批处理文件自删除 - 代码格式化和 import 排序优化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -75,8 +75,7 @@ mod tests {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_macos_app_bundle_path_valid() {
|
fn test_get_macos_app_bundle_path_valid() {
|
||||||
let exe_path =
|
let exe_path = std::path::Path::new("/Applications/CC Switch.app/Contents/MacOS/CC Switch");
|
||||||
std::path::Path::new("/Applications/CC Switch.app/Contents/MacOS/CC Switch");
|
|
||||||
let result = get_macos_app_bundle_path(exe_path);
|
let result = get_macos_app_bundle_path(exe_path);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
@@ -92,7 +91,9 @@ mod tests {
|
|||||||
let result = get_macos_app_bundle_path(exe_path);
|
let result = get_macos_app_bundle_path(exe_path);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Some(std::path::PathBuf::from("/Users/test/My Apps/CC Switch.app"))
|
Some(std::path::PathBuf::from(
|
||||||
|
"/Users/test/My Apps/CC Switch.app"
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+209
-195
@@ -3,10 +3,10 @@
|
|||||||
use crate::app_config::AppType;
|
use crate::app_config::AppType;
|
||||||
use crate::init_status::InitErrorPayload;
|
use crate::init_status::InitErrorPayload;
|
||||||
use crate::services::ProviderService;
|
use crate::services::ProviderService;
|
||||||
|
use std::str::FromStr;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
@@ -304,7 +304,8 @@ pub async fn open_provider_terminal(
|
|||||||
let providers = ProviderService::list(state.inner(), app_type.clone())
|
let providers = ProviderService::list(state.inner(), app_type.clone())
|
||||||
.map_err(|e| format!("获取提供商列表失败: {e}"))?;
|
.map_err(|e| format!("获取提供商列表失败: {e}"))?;
|
||||||
|
|
||||||
let provider = providers.get(&providerId)
|
let provider = providers
|
||||||
|
.get(&providerId)
|
||||||
.ok_or_else(|| format!("提供商 {providerId} 不存在"))?;
|
.ok_or_else(|| format!("提供商 {providerId} 不存在"))?;
|
||||||
|
|
||||||
// 从提供商配置中提取环境变量
|
// 从提供商配置中提取环境变量
|
||||||
@@ -324,51 +325,43 @@ fn extract_env_vars_from_config(
|
|||||||
) -> Vec<(String, String)> {
|
) -> Vec<(String, String)> {
|
||||||
let mut env_vars = Vec::new();
|
let mut env_vars = Vec::new();
|
||||||
|
|
||||||
if let Some(obj) = config.as_object() {
|
let Some(obj) = config.as_object() else {
|
||||||
// Claude 使用 env 字段
|
return env_vars;
|
||||||
if let Some(env) = obj.get("env").and_then(|v| v.as_object()) {
|
};
|
||||||
for (key, value) in env {
|
|
||||||
if let Some(str_val) = value.as_str() {
|
// 处理 env 字段(Claude/Gemini 通用)
|
||||||
env_vars.push((key.clone(), str_val.to_string()));
|
if let Some(env) = obj.get("env").and_then(|v| v.as_object()) {
|
||||||
}
|
for (key, value) in env {
|
||||||
|
if let Some(str_val) = value.as_str() {
|
||||||
|
env_vars.push((key.clone(), str_val.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codex 使用 auth 字段
|
// 处理 base_url: 根据应用类型添加对应的环境变量
|
||||||
|
let base_url_key = match app_type {
|
||||||
|
AppType::Claude => Some("ANTHROPIC_BASE_URL"),
|
||||||
|
AppType::Gemini => Some("GOOGLE_GEMINI_BASE_URL"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(key) = base_url_key {
|
||||||
|
if let Some(url_str) = env.get(key).and_then(|v| v.as_str()) {
|
||||||
|
env_vars.push((key.to_string(), url_str.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codex 使用 auth 字段转换为 OPENAI_API_KEY
|
||||||
|
if *app_type == AppType::Codex {
|
||||||
if let Some(auth) = obj.get("auth").and_then(|v| v.as_str()) {
|
if let Some(auth) = obj.get("auth").and_then(|v| v.as_str()) {
|
||||||
match app_type {
|
env_vars.push(("OPENAI_API_KEY".to_string(), auth.to_string()));
|
||||||
AppType::Codex => {
|
|
||||||
env_vars.push(("OPENAI_API_KEY".to_string(), auth.to_string()));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Gemini 使用 API_KEY
|
// Gemini 使用 api_key 字段转换为 GEMINI_API_KEY
|
||||||
|
if *app_type == AppType::Gemini {
|
||||||
if let Some(api_key) = obj.get("api_key").and_then(|v| v.as_str()) {
|
if let Some(api_key) = obj.get("api_key").and_then(|v| v.as_str()) {
|
||||||
match app_type {
|
env_vars.push(("GEMINI_API_KEY".to_string(), api_key.to_string()));
|
||||||
AppType::Gemini => {
|
|
||||||
env_vars.push(("GEMINI_API_KEY".to_string(), api_key.to_string()));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取 base_url(如果存在)
|
|
||||||
if let Some(env) = obj.get("env").and_then(|v| v.as_object()) {
|
|
||||||
if let Some(base_url) = env.get("ANTHROPIC_BASE_URL").or_else(|| env.get("GOOGLE_GEMINI_BASE_URL")) {
|
|
||||||
if let Some(url_str) = base_url.as_str() {
|
|
||||||
match app_type {
|
|
||||||
AppType::Claude => {
|
|
||||||
env_vars.push(("ANTHROPIC_BASE_URL".to_string(), url_str.to_string()));
|
|
||||||
}
|
|
||||||
AppType::Gemini => {
|
|
||||||
env_vars.push(("GOOGLE_GEMINI_BASE_URL".to_string(), url_str.to_string()));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,180 +370,201 @@ fn extract_env_vars_from_config(
|
|||||||
|
|
||||||
/// 创建临时配置文件并启动 claude 终端
|
/// 创建临时配置文件并启动 claude 终端
|
||||||
/// 使用 --settings 参数传入提供商特定的 API 配置
|
/// 使用 --settings 参数传入提供商特定的 API 配置
|
||||||
fn launch_terminal_with_env(env_vars: Vec<(String, String)>, provider_id: &str) -> Result<(), String> {
|
fn launch_terminal_with_env(
|
||||||
use std::process::Command;
|
env_vars: Vec<(String, String)>,
|
||||||
|
provider_id: &str,
|
||||||
// 创建临时配置文件,使用提供商ID和进程ID确保唯一性
|
) -> Result<(), String> {
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_dir = std::env::temp_dir();
|
||||||
let config_file = temp_dir.join(format!("claude_{}_{}.json", provider_id, std::process::id()));
|
let config_file = temp_dir.join(format!(
|
||||||
|
"claude_{}_{}.json",
|
||||||
|
provider_id,
|
||||||
|
std::process::id()
|
||||||
|
));
|
||||||
|
|
||||||
// 构建 claude 配置 JSON 格式
|
// 创建并写入配置文件
|
||||||
let mut config_obj = serde_json::Map::new();
|
write_claude_config(&config_file, &env_vars)?;
|
||||||
let mut env_obj = serde_json::Map::new();
|
|
||||||
|
|
||||||
for (key, value) in &env_vars {
|
|
||||||
env_obj.insert(key.clone(), serde_json::Value::String(value.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
config_obj.insert("env".to_string(), serde_json::Value::Object(env_obj));
|
|
||||||
|
|
||||||
let config_json = serde_json::to_string_pretty(&config_obj)
|
|
||||||
.map_err(|e| format!("序列化配置失败: {e}"))?;
|
|
||||||
|
|
||||||
// 写入临时配置文件
|
|
||||||
std::fs::write(&config_file, config_json)
|
|
||||||
.map_err(|e| format!("写入配置文件失败: {e}"))?;
|
|
||||||
|
|
||||||
// 转义配置文件路径用于 shell
|
// 转义配置文件路径用于 shell
|
||||||
let config_path_escaped = config_file.to_string_lossy()
|
let config_path_escaped = escape_shell_path(&config_file);
|
||||||
.replace('\\', "\\\\")
|
|
||||||
.replace('"', "\\\"")
|
|
||||||
.replace('$', "\\$")
|
|
||||||
.replace(' ', "\\ ");
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
// macOS: 使用 Terminal.app 启动 claude,使用包装脚本来处理清理
|
launch_macos_terminal(&config_file, &config_path_escaped)?;
|
||||||
let mut terminal_cmd = Command::new("osascript");
|
|
||||||
terminal_cmd.arg("-e");
|
|
||||||
|
|
||||||
// 使用 bash 的 trap 来清理配置文件
|
|
||||||
let config_path_for_script = config_file.to_string_lossy()
|
|
||||||
.replace('\\', "\\\\")
|
|
||||||
.replace('"', "\\\"");
|
|
||||||
|
|
||||||
let shell_script = format!(
|
|
||||||
"bash -c 'trap \"rm -f \\\"{}\\\"\" EXIT; echo \"Using provider-specific claude config:\"; echo \"{}\"; claude --settings \"{}\"; exec bash --norc --noprofile'",
|
|
||||||
config_path_for_script,
|
|
||||||
config_path_escaped,
|
|
||||||
config_path_escaped
|
|
||||||
);
|
|
||||||
|
|
||||||
let script = format!(
|
|
||||||
r#"tell application "Terminal"
|
|
||||||
activate
|
|
||||||
do script "{}"
|
|
||||||
end tell"#,
|
|
||||||
shell_script.replace('\\', "\\\\").replace('"', "\\\"")
|
|
||||||
);
|
|
||||||
|
|
||||||
terminal_cmd.arg(&script);
|
|
||||||
|
|
||||||
terminal_cmd
|
|
||||||
.spawn()
|
|
||||||
.map_err(|e| format!("启动 macOS 终端失败: {e}"))?;
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
// Linux: 尝试使用常见终端,使用包装脚本在 shell 退出时清理配置文件
|
launch_linux_terminal(&config_file, &config_path_escaped)?;
|
||||||
let terminals = [
|
Ok(())
|
||||||
"gnome-terminal", "konsole", "xfce4-terminal",
|
|
||||||
"mate-terminal", "lxterminal", "alacritty", "kitty",
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut last_error = String::from("未找到可用的终端");
|
|
||||||
|
|
||||||
// 使用 bash 创建包装脚本来处理清理
|
|
||||||
let config_path_for_bash = config_file.to_string_lossy();
|
|
||||||
let shell_cmd = format!(
|
|
||||||
"bash -c 'trap \"rm -f \\\"{}\\\"\" EXIT; echo \"Using provider-specific claude config:\"; echo \"{}\"; claude --settings \"{}\"; exec bash --norc --noprofile'",
|
|
||||||
config_path_for_bash, config_path_escaped, config_path_escaped
|
|
||||||
);
|
|
||||||
|
|
||||||
for terminal in terminals {
|
|
||||||
// 检查终端是否存在
|
|
||||||
if Command::new("which").arg(terminal).output().is_err() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不同的终端使用不同的参数格式
|
|
||||||
let result = match terminal {
|
|
||||||
"gnome-terminal" | "mate-terminal" => {
|
|
||||||
Command::new(terminal)
|
|
||||||
.arg("--")
|
|
||||||
.arg("bash")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(&shell_cmd)
|
|
||||||
.spawn()
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
Command::new(terminal)
|
|
||||||
.arg("-e")
|
|
||||||
.arg("bash")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(&shell_cmd)
|
|
||||||
.spawn()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
last_error = format!("启动 {} 失败: {}", terminal, e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果所有终端都失败,清理配置文件
|
|
||||||
let _ = std::fs::remove_file(&config_file);
|
|
||||||
return Err(last_error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use std::io::Write;
|
launch_windows_terminal(&temp_dir, &config_file)?;
|
||||||
|
|
||||||
// Windows: 创建临时批处理文件,并在执行完毕后清理配置文件
|
|
||||||
let bat_file = temp_dir.join(format!("cc_switch_claude_{}.bat", std::process::id()));
|
|
||||||
|
|
||||||
// 转义配置文件路径用于批处理
|
|
||||||
let config_path_for_batch = config_file.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
.replace('&', "^&");
|
|
||||||
|
|
||||||
let mut content = String::from("@echo off\n");
|
|
||||||
content.push_str(&format!("echo Using provider-specific claude config:\n"));
|
|
||||||
content.push_str(&format!("echo {}\n", config_path_for_batch));
|
|
||||||
content.push_str(&format!("claude --settings \"{}\"\n", config_path_for_batch));
|
|
||||||
|
|
||||||
// 在 claude 执行完毕后(无论成功与否),删除临时配置文件和批处理文件本身
|
|
||||||
content.push_str(&format!("del \"{}\" >nul 2>&1\n", config_path_for_batch));
|
|
||||||
content.push_str(&format!("del \"%%~f0\" >nul 2>&1\n")); // %%~f0 表示批处理文件自身
|
|
||||||
|
|
||||||
// 如果 claude 出错,暂停以便用户查看错误信息
|
|
||||||
content.push_str("if errorlevel 1 (\n");
|
|
||||||
content.push_str(" echo.\n");
|
|
||||||
content.push_str(" echo Press any key to close...\n");
|
|
||||||
content.push_str(" pause >nul\n");
|
|
||||||
content.push_str(")\n");
|
|
||||||
|
|
||||||
std::fs::write(&bat_file, content)
|
|
||||||
.map_err(|e| format!("写入批处理文件失败: {e}"))?;
|
|
||||||
|
|
||||||
// 启动新的 cmd 窗口执行批处理文件
|
|
||||||
Command::new("cmd")
|
|
||||||
.args(["/C", "start", "cmd", "/C", &bat_file.to_string_lossy().to_string()])
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.spawn()
|
|
||||||
.map_err(|e| format!("启动 Windows 终端失败: {e}"))?;
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这个代码在所有支持的平台上都不可达,因为前面的平台特定块都已经返回了
|
|
||||||
// 使用 cfg 和 allow 来避免编译器警告和错误
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
|
||||||
#[allow(unreachable_code)]
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||||
Err("不支持的操作系统".to_string())
|
Err("不支持的操作系统".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 写入 claude 配置文件
|
||||||
|
fn write_claude_config(
|
||||||
|
config_file: &std::path::Path,
|
||||||
|
env_vars: &[(String, String)],
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut config_obj = serde_json::Map::new();
|
||||||
|
let mut env_obj = serde_json::Map::new();
|
||||||
|
|
||||||
|
for (key, value) in env_vars {
|
||||||
|
env_obj.insert(key.clone(), serde_json::Value::String(value.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
config_obj.insert("env".to_string(), serde_json::Value::Object(env_obj));
|
||||||
|
|
||||||
|
let config_json =
|
||||||
|
serde_json::to_string_pretty(&config_obj).map_err(|e| format!("序列化配置失败: {e}"))?;
|
||||||
|
|
||||||
|
std::fs::write(config_file, config_json).map_err(|e| format!("写入配置文件失败: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 转义 shell 路径
|
||||||
|
fn escape_shell_path(path: &std::path::Path) -> String {
|
||||||
|
path.to_string_lossy()
|
||||||
|
.replace('\\', "\\\\")
|
||||||
|
.replace('"', "\\\"")
|
||||||
|
.replace('$', "\\$")
|
||||||
|
.replace(' ', "\\ ")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 生成 bash 包装脚本,用于清理临时文件
|
||||||
|
fn generate_wrapper_script(config_path: &str, escaped_path: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"bash -c 'trap \"rm -f \\\"{}\\\"\" EXIT; echo \"Using provider-specific claude config:\"; echo \"{}\"; claude --settings \"{}\"; exec bash --norc --noprofile'",
|
||||||
|
config_path, escaped_path, escaped_path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// macOS: 使用 Terminal.app 启动
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn launch_macos_terminal(
|
||||||
|
config_file: &std::path::Path,
|
||||||
|
config_path_escaped: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let config_path_for_script = config_file.to_string_lossy().replace('\"', "\\\"");
|
||||||
|
|
||||||
|
let shell_script = generate_wrapper_script(&config_path_for_script, config_path_escaped);
|
||||||
|
|
||||||
|
let script = format!(
|
||||||
|
r#"tell application "Terminal"
|
||||||
|
activate
|
||||||
|
do script "{}"
|
||||||
|
end tell"#,
|
||||||
|
shell_script.replace('\"', "\\\"")
|
||||||
|
);
|
||||||
|
|
||||||
|
Command::new("osascript")
|
||||||
|
.arg("-e")
|
||||||
|
.arg(&script)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("启动 macOS 终端失败: {e}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Linux: 尝试使用常见终端启动
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn launch_linux_terminal(
|
||||||
|
config_file: &std::path::Path,
|
||||||
|
config_path_escaped: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let terminals = [
|
||||||
|
"gnome-terminal",
|
||||||
|
"konsole",
|
||||||
|
"xfce4-terminal",
|
||||||
|
"mate-terminal",
|
||||||
|
"lxterminal",
|
||||||
|
"alacritty",
|
||||||
|
"kitty",
|
||||||
|
];
|
||||||
|
|
||||||
|
let config_path_for_bash = config_file.to_string_lossy();
|
||||||
|
let shell_cmd = generate_wrapper_script(&config_path_for_bash, config_path_escaped);
|
||||||
|
|
||||||
|
let mut last_error = String::from("未找到可用的终端");
|
||||||
|
|
||||||
|
for terminal in terminals {
|
||||||
|
// 检查终端是否存在
|
||||||
|
if std::path::Path::new(&format!("/usr/bin/{}", terminal)).exists()
|
||||||
|
|| std::path::Path::new(&format!("/bin/{}", terminal)).exists()
|
||||||
|
{
|
||||||
|
let result = match terminal {
|
||||||
|
"gnome-terminal" | "mate-terminal" => Command::new(terminal)
|
||||||
|
.arg("--")
|
||||||
|
.arg("bash")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&shell_cmd)
|
||||||
|
.spawn(),
|
||||||
|
_ => Command::new(terminal)
|
||||||
|
.arg("-e")
|
||||||
|
.arg("bash")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&shell_cmd)
|
||||||
|
.spawn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
last_error = format!("启动 {} 失败: {}", terminal, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理配置文件
|
||||||
|
let _ = std::fs::remove_file(config_file);
|
||||||
|
Err(last_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Windows: 创建临时批处理文件启动
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn launch_windows_terminal(
|
||||||
|
temp_dir: &std::path::Path,
|
||||||
|
config_file: &std::path::Path,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let bat_file = temp_dir.join(format!("cc_switch_claude_{}.bat", std::process::id()));
|
||||||
|
let config_path_for_batch = config_file.to_string_lossy().replace('&', "^&");
|
||||||
|
|
||||||
|
let content = format!(
|
||||||
|
"@echo off
|
||||||
|
echo Using provider-specific claude config:
|
||||||
|
echo {}
|
||||||
|
claude --settings \"{}\"
|
||||||
|
del \"{}\" >nul 2>&1
|
||||||
|
del \"%~f0\" >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo Press any key to close...
|
||||||
|
pause >nul
|
||||||
|
)",
|
||||||
|
config_path_for_batch, config_path_for_batch, config_path_for_batch
|
||||||
|
);
|
||||||
|
|
||||||
|
std::fs::write(&bat_file, content).map_err(|e| format!("写入批处理文件失败: {e}"))?;
|
||||||
|
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(["/C", "start", "cmd", "/C", &bat_file.to_string_lossy()])
|
||||||
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("启动 Windows 终端失败: {e}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,22 +12,22 @@ const UNIVERSAL_PROVIDERS_KEY: &str = "universal_providers";
|
|||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
/// 获取所有统一供应商
|
/// 获取所有统一供应商
|
||||||
pub fn get_all_universal_providers(&self) -> Result<HashMap<String, UniversalProvider>, AppError> {
|
pub fn get_all_universal_providers(
|
||||||
|
&self,
|
||||||
|
) -> Result<HashMap<String, UniversalProvider>, AppError> {
|
||||||
let conn = lock_conn!(self.conn);
|
let conn = lock_conn!(self.conn);
|
||||||
|
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT value FROM settings WHERE key = ?")
|
.prepare("SELECT value FROM settings WHERE key = ?")
|
||||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||||
|
|
||||||
let result: Option<String> = stmt
|
let result: Option<String> = stmt
|
||||||
.query_row([UNIVERSAL_PROVIDERS_KEY], |row| row.get(0))
|
.query_row([UNIVERSAL_PROVIDERS_KEY], |row| row.get(0))
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(json) => {
|
Some(json) => serde_json::from_str(&json)
|
||||||
serde_json::from_str(&json)
|
.map_err(|e| AppError::Database(format!("解析统一供应商数据失败: {e}"))),
|
||||||
.map_err(|e| AppError::Database(format!("解析统一供应商数据失败: {e}")))
|
|
||||||
}
|
|
||||||
None => Ok(HashMap::new()),
|
None => Ok(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,14 +62,13 @@ impl Database {
|
|||||||
) -> Result<(), AppError> {
|
) -> Result<(), AppError> {
|
||||||
let conn = lock_conn!(self.conn);
|
let conn = lock_conn!(self.conn);
|
||||||
let json = to_json_string(providers)?;
|
let json = to_json_string(providers)?;
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
||||||
[UNIVERSAL_PROVIDERS_KEY, &json],
|
[UNIVERSAL_PROVIDERS_KEY, &json],
|
||||||
)
|
)
|
||||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ mod tray;
|
|||||||
mod usage_script;
|
mod usage_script;
|
||||||
|
|
||||||
pub use app_config::{AppType, McpApps, McpServer, MultiAppConfig};
|
pub use app_config::{AppType, McpApps, McpServer, MultiAppConfig};
|
||||||
pub use commands::open_provider_terminal;
|
|
||||||
pub use codex_config::{get_codex_auth_path, get_codex_config_path, write_codex_live_atomic};
|
pub use codex_config::{get_codex_auth_path, get_codex_config_path, write_codex_live_atomic};
|
||||||
|
pub use commands::open_provider_terminal;
|
||||||
pub use commands::*;
|
pub use commands::*;
|
||||||
pub use config::{get_claude_mcp_path, get_claude_settings_path, read_json_file};
|
pub use config::{get_claude_mcp_path, get_claude_settings_path, read_json_file};
|
||||||
pub use database::Database;
|
pub use database::Database;
|
||||||
|
|||||||
Reference in New Issue
Block a user