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.
@@ -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, RECOMMENDED_SKILLS, RECOMMENDED_SKILL_SETS, classifyStarReliability } from "../../data/skill-repos.js";
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 RECOMMENDED_SKILLS.map((r) => {
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
- showStatus(`Installed ${selectedSkill.name} to ${scope}`);
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
  }