mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat: add Hermes Agent as 6th supported app type (Phase 1)
Register AppType::Hermes across the entire Rust backend: - Add Hermes variant to AppType enum with additive mode and MCP support - Add hermes field to McpApps, SkillApps, CommonConfigSnippets, and all per-app structs (McpRoot, PromptRoot, VisibleApps, AppSettings) - Create minimal hermes_config.rs with get_hermes_dir() respecting settings override, matching the pattern of other app config modules - Update all match arms in commands, services, deeplink, proxy, mcp, session_manager, and test files - Extract shared build_additive_app_settings() to eliminate duplication between OpenClaw and Hermes deep link handling - Combine identical OpenClaw/Hermes proxy match arms into unified arms
This commit is contained in:
@@ -15,6 +15,8 @@ pub struct McpApps {
|
||||
pub gemini: bool,
|
||||
#[serde(default)]
|
||||
pub opencode: bool,
|
||||
#[serde(default)]
|
||||
pub hermes: bool,
|
||||
}
|
||||
|
||||
impl McpApps {
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +93,7 @@ impl SkillApps {
|
||||
AppType::Gemini => self.gemini,
|
||||
AppType::OpenCode => self.opencode,
|
||||
AppType::OpenClaw => false, // OpenClaw doesn't support Skills
|
||||
AppType::Hermes => false, // Hermes doesn't support Skills yet
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +105,7 @@ impl SkillApps {
|
||||
AppType::Gemini => self.gemini = enabled,
|
||||
AppType::OpenCode => self.opencode = enabled,
|
||||
AppType::OpenClaw => {} // OpenClaw doesn't support Skills, ignore
|
||||
AppType::Hermes => {} // Hermes doesn't support Skills yet, ignore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +260,9 @@ pub struct McpRoot {
|
||||
/// OpenClaw MCP 配置(v4.1.0+,实际使用 openclaw.json)
|
||||
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
|
||||
pub openclaw: McpConfig,
|
||||
/// Hermes MCP 配置(实际使用 config.yaml)
|
||||
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
|
||||
pub hermes: McpConfig,
|
||||
}
|
||||
|
||||
impl Default for McpRoot {
|
||||
@@ -264,6 +276,7 @@ impl Default for McpRoot {
|
||||
gemini: McpConfig::default(),
|
||||
opencode: McpConfig::default(),
|
||||
openclaw: McpConfig::default(),
|
||||
hermes: McpConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,6 +301,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};
|
||||
@@ -304,6 +319,7 @@ pub enum AppType {
|
||||
Gemini,
|
||||
OpenCode,
|
||||
OpenClaw,
|
||||
Hermes,
|
||||
}
|
||||
|
||||
impl AppType {
|
||||
@@ -314,15 +330,19 @@ 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)
|
||||
/// - Additive mode (true): All providers are written to live config (OpenCode, OpenClaw)
|
||||
/// - Additive mode (true): All providers are written to live config (OpenCode, OpenClaw, Hermes)
|
||||
pub fn is_additive_mode(&self) -> bool {
|
||||
matches!(self, AppType::OpenCode | AppType::OpenClaw)
|
||||
matches!(
|
||||
self,
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes
|
||||
)
|
||||
}
|
||||
|
||||
/// Return an iterator over all app types
|
||||
@@ -333,6 +353,7 @@ impl AppType {
|
||||
AppType::Gemini,
|
||||
AppType::OpenCode,
|
||||
AppType::OpenClaw,
|
||||
AppType::Hermes,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
@@ -349,10 +370,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."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -375,6 +397,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 {
|
||||
@@ -386,6 +411,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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +423,7 @@ impl CommonConfigSnippets {
|
||||
AppType::Gemini => self.gemini = snippet,
|
||||
AppType::OpenCode => self.opencode = snippet,
|
||||
AppType::OpenClaw => self.openclaw = snippet,
|
||||
AppType::Hermes => self.hermes = snippet,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,6 +465,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,
|
||||
@@ -598,6 +626,7 @@ impl MultiAppConfig {
|
||||
AppType::Gemini => &self.mcp.gemini,
|
||||
AppType::OpenCode => &self.mcp.opencode,
|
||||
AppType::OpenClaw => &self.mcp.openclaw,
|
||||
AppType::Hermes => &self.mcp.hermes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,6 +638,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,6 +654,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)
|
||||
}
|
||||
@@ -645,6 +676,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);
|
||||
}
|
||||
@@ -658,6 +690,7 @@ impl MultiAppConfig {
|
||||
AppType::Gemini,
|
||||
AppType::OpenCode,
|
||||
AppType::OpenClaw,
|
||||
AppType::Hermes,
|
||||
] {
|
||||
// 复用已有的单应用导入逻辑
|
||||
if Self::auto_import_prompt_if_exists(self, app)? {
|
||||
@@ -729,6 +762,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);
|
||||
@@ -769,6 +803,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 didn't exist in v3.6.x, skip
|
||||
};
|
||||
|
||||
for (id, entry) in old_servers {
|
||||
|
||||
@@ -101,6 +101,15 @@ pub async fn get_config_status(app: String) -> Result<ConfigStatus, String> {
|
||||
|
||||
Ok(ConfigStatus { exists, path })
|
||||
}
|
||||
AppType::Hermes => {
|
||||
let config_path = crate::hermes_config::get_hermes_config_path();
|
||||
let exists = config_path.exists();
|
||||
let path = crate::hermes_config::get_hermes_dir()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
Ok(ConfigStatus { exists, path })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +126,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::hermes_config::get_hermes_dir(),
|
||||
};
|
||||
|
||||
Ok(dir.to_string_lossy().to_string())
|
||||
@@ -130,6 +140,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::hermes_config::get_hermes_dir(),
|
||||
};
|
||||
|
||||
if !config_dir.exists() {
|
||||
|
||||
@@ -46,6 +46,7 @@ impl Database {
|
||||
codex: enabled_codex,
|
||||
gemini: enabled_gemini,
|
||||
opencode: enabled_opencode,
|
||||
hermes: false,
|
||||
},
|
||||
description,
|
||||
homepage,
|
||||
|
||||
@@ -167,6 +167,7 @@ pub(crate) fn parse_mcp_apps(apps_str: &str) -> Result<McpApps, AppError> {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
};
|
||||
|
||||
for app in apps_str.split(',') {
|
||||
@@ -179,6 +180,10 @@ pub(crate) fn parse_mcp_apps(apps_str: &str) -> Result<McpApps, AppError> {
|
||||
// OpenClaw doesn't support MCP, ignore silently
|
||||
log::debug!("OpenClaw doesn't support MCP, ignoring in apps parameter");
|
||||
}
|
||||
"hermes" => {
|
||||
// TODO: Hermes MCP sync not yet implemented, ignore silently for now
|
||||
log::debug!("Hermes MCP sync not yet implemented, ignoring in apps parameter");
|
||||
}
|
||||
other => {
|
||||
return Err(AppError::InvalidInput(format!(
|
||||
"Invalid app in 'apps': {other}"
|
||||
|
||||
@@ -81,10 +81,10 @@ fn parse_provider_deeplink(
|
||||
// Validate app type
|
||||
if !matches!(
|
||||
app.as_str(),
|
||||
"claude" | "codex" | "gemini" | "opencode" | "openclaw"
|
||||
"claude" | "codex" | "gemini" | "opencode" | "openclaw" | "hermes"
|
||||
) {
|
||||
return Err(AppError::InvalidInput(format!(
|
||||
"Invalid app type: must be 'claude', 'codex', 'gemini', 'opencode', or 'openclaw', got '{app}'"
|
||||
"Invalid app type: must be 'claude', 'codex', 'gemini', 'opencode', 'openclaw', or 'hermes', got '{app}'"
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -190,10 +190,10 @@ fn parse_prompt_deeplink(
|
||||
// Validate app type
|
||||
if !matches!(
|
||||
app.as_str(),
|
||||
"claude" | "codex" | "gemini" | "opencode" | "openclaw"
|
||||
"claude" | "codex" | "gemini" | "opencode" | "openclaw" | "hermes"
|
||||
) {
|
||||
return Err(AppError::InvalidInput(format!(
|
||||
"Invalid app type: must be 'claude', 'codex', 'gemini', 'opencode', or 'openclaw', got '{app}'"
|
||||
"Invalid app type: must be 'claude', 'codex', 'gemini', 'opencode', 'openclaw', or 'hermes', got '{app}'"
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -262,10 +262,10 @@ fn parse_mcp_deeplink(
|
||||
let trimmed = app.trim();
|
||||
if !matches!(
|
||||
trimmed,
|
||||
"claude" | "codex" | "gemini" | "opencode" | "openclaw"
|
||||
"claude" | "codex" | "gemini" | "opencode" | "openclaw" | "hermes"
|
||||
) {
|
||||
return Err(AppError::InvalidInput(format!(
|
||||
"Invalid app in 'apps': must be 'claude', 'codex', 'gemini', 'opencode', or 'openclaw', got '{trimmed}'"
|
||||
"Invalid app in 'apps': must be 'claude', 'codex', 'gemini', 'opencode', 'openclaw', or 'hermes', got '{trimmed}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ pub(crate) fn build_provider_from_request(
|
||||
AppType::Codex => build_codex_settings(request),
|
||||
AppType::Gemini => build_gemini_settings(request),
|
||||
AppType::OpenCode => build_opencode_settings(request),
|
||||
AppType::OpenClaw => build_openclaw_settings(request),
|
||||
AppType::OpenClaw | AppType::Hermes => build_additive_app_settings(request),
|
||||
};
|
||||
|
||||
// Build usage script configuration if provided
|
||||
@@ -393,11 +393,11 @@ fn build_opencode_settings(request: &DeepLinkImportRequest) -> serde_json::Value
|
||||
})
|
||||
}
|
||||
|
||||
fn build_openclaw_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
|
||||
/// Build settings for additive-mode apps (OpenClaw, Hermes)
|
||||
/// Format: { baseUrl, apiKey, api, models }
|
||||
fn build_additive_app_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
|
||||
let endpoint = get_primary_endpoint(request);
|
||||
|
||||
// Build OpenClaw provider config
|
||||
// Format: { baseUrl, apiKey, api, models }
|
||||
let mut config = serde_json::Map::new();
|
||||
|
||||
if !endpoint.is_empty() {
|
||||
@@ -408,10 +408,8 @@ fn build_openclaw_settings(request: &DeepLinkImportRequest) -> serde_json::Value
|
||||
config.insert("apiKey".to_string(), json!(api_key));
|
||||
}
|
||||
|
||||
// Default to OpenAI-compatible API
|
||||
config.insert("api".to_string(), json!("openai-completions"));
|
||||
|
||||
// Build models array
|
||||
if let Some(model) = &request.model {
|
||||
config.insert(
|
||||
"models".to_string(),
|
||||
@@ -484,7 +482,7 @@ pub fn parse_and_merge_config(
|
||||
"codex" => merge_codex_config(&mut merged, &config_value)?,
|
||||
"gemini" => merge_gemini_config(&mut merged, &config_value)?,
|
||||
// Additive mode apps use JSON config directly; pass through as-is
|
||||
"openclaw" | "opencode" => {
|
||||
"openclaw" | "opencode" | "hermes" => {
|
||||
merge_additive_config(&mut merged, &config_value)?;
|
||||
}
|
||||
"" => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Hermes Agent 配置文件读写模块
|
||||
//!
|
||||
//! 处理 `~/.hermes/config.yaml` 配置文件的读写操作(YAML 格式)。
|
||||
//! Hermes 使用累加式供应商管理,所有供应商配置共存于同一配置文件中。
|
||||
|
||||
use crate::settings::get_hermes_override_dir;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// ============================================================================
|
||||
// Path Functions
|
||||
// ============================================================================
|
||||
|
||||
/// 获取 Hermes 配置目录
|
||||
///
|
||||
/// 默认路径: `~/.hermes/`
|
||||
/// 可通过 settings.hermes_config_dir 覆盖
|
||||
pub fn get_hermes_dir() -> PathBuf {
|
||||
if let Some(override_dir) = get_hermes_override_dir() {
|
||||
return override_dir;
|
||||
}
|
||||
|
||||
crate::config::get_home_dir().join(".hermes")
|
||||
}
|
||||
|
||||
/// 获取 Hermes 配置文件路径
|
||||
///
|
||||
/// 返回 `~/.hermes/config.yaml`
|
||||
pub fn get_hermes_config_path() -> PathBuf {
|
||||
get_hermes_dir().join("config.yaml")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ mod deeplink;
|
||||
mod error;
|
||||
mod gemini_config;
|
||||
mod gemini_mcp;
|
||||
mod hermes_config;
|
||||
mod init_status;
|
||||
mod lightweight;
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -639,6 +640,7 @@ pub fn run() {
|
||||
crate::app_config::AppType::Gemini,
|
||||
crate::app_config::AppType::OpenCode,
|
||||
crate::app_config::AppType::OpenClaw,
|
||||
crate::app_config::AppType::Hermes,
|
||||
] {
|
||||
match crate::services::prompt::PromptService::import_from_file_on_first_launch(
|
||||
&app_state,
|
||||
|
||||
@@ -92,6 +92,7 @@ pub fn import_from_claude(config: &mut MultiAppConfig) -> Result<usize, AppError
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -236,6 +236,7 @@ pub fn import_from_codex(config: &mut MultiAppConfig) -> Result<usize, AppError>
|
||||
codex: true,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -88,6 +88,7 @@ pub fn import_from_gemini(config: &mut MultiAppConfig) -> Result<usize, AppError
|
||||
codex: false,
|
||||
gemini: true,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -259,6 +259,7 @@ pub fn import_from_opencode(config: &mut MultiAppConfig) -> Result<usize, AppErr
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: true,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -16,14 +16,14 @@ pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
||||
AppType::Gemini => get_gemini_dir(),
|
||||
AppType::OpenCode => get_opencode_dir(),
|
||||
AppType::OpenClaw => get_openclaw_dir(),
|
||||
AppType::Hermes => crate::hermes_config::get_hermes_dir(),
|
||||
};
|
||||
|
||||
let filename = match app {
|
||||
AppType::Claude => "CLAUDE.md",
|
||||
AppType::Codex => "AGENTS.md",
|
||||
AppType::Gemini => "GEMINI.md",
|
||||
AppType::OpenCode => "AGENTS.md",
|
||||
AppType::OpenClaw => "AGENTS.md", // OpenClaw uses AGENTS.md for agent instructions
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => "AGENTS.md",
|
||||
};
|
||||
|
||||
Ok(base_dir.join(filename))
|
||||
|
||||
@@ -174,13 +174,9 @@ impl ProviderType {
|
||||
}
|
||||
ProviderType::Gemini
|
||||
}
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy, but return a default type for completeness
|
||||
ProviderType::Codex // Fallback to Codex-like type
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy, but return a default type for completeness
|
||||
ProviderType::Codex // Fallback to Codex-like type
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy, fallback to Codex-like type
|
||||
ProviderType::Codex
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,12 +228,8 @@ pub fn get_adapter(app_type: &AppType) -> Box<dyn ProviderAdapter> {
|
||||
AppType::Claude => Box::new(ClaudeAdapter::new()),
|
||||
AppType::Codex => Box::new(CodexAdapter::new()),
|
||||
AppType::Gemini => Box::new(GeminiAdapter::new()),
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy, fallback to Codex adapter
|
||||
Box::new(CodexAdapter::new())
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy, fallback to Codex adapter
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy, fallback to Codex adapter
|
||||
Box::new(CodexAdapter::new())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,9 @@ impl ConfigService {
|
||||
// OpenClaw uses additive mode, no live sync needed
|
||||
// OpenClaw providers are managed directly in the config file
|
||||
}
|
||||
AppType::Hermes => {
|
||||
// Hermes uses additive mode, no live sync needed
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -128,6 +128,10 @@ impl McpService {
|
||||
// Skip for now
|
||||
log::debug!("OpenClaw MCP support is still in development, skipping sync");
|
||||
}
|
||||
AppType::Hermes => {
|
||||
// TODO: Hermes MCP sync not yet implemented
|
||||
log::debug!("Hermes MCP sync not yet implemented, skipping sync");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -157,6 +161,10 @@ impl McpService {
|
||||
// OpenClaw MCP support is still in development
|
||||
log::debug!("OpenClaw MCP support is still in development, skipping remove");
|
||||
}
|
||||
AppType::Hermes => {
|
||||
// TODO: Hermes MCP sync not yet implemented
|
||||
log::debug!("Hermes MCP sync not yet implemented, skipping remove");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -166,7 +174,7 @@ impl McpService {
|
||||
let servers = Self::get_all_servers(state)?;
|
||||
|
||||
for app in AppType::all() {
|
||||
if matches!(app, AppType::OpenClaw) {
|
||||
if matches!(app, AppType::OpenClaw | AppType::Hermes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@ pub(crate) fn provider_exists_in_live_config(
|
||||
.map(|providers| providers.contains_key(provider_id)),
|
||||
AppType::OpenClaw => crate::openclaw_config::get_providers()
|
||||
.map(|providers| providers.contains_key(provider_id)),
|
||||
AppType::Hermes => {
|
||||
// TODO: hermes_config module not yet implemented
|
||||
Ok(false)
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
@@ -345,7 +349,7 @@ fn settings_contain_common_config(app_type: &AppType, settings: &Value, snippet:
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
AppType::OpenCode | AppType::OpenClaw => false,
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +419,7 @@ pub(crate) fn remove_common_config_from_settings(
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
AppType::OpenCode | AppType::OpenClaw => Ok(settings.clone()),
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => Ok(settings.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +474,7 @@ fn apply_common_config_to_settings(
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
AppType::OpenCode | AppType::OpenClaw => Ok(settings.clone()),
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => Ok(settings.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,6 +794,13 @@ pub(crate) fn write_live_snapshot(app_type: &AppType, provider: &Provider) -> Re
|
||||
}
|
||||
}
|
||||
}
|
||||
AppType::Hermes => {
|
||||
// TODO: hermes_config module not yet implemented
|
||||
log::debug!(
|
||||
"Hermes provider '{}' write to live config not yet implemented",
|
||||
provider.id
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -985,6 +996,18 @@ pub fn read_live_settings(app_type: AppType) -> Result<Value, AppError> {
|
||||
let config = read_openclaw_config()?;
|
||||
Ok(config)
|
||||
}
|
||||
AppType::Hermes => {
|
||||
let config_path = crate::hermes_config::get_hermes_config_path();
|
||||
if !config_path.exists() {
|
||||
return Err(AppError::localized(
|
||||
"hermes.config.missing",
|
||||
"Hermes 配置文件不存在",
|
||||
"Hermes configuration file not found",
|
||||
));
|
||||
}
|
||||
// Return empty object until hermes_config is implemented
|
||||
Ok(json!({}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,8 +1090,8 @@ pub fn import_default_config(state: &AppState, app_type: AppType) -> Result<bool
|
||||
"config": config_obj
|
||||
})
|
||||
}
|
||||
// OpenCode and OpenClaw use additive mode and are handled by early return above
|
||||
AppType::OpenCode | AppType::OpenClaw => {
|
||||
// OpenCode, OpenClaw and Hermes use additive mode and are handled by early return above
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
unreachable!("additive mode apps are handled by early return")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1282,6 +1282,12 @@ impl ProviderService {
|
||||
match app_type {
|
||||
AppType::OpenCode => remove_opencode_provider_from_live(id)?,
|
||||
AppType::OpenClaw => remove_openclaw_provider_from_live(id)?,
|
||||
AppType::Hermes => {
|
||||
// TODO: hermes_config module not yet implemented
|
||||
log::debug!(
|
||||
"Hermes provider '{id}' removal from live config not yet implemented"
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1344,6 +1350,10 @@ impl ProviderService {
|
||||
AppType::OpenClaw => {
|
||||
remove_openclaw_provider_from_live(id)?;
|
||||
}
|
||||
AppType::Hermes => {
|
||||
// TODO: hermes_config module not yet implemented
|
||||
log::debug!("Hermes provider '{id}' removal from live config not yet implemented");
|
||||
}
|
||||
_ => {
|
||||
return Err(AppError::Message(format!(
|
||||
"App {} does not support remove from live config",
|
||||
@@ -1532,6 +1542,10 @@ impl ProviderService {
|
||||
let rollback_result = match app_type {
|
||||
AppType::OpenCode => remove_opencode_provider_from_live(&provider.id),
|
||||
AppType::OpenClaw => remove_openclaw_provider_from_live(&provider.id),
|
||||
AppType::Hermes => {
|
||||
// TODO: hermes_config module not yet implemented
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
@@ -1708,6 +1722,7 @@ impl ProviderService {
|
||||
AppType::Gemini => Self::extract_gemini_common_config(&provider.settings_config),
|
||||
AppType::OpenCode => Self::extract_opencode_common_config(&provider.settings_config),
|
||||
AppType::OpenClaw => Self::extract_openclaw_common_config(&provider.settings_config),
|
||||
AppType::Hermes => Ok(String::new()), // Hermes doesn't use common config snippets
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1722,6 +1737,7 @@ impl ProviderService {
|
||||
AppType::Gemini => Self::extract_gemini_common_config(settings_config),
|
||||
AppType::OpenCode => Self::extract_opencode_common_config(settings_config),
|
||||
AppType::OpenClaw => Self::extract_openclaw_common_config(settings_config),
|
||||
AppType::Hermes => Ok(String::new()), // Hermes doesn't use common config snippets
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2087,6 +2103,16 @@ impl ProviderService {
|
||||
));
|
||||
}
|
||||
}
|
||||
AppType::Hermes => {
|
||||
// Hermes: accept any JSON object for now
|
||||
if !provider.settings_config.is_object() {
|
||||
return Err(AppError::localized(
|
||||
"provider.hermes.settings.not_object",
|
||||
"Hermes 配置必须是 JSON 对象",
|
||||
"Hermes configuration must be a JSON object",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and clean UsageScript configuration (common for all app types)
|
||||
@@ -2258,8 +2284,8 @@ impl ProviderService {
|
||||
|
||||
Ok((api_key, base_url))
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw uses apiKey and baseUrl directly on the object
|
||||
AppType::OpenClaw | AppType::Hermes => {
|
||||
// OpenClaw/Hermes use apiKey and baseUrl directly on the object
|
||||
let api_key = provider
|
||||
.settings_config
|
||||
.get("apiKey")
|
||||
|
||||
@@ -466,13 +466,9 @@ impl ProxyService {
|
||||
AppType::Claude => self.read_claude_live()?,
|
||||
AppType::Codex => self.read_codex_live()?,
|
||||
AppType::Gemini => self.read_gemini_live()?,
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features
|
||||
return Err("OpenCode 不支持代理功能".to_string());
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features
|
||||
return Err("OpenClaw 不支持代理功能".to_string());
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features
|
||||
return Err("该应用不支持代理功能".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -687,11 +683,8 @@ impl ProxyService {
|
||||
}
|
||||
}
|
||||
}
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features, skip silently
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features, skip silently
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features, skip silently
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,13 +864,9 @@ impl ProxyService {
|
||||
AppType::Claude => ("claude", self.read_claude_live()?),
|
||||
AppType::Codex => ("codex", self.read_codex_live()?),
|
||||
AppType::Gemini => ("gemini", self.read_gemini_live()?),
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features
|
||||
return Err("OpenCode 不支持代理功能".to_string());
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features
|
||||
return Err("OpenClaw 不支持代理功能".to_string());
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features
|
||||
return Err("该应用不支持代理功能".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1019,13 +1008,9 @@ impl ProxyService {
|
||||
self.write_gemini_live(&live_config)?;
|
||||
log::info!("Gemini Live 配置已接管,代理地址: {proxy_url}");
|
||||
}
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features
|
||||
return Err("OpenCode 不支持代理功能".to_string());
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features
|
||||
return Err("OpenClaw 不支持代理功能".to_string());
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features
|
||||
return Err("该应用不支持代理功能".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,11 +1061,8 @@ impl ProxyService {
|
||||
let _ = self.write_gemini_live(&live_config);
|
||||
}
|
||||
}
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features, skip silently
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features, skip silently
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features, skip silently
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,11 +1101,8 @@ impl ProxyService {
|
||||
log::info!("Gemini Live 配置已恢复");
|
||||
}
|
||||
}
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features, skip silently
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features, skip silently
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features, skip silently
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,13 +1192,9 @@ impl ProxyService {
|
||||
AppType::Claude => self.write_claude_live(config),
|
||||
AppType::Codex => self.write_codex_live(config),
|
||||
AppType::Gemini => self.write_gemini_live(config),
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features
|
||||
Err("OpenCode 不支持代理功能".to_string())
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features
|
||||
Err("OpenClaw 不支持代理功能".to_string())
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features
|
||||
Err("该应用不支持代理功能".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1238,12 +1213,8 @@ impl ProxyService {
|
||||
Ok(config) => Self::is_gemini_live_taken_over(&config),
|
||||
Err(_) => false,
|
||||
},
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy takeover
|
||||
false
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy takeover
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy takeover
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1285,12 +1256,8 @@ impl ProxyService {
|
||||
AppType::Claude => self.cleanup_claude_takeover_placeholders_in_live(),
|
||||
AppType::Codex => self.cleanup_codex_takeover_placeholders_in_live(),
|
||||
AppType::Gemini => self.cleanup_gemini_takeover_placeholders_in_live(),
|
||||
AppType::OpenCode => {
|
||||
// OpenCode doesn't support proxy features
|
||||
Ok(())
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw doesn't support proxy features
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// These apps don't support proxy features
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1540,7 +1507,7 @@ impl ProxyService {
|
||||
serde_json::to_string(&env_backup)
|
||||
.map_err(|e| format!("序列化 Gemini 配置失败: {e}"))?
|
||||
}
|
||||
AppType::OpenCode | AppType::OpenClaw => {
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
return Err(format!("未知的应用类型: {app_type}"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -529,6 +529,11 @@ impl SkillService {
|
||||
return Ok(custom.join("skills"));
|
||||
}
|
||||
}
|
||||
AppType::Hermes => {
|
||||
if let Some(custom) = crate::settings::get_hermes_override_dir() {
|
||||
return Ok(custom.join("skills"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认路径:回退到用户主目录下的标准位置
|
||||
@@ -544,6 +549,7 @@ impl SkillService {
|
||||
AppType::Gemini => home.join(".gemini").join("skills"),
|
||||
AppType::OpenCode => home.join(".config").join("opencode").join("skills"),
|
||||
AppType::OpenClaw => home.join(".openclaw").join("skills"),
|
||||
AppType::Hermes => crate::hermes_config::get_hermes_dir().join("skills"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,10 @@ impl StreamCheckService {
|
||||
// OpenCode / OpenClaw 的 settings_config 结构与 Claude/Codex/Gemini 不同
|
||||
// (baseUrl / apiKey 直接作为根字段而非嵌套在 env),并且协议由 `api`
|
||||
// 或 `npm` 字段显式指定。它们不走 get_adapter 路径,而是直接分发。
|
||||
if matches!(app_type, AppType::OpenCode | AppType::OpenClaw) {
|
||||
if matches!(
|
||||
app_type,
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes
|
||||
) {
|
||||
return Self::check_once_without_adapter(app_type, provider, config, start).await;
|
||||
}
|
||||
|
||||
@@ -270,9 +273,9 @@ impl StreamCheckService {
|
||||
)
|
||||
.await
|
||||
}
|
||||
AppType::OpenCode | AppType::OpenClaw => {
|
||||
AppType::OpenCode | AppType::OpenClaw | AppType::Hermes => {
|
||||
// Already handled via early dispatch above
|
||||
unreachable!("OpenCode/OpenClaw 已通过 check_once_without_adapter 处理")
|
||||
unreachable!("OpenCode/OpenClaw/Hermes 已通过 check_once_without_adapter 处理")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -700,7 +703,18 @@ impl StreamCheckService {
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => unreachable!("check_once_without_adapter 只处理 OpenCode/OpenClaw"),
|
||||
AppType::Hermes => {
|
||||
// Hermes uses the same check path as OpenClaw for now
|
||||
Self::check_openclaw_stream(
|
||||
&client,
|
||||
provider,
|
||||
&model_to_test,
|
||||
test_prompt,
|
||||
request_timeout,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => unreachable!("check_once_without_adapter 只处理 OpenCode/OpenClaw/Hermes"),
|
||||
};
|
||||
|
||||
let response_time = start.elapsed().as_millis() as u64;
|
||||
@@ -1241,8 +1255,8 @@ impl StreamCheckService {
|
||||
// Try to extract first model from the models object
|
||||
Self::extract_opencode_model(provider).unwrap_or_else(|| "gpt-4o".to_string())
|
||||
}
|
||||
AppType::OpenClaw => {
|
||||
// OpenClaw uses models array in settings_config
|
||||
AppType::OpenClaw | AppType::Hermes => {
|
||||
// OpenClaw/Hermes use models array in settings_config
|
||||
// Try to extract first model from the models array
|
||||
Self::extract_openclaw_model(provider).unwrap_or_else(|| "gpt-4o".to_string())
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ pub fn load_messages(provider_id: &str, source_path: &str) -> Result<Vec<Session
|
||||
"opencode" => opencode::load_messages(path),
|
||||
"openclaw" => openclaw::load_messages(path),
|
||||
"gemini" => gemini::load_messages(path),
|
||||
"hermes" => Err("Hermes session loading not yet implemented".to_string()),
|
||||
_ => Err(format!("Unsupported provider: {provider_id}")),
|
||||
}
|
||||
}
|
||||
@@ -150,6 +151,7 @@ fn delete_session_with_root(
|
||||
"opencode" => opencode::delete_session(&validated_root, &validated_source, session_id),
|
||||
"openclaw" => openclaw::delete_session(&validated_root, &validated_source, session_id),
|
||||
"gemini" => gemini::delete_session(&validated_root, &validated_source, session_id),
|
||||
"hermes" => Err("Hermes session deletion not yet implemented".to_string()),
|
||||
_ => Err(format!("Unsupported provider: {provider_id}")),
|
||||
}
|
||||
}
|
||||
@@ -161,6 +163,7 @@ fn provider_root(provider_id: &str) -> Result<PathBuf, String> {
|
||||
"opencode" => opencode::get_opencode_data_dir(),
|
||||
"openclaw" => crate::openclaw_config::get_openclaw_dir().join("agents"),
|
||||
"gemini" => crate::gemini_config::get_gemini_dir().join("tmp"),
|
||||
"hermes" => crate::hermes_config::get_hermes_dir().join("sessions"),
|
||||
_ => return Err(format!("Unsupported provider: {provider_id}")),
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ pub struct VisibleApps {
|
||||
pub opencode: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub openclaw: bool,
|
||||
#[serde(default)]
|
||||
pub hermes: bool,
|
||||
}
|
||||
|
||||
impl Default for VisibleApps {
|
||||
@@ -46,6 +48,7 @@ impl Default for VisibleApps {
|
||||
gemini: true,
|
||||
opencode: true,
|
||||
openclaw: true,
|
||||
hermes: false, // 默认不显示,需用户手动启用
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +62,7 @@ impl VisibleApps {
|
||||
AppType::Gemini => self.gemini,
|
||||
AppType::OpenCode => self.opencode,
|
||||
AppType::OpenClaw => self.openclaw,
|
||||
AppType::Hermes => self.hermes,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,6 +235,8 @@ pub struct AppSettings {
|
||||
pub opencode_config_dir: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub openclaw_config_dir: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub hermes_config_dir: Option<String>,
|
||||
|
||||
// ===== 当前供应商 ID(设备级)=====
|
||||
/// 当前 Claude 供应商 ID(本地存储,优先于数据库 is_current)
|
||||
@@ -248,6 +254,9 @@ pub struct AppSettings {
|
||||
/// 当前 OpenClaw 供应商 ID(本地存储,对 OpenClaw 可能无意义,但保持结构一致)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub current_provider_openclaw: Option<String>,
|
||||
/// 当前 Hermes 供应商 ID(本地存储,保持结构一致)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub current_provider_hermes: Option<String>,
|
||||
|
||||
// ===== Skill 同步设置 =====
|
||||
/// Skill 同步方式:auto(默认,优先 symlink)、symlink、copy
|
||||
@@ -315,11 +324,13 @@ impl Default for AppSettings {
|
||||
gemini_config_dir: None,
|
||||
opencode_config_dir: None,
|
||||
openclaw_config_dir: None,
|
||||
hermes_config_dir: None,
|
||||
current_provider_claude: None,
|
||||
current_provider_codex: None,
|
||||
current_provider_gemini: None,
|
||||
current_provider_opencode: None,
|
||||
current_provider_openclaw: None,
|
||||
current_provider_hermes: None,
|
||||
skill_sync_method: SyncMethod::default(),
|
||||
skill_storage_location: SkillStorageLocation::default(),
|
||||
webdav_sync: None,
|
||||
@@ -377,6 +388,13 @@ impl AppSettings {
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
self.hermes_config_dir = self
|
||||
.hermes_config_dir
|
||||
.as_ref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
self.language = self
|
||||
.language
|
||||
.as_ref()
|
||||
@@ -577,6 +595,14 @@ pub fn get_openclaw_override_dir() -> Option<PathBuf> {
|
||||
.map(|p| resolve_override_path(p))
|
||||
}
|
||||
|
||||
pub fn get_hermes_override_dir() -> Option<PathBuf> {
|
||||
let settings = settings_store().read().ok()?;
|
||||
settings
|
||||
.hermes_config_dir
|
||||
.as_ref()
|
||||
.map(|p| resolve_override_path(p))
|
||||
}
|
||||
|
||||
// ===== 当前供应商管理函数 =====
|
||||
|
||||
/// 获取指定应用类型的当前供应商 ID(从本地 settings 读取)
|
||||
@@ -591,6 +617,7 @@ pub fn get_current_provider(app_type: &AppType) -> Option<String> {
|
||||
AppType::Gemini => settings.current_provider_gemini.clone(),
|
||||
AppType::OpenCode => settings.current_provider_opencode.clone(),
|
||||
AppType::OpenClaw => settings.current_provider_openclaw.clone(),
|
||||
AppType::Hermes => settings.current_provider_hermes.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +633,7 @@ pub fn set_current_provider(app_type: &AppType, id: Option<&str>) -> Result<(),
|
||||
AppType::Gemini => settings.current_provider_gemini = id_owned.clone(),
|
||||
AppType::OpenCode => settings.current_provider_opencode = id_owned.clone(),
|
||||
AppType::OpenClaw => settings.current_provider_openclaw = id_owned.clone(),
|
||||
AppType::Hermes => settings.current_provider_hermes = id_owned.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -554,6 +554,7 @@ command = "echo"
|
||||
codex: false, // 初始未启用
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -682,6 +683,7 @@ fn import_from_claude_merges_into_config() {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -217,6 +217,7 @@ fn set_mcp_enabled_for_codex_writes_live_config() {
|
||||
codex: false, // 初始未启用
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -281,6 +282,7 @@ fn enabling_codex_mcp_skips_when_codex_dir_missing() {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -325,6 +327,7 @@ fn upsert_mcp_server_disabling_app_removes_from_claude_live_config() {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -358,6 +361,7 @@ fn upsert_mcp_server_disabling_app_removes_from_claude_live_config() {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -490,6 +494,7 @@ fn enabling_gemini_mcp_skips_when_gemini_dir_missing() {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -544,6 +549,7 @@ fn enabling_claude_mcp_skips_when_claude_config_absent() {
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -604,6 +610,7 @@ fn sync_all_enabled_removes_known_disabled_but_preserves_unknown_live_entries()
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
@@ -625,6 +632,7 @@ fn sync_all_enabled_removes_known_disabled_but_preserves_unknown_live_entries()
|
||||
codex: false,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -75,6 +75,7 @@ command = "say"
|
||||
codex: true, // 启用 Codex
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
@@ -163,6 +163,7 @@ command = "say"
|
||||
codex: true,
|
||||
gemini: false,
|
||||
opencode: false,
|
||||
hermes: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
|
||||
Reference in New Issue
Block a user