claudeup 4.32.1 → 4.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/__tests__/content-drift.test.ts +10 -1
- package/src/__tests__/gitignore-detector.test.ts +13 -1
- package/src/__tests__/mcp-registry.test.ts +158 -0
- package/src/__tests__/skill-install-files.test.ts +128 -0
- package/src/__tests__/skills-catalog-client.test.ts +48 -0
- package/src/data/mcp-servers.ts +5 -480
- package/src/data/skill-repos.ts +6 -108
- package/src/services/mcp-registry.ts +118 -52
- package/src/services/skills-manager.ts +143 -52
- package/src/services/skillsmp-client.ts +82 -8
- package/src/ui/screens/McpScreen.tsx +13 -5
- package/src/ui/screens/SkillsScreen.tsx +51 -21
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
installSkill,
|
|
16
16
|
uninstallSkill,
|
|
17
17
|
} from "../../services/skills-manager.js";
|
|
18
|
-
import { searchSkills } from "../../services/skillsmp-client.js";
|
|
19
|
-
import { DEFAULT_SKILL_REPOS,
|
|
18
|
+
import { fetchCuratedSkillsCatalog, searchSkills } from "../../services/skillsmp-client.js";
|
|
19
|
+
import { DEFAULT_SKILL_REPOS, classifyStarReliability, type RecommendedSkill } from "../../data/skill-repos.js";
|
|
20
20
|
import type { SkillInfo, SkillSetInfo, SkillSource } from "../../types/index.js";
|
|
21
21
|
import { buildSkillBrowserItems } from "../adapters/skillsAdapter.js";
|
|
22
22
|
import type { SkillBrowserItem } from "../adapters/skillsAdapter.js";
|
|
@@ -28,6 +28,9 @@ export function SkillsScreen() {
|
|
|
28
28
|
const { skills: skillsState } = state;
|
|
29
29
|
const modal = useModal();
|
|
30
30
|
const dimensions = useDimensions();
|
|
31
|
+
const [curatedSkills, setCuratedSkills] = useState<RecommendedSkill[]>([]);
|
|
32
|
+
const [skillSets, setSkillSets] = useState<SkillSetInfo[]>([]);
|
|
33
|
+
const [expandedSets, setExpandedSets] = useState<Set<string>>(new Set());
|
|
31
34
|
|
|
32
35
|
const isSearchActive =
|
|
33
36
|
state.isSearching &&
|
|
@@ -39,9 +42,30 @@ export function SkillsScreen() {
|
|
|
39
42
|
const fetchData = useCallback(async () => {
|
|
40
43
|
dispatch({ type: "SKILLS_DATA_LOADING" });
|
|
41
44
|
try {
|
|
45
|
+
const catalog = await fetchCuratedSkillsCatalog();
|
|
46
|
+
setCuratedSkills(catalog.skills);
|
|
47
|
+
setSkillSets((previous) =>
|
|
48
|
+
catalog.skillSets.map((skillSet) => {
|
|
49
|
+
const existing = previous.find((item) => item.repo === skillSet.repo);
|
|
50
|
+
return existing
|
|
51
|
+
? { ...existing, ...skillSet, id: skillSet.repo }
|
|
52
|
+
: {
|
|
53
|
+
id: skillSet.repo,
|
|
54
|
+
name: skillSet.name,
|
|
55
|
+
description: skillSet.description,
|
|
56
|
+
repo: skillSet.repo,
|
|
57
|
+
icon: skillSet.icon,
|
|
58
|
+
stars: skillSet.stars,
|
|
59
|
+
skills: [],
|
|
60
|
+
loaded: false,
|
|
61
|
+
loading: false,
|
|
62
|
+
};
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
42
65
|
const skills = await fetchAvailableSkills(
|
|
43
66
|
DEFAULT_SKILL_REPOS,
|
|
44
67
|
state.projectPath,
|
|
68
|
+
catalog.skills,
|
|
45
69
|
);
|
|
46
70
|
dispatch({ type: "SKILLS_DATA_SUCCESS", skills });
|
|
47
71
|
} catch (error) {
|
|
@@ -159,20 +183,6 @@ export function SkillsScreen() {
|
|
|
159
183
|
|
|
160
184
|
// ── Skill Sets state ──────────────────────────────────────────────────────
|
|
161
185
|
|
|
162
|
-
const [skillSets, setSkillSets] = useState<SkillSetInfo[]>(() =>
|
|
163
|
-
RECOMMENDED_SKILL_SETS.map((rs) => ({
|
|
164
|
-
id: rs.repo,
|
|
165
|
-
name: rs.name,
|
|
166
|
-
description: rs.description,
|
|
167
|
-
repo: rs.repo,
|
|
168
|
-
icon: rs.icon,
|
|
169
|
-
stars: rs.stars,
|
|
170
|
-
skills: [],
|
|
171
|
-
loaded: false,
|
|
172
|
-
loading: false,
|
|
173
|
-
})),
|
|
174
|
-
);
|
|
175
|
-
const [expandedSets, setExpandedSets] = useState<Set<string>>(new Set());
|
|
176
186
|
|
|
177
187
|
// Re-mark installed status on child skills when disk state changes
|
|
178
188
|
useEffect(() => {
|
|
@@ -261,10 +271,12 @@ export function SkillsScreen() {
|
|
|
261
271
|
showStatus(`Installing ${toInstall.length} skills from ${set.name}...`);
|
|
262
272
|
let installed = 0;
|
|
263
273
|
let failed = 0;
|
|
274
|
+
let degraded = 0;
|
|
264
275
|
for (const skill of toInstall) {
|
|
265
276
|
try {
|
|
266
|
-
await installSkill(skill, scope, state.projectPath);
|
|
277
|
+
const result = await installSkill(skill, scope, state.projectPath);
|
|
267
278
|
installed++;
|
|
279
|
+
if (result.degraded) degraded++;
|
|
268
280
|
} catch {
|
|
269
281
|
failed++;
|
|
270
282
|
}
|
|
@@ -272,6 +284,13 @@ export function SkillsScreen() {
|
|
|
272
284
|
await scanDisk();
|
|
273
285
|
if (failed > 0) {
|
|
274
286
|
showStatus(`Installed ${installed}/${toInstall.length} (${failed} failed)`, "error");
|
|
287
|
+
} else if (degraded > 0) {
|
|
288
|
+
// Incomplete is not the same as installed. Say so rather than let a
|
|
289
|
+
// batch of half-written skills report a clean success.
|
|
290
|
+
showStatus(
|
|
291
|
+
`Installed ${installed} skills from ${set.name} to ${scope} — ${degraded} incomplete (missing supporting files)`,
|
|
292
|
+
"error",
|
|
293
|
+
);
|
|
275
294
|
} else {
|
|
276
295
|
showStatus(`Installed ${installed} skills from ${set.name} to ${scope}`);
|
|
277
296
|
}
|
|
@@ -282,7 +301,7 @@ export function SkillsScreen() {
|
|
|
282
301
|
// ── Derived data ──────────────────────────────────────────────────────────
|
|
283
302
|
|
|
284
303
|
const staticRecommended = useMemo((): SkillInfo[] => {
|
|
285
|
-
return
|
|
304
|
+
return curatedSkills.map((r) => {
|
|
286
305
|
const slug = r.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
287
306
|
const isUser = installedFromDisk.user.has(slug) || installedFromDisk.user.has(r.name);
|
|
288
307
|
const isProj = installedFromDisk.project.has(slug) || installedFromDisk.project.has(r.name);
|
|
@@ -302,7 +321,7 @@ export function SkillsScreen() {
|
|
|
302
321
|
starReliability: classifyStarReliability(r.repo, r.stars),
|
|
303
322
|
};
|
|
304
323
|
});
|
|
305
|
-
}, [installedFromDisk]);
|
|
324
|
+
}, [curatedSkills, installedFromDisk]);
|
|
306
325
|
|
|
307
326
|
const mergedRecommended = useMemo((): SkillInfo[] => {
|
|
308
327
|
if (skillsState.skills.status !== "success") return staticRecommended;
|
|
@@ -433,9 +452,20 @@ export function SkillsScreen() {
|
|
|
433
452
|
const handleInstall = useCallback(async (scope: "user" | "project") => {
|
|
434
453
|
if (!selectedSkill) return;
|
|
435
454
|
try {
|
|
436
|
-
await installSkill(selectedSkill, scope, state.projectPath);
|
|
455
|
+
const result = await installSkill(selectedSkill, scope, state.projectPath);
|
|
437
456
|
await scanDisk(); // Re-scan disk only — no network re-fetch needed
|
|
438
|
-
|
|
457
|
+
const files =
|
|
458
|
+
result.fileCount === 1 ? "1 file" : `${result.fileCount} files`;
|
|
459
|
+
// An install that silently dropped supporting files used to report plain
|
|
460
|
+
// success, so a half-installed skill looked identical to a good one.
|
|
461
|
+
if (result.degraded) {
|
|
462
|
+
showStatus(
|
|
463
|
+
`Installed ${selectedSkill.name} to ${scope} (${files}) — ${result.degraded}`,
|
|
464
|
+
"error",
|
|
465
|
+
);
|
|
466
|
+
} else {
|
|
467
|
+
showStatus(`Installed ${selectedSkill.name} to ${scope} (${files})`);
|
|
468
|
+
}
|
|
439
469
|
} catch (error) {
|
|
440
470
|
showStatus(`Failed: ${error}`, "error");
|
|
441
471
|
}
|