claudeup 4.32.0 → 4.33.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__/mcp-registry.test.ts +158 -0
- package/src/__tests__/scope-action.test.ts +82 -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 +0 -108
- package/src/services/mcp-registry.ts +118 -52
- package/src/services/plugin-manager.ts +42 -0
- package/src/services/skills-manager.ts +5 -49
- package/src/services/skillsmp-client.ts +82 -8
- package/src/ui/renderers/pluginRenderers.tsx +96 -48
- package/src/ui/screens/McpScreen.tsx +13 -5
- package/src/ui/screens/PluginsScreen.tsx +18 -35
- package/src/ui/screens/SkillsScreen.tsx +28 -18
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getCategoryDisplayName,
|
|
10
10
|
categoryOrder,
|
|
11
11
|
} from "../../data/mcp-servers.js";
|
|
12
|
+
import { fetchCuratedMcpServers } from "../../services/mcp-registry.js";
|
|
12
13
|
import {
|
|
13
14
|
addMcpServer,
|
|
14
15
|
removeMcpServer,
|
|
@@ -33,7 +34,10 @@ export function McpScreen() {
|
|
|
33
34
|
const fetchData = useCallback(async () => {
|
|
34
35
|
dispatch({ type: "MCP_DATA_LOADING" });
|
|
35
36
|
try {
|
|
36
|
-
const
|
|
37
|
+
const curatedServers = await fetchCuratedMcpServers(
|
|
38
|
+
AbortSignal.timeout(10_000),
|
|
39
|
+
);
|
|
40
|
+
const serversByCategory = getMcpServersByCategory(curatedServers);
|
|
37
41
|
const installedServers = await getInstalledMcpServers(state.projectPath);
|
|
38
42
|
const enabledServers = await getEnabledMcpServers(state.projectPath);
|
|
39
43
|
|
|
@@ -70,7 +74,7 @@ export function McpScreen() {
|
|
|
70
74
|
const allListItems = useMemo((): McpListItem[] => {
|
|
71
75
|
if (mcp.servers.status !== "success") return [];
|
|
72
76
|
|
|
73
|
-
const serversByCategory = getMcpServersByCategory();
|
|
77
|
+
const serversByCategory = getMcpServersByCategory(mcp.servers.data);
|
|
74
78
|
const items: McpListItem[] = [];
|
|
75
79
|
|
|
76
80
|
for (const category of categoryOrder) {
|
|
@@ -86,7 +90,7 @@ export function McpScreen() {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
return items;
|
|
89
|
-
}, [mcp.servers
|
|
93
|
+
}, [mcp.servers, mcp.installedServers]);
|
|
90
94
|
|
|
91
95
|
useKeyboard((event) => {
|
|
92
96
|
if (state.isSearching || state.modal) return;
|
|
@@ -142,10 +146,14 @@ export function McpScreen() {
|
|
|
142
146
|
let config: McpServerConfig;
|
|
143
147
|
|
|
144
148
|
if (server.type === "http") {
|
|
145
|
-
|
|
149
|
+
if (!server.url) throw new Error(`${server.name} is missing its HTTP URL`);
|
|
150
|
+
config = { type: "http", url: server.url };
|
|
146
151
|
} else {
|
|
152
|
+
if (!server.command) {
|
|
153
|
+
throw new Error(`${server.name} is missing its launch command`);
|
|
154
|
+
}
|
|
147
155
|
config = {
|
|
148
|
-
command: server.command
|
|
156
|
+
command: server.command,
|
|
149
157
|
args: server.args ? [...server.args] : undefined,
|
|
150
158
|
env: server.env ? { ...server.env } : undefined,
|
|
151
159
|
};
|
|
@@ -18,8 +18,11 @@ import {
|
|
|
18
18
|
refreshAllMarketplaces,
|
|
19
19
|
clearMarketplaceCache,
|
|
20
20
|
getLocalMarketplacesInfo,
|
|
21
|
+
isInstalledInScope,
|
|
22
|
+
resolveScopeAction,
|
|
21
23
|
saveInstalledPluginVersion,
|
|
22
24
|
type PluginInfo,
|
|
25
|
+
type ScopeStatus,
|
|
23
26
|
} from "../../services/plugin-manager.js";
|
|
24
27
|
import {
|
|
25
28
|
setMcpEnvVar,
|
|
@@ -605,10 +608,10 @@ export function PluginsScreen() {
|
|
|
605
608
|
|
|
606
609
|
const buildScopeLabel = (
|
|
607
610
|
name: string,
|
|
608
|
-
scope:
|
|
611
|
+
scope: ScopeStatus | undefined,
|
|
609
612
|
desc: string,
|
|
610
613
|
) => {
|
|
611
|
-
const installed = scope
|
|
614
|
+
const installed = isInstalledInScope(scope);
|
|
612
615
|
const ver = scope?.version;
|
|
613
616
|
const hasUpdate =
|
|
614
617
|
ver &&
|
|
@@ -619,6 +622,10 @@ export function PluginsScreen() {
|
|
|
619
622
|
label += ` (${desc})`;
|
|
620
623
|
if (ver) label += ` v${ver}`;
|
|
621
624
|
if (hasUpdate) label += ` → v${latestVersion}`;
|
|
625
|
+
// Enabled in settings with nothing installed. Without saying so the
|
|
626
|
+
// row reads exactly like a scope that was never enabled, and the
|
|
627
|
+
// user cannot tell that picking it repairs rather than adds.
|
|
628
|
+
if (!installed && scope?.enabled) label += " — not installed, repairs";
|
|
622
629
|
return label;
|
|
623
630
|
};
|
|
624
631
|
|
|
@@ -650,8 +657,6 @@ export function PluginsScreen() {
|
|
|
650
657
|
: scopeValue === "project"
|
|
651
658
|
? plugin.projectScope
|
|
652
659
|
: plugin.localScope;
|
|
653
|
-
const isInstalledInScope = selectedScope?.enabled;
|
|
654
|
-
const installedVersion = selectedScope?.version;
|
|
655
660
|
const scopeLabel =
|
|
656
661
|
scopeValue === "user"
|
|
657
662
|
? "User"
|
|
@@ -659,20 +664,9 @@ export function PluginsScreen() {
|
|
|
659
664
|
? "Project"
|
|
660
665
|
: "Local";
|
|
661
666
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
latestVersion !== "0.0.0" &&
|
|
666
|
-
installedVersion !== latestVersion;
|
|
667
|
-
|
|
668
|
-
let action: "update" | "install" | "uninstall";
|
|
669
|
-
if (isInstalledInScope && hasUpdateInScope) {
|
|
670
|
-
action = "update";
|
|
671
|
-
} else if (isInstalledInScope) {
|
|
672
|
-
action = "uninstall";
|
|
673
|
-
} else {
|
|
674
|
-
action = "install";
|
|
675
|
-
}
|
|
667
|
+
// Decided from the registry-backed version, not the `enabledPlugins`
|
|
668
|
+
// flag alone — see resolveScopeAction.
|
|
669
|
+
const action = resolveScopeAction(selectedScope, latestVersion);
|
|
676
670
|
|
|
677
671
|
try {
|
|
678
672
|
const scope = scopeValue as PluginScope;
|
|
@@ -843,23 +837,12 @@ export function PluginsScreen() {
|
|
|
843
837
|
: scope === "project"
|
|
844
838
|
? plugin.projectScope
|
|
845
839
|
: plugin.localScope;
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
latestVersion !== "0.0.0" &&
|
|
853
|
-
installedVersion !== latestVersion;
|
|
854
|
-
|
|
855
|
-
let action: "update" | "install" | "uninstall";
|
|
856
|
-
if (isInstalledInScope && hasUpdateInScope) {
|
|
857
|
-
action = "update";
|
|
858
|
-
} else if (isInstalledInScope) {
|
|
859
|
-
action = "uninstall";
|
|
860
|
-
} else {
|
|
861
|
-
action = "install";
|
|
862
|
-
}
|
|
840
|
+
|
|
841
|
+
// This used to key off `scopeData.enabled` alone, so a plugin the list
|
|
842
|
+
// had just labelled "not installed" answered its own scope key by
|
|
843
|
+
// uninstalling itself — the `enabledPlugins` flag was set, the registry
|
|
844
|
+
// entry was missing, and only the flag was consulted.
|
|
845
|
+
const action = resolveScopeAction(scopeData, latestVersion);
|
|
863
846
|
|
|
864
847
|
try {
|
|
865
848
|
if (action === "uninstall") {
|
|
@@ -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(() => {
|
|
@@ -282,7 +292,7 @@ export function SkillsScreen() {
|
|
|
282
292
|
// ── Derived data ──────────────────────────────────────────────────────────
|
|
283
293
|
|
|
284
294
|
const staticRecommended = useMemo((): SkillInfo[] => {
|
|
285
|
-
return
|
|
295
|
+
return curatedSkills.map((r) => {
|
|
286
296
|
const slug = r.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
287
297
|
const isUser = installedFromDisk.user.has(slug) || installedFromDisk.user.has(r.name);
|
|
288
298
|
const isProj = installedFromDisk.project.has(slug) || installedFromDisk.project.has(r.name);
|
|
@@ -302,7 +312,7 @@ export function SkillsScreen() {
|
|
|
302
312
|
starReliability: classifyStarReliability(r.repo, r.stars),
|
|
303
313
|
};
|
|
304
314
|
});
|
|
305
|
-
}, [installedFromDisk]);
|
|
315
|
+
}, [curatedSkills, installedFromDisk]);
|
|
306
316
|
|
|
307
317
|
const mergedRecommended = useMemo((): SkillInfo[] => {
|
|
308
318
|
if (skillsState.skills.status !== "success") return staticRecommended;
|