//! Skills 命令层 //! //! v3.10.0+ 统一管理架构: //! - 支持三应用开关(Claude/Codex/Gemini) //! - SSOT 存储在 ~/.cc-switch/skills/ use crate::app_config::{AppType, InstalledSkill, UnmanagedSkill}; use crate::error::format_skill_error; use crate::services::skill::{ DiscoverableSkill, ImportSkillSelection, Skill, SkillBackupEntry, SkillRepo, SkillService, SkillUninstallResult, }; use crate::store::AppState; use std::sync::Arc; use tauri::State; /// SkillService 状态包装 pub struct SkillServiceState(pub Arc); /// 解析 app 参数为 AppType fn parse_app_type(app: &str) -> Result { match app.to_lowercase().as_str() { "claude" => Ok(AppType::Claude), "codex" => Ok(AppType::Codex), "gemini" => Ok(AppType::Gemini), "opencode" => Ok(AppType::OpenCode), _ => Err(format!("不支持的 app 类型: {app}")), } } // ========== 统一管理命令 ========== /// 获取所有已安装的 Skills #[tauri::command] pub fn get_installed_skills(app_state: State<'_, AppState>) -> Result, String> { SkillService::get_all_installed(&app_state.db).map_err(|e| e.to_string()) } #[tauri::command] pub fn get_skill_backups() -> Result, String> { SkillService::list_backups().map_err(|e| e.to_string()) } #[tauri::command] pub fn delete_skill_backup(backup_id: String) -> Result { SkillService::delete_backup(&backup_id).map_err(|e| e.to_string())?; Ok(true) } /// 安装 Skill(新版统一安装) /// /// 参数: /// - skill: 从发现列表获取的技能信息 /// - current_app: 当前选中的应用,安装后默认启用该应用 #[tauri::command] pub async fn install_skill_unified( skill: DiscoverableSkill, current_app: String, service: State<'_, SkillServiceState>, app_state: State<'_, AppState>, ) -> Result { let app_type = parse_app_type(¤t_app)?; service .0 .install(&app_state.db, &skill, &app_type) .await .map_err(|e| e.to_string()) } /// 卸载 Skill(新版统一卸载) #[tauri::command] pub fn uninstall_skill_unified( id: String, app_state: State<'_, AppState>, ) -> Result { SkillService::uninstall(&app_state.db, &id).map_err(|e| e.to_string()) } #[tauri::command] pub fn restore_skill_backup( backup_id: String, current_app: String, app_state: State<'_, AppState>, ) -> Result { let app_type = parse_app_type(¤t_app)?; SkillService::restore_from_backup(&app_state.db, &backup_id, &app_type) .map_err(|e| e.to_string()) } /// 切换 Skill 的应用启用状态 #[tauri::command] pub fn toggle_skill_app( id: String, app: String, enabled: bool, app_state: State<'_, AppState>, ) -> Result { let app_type = parse_app_type(&app)?; SkillService::toggle_app(&app_state.db, &id, &app_type, enabled).map_err(|e| e.to_string())?; Ok(true) } /// 扫描未管理的 Skills #[tauri::command] pub fn scan_unmanaged_skills( app_state: State<'_, AppState>, ) -> Result, String> { SkillService::scan_unmanaged(&app_state.db).map_err(|e| e.to_string()) } /// 从应用目录导入 Skills #[tauri::command] pub fn import_skills_from_apps( imports: Vec, app_state: State<'_, AppState>, ) -> Result, String> { SkillService::import_from_apps(&app_state.db, imports).map_err(|e| e.to_string()) } // ========== 发现功能命令 ========== /// 发现可安装的 Skills(从仓库获取) #[tauri::command] pub async fn discover_available_skills( service: State<'_, SkillServiceState>, app_state: State<'_, AppState>, ) -> Result, String> { let repos = app_state.db.get_skill_repos().map_err(|e| e.to_string())?; service .0 .discover_available(repos) .await .map_err(|e| e.to_string()) } // ========== 兼容旧 API 的命令 ========== /// 获取技能列表(兼容旧 API) #[tauri::command] pub async fn get_skills( service: State<'_, SkillServiceState>, app_state: State<'_, AppState>, ) -> Result, String> { let repos = app_state.db.get_skill_repos().map_err(|e| e.to_string())?; service .0 .list_skills(repos, &app_state.db) .await .map_err(|e| e.to_string()) } /// 获取指定应用的技能列表(兼容旧 API) #[tauri::command] pub async fn get_skills_for_app( app: String, service: State<'_, SkillServiceState>, app_state: State<'_, AppState>, ) -> Result, String> { // 新版本不再区分应用,统一返回所有技能 let _ = parse_app_type(&app)?; // 验证 app 参数有效 get_skills(service, app_state).await } /// 安装技能(兼容旧 API) #[tauri::command] pub async fn install_skill( directory: String, service: State<'_, SkillServiceState>, app_state: State<'_, AppState>, ) -> Result { install_skill_for_app("claude".to_string(), directory, service, app_state).await } /// 安装指定应用的技能(兼容旧 API) #[tauri::command] pub async fn install_skill_for_app( app: String, directory: String, service: State<'_, SkillServiceState>, app_state: State<'_, AppState>, ) -> Result { let app_type = parse_app_type(&app)?; // 先获取技能信息 let repos = app_state.db.get_skill_repos().map_err(|e| e.to_string())?; let skills = service .0 .discover_available(repos) .await .map_err(|e| e.to_string())?; let skill = skills .into_iter() .find(|s| { let install_name = std::path::Path::new(&s.directory) .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| s.directory.clone()); install_name.eq_ignore_ascii_case(&directory) || s.directory.eq_ignore_ascii_case(&directory) }) .ok_or_else(|| { format_skill_error( "SKILL_NOT_FOUND", &[("directory", &directory)], Some("checkRepoUrl"), ) })?; service .0 .install(&app_state.db, &skill, &app_type) .await .map_err(|e| e.to_string())?; Ok(true) } /// 卸载技能(兼容旧 API) #[tauri::command] pub fn uninstall_skill( directory: String, app_state: State<'_, AppState>, ) -> Result { uninstall_skill_for_app("claude".to_string(), directory, app_state) } /// 卸载指定应用的技能(兼容旧 API) #[tauri::command] pub fn uninstall_skill_for_app( app: String, directory: String, app_state: State<'_, AppState>, ) -> Result { let _ = parse_app_type(&app)?; // 验证参数 // 通过 directory 找到对应的 skill id let skills = SkillService::get_all_installed(&app_state.db).map_err(|e| e.to_string())?; let skill = skills .into_iter() .find(|s| s.directory.eq_ignore_ascii_case(&directory)) .ok_or_else(|| format!("未找到已安装的 Skill: {directory}"))?; SkillService::uninstall(&app_state.db, &skill.id).map_err(|e| e.to_string()) } // ========== 仓库管理命令 ========== /// 获取技能仓库列表 #[tauri::command] pub fn get_skill_repos(app_state: State<'_, AppState>) -> Result, String> { app_state.db.get_skill_repos().map_err(|e| e.to_string()) } /// 添加技能仓库 #[tauri::command] pub fn add_skill_repo(repo: SkillRepo, app_state: State<'_, AppState>) -> Result { app_state .db .save_skill_repo(&repo) .map_err(|e| e.to_string())?; Ok(true) } /// 删除技能仓库 #[tauri::command] pub fn remove_skill_repo( owner: String, name: String, app_state: State<'_, AppState>, ) -> Result { app_state .db .delete_skill_repo(&owner, &name) .map_err(|e| e.to_string())?; Ok(true) } /// 从 ZIP 文件安装 Skills #[tauri::command] pub fn install_skills_from_zip( file_path: String, current_app: String, app_state: State<'_, AppState>, ) -> Result, String> { let app_type = parse_app_type(¤t_app)?; let path = std::path::Path::new(&file_path); SkillService::install_from_zip(&app_state.db, path, &app_type).map_err(|e| e.to_string()) }