소스 검색

删除环境变量

黄中银 2 주 전
부모
커밋
3aa558991a
7개의 변경된 파일270개의 추가작업 그리고 3개의 파일을 삭제
  1. 1 0
      src-tauri/Cargo.toml
  2. 213 0
      src-tauri/src/commands/claude_code.rs
  3. 1 0
      src-tauri/src/lib.rs
  4. 4 0
      src/api/tauri.ts
  5. 5 1
      src/i18n/en-US.ts
  6. 5 1
      src/i18n/zh-CN.ts
  7. 41 1
      src/views/ClaudeCodeView.vue

+ 1 - 0
src-tauri/Cargo.toml

@@ -39,6 +39,7 @@ regex = "1"
 
 [target.'cfg(windows)'.dependencies]
 winreg = "0.52"
+windows-sys = { version = "0.59", features = ["Win32_UI_WindowsAndMessaging"] }
 
 [target.'cfg(unix)'.dependencies]
 libc = "0.2"

+ 213 - 0
src-tauri/src/commands/claude_code.rs

@@ -3,6 +3,11 @@ use serde::{Deserialize, Serialize};
 use std::fs;
 use std::path::PathBuf;
 
+#[cfg(target_os = "windows")]
+use winreg::enums::*;
+#[cfg(target_os = "windows")]
+use winreg::RegKey;
+
 #[derive(Serialize, Deserialize)]
 pub struct ClaudeCodeStatus {
     pub installed: bool,
@@ -253,3 +258,211 @@ pub async fn open_claude_code_config() -> CommandResult {
         }
     }
 }
+
+/// Claude Code 相关的环境变量名称(用于用户验证和服务器地址)
+const CLAUDE_ENV_VARS: &[&str] = &[
+    // 用户验证相关
+    "ANTHROPIC_AUTH_TOKEN",
+    "ANTHROPIC_API_KEY",
+    "CLAUDE_API_KEY",
+    // 服务器地址相关
+    "ANTHROPIC_BASE_URL",
+    "CLAUDE_BASE_URL",
+    "ANTHROPIC_API_BASE",
+    // 模型配置相关
+    "ANTHROPIC_MODEL",
+    "ANTHROPIC_DEFAULT_MODEL",
+    "ANTHROPIC_DEFAULT_HAIKU_MODEL",
+    "ANTHROPIC_DEFAULT_OPUS_MODEL",
+    "ANTHROPIC_DEFAULT_SONNET_MODEL",
+];
+
+/// 删除 Claude Code 相关的环境变量
+#[tauri::command]
+pub async fn delete_claude_env_vars() -> CommandResult {
+    #[cfg(target_os = "windows")]
+    {
+        delete_claude_env_vars_windows()
+    }
+
+    #[cfg(target_os = "macos")]
+    {
+        delete_claude_env_vars_unix()
+    }
+
+    #[cfg(target_os = "linux")]
+    {
+        delete_claude_env_vars_unix()
+    }
+}
+
+/// Windows 平台删除环境变量
+#[cfg(target_os = "windows")]
+fn delete_claude_env_vars_windows() -> CommandResult {
+    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
+
+    let env_key = match hkcu.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) {
+        Ok(key) => key,
+        Err(e) => {
+            return CommandResult {
+                success: false,
+                error: Some(format!("Failed to open registry: {}", e)),
+            }
+        }
+    };
+
+    let mut deleted_vars = Vec::new();
+    let mut errors = Vec::new();
+
+    for var_name in CLAUDE_ENV_VARS {
+        // 检查环境变量是否存在
+        if env_key.get_value::<String, _>(*var_name).is_ok() {
+            match env_key.delete_value(*var_name) {
+                Ok(_) => {
+                    deleted_vars.push(*var_name);
+                    log::info!("Deleted environment variable: {}", var_name);
+                }
+                Err(e) => {
+                    errors.push(format!("{}: {}", var_name, e));
+                    log::error!("Failed to delete {}: {}", var_name, e);
+                }
+            }
+        }
+    }
+
+    // 通知系统环境变量已更改
+    if !deleted_vars.is_empty() {
+        unsafe {
+            use windows_sys::Win32::UI::WindowsAndMessaging::{
+                SendMessageTimeoutW, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
+            };
+            let param: Vec<u16> = "Environment\0".encode_utf16().collect();
+            SendMessageTimeoutW(
+                HWND_BROADCAST,
+                WM_SETTINGCHANGE,
+                0,
+                param.as_ptr() as isize,
+                SMTO_ABORTIFHUNG,
+                5000,
+                std::ptr::null_mut(),
+            );
+        }
+    }
+
+    if errors.is_empty() {
+        if deleted_vars.is_empty() {
+            CommandResult {
+                success: true,
+                error: None,
+            }
+        } else {
+            CommandResult {
+                success: true,
+                error: None,
+            }
+        }
+    } else {
+        CommandResult {
+            success: false,
+            error: Some(errors.join("; ")),
+        }
+    }
+}
+
+/// Unix 平台删除环境变量(macOS/Linux)
+#[cfg(any(target_os = "macos", target_os = "linux"))]
+fn delete_claude_env_vars_unix() -> CommandResult {
+    // 在 Unix 系统上,环境变量通常存储在 shell 配置文件中
+    // 我们需要从这些文件中移除相关的 export 语句
+    let home = match dirs::home_dir() {
+        Some(h) => h,
+        None => {
+            return CommandResult {
+                success: false,
+                error: Some("Cannot determine home directory".to_string()),
+            }
+        }
+    };
+
+    let shell_configs = [
+        ".bashrc",
+        ".bash_profile",
+        ".zshrc",
+        ".profile",
+    ];
+
+    let mut modified_files = Vec::new();
+    let mut errors = Vec::new();
+
+    for config_file in shell_configs {
+        let config_path = home.join(config_file);
+        if config_path.exists() {
+            match remove_env_vars_from_file(&config_path) {
+                Ok(modified) => {
+                    if modified {
+                        modified_files.push(config_file);
+                        log::info!("Modified shell config: {}", config_file);
+                    }
+                }
+                Err(e) => {
+                    errors.push(format!("{}: {}", config_file, e));
+                    log::error!("Failed to modify {}: {}", config_file, e);
+                }
+            }
+        }
+    }
+
+    if errors.is_empty() {
+        CommandResult {
+            success: true,
+            error: None,
+        }
+    } else {
+        CommandResult {
+            success: false,
+            error: Some(errors.join("; ")),
+        }
+    }
+}
+
+/// 从文件中移除 Claude 相关的环境变量导出语句
+#[cfg(any(target_os = "macos", target_os = "linux"))]
+fn remove_env_vars_from_file(path: &PathBuf) -> Result<bool, String> {
+    let content = fs::read_to_string(path).map_err(|e| e.to_string())?;
+
+    let mut modified = false;
+    let mut new_lines = Vec::new();
+
+    for line in content.lines() {
+        let trimmed = line.trim();
+        let mut should_remove = false;
+
+        for var_name in CLAUDE_ENV_VARS {
+            // 匹配 export VAR_NAME= 或 VAR_NAME= 格式
+            if trimmed.starts_with(&format!("export {}=", var_name))
+                || trimmed.starts_with(&format!("{}=", var_name))
+            {
+                should_remove = true;
+                modified = true;
+                break;
+            }
+        }
+
+        if !should_remove {
+            new_lines.push(line);
+        }
+    }
+
+    if modified {
+        let new_content = new_lines.join("\n");
+        // 保留原文件的换行符风格
+        let new_content = if content.ends_with('\n') {
+            format!("{}\n", new_content)
+        } else {
+            new_content
+        };
+        fs::write(path, new_content).map_err(|e| e.to_string())?;
+    }
+
+    Ok(modified)
+}

+ 1 - 0
src-tauri/src/lib.rs

@@ -63,6 +63,7 @@ pub fn run() {
             commands::claude_code::install_claude_code,
             commands::claude_code::launch_claude_code,
             commands::claude_code::open_claude_code_config,
+            commands::claude_code::delete_claude_env_vars,
             // VS Code 扩展
             commands::vscode::check_vscode_extension,
             commands::vscode::install_vscode_extension,

+ 4 - 0
src/api/tauri.ts

@@ -276,6 +276,10 @@ export const tauriAPI = {
     return await invoke('open_claude_code_config')
   },
 
+  async deleteClaudeEnvVars(): Promise<{ success: boolean; error?: string }> {
+    return await invoke('delete_claude_env_vars')
+  },
+
   // ==================== VS Code Extensions ====================
 
   async checkVscodeExtension(extensionId: string): Promise<{ installed: boolean; version?: string }> {

+ 5 - 1
src/i18n/en-US.ts

@@ -121,7 +121,11 @@ export default {
     refresh: 'Refresh Status',
     openConfig: 'Open Config File',
     openConfigFailed: 'Failed to open config file',
-    requiresNodejsGit: 'Requires Node.js and Git'
+    requiresNodejsGit: 'Requires Node.js and Git',
+    deleteEnvVars: 'Delete Env Vars',
+    deletingEnvVars: 'Deleting environment variables...',
+    deleteEnvVarsSuccess: 'Environment variables deleted',
+    deleteEnvVarsFailed: 'Failed to delete environment variables'
   },
   install: {
     start: 'Install',

+ 5 - 1
src/i18n/zh-CN.ts

@@ -121,7 +121,11 @@ export default {
     refresh: '刷新状态',
     openConfig: '打开配置文件',
     openConfigFailed: '打开配置文件失败',
-    requiresNodejsGit: '需要 Node.js 和 Git'
+    requiresNodejsGit: '需要 Node.js 和 Git',
+    deleteEnvVars: '删除环境变量',
+    deletingEnvVars: '正在删除环境变量...',
+    deleteEnvVarsSuccess: '环境变量已删除',
+    deleteEnvVarsFailed: '删除环境变量失败'
   },
   install: {
     start: '开始安装',

+ 41 - 1
src/views/ClaudeCodeView.vue

@@ -10,11 +10,14 @@ const installStore = useInstallStore()
 const isLaunching = ref(false)
 const isInstalling = ref(false)
 const isUninstalling = ref(false)
+const isDeletingEnvVars = ref(false)
 const launchError = ref('')
 const launchSuccess = ref(false)
 const installError = ref('')
 const uninstallError = ref('')
 const uninstallSuccess = ref(false)
+const deleteEnvVarsError = ref('')
+const deleteEnvVarsSuccess = ref(false)
 const isChecking = ref(false)
 
 const nodejsInstalled = computed(() => installStore.isInstalled('nodejs'))
@@ -35,12 +38,15 @@ const statusText = computed(() => {
   if (isChecking.value) return t('common.loading')
   if (isInstalling.value) return t('claudeCode.installing')
   if (isUninstalling.value) return t('claudeCode.uninstalling')
+  if (isDeletingEnvVars.value) return t('claudeCode.deletingEnvVars')
   if (isLaunching.value) return t('claudeCode.launching')
   if (launchSuccess.value) return t('claudeCode.launchSuccess')
   if (uninstallSuccess.value) return t('claudeCode.uninstallSuccess')
+  if (deleteEnvVarsSuccess.value) return t('claudeCode.deleteEnvVarsSuccess')
   if (launchError.value) return launchError.value
   if (installError.value) return installError.value
   if (uninstallError.value) return uninstallError.value
+  if (deleteEnvVarsError.value) return deleteEnvVarsError.value
   if (!nodejsInstalled.value) return t('claudeCode.nodejsRequired')
   if (!gitInstalled.value) return t('claudeCode.gitRequired')
   if (!claudeCodeInstalled.value) return t('claudeCode.notInstalled')
@@ -127,6 +133,32 @@ async function handleOpenConfig() {
     installStore.addLog(`${t('claudeCode.openConfigFailed')}: ${(error as Error).message}`, 'error')
   }
 }
+
+async function handleDeleteEnvVars() {
+  isDeletingEnvVars.value = true
+  deleteEnvVarsError.value = ''
+  deleteEnvVarsSuccess.value = false
+  launchSuccess.value = false
+  uninstallSuccess.value = false
+
+  installStore.addLog(t('claudeCode.deletingEnvVars'))
+
+  try {
+    const result = await window.electronAPI.deleteClaudeEnvVars()
+    if (result.success) {
+      deleteEnvVarsSuccess.value = true
+      installStore.addLog(t('claudeCode.deleteEnvVarsSuccess'))
+    } else {
+      deleteEnvVarsError.value = result.error || t('claudeCode.deleteEnvVarsFailed')
+      installStore.addLog(`${t('claudeCode.deleteEnvVarsFailed')}: ${result.error}`, 'error')
+    }
+  } catch (error) {
+    deleteEnvVarsError.value = (error as Error).message || t('claudeCode.deleteEnvVarsFailed')
+    installStore.addLog(`${t('claudeCode.deleteEnvVarsFailed')}: ${deleteEnvVarsError.value}`, 'error')
+  } finally {
+    isDeletingEnvVars.value = false
+  }
+}
 </script>
 
 <template>
@@ -191,13 +223,21 @@ async function handleOpenConfig() {
         <el-button :disabled="isChecking || isInstalling || isUninstalling" @click="refreshStatus">
           {{ t('claudeCode.refresh') }}
         </el-button>
+        <el-button
+          v-if="claudeCodeInstalled"
+          :disabled="isDeletingEnvVars || isChecking || isInstalling || isUninstalling"
+          :loading="isDeletingEnvVars"
+          @click="handleDeleteEnvVars"
+        >
+          {{ isDeletingEnvVars ? t('claudeCode.deletingEnvVars') : t('claudeCode.deleteEnvVars') }}
+        </el-button>
         <el-button v-if="claudeCodeInstalled" @click="handleOpenConfig">
           {{ t('claudeCode.openConfig') }}
         </el-button>
       </div>
 
       <div class="status-container">
-        <p :class="['status-text', { success: launchSuccess || uninstallSuccess, error: !!launchError || !!installError || !!uninstallError }]">
+        <p :class="['status-text', { success: launchSuccess || uninstallSuccess || deleteEnvVarsSuccess, error: !!launchError || !!installError || !!uninstallError || !!deleteEnvVarsError }]">
           {{ statusText }}
         </p>
       </div>