Files
cc-switch/src/hooks/useSkills.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

174 lines
4.2 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 { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
skillsApi,
type DiscoverableSkill,
type ImportSkillSelection,
type InstalledSkill,
} from "@/lib/api/skills";
import type { AppId } from "@/lib/api/types";
/**
* 查询所有已安装的 Skills
*/
export function useInstalledSkills() {
return useQuery({
queryKey: ["skills", "installed"],
queryFn: () => skillsApi.getInstalled(),
});
}
/**
* 发现可安装的 Skills从仓库获取
*/
export function useDiscoverableSkills() {
return useQuery({
queryKey: ["skills", "discoverable"],
queryFn: () => skillsApi.discoverAvailable(),
staleTime: Infinity, // 无限缓存,直到仓库变化时 invalidate
});
}
/**
* 安装 Skill
*/
export function useInstallSkill() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
skill,
currentApp,
}: {
skill: DiscoverableSkill;
currentApp: AppId;
}) => skillsApi.installUnified(skill, currentApp),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
},
});
}
/**
* 卸载 Skill
*/
export function useUninstallSkill() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => skillsApi.uninstallUnified(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
},
});
}
/**
* 切换 Skill 在特定应用的启用状态
*/
export function useToggleSkillApp() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
id,
app,
enabled,
}: {
id: string;
app: AppId;
enabled: boolean;
}) => skillsApi.toggleApp(id, app, enabled),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
},
});
}
/**
* 扫描未管理的 Skills
*/
export function useScanUnmanagedSkills() {
return useQuery({
queryKey: ["skills", "unmanaged"],
queryFn: () => skillsApi.scanUnmanaged(),
enabled: false, // 手动触发
});
}
/**
* 从应用目录导入 Skills
*/
export function useImportSkillsFromApps() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (imports: ImportSkillSelection[]) =>
skillsApi.importFromApps(imports),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] });
},
});
}
/**
* 获取仓库列表
*/
export function useSkillRepos() {
return useQuery({
queryKey: ["skills", "repos"],
queryFn: () => skillsApi.getRepos(),
});
}
/**
* 添加仓库
*/
export function useAddSkillRepo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: skillsApi.addRepo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "repos"] });
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
},
});
}
/**
* 删除仓库
*/
export function useRemoveSkillRepo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ owner, name }: { owner: string; name: string }) =>
skillsApi.removeRepo(owner, name),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "repos"] });
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
},
});
}
/**
* 从 ZIP 文件安装 Skills
*/
export function useInstallSkillsFromZip() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
filePath,
currentApp,
}: {
filePath: string;
currentApp: AppId;
}) => skillsApi.installFromZip(filePath, currentApp),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] });
},
});
}
// ========== 辅助类型 ==========
export type { InstalledSkill, DiscoverableSkill, ImportSkillSelection, AppId };