Files
cc-switch/src/lib/api/skills.ts
Jason 7097a0d710 fix: replace implicit app inference with explicit selection for Skills import and sync
Skills import previously inferred app enablement from filesystem presence,
causing incorrect multi-app activation when the same skill directory existed
under multiple app paths. Now the frontend submits explicit app selections
via ImportSkillSelection, and schema migration preserves a snapshot of
legacy app mappings to avoid lossy reconstruction.

Also adds reconciliation to sync_to_app (removes disabled/orphaned symlinks)
and MCP sync_all_enabled (removes disabled servers from live config).
2026-03-14 23:41:36 +08:00

181 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { invoke } from "@tauri-apps/api/core";
import type { AppId } from "@/lib/api/types";
export type AppType = "claude" | "codex" | "gemini" | "opencode" | "openclaw";
/** Skill 应用启用状态 */
export interface SkillApps {
claude: boolean;
codex: boolean;
gemini: boolean;
opencode: boolean;
openclaw: boolean;
}
/** 已安装的 Skillv3.10.0+ 统一结构) */
export interface InstalledSkill {
id: string;
name: string;
description?: string;
directory: string;
repoOwner?: string;
repoName?: string;
repoBranch?: string;
readmeUrl?: string;
apps: SkillApps;
installedAt: number;
}
/** 可发现的 Skill来自仓库 */
export interface DiscoverableSkill {
key: string;
name: string;
description: string;
directory: string;
readmeUrl?: string;
repoOwner: string;
repoName: string;
repoBranch: string;
}
/** 未管理的 Skill用于导入 */
export interface UnmanagedSkill {
directory: string;
name: string;
description?: string;
foundIn: string[];
path: string;
}
/** 导入已有 Skill 时提交的应用启用状态 */
export interface ImportSkillSelection {
directory: string;
apps: SkillApps;
}
/** 技能对象(兼容旧 API */
export interface Skill {
key: string;
name: string;
description: string;
directory: string;
readmeUrl?: string;
installed: boolean;
repoOwner?: string;
repoName?: string;
repoBranch?: string;
}
/** 仓库配置 */
export interface SkillRepo {
owner: string;
name: string;
branch: string;
enabled: boolean;
}
// ========== API ==========
export const skillsApi = {
// ========== 统一管理 API (v3.10.0+) ==========
/** 获取所有已安装的 Skills */
async getInstalled(): Promise<InstalledSkill[]> {
return await invoke("get_installed_skills");
},
/** 安装 Skill统一安装 */
async installUnified(
skill: DiscoverableSkill,
currentApp: AppId,
): Promise<InstalledSkill> {
return await invoke("install_skill_unified", { skill, currentApp });
},
/** 卸载 Skill统一卸载 */
async uninstallUnified(id: string): Promise<boolean> {
return await invoke("uninstall_skill_unified", { id });
},
/** 切换 Skill 的应用启用状态 */
async toggleApp(id: string, app: AppId, enabled: boolean): Promise<boolean> {
return await invoke("toggle_skill_app", { id, app, enabled });
},
/** 扫描未管理的 Skills */
async scanUnmanaged(): Promise<UnmanagedSkill[]> {
return await invoke("scan_unmanaged_skills");
},
/** 从应用目录导入 Skills */
async importFromApps(
imports: ImportSkillSelection[],
): Promise<InstalledSkill[]> {
return await invoke("import_skills_from_apps", { imports });
},
/** 发现可安装的 Skills从仓库获取 */
async discoverAvailable(): Promise<DiscoverableSkill[]> {
return await invoke("discover_available_skills");
},
// ========== 兼容旧 API ==========
/** 获取技能列表(兼容旧 API */
async getAll(app: AppId = "claude"): Promise<Skill[]> {
if (app === "claude") {
return await invoke("get_skills");
}
return await invoke("get_skills_for_app", { app });
},
/** 安装技能(兼容旧 API */
async install(directory: string, app: AppId = "claude"): Promise<boolean> {
if (app === "claude") {
return await invoke("install_skill", { directory });
}
return await invoke("install_skill_for_app", { app, directory });
},
/** 卸载技能(兼容旧 API */
async uninstall(directory: string, app: AppId = "claude"): Promise<boolean> {
if (app === "claude") {
return await invoke("uninstall_skill", { directory });
}
return await invoke("uninstall_skill_for_app", { app, directory });
},
// ========== 仓库管理 ==========
/** 获取仓库列表 */
async getRepos(): Promise<SkillRepo[]> {
return await invoke("get_skill_repos");
},
/** 添加仓库 */
async addRepo(repo: SkillRepo): Promise<boolean> {
return await invoke("add_skill_repo", { repo });
},
/** 删除仓库 */
async removeRepo(owner: string, name: string): Promise<boolean> {
return await invoke("remove_skill_repo", { owner, name });
},
// ========== ZIP 安装 ==========
/** 打开 ZIP 文件选择对话框 */
async openZipFileDialog(): Promise<string | null> {
return await invoke("open_zip_file_dialog");
},
/** 从 ZIP 文件安装 Skills */
async installFromZip(
filePath: string,
currentApp: AppId,
): Promise<InstalledSkill[]> {
return await invoke("install_skills_from_zip", { filePath, currentApp });
},
};