Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions src-tauri/src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct McpApps {
pub gemini: bool,
#[serde(default)]
pub opencode: bool,
#[serde(default)]
pub hermes: bool,
}

impl McpApps {
Expand All @@ -26,6 +28,7 @@ impl McpApps {
AppType::Gemini => self.gemini,
AppType::OpenCode => self.opencode,
AppType::OpenClaw => false, // OpenClaw doesn't support MCP
AppType::Hermes => self.hermes,
}
}

Expand All @@ -37,6 +40,7 @@ impl McpApps {
AppType::Gemini => self.gemini = enabled,
AppType::OpenCode => self.opencode = enabled,
AppType::OpenClaw => {} // OpenClaw doesn't support MCP, ignore
AppType::Hermes => self.hermes = enabled,
}
}

Expand All @@ -55,12 +59,15 @@ impl McpApps {
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
}
}

Expand All @@ -75,6 +82,8 @@ pub struct SkillApps {
pub gemini: bool,
#[serde(default)]
pub opencode: bool,
#[serde(default)]
pub hermes: bool,
}

impl SkillApps {
Expand All @@ -86,6 +95,7 @@ impl SkillApps {
AppType::Gemini => self.gemini,
AppType::OpenCode => self.opencode,
AppType::OpenClaw => false, // OpenClaw doesn't support Skills
AppType::Hermes => self.hermes,
}
}

Expand All @@ -97,6 +107,7 @@ impl SkillApps {
AppType::Gemini => self.gemini = enabled,
AppType::OpenCode => self.opencode = enabled,
AppType::OpenClaw => {} // OpenClaw doesn't support Skills, ignore
AppType::Hermes => self.hermes = enabled,
}
}

Expand All @@ -115,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
}

/// 仅启用指定应用(其他应用设为禁用)
Expand Down Expand Up @@ -251,6 +265,9 @@ pub struct McpRoot {
/// OpenClaw MCP 配置(v4.1.0+,实际使用 openclaw.json)
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
pub openclaw: McpConfig,
/// Hermes MCP 配置(v4.2.0+,实际使用 hermes.json)
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
pub hermes: McpConfig,
}

impl Default for McpRoot {
Expand All @@ -264,6 +281,7 @@ impl Default for McpRoot {
gemini: McpConfig::default(),
opencode: McpConfig::default(),
openclaw: McpConfig::default(),
hermes: McpConfig::default(),
}
}
}
Expand All @@ -288,6 +306,8 @@ pub struct PromptRoot {
pub opencode: PromptConfig,
#[serde(default)]
pub openclaw: PromptConfig,
#[serde(default)]
pub hermes: PromptConfig,
}

use crate::config::{copy_file, get_app_config_dir, get_app_config_path, write_json_file};
Expand All @@ -304,6 +324,7 @@ pub enum AppType {
Gemini,
OpenCode,
OpenClaw,
Hermes,
}

impl AppType {
Expand All @@ -314,12 +335,13 @@ impl AppType {
AppType::Gemini => "gemini",
AppType::OpenCode => "opencode",
AppType::OpenClaw => "openclaw",
AppType::Hermes => "hermes",
}
}

/// Check if this app uses additive mode
///
/// - Switch mode (false): Only the current provider is written to live config (Claude, Codex, Gemini)
/// - Switch mode (false): Only the current provider is written to live config (Claude, Codex, Gemini, Hermes)
/// - Additive mode (true): All providers are written to live config (OpenCode, OpenClaw)
pub fn is_additive_mode(&self) -> bool {
matches!(self, AppType::OpenCode | AppType::OpenClaw)
Expand All @@ -333,6 +355,7 @@ impl AppType {
AppType::Gemini,
AppType::OpenCode,
AppType::OpenClaw,
AppType::Hermes,
]
.into_iter()
}
Expand All @@ -349,10 +372,11 @@ impl FromStr for AppType {
"gemini" => Ok(AppType::Gemini),
"opencode" => Ok(AppType::OpenCode),
"openclaw" => Ok(AppType::OpenClaw),
"hermes" => Ok(AppType::Hermes),
other => Err(AppError::localized(
"unsupported_app",
format!("不支持的应用标识: '{other}'。可选值: claude, codex, gemini, opencode, openclaw。"),
format!("Unsupported app id: '{other}'. Allowed: claude, codex, gemini, opencode, openclaw."),
format!("不支持的应用标识: '{other}'。可选值: claude, codex, gemini, opencode, openclaw, hermes。"),
format!("Unsupported app id: '{other}'. Allowed: claude, codex, gemini, opencode, openclaw, hermes."),
)),
}
}
Expand All @@ -375,6 +399,9 @@ pub struct CommonConfigSnippets {

#[serde(default, skip_serializing_if = "Option::is_none")]
pub openclaw: Option<String>,

#[serde(default, skip_serializing_if = "Option::is_none")]
pub hermes: Option<String>,
}

impl CommonConfigSnippets {
Expand All @@ -386,6 +413,7 @@ impl CommonConfigSnippets {
AppType::Gemini => self.gemini.as_ref(),
AppType::OpenCode => self.opencode.as_ref(),
AppType::OpenClaw => self.openclaw.as_ref(),
AppType::Hermes => self.hermes.as_ref(),
}
}

Expand All @@ -397,6 +425,7 @@ impl CommonConfigSnippets {
AppType::Gemini => self.gemini = snippet,
AppType::OpenCode => self.opencode = snippet,
AppType::OpenClaw => self.openclaw = snippet,
AppType::Hermes => self.hermes = snippet,
}
}
}
Expand Down Expand Up @@ -438,6 +467,7 @@ impl Default for MultiAppConfig {
apps.insert("gemini".to_string(), ProviderManager::default());
apps.insert("opencode".to_string(), ProviderManager::default());
apps.insert("openclaw".to_string(), ProviderManager::default());
apps.insert("hermes".to_string(), ProviderManager::default());

Self {
version: 2,
Expand Down Expand Up @@ -598,6 +628,7 @@ impl MultiAppConfig {
AppType::Gemini => &self.mcp.gemini,
AppType::OpenCode => &self.mcp.opencode,
AppType::OpenClaw => &self.mcp.openclaw,
AppType::Hermes => &self.mcp.hermes,
}
}

Expand All @@ -609,6 +640,7 @@ impl MultiAppConfig {
AppType::Gemini => &mut self.mcp.gemini,
AppType::OpenCode => &mut self.mcp.opencode,
AppType::OpenClaw => &mut self.mcp.openclaw,
AppType::Hermes => &mut self.mcp.hermes,
}
}

Expand All @@ -624,6 +656,7 @@ impl MultiAppConfig {
Self::auto_import_prompt_if_exists(&mut config, AppType::Gemini)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::OpenCode)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::OpenClaw)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::Hermes)?;

Ok(config)
}
Expand All @@ -645,6 +678,7 @@ impl MultiAppConfig {
|| !self.prompts.gemini.prompts.is_empty()
|| !self.prompts.opencode.prompts.is_empty()
|| !self.prompts.openclaw.prompts.is_empty()
|| !self.prompts.hermes.prompts.is_empty()
{
return Ok(false);
}
Expand All @@ -658,6 +692,7 @@ impl MultiAppConfig {
AppType::Gemini,
AppType::OpenCode,
AppType::OpenClaw,
AppType::Hermes,
] {
// 复用已有的单应用导入逻辑
if Self::auto_import_prompt_if_exists(self, app)? {
Expand Down Expand Up @@ -729,6 +764,7 @@ impl MultiAppConfig {
AppType::Gemini => &mut config.prompts.gemini.prompts,
AppType::OpenCode => &mut config.prompts.opencode.prompts,
AppType::OpenClaw => &mut config.prompts.openclaw.prompts,
AppType::Hermes => &mut config.prompts.hermes.prompts,
};

prompts.insert(id, prompt);
Expand Down Expand Up @@ -769,6 +805,7 @@ impl MultiAppConfig {
AppType::Gemini => &self.mcp.gemini.servers,
AppType::OpenCode => &self.mcp.opencode.servers,
AppType::OpenClaw => continue, // OpenClaw MCP is still in development, skip
AppType::Hermes => continue, // Hermes MCP is still in development, skip
};

for (id, entry) in old_servers {
Expand Down
8 changes: 8 additions & 0 deletions src-tauri/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ pub async fn get_config_status(app: String) -> Result<ConfigStatus, String> {

Ok(ConfigStatus { exists, path })
}
AppType::Hermes => {
let hermes_dir = crate::config::get_home_dir().join(".hermes");
let exists = hermes_dir.exists();
let path = hermes_dir.to_string_lossy().to_string();
Ok(ConfigStatus { exists, path })
}
}
}

Expand All @@ -117,6 +123,7 @@ pub async fn get_config_dir(app: String) -> Result<String, String> {
AppType::Gemini => crate::gemini_config::get_gemini_dir(),
AppType::OpenCode => crate::opencode_config::get_opencode_dir(),
AppType::OpenClaw => crate::openclaw_config::get_openclaw_dir(),
AppType::Hermes => crate::config::get_home_dir().join(".hermes"),
};

Ok(dir.to_string_lossy().to_string())
Expand All @@ -130,6 +137,7 @@ pub async fn open_config_folder(handle: AppHandle, app: String) -> Result<bool,
AppType::Gemini => crate::gemini_config::get_gemini_dir(),
AppType::OpenCode => crate::opencode_config::get_opencode_dir(),
AppType::OpenClaw => crate::openclaw_config::get_openclaw_dir(),
AppType::Hermes => crate::config::get_home_dir().join(".hermes"),
};

if !config_dir.exists() {
Expand Down
9 changes: 6 additions & 3 deletions src-tauri/src/database/dao/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ impl Database {
pub fn get_all_mcp_servers(&self) -> Result<IndexMap<String, McpServer>, AppError> {
let conn = lock_conn!(self.conn);
let mut stmt = conn.prepare(
"SELECT id, name, server_config, description, homepage, docs, tags, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode
"SELECT id, name, server_config, description, homepage, docs, tags, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes
FROM mcp_servers
ORDER BY name ASC, id ASC"
).map_err(|e| AppError::Database(e.to_string()))?;
Expand All @@ -31,6 +31,7 @@ impl Database {
let enabled_codex: bool = row.get(8)?;
let enabled_gemini: bool = row.get(9)?;
let enabled_opencode: bool = row.get(10)?;
let enabled_hermes: bool = row.get(11)?;

let server = serde_json::from_str(&server_config_str).unwrap_or_default();
let tags = serde_json::from_str(&tags_str).unwrap_or_default();
Expand All @@ -46,6 +47,7 @@ impl Database {
codex: enabled_codex,
gemini: enabled_gemini,
opencode: enabled_opencode,
hermes: enabled_hermes,
},
description,
homepage,
Expand All @@ -70,8 +72,8 @@ impl Database {
conn.execute(
"INSERT OR REPLACE INTO mcp_servers (
id, name, server_config, description, homepage, docs, tags,
enabled_claude, enabled_codex, enabled_gemini, enabled_opencode
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
params![
server.id,
server.name,
Expand All @@ -87,6 +89,7 @@ impl Database {
server.apps.codex,
server.apps.gemini,
server.apps.opencode,
server.apps.hermes,
],
)
.map_err(|e| AppError::Database(e.to_string()))?;
Expand Down
Loading