diff --git a/src-tauri/src/app_config.rs b/src-tauri/src/app_config.rs index 321d2c484..0262bf2c4 100644 --- a/src-tauri/src/app_config.rs +++ b/src-tauri/src/app_config.rs @@ -82,6 +82,8 @@ pub struct SkillApps { pub gemini: bool, #[serde(default)] pub opencode: bool, + #[serde(default)] + pub hermes: bool, } impl SkillApps { @@ -92,8 +94,8 @@ impl SkillApps { AppType::Codex => self.codex, AppType::Gemini => self.gemini, AppType::OpenCode => self.opencode, + AppType::Hermes => self.hermes, AppType::OpenClaw => false, // OpenClaw doesn't support Skills - AppType::Hermes => false, // Hermes doesn't support Skills yet } } @@ -104,8 +106,8 @@ impl SkillApps { AppType::Codex => self.codex = enabled, AppType::Gemini => self.gemini = enabled, AppType::OpenCode => self.opencode = enabled, + AppType::Hermes => self.hermes = enabled, AppType::OpenClaw => {} // OpenClaw doesn't support Skills, ignore - AppType::Hermes => {} // Hermes doesn't support Skills yet, ignore } } @@ -124,12 +126,15 @@ impl SkillApps { if self.opencode { apps.push(AppType::OpenCode); } + if self.hermes { + apps.push(AppType::Hermes); + } apps } /// 检查是否所有应用都未启用 pub fn is_empty(&self) -> bool { - !self.claude && !self.codex && !self.gemini && !self.opencode + !self.claude && !self.codex && !self.gemini && !self.opencode && !self.hermes } /// 仅启用指定应用(其他应用设为禁用) diff --git a/src-tauri/src/commands/skill.rs b/src-tauri/src/commands/skill.rs index 5bec2b17b..695e8d08a 100644 --- a/src-tauri/src/commands/skill.rs +++ b/src-tauri/src/commands/skill.rs @@ -25,6 +25,7 @@ fn parse_app_type(app: &str) -> Result { "codex" => Ok(AppType::Codex), "gemini" => Ok(AppType::Gemini), "opencode" => Ok(AppType::OpenCode), + "hermes" => Ok(AppType::Hermes), _ => Err(format!("不支持的 app 类型: {app}")), } } diff --git a/src-tauri/src/database/dao/skills.rs b/src-tauri/src/database/dao/skills.rs index 530541a1d..9c94eb5ec 100644 --- a/src-tauri/src/database/dao/skills.rs +++ b/src-tauri/src/database/dao/skills.rs @@ -23,7 +23,7 @@ impl Database { .prepare( "SELECT id, name, description, directory, repo_owner, repo_name, repo_branch, readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, - installed_at, content_hash, updated_at + enabled_hermes, installed_at, content_hash, updated_at FROM skills ORDER BY name ASC", ) .map_err(|e| AppError::Database(e.to_string()))?; @@ -44,10 +44,11 @@ impl Database { codex: row.get(9)?, gemini: row.get(10)?, opencode: row.get(11)?, + hermes: row.get(12)?, }, - installed_at: row.get(12)?, - content_hash: row.get(13)?, - updated_at: row.get::<_, i64>(14).unwrap_or(0), + installed_at: row.get(13)?, + content_hash: row.get(14)?, + updated_at: row.get::<_, i64>(15).unwrap_or(0), }) }) .map_err(|e| AppError::Database(e.to_string()))?; @@ -67,7 +68,7 @@ impl Database { .prepare( "SELECT id, name, description, directory, repo_owner, repo_name, repo_branch, readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, - installed_at, content_hash, updated_at + enabled_hermes, installed_at, content_hash, updated_at FROM skills WHERE id = ?1", ) .map_err(|e| AppError::Database(e.to_string()))?; @@ -87,10 +88,11 @@ impl Database { codex: row.get(9)?, gemini: row.get(10)?, opencode: row.get(11)?, + hermes: row.get(12)?, }, - installed_at: row.get(12)?, - content_hash: row.get(13)?, - updated_at: row.get::<_, i64>(14).unwrap_or(0), + installed_at: row.get(13)?, + content_hash: row.get(14)?, + updated_at: row.get::<_, i64>(15).unwrap_or(0), }) }); @@ -109,7 +111,7 @@ impl Database { (id, name, description, directory, repo_owner, repo_name, repo_branch, readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes, installed_at, content_hash, updated_at) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, 0, ?13, ?14, ?15)", + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)", params![ skill.id, skill.name, @@ -123,6 +125,7 @@ impl Database { skill.apps.codex, skill.apps.gemini, skill.apps.opencode, + skill.apps.hermes, skill.installed_at, skill.content_hash, skill.updated_at, @@ -154,8 +157,8 @@ impl Database { let conn = lock_conn!(self.conn); let affected = conn .execute( - "UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_opencode = ?4 WHERE id = ?5", - params![apps.claude, apps.codex, apps.gemini, apps.opencode, id], + "UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_opencode = ?4, enabled_hermes = ?5 WHERE id = ?6", + params![apps.claude, apps.codex, apps.gemini, apps.opencode, apps.hermes, id], ) .map_err(|e| AppError::Database(e.to_string()))?; Ok(affected > 0) diff --git a/src-tauri/tests/skill_sync.rs b/src-tauri/tests/skill_sync.rs index b1e0c3a61..bf960bbe7 100644 --- a/src-tauri/tests/skill_sync.rs +++ b/src-tauri/tests/skill_sync.rs @@ -53,10 +53,8 @@ fn import_from_apps_respects_explicit_app_selection() { vec![ImportSkillSelection { directory: "shared-skill".to_string(), apps: SkillApps { - claude: false, - codex: false, - gemini: false, opencode: true, + ..Default::default() }, }], ) @@ -103,12 +101,7 @@ fn sync_to_app_removes_disabled_and_orphaned_ssot_symlinks() { repo_name: None, repo_branch: None, readme_url: None, - apps: SkillApps { - claude: false, - codex: false, - gemini: false, - opencode: false, - }, + apps: SkillApps::default(), installed_at: 0, content_hash: None, updated_at: 0, @@ -151,9 +144,7 @@ fn uninstall_skill_creates_backup_before_removing_ssot() { readme_url: None, apps: SkillApps { claude: true, - codex: false, - gemini: false, - opencode: false, + ..Default::default() }, installed_at: 123, content_hash: None, @@ -221,9 +212,7 @@ fn restore_skill_backup_restores_files_to_ssot_and_current_app() { readme_url: None, apps: SkillApps { claude: true, - codex: false, - gemini: false, - opencode: false, + ..Default::default() }, installed_at: 456, content_hash: None, @@ -304,9 +293,7 @@ fn delete_skill_backup_removes_backup_directory() { readme_url: None, apps: SkillApps { claude: true, - codex: false, - gemini: false, - opencode: false, + ..Default::default() }, installed_at: 789, content_hash: None, diff --git a/src/App.tsx b/src/App.tsx index 01a1e105d..f9f9ca69c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1388,6 +1388,15 @@ function App() { > {activeApp === "hermes" ? ( <> +