mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(skills): enable Hermes in unified Skills management
Wire hermes through SkillApps struct, DAO SQL, command parser, and SKILLS_APP_IDS. Add a Skills entry to the Hermes toolbar. Simplify skill_sync test fixtures to use SkillApps::default().
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
/// 仅启用指定应用(其他应用设为禁用)
|
||||
|
||||
@@ -25,6 +25,7 @@ fn parse_app_type(app: &str) -> Result<AppType, String> {
|
||||
"codex" => Ok(AppType::Codex),
|
||||
"gemini" => Ok(AppType::Gemini),
|
||||
"opencode" => Ok(AppType::OpenCode),
|
||||
"hermes" => Ok(AppType::Hermes),
|
||||
_ => Err(format!("不支持的 app 类型: {app}")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1388,6 +1388,15 @@ function App() {
|
||||
>
|
||||
{activeApp === "hermes" ? (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("skills")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||
title={t("skills.manage")}
|
||||
>
|
||||
<Wrench className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
||||
@@ -24,16 +24,17 @@ export const APP_IDS: AppId[] = [
|
||||
"hermes",
|
||||
];
|
||||
|
||||
/** App IDs shown in Skills panels (excludes OpenClaw and Hermes — neither supports Skills) */
|
||||
/** App IDs shown in Skills panels (excludes OpenClaw — it doesn't support Skills) */
|
||||
export const SKILLS_APP_IDS: AppId[] = [
|
||||
"claude",
|
||||
"codex",
|
||||
"gemini",
|
||||
"opencode",
|
||||
"hermes",
|
||||
];
|
||||
|
||||
/** App IDs shown in MCP panels (excludes OpenClaw) */
|
||||
export const MCP_APP_IDS: AppId[] = [...SKILLS_APP_IDS, "hermes"];
|
||||
export const MCP_APP_IDS: AppId[] = [...SKILLS_APP_IDS];
|
||||
|
||||
export const APP_ICON_MAP: Record<AppId, AppConfig> = {
|
||||
claude: {
|
||||
|
||||
Reference in New Issue
Block a user