Skip to content
Open
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
127 changes: 0 additions & 127 deletions src-tauri/src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;

use crate::prompt_files::prompt_file_path;
use crate::services::skill::SkillStore;

/// MCP 服务器应用状态(标记应用到哪些客户端)
Expand Down Expand Up @@ -636,132 +635,6 @@ impl MultiAppConfig {
}
}

/// 创建默认配置并自动导入已存在的提示词文件
fn default_with_auto_import() -> Result<Self, AppError> {
log::info!("首次启动,创建默认配置并检测提示词文件");

let mut config = Self::default();

// 为每个应用尝试自动导入提示词
Self::auto_import_prompt_if_exists(&mut config, AppType::Claude)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::Codex)?;
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::Hermes)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::OpenClaw)?;

Ok(config)
}

/// 已存在配置文件时的 Prompt 自动导入逻辑
///
/// 适用于「老版本已经生成过 config.json,但当时还没有 Prompt 功能」的升级场景。
/// 判定规则:
/// - 仅当所有应用的 prompts 都为空时才尝试导入(避免打扰已经在使用 Prompt 功能的用户)
/// - 每个应用最多导入一次,对应各自的提示词文件(如 CLAUDE.md/AGENTS.md/GEMINI.md)
///
/// 返回值:
/// - Ok(true) 表示至少有一个应用成功导入了提示词
/// - Ok(false) 表示无需导入或未导入任何内容
fn maybe_auto_import_prompts_for_existing_config(&mut self) -> Result<bool, AppError> {
// 如果任一应用已经有提示词配置,说明用户已经在使用 Prompt 功能,避免再次自动导入
if !self.prompts.claude.prompts.is_empty()
|| !self.prompts.codex.prompts.is_empty()
|| !self.prompts.gemini.prompts.is_empty()
|| !self.prompts.opencode.prompts.is_empty()
|| !self.prompts.hermes.prompts.is_empty()
|| !self.prompts.openclaw.prompts.is_empty()
{
return Ok(false);
}

log::info!("检测到已存在配置文件且 Prompt 列表为空,将尝试从现有提示词文件自动导入");

let mut imported = false;
for app in [
AppType::Claude,
AppType::Codex,
AppType::Gemini,
AppType::OpenCode,
AppType::Hermes,
AppType::OpenClaw,
] {
// 复用已有的单应用导入逻辑
if Self::auto_import_prompt_if_exists(self, app)? {
imported = true;
}
}

Ok(imported)
}

/// 检查并自动导入单个应用的提示词文件
///
/// 返回值:
/// - Ok(true) 表示成功导入了非空文件
/// - Ok(false) 表示未导入(文件不存在、内容为空或读取失败)
fn auto_import_prompt_if_exists(config: &mut Self, app: AppType) -> Result<bool, AppError> {
let file_path = prompt_file_path(&app)?;

// 检查文件是否存在
if !file_path.exists() {
log::debug!("提示词文件不存在,跳过自动导入: {file_path:?}");
return Ok(false);
}

// 读取文件内容
let content = match std::fs::read_to_string(&file_path) {
Ok(c) => c,
Err(e) => {
log::warn!("读取提示词文件失败: {file_path:?}, 错误: {e}");
return Ok(false); // 失败时不中断,继续处理其他应用
}
};

// 检查内容是否为空
if content.trim().is_empty() {
log::debug!("提示词文件内容为空,跳过导入: {file_path:?}");
return Ok(false);
}

log::info!("发现提示词文件,自动导入: {file_path:?}");

// 创建提示词对象
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;

let id = format!("auto-imported-{timestamp}");
let prompt = crate::prompt::Prompt {
id: id.clone(),
name: format!(
"Auto-imported Prompt {}",
chrono::Local::now().format("%Y-%m-%d %H:%M")
),
content,
description: Some("Automatically imported on first launch".to_string()),
enabled: true, // 自动启用
created_at: Some(timestamp),
updated_at: Some(timestamp),
};

// 插入到对应的应用配置中
let prompts = match app {
AppType::Claude => &mut config.prompts.claude.prompts,
AppType::Codex => &mut config.prompts.codex.prompts,
AppType::Gemini => &mut config.prompts.gemini.prompts,
AppType::OpenCode => &mut config.prompts.opencode.prompts,
AppType::Hermes => &mut config.prompts.hermes.prompts,
AppType::OpenClaw => &mut config.prompts.openclaw.prompts,
};

prompts.insert(id, prompt);

log::info!("自动导入完成: {}", app.as_str());
Ok(true)
}

/// 将 v3.6.x 的分应用 MCP 结构迁移到 v3.7.0 的统一结构
///
/// 迁移策略:
Expand Down
5 changes: 5 additions & 0 deletions src-tauri/src/claude_mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::error::AppError;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
pub struct McpStatus {
pub user_config_path: String,
pub user_config_exists: bool,
Expand Down Expand Up @@ -79,6 +80,7 @@ fn write_json_value(path: &Path, value: &Value) -> Result<(), AppError> {
atomic_write(path, json.as_bytes())
}

#[allow(dead_code)]
pub fn get_mcp_status() -> Result<McpStatus, AppError> {
let path = user_config_path();
let (exists, count) = if path.exists() {
Expand Down Expand Up @@ -154,6 +156,7 @@ pub fn clear_has_completed_onboarding() -> Result<bool, AppError> {
Ok(true)
}

#[allow(dead_code)]
pub fn upsert_mcp_server(id: &str, spec: Value) -> Result<bool, AppError> {
if id.trim().is_empty() {
return Err(AppError::InvalidInput("MCP 服务器 ID 不能为空".into()));
Expand Down Expand Up @@ -226,6 +229,7 @@ pub fn upsert_mcp_server(id: &str, spec: Value) -> Result<bool, AppError> {
Ok(true)
}

#[allow(dead_code)]
pub fn delete_mcp_server(id: &str) -> Result<bool, AppError> {
if id.trim().is_empty() {
return Err(AppError::InvalidInput("MCP 服务器 ID 不能为空".into()));
Expand All @@ -246,6 +250,7 @@ pub fn delete_mcp_server(id: &str) -> Result<bool, AppError> {
Ok(true)
}

#[allow(dead_code)]
pub fn validate_command_in_path(cmd: &str) -> Result<bool, AppError> {
if cmd.trim().is_empty() {
return Ok(false);
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/claude_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub fn read_claude_config() -> Result<Option<String>, AppError> {
}
}

#[allow(dead_code)]
fn is_managed_config(content: &str) -> bool {
match serde_json::from_str::<serde_json::Value>(content) {
Ok(value) => value
Expand Down Expand Up @@ -120,11 +121,13 @@ pub fn clear_claude_config() -> Result<bool, AppError> {
Ok(true)
}

#[allow(dead_code)]
pub fn claude_config_status() -> Result<(bool, PathBuf), AppError> {
let path = claude_config_path()?;
Ok((path.exists(), path))
}

#[allow(dead_code)]
pub fn is_claude_config_applied() -> Result<bool, AppError> {
match read_claude_config()? {
Some(content) => Ok(is_managed_config(&content)),
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/cli/claude_temp_launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::error::AppError;
#[cfg(test)]
use crate::provider::Provider;
use crate::services::provider::ProviderService;
use serde_json::Value;
Expand All @@ -21,6 +22,8 @@ impl PreparedClaudeLaunch {
}
}

#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn prepare_launch(
provider: &Provider,
temp_dir: &Path,
Expand All @@ -36,6 +39,7 @@ pub(crate) fn prepare_launch_from_settings(
prepare_launch_from_settings_with(provider_id, settings, temp_dir, resolve_claude_binary)
}

#[cfg(test)]
pub(crate) fn prepare_launch_with<Resolve>(
provider: &Provider,
temp_dir: &Path,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/cli/commands/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ fn current_executable() -> Result<PathBuf, AppError> {
fn detach_into_background() -> Result<(), AppError> {
// Double-fork via libc::daemon. nochdir=1 keeps cwd, noclose=0 redirects
// stdio to /dev/null so the daemon doesn't keep the parent terminal open.
#[allow(deprecated)]
let rc = unsafe { libc::daemon(1, 0) };
if rc != 0 {
let err = std::io::Error::last_os_error();
Expand Down
20 changes: 4 additions & 16 deletions src-tauri/src/cli/commands/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ struct DownloadedAsset {
#[derive(Debug, Deserialize, Clone)]
struct UpdateManifest {
version: String,
#[serde(default)]
notes: Option<String>,
#[serde(default)]
pub_date: Option<String>,
#[serde(default, rename = "notes")]
_notes: Option<String>,
#[serde(default, rename = "pub_date")]
_pub_date: Option<String>,
platforms: BTreeMap<String, UpdatePlatformEntry>,
}

Expand Down Expand Up @@ -657,18 +657,6 @@ async fn resolve_target_release(
})
}

async fn resolve_target_tag(
client: &reqwest::Client,
version: Option<&str>,
) -> Result<String, AppError> {
let tag = match version.map(str::trim).filter(|v| !v.is_empty()) {
Some(version) => normalize_tag(version),
None => fetch_latest_release_tag(client, REPO_URL).await?,
};
validate_target_tag(&tag)?;
Ok(tag)
}

fn validate_target_tag(tag: &str) -> Result<(), AppError> {
if !tag.starts_with('v') {
return Err(AppError::Message(format!(
Expand Down
12 changes: 6 additions & 6 deletions src-tauri/src/cli/commands/update/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ fn validate_download_size_limit_rejects_oversized_asset() {
fn select_manifest_asset_prefers_linux_glibc_variant_when_overridden() {
let manifest = UpdateManifest {
version: "v4.6.3".to_string(),
notes: None,
pub_date: None,
_notes: None,
_pub_date: None,
platforms: BTreeMap::from([(
"linux-x86_64".to_string(),
UpdatePlatformEntry {
Expand Down Expand Up @@ -312,8 +312,8 @@ fn select_manifest_asset_prefers_linux_glibc_variant_when_overridden() {
fn select_manifest_asset_accepts_glibc_primary_entry_without_variant() {
let manifest = UpdateManifest {
version: "v4.6.3".to_string(),
notes: None,
pub_date: None,
_notes: None,
_pub_date: None,
platforms: BTreeMap::from([(
"linux-x86_64".to_string(),
UpdatePlatformEntry {
Expand All @@ -334,8 +334,8 @@ fn select_manifest_asset_accepts_glibc_primary_entry_without_variant() {
fn manifest_linux_asset_candidates_keep_musl_strict_when_forced() {
let manifest = UpdateManifest {
version: "v4.6.3".to_string(),
notes: None,
pub_date: None,
_notes: None,
_pub_date: None,
platforms: BTreeMap::from([(
"linux-x86_64".to_string(),
UpdatePlatformEntry {
Expand Down
24 changes: 0 additions & 24 deletions src-tauri/src/cli/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2458,22 +2458,6 @@ pub mod texts {
}
}

pub fn tui_usage_query_copilot_auto_auth() -> &'static str {
if is_chinese() {
"自动使用 OAuth 认证,无需手动配置凭证"
} else {
"Auto OAuth authentication, no manual credentials needed"
}
}

pub fn tui_usage_query_token_plan_hint() -> &'static str {
if is_chinese() {
"自动使用供应商的 API Key 和 Base URL 查询 Token Plan 额度"
} else {
"Automatically uses the provider's API Key and Base URL to query Token Plan quota"
}
}

pub fn tui_usage_query_balance_hint() -> &'static str {
if is_chinese() {
"自动使用供应商的 API Key 查询账户余额"
Expand All @@ -2498,14 +2482,6 @@ pub mod texts {
}
}

pub fn tui_usage_query_coding_plan_provider() -> &'static str {
if is_chinese() {
"Coding Plan 供应商"
} else {
"Coding Plan Provider"
}
}

pub fn tui_usage_query_info() -> &'static str {
if is_chinese() {
"说明"
Expand Down
24 changes: 0 additions & 24 deletions src-tauri/src/cli/i18n/texts/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,22 +318,6 @@ pub fn tui_usage_query_script_help_title() -> &'static str {
}
}

pub fn tui_usage_query_copilot_auto_auth() -> &'static str {
if is_chinese() {
"自动使用 OAuth 认证,无需手动配置凭证"
} else {
"Auto OAuth authentication, no manual credentials needed"
}
}

pub fn tui_usage_query_token_plan_hint() -> &'static str {
if is_chinese() {
"自动使用供应商的 API Key 和 Base URL 查询 Token Plan 额度"
} else {
"Automatically uses the provider's API Key and Base URL to query Token Plan quota"
}
}

pub fn tui_usage_query_balance_hint() -> &'static str {
if is_chinese() {
"自动使用供应商的 API Key 查询账户余额"
Expand All @@ -358,14 +342,6 @@ pub fn tui_usage_query_must_have_return() -> &'static str {
}
}

pub fn tui_usage_query_coding_plan_provider() -> &'static str {
if is_chinese() {
"Coding Plan 供应商"
} else {
"Coding Plan Provider"
}
}

pub fn tui_usage_query_info() -> &'static str {
if is_chinese() {
"说明"
Expand Down
Loading
Loading