From d9a4c7cb86a166ce9712444b4f98753b88e88441 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Fri, 28 Nov 2025 16:23:06 +0800 Subject: [PATCH] refactor(skill): remove skillsPath configuration Remove the skillsPath field from SkillRepo and Skill structs since recursive scanning now automatically discovers skills in all directories. Simplify the UI by removing the path input field. --- src-tauri/src/commands/skill.rs | 1 - src-tauri/src/database/dao/skills.rs | 9 ++-- src-tauri/src/database/migration.rs | 4 +- src-tauri/src/database/schema.rs | 3 +- src-tauri/src/deeplink/mod.rs | 4 -- src-tauri/src/deeplink/parser.rs | 8 --- src-tauri/src/deeplink/skill.rs | 1 - src-tauri/src/deeplink/tests.rs | 7 +-- src-tauri/src/services/skill.rs | 50 ++----------------- src/components/deeplink/SkillConfirmation.tsx | 21 ++------ src/components/skills/RepoManager.tsx | 16 ------ src/components/skills/RepoManagerPanel.tsx | 45 ++++------------- src/lib/api/deeplink.ts | 1 - src/lib/api/skills.ts | 2 - 14 files changed, 30 insertions(+), 142 deletions(-) diff --git a/src-tauri/src/commands/skill.rs b/src-tauri/src/commands/skill.rs index 64ea9506..b81c5096 100644 --- a/src-tauri/src/commands/skill.rs +++ b/src-tauri/src/commands/skill.rs @@ -90,7 +90,6 @@ pub async fn install_skill( .clone() .unwrap_or_else(|| "main".to_string()), enabled: true, - skills_path: skill.skills_path.clone(), // 使用技能记录的 skills_path }; service diff --git a/src-tauri/src/database/dao/skills.rs b/src-tauri/src/database/dao/skills.rs index 59c90d72..5fc02ee0 100644 --- a/src-tauri/src/database/dao/skills.rs +++ b/src-tauri/src/database/dao/skills.rs @@ -58,7 +58,9 @@ impl Database { pub fn get_skill_repos(&self) -> Result, AppError> { let conn = lock_conn!(self.conn); let mut stmt = conn - .prepare("SELECT owner, name, branch, enabled, skills_path FROM skill_repos ORDER BY owner ASC, name ASC") + .prepare( + "SELECT owner, name, branch, enabled FROM skill_repos ORDER BY owner ASC, name ASC", + ) .map_err(|e| AppError::Database(e.to_string()))?; let repo_iter = stmt @@ -68,7 +70,6 @@ impl Database { name: row.get(1)?, branch: row.get(2)?, enabled: row.get(3)?, - skills_path: row.get(4)?, }) }) .map_err(|e| AppError::Database(e.to_string()))?; @@ -84,8 +85,8 @@ impl Database { pub fn save_skill_repo(&self, repo: &SkillRepo) -> Result<(), AppError> { let conn = lock_conn!(self.conn); conn.execute( - "INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled, skills_path) VALUES (?1, ?2, ?3, ?4, ?5)", - params![repo.owner, repo.name, repo.branch, repo.enabled, repo.skills_path], + "INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)", + params![repo.owner, repo.name, repo.branch, repo.enabled], ).map_err(|e| AppError::Database(e.to_string()))?; Ok(()) } diff --git a/src-tauri/src/database/migration.rs b/src-tauri/src/database/migration.rs index c96eb4e2..44c9dc14 100644 --- a/src-tauri/src/database/migration.rs +++ b/src-tauri/src/database/migration.rs @@ -202,8 +202,8 @@ impl Database { for repo in &config.skills.repos { tx.execute( - "INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled, skills_path) VALUES (?1, ?2, ?3, ?4, ?5)", - params![repo.owner, repo.name, repo.branch, repo.enabled, repo.skills_path], + "INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)", + params![repo.owner, repo.name, repo.branch, repo.enabled], ).map_err(|e| AppError::Database(format!("Migrate skill repo failed: {e}")))?; } diff --git a/src-tauri/src/database/schema.rs b/src-tauri/src/database/schema.rs index fd75818b..58005148 100644 --- a/src-tauri/src/database/schema.rs +++ b/src-tauri/src/database/schema.rs @@ -104,7 +104,6 @@ impl Database { name TEXT NOT NULL, branch TEXT NOT NULL DEFAULT 'main', enabled BOOLEAN NOT NULL DEFAULT 1, - skills_path TEXT, PRIMARY KEY (owner, name) )", [], @@ -233,7 +232,7 @@ impl Database { "TEXT NOT NULL DEFAULT 'main'", )?; Self::add_column_if_missing(conn, "skill_repos", "enabled", "BOOLEAN NOT NULL DEFAULT 1")?; - Self::add_column_if_missing(conn, "skill_repos", "skills_path", "TEXT")?; + // 注意: skills_path 字段已被移除,因为现在支持全仓库递归扫描 Ok(()) } diff --git a/src-tauri/src/deeplink/mod.rs b/src-tauri/src/deeplink/mod.rs index feb6fc58..5c1e1a72 100644 --- a/src-tauri/src/deeplink/mod.rs +++ b/src-tauri/src/deeplink/mod.rs @@ -100,12 +100,8 @@ pub struct DeepLinkImportRequest { /// Skill directory name #[serde(skip_serializing_if = "Option::is_none")] pub directory: Option, - /// Repository branch (default: "main") #[serde(skip_serializing_if = "Option::is_none")] pub branch: Option, - /// Skills subdirectory path (e.g., "skills") - #[serde(skip_serializing_if = "Option::is_none")] - pub skills_path: Option, // ============ Config file fields (v3.8+) ============ /// Base64 encoded config content diff --git a/src-tauri/src/deeplink/parser.rs b/src-tauri/src/deeplink/parser.rs index fb0bedf5..316af003 100644 --- a/src-tauri/src/deeplink/parser.rs +++ b/src-tauri/src/deeplink/parser.rs @@ -143,7 +143,6 @@ fn parse_provider_deeplink( repo: None, directory: None, branch: None, - skills_path: None, config, config_format, config_url, @@ -204,7 +203,6 @@ fn parse_prompt_deeplink( repo: None, directory: None, branch: None, - skills_path: None, config: None, config_format: None, config_url: None, @@ -262,7 +260,6 @@ fn parse_mcp_deeplink( repo: None, directory: None, branch: None, - skills_path: None, config_url: None, }) } @@ -287,10 +284,6 @@ fn parse_skill_deeplink( let directory = params.get("directory").cloned(); let branch = params.get("branch").cloned(); - let skills_path = params - .get("skills_path") - .or_else(|| params.get("skillsPath")) - .cloned(); Ok(DeepLinkImportRequest { version, @@ -298,7 +291,6 @@ fn parse_skill_deeplink( repo: Some(repo), directory, branch, - skills_path, icon: None, app: Some("claude".to_string()), // Skills are Claude-only name: None, diff --git a/src-tauri/src/deeplink/skill.rs b/src-tauri/src/deeplink/skill.rs index 8dc3c8de..d4369eaf 100644 --- a/src-tauri/src/deeplink/skill.rs +++ b/src-tauri/src/deeplink/skill.rs @@ -40,7 +40,6 @@ pub fn import_skill_from_deeplink( name: name.clone(), branch: request.branch.unwrap_or_else(|| "main".to_string()), enabled: request.enabled.unwrap_or(true), - skills_path: request.skills_path, }; // Save using Database diff --git a/src-tauri/src/deeplink/tests.rs b/src-tauri/src/deeplink/tests.rs index 40568154..b20fa55c 100644 --- a/src-tauri/src/deeplink/tests.rs +++ b/src-tauri/src/deeplink/tests.rs @@ -142,7 +142,6 @@ fn test_build_gemini_provider_with_model() { repo: None, directory: None, branch: None, - skills_path: None, content: None, description: None, enabled: None, @@ -189,7 +188,6 @@ fn test_build_gemini_provider_without_model() { repo: None, directory: None, branch: None, - skills_path: None, content: None, description: None, enabled: None, @@ -231,7 +229,6 @@ fn test_parse_and_merge_config_claude() { repo: None, directory: None, branch: None, - skills_path: None, content: None, description: None, enabled: None, @@ -275,7 +272,6 @@ fn test_parse_and_merge_config_url_override() { repo: None, directory: None, branch: None, - skills_path: None, content: None, description: None, enabled: None, @@ -372,12 +368,11 @@ fn test_parse_mcp_deeplink() { #[test] fn test_parse_skill_deeplink() { - let url = "ccswitch://v1/import?resource=skill&repo=owner/repo&directory=skills&branch=dev&skills_path=src"; + let url = "ccswitch://v1/import?resource=skill&repo=owner/repo&directory=skills&branch=dev"; let request = parse_deeplink_url(&url).unwrap(); assert_eq!(request.resource, "skill"); assert_eq!(request.repo.unwrap(), "owner/repo"); assert_eq!(request.directory.unwrap(), "skills"); assert_eq!(request.branch.unwrap(), "dev"); - assert_eq!(request.skills_path.unwrap(), "src"); } diff --git a/src-tauri/src/services/skill.rs b/src-tauri/src/services/skill.rs index 23a3e4d2..3130affd 100644 --- a/src-tauri/src/services/skill.rs +++ b/src-tauri/src/services/skill.rs @@ -34,9 +34,6 @@ pub struct Skill { /// 分支名称 #[serde(rename = "repoBranch")] pub repo_branch: Option, - /// 技能所在的子目录路径 (可选, 如 "skills") - #[serde(rename = "skillsPath")] - pub skills_path: Option, } /// 仓库配置 @@ -50,9 +47,6 @@ pub struct SkillRepo { pub branch: String, /// 是否启用 pub enabled: bool, - /// 技能所在的子目录路径 (可选, 如 "skills", "my-skills/subdir") - #[serde(rename = "skillsPath")] - pub skills_path: Option, } /// 技能安装状态 @@ -84,21 +78,18 @@ impl Default for SkillStore { name: "awesome-claude-skills".to_string(), branch: "main".to_string(), enabled: true, - skills_path: None, // 扫描根目录 }, SkillRepo { owner: "anthropics".to_string(), name: "skills".to_string(), branch: "main".to_string(), enabled: true, - skills_path: None, // 扫描根目录 }, SkillRepo { owner: "cexll".to_string(), name: "myclaude".to_string(), branch: "master".to_string(), enabled: true, - skills_path: Some("skills".to_string()), // 扫描 skills 子目录 }, ], } @@ -194,25 +185,8 @@ impl SkillService { })??; let mut skills = Vec::new(); - // 确定要扫描的目录路径 - let scan_dir = if let Some(ref skills_path) = repo.skills_path { - // 如果指定了 skillsPath,则扫描该子目录 - let subdir = temp_dir.join(skills_path.trim_matches('/')); - if !subdir.exists() { - log::warn!( - "仓库 {}/{} 中指定的技能路径 '{}' 不存在", - repo.owner, - repo.name, - skills_path - ); - let _ = fs::remove_dir_all(&temp_dir); - return Ok(skills); - } - subdir - } else { - // 否则扫描仓库根目录 - temp_dir.clone() - }; + // 扫描仓库根目录(支持全仓库递归扫描) + let scan_dir = temp_dir.clone(); // 递归扫描目录查找所有技能 self.scan_dir_recursive(&scan_dir, &scan_dir, repo, &mut skills)?; @@ -284,11 +258,7 @@ impl SkillService { let meta = self.parse_skill_metadata(skill_md)?; // 构建 README URL - let readme_path = if let Some(ref skills_path) = repo.skills_path { - format!("{}/{}", skills_path.trim_matches('/'), directory) - } else { - directory.to_string() - }; + let readme_path = directory.to_string(); Ok(Skill { key: format!("{}/{}:{}", repo.owner, repo.name, directory), @@ -303,7 +273,6 @@ impl SkillService { repo_owner: Some(repo.owner.clone()), repo_name: Some(repo.name.clone()), repo_branch: Some(repo.branch.clone()), - skills_path: repo.skills_path.clone(), }) } @@ -405,7 +374,6 @@ impl SkillService { repo_owner: None, repo_name: None, repo_branch: None, - skills_path: None, }); } @@ -574,16 +542,8 @@ impl SkillService { )) })??; - // 根据 skills_path 确定源目录路径 - let source = if let Some(ref skills_path) = repo.skills_path { - // 如果指定了 skills_path,源路径为: temp_dir/skills_path/directory - temp_dir - .join(skills_path.trim_matches('/')) - .join(&directory) - } else { - // 否则源路径为: temp_dir/directory - temp_dir.join(&directory) - }; + // 确定源目录路径(技能相对于仓库根目录的路径) + let source = temp_dir.join(&directory); if !source.exists() { let _ = fs::remove_dir_all(&temp_dir); diff --git a/src/components/deeplink/SkillConfirmation.tsx b/src/components/deeplink/SkillConfirmation.tsx index 265379c8..40b2fa8a 100644 --- a/src/components/deeplink/SkillConfirmation.tsx +++ b/src/components/deeplink/SkillConfirmation.tsx @@ -30,22 +30,11 @@ export function SkillConfirmation({ -
-
- -
{request.branch || "main"}
-
- - {request.skillsPath && ( -
- -
{request.skillsPath}
-
- )} +
+ +
{request.branch || "main"}
diff --git a/src/components/skills/RepoManager.tsx b/src/components/skills/RepoManager.tsx index fdadf4eb..7a4839c4 100644 --- a/src/components/skills/RepoManager.tsx +++ b/src/components/skills/RepoManager.tsx @@ -34,7 +34,6 @@ export function RepoManager({ const { t } = useTranslation(); const [repoUrl, setRepoUrl] = useState(""); const [branch, setBranch] = useState(""); - const [skillsPath, setSkillsPath] = useState(""); const [error, setError] = useState(""); const getSkillCount = (repo: SkillRepo) => @@ -80,12 +79,10 @@ export function RepoManager({ name: parsed.name, branch: branch || "main", enabled: true, - skillsPath: skillsPath.trim() || undefined, // 仅在有值时传递 }); setRepoUrl(""); setBranch(""); - setSkillsPath(""); } catch (e) { setError(e instanceof Error ? e.message : t("skills.repo.addFailed")); } @@ -130,13 +127,6 @@ export function RepoManager({ onChange={(e) => setBranch(e.target.value)} className="flex-1" /> - setSkillsPath(e.target.value)} - className="flex-1" - />