feat(skills): auto-backup skill files before uninstall

Create a local backup under ~/.cc-switch/skill-backups/ before removing
skill directories. The backup includes all skill files and a meta.json
with original skill metadata. Old backups are pruned to keep at most 20.
The backup path is returned to the frontend and shown in the success
toast. Bump version to 3.12.3.
This commit is contained in:
Jason
2026-03-15 23:26:50 +08:00
parent 04254d6ffe
commit 9336001746
20 changed files with 850 additions and 28 deletions
+4 -1
View File
@@ -80,9 +80,12 @@ const UnifiedSkillsPanel = React.forwardRef<
message: t("skills.uninstallConfirm", { name: skill.name }),
onConfirm: async () => {
try {
await uninstallMutation.mutateAsync(skill.id);
const result = await uninstallMutation.mutateAsync(skill.id);
setConfirmDialog(null);
toast.success(t("skills.uninstallSuccess", { name: skill.name }), {
description: result.backupPath
? t("skills.backup.location", { path: result.backupPath })
: undefined,
closeButton: true,
});
} catch (error) {
+4 -1
View File
@@ -1564,9 +1564,12 @@
"noUnmanagedFound": "No skills to import found. All skills are already managed by CC Switch.",
"foundIn": "Found in",
"local": "Local",
"uninstallConfirm": "Are you sure you want to uninstall \"{{name}}\"? This will remove the skill from all apps.",
"uninstallConfirm": "Are you sure you want to uninstall \"{{name}}\"? This will remove the skill from all apps and create a local backup first.",
"uninstallInMainPanel": "Please uninstall skills from the main panel",
"notFound": "Skill not found",
"backup": {
"location": "Backup location: {{path}}"
},
"apps": {
"claude": "Claude",
"codex": "Codex",
+4 -1
View File
@@ -1564,9 +1564,12 @@
"noUnmanagedFound": "インポートするスキルが見つかりませんでした。すべてのスキルは CC Switch で管理されています。",
"foundIn": "発見場所",
"local": "ローカル",
"uninstallConfirm": "「{{name}}」をアンインストールしますか?すべてのアプリからこのスキルが削除されます。",
"uninstallConfirm": "「{{name}}」をアンインストールしますか?すべてのアプリからこのスキルが削除され、削除前にローカルバックアップが自動作成されます。",
"uninstallInMainPanel": "メインパネルからスキルをアンインストールしてください",
"notFound": "スキルが見つかりません",
"backup": {
"location": "バックアップ場所: {{path}}"
},
"apps": {
"claude": "Claude",
"codex": "Codex",
+4 -1
View File
@@ -1564,9 +1564,12 @@
"noUnmanagedFound": "未发现需要导入的技能。所有技能已在 CC Switch 统一管理中。",
"foundIn": "发现于",
"local": "本地",
"uninstallConfirm": "确定要卸载技能 \"{{name}}\" 吗?这将从所有应用中移除该技能。",
"uninstallConfirm": "确定要卸载技能 \"{{name}}\" 吗?这将从所有应用中移除该技能,并在删除前自动创建本地备份。",
"uninstallInMainPanel": "请在主面板中卸载技能",
"notFound": "未找到技能",
"backup": {
"location": "备份位置: {{path}}"
},
"apps": {
"claude": "Claude",
"codex": "Codex",
+9 -2
View File
@@ -27,6 +27,10 @@ export interface InstalledSkill {
installedAt: number;
}
export interface SkillUninstallResult {
backupPath?: string;
}
/** 可发现的 Skill(来自仓库) */
export interface DiscoverableSkill {
key: string;
@@ -94,7 +98,7 @@ export const skillsApi = {
},
/** 卸载 Skill(统一卸载) */
async uninstallUnified(id: string): Promise<boolean> {
async uninstallUnified(id: string): Promise<SkillUninstallResult> {
return await invoke("uninstall_skill_unified", { id });
},
@@ -139,7 +143,10 @@ export const skillsApi = {
},
/** 卸载技能(兼容旧 API) */
async uninstall(directory: string, app: AppId = "claude"): Promise<boolean> {
async uninstall(
directory: string,
app: AppId = "claude",
): Promise<SkillUninstallResult> {
if (app === "claude") {
return await invoke("uninstall_skill", { directory });
}