claudeup 3.16.0 → 4.0.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 +1 -1
- package/src/data/predefined-profiles.js +191 -0
- package/src/data/predefined-profiles.ts +205 -0
- package/src/ui/adapters/pluginsAdapter.js +139 -0
- package/src/ui/adapters/pluginsAdapter.ts +202 -0
- package/src/ui/adapters/settingsAdapter.js +111 -0
- package/src/ui/adapters/settingsAdapter.ts +165 -0
- package/src/ui/components/ScrollableList.js +4 -4
- package/src/ui/components/ScrollableList.tsx +4 -4
- package/src/ui/components/SearchInput.js +2 -2
- package/src/ui/components/SearchInput.tsx +3 -3
- package/src/ui/components/StyledText.js +1 -1
- package/src/ui/components/StyledText.tsx +5 -1
- package/src/ui/components/layout/ProgressBar.js +1 -1
- package/src/ui/components/layout/ProgressBar.tsx +1 -5
- package/src/ui/components/modals/InputModal.tsx +1 -6
- package/src/ui/components/modals/LoadingModal.js +1 -1
- package/src/ui/components/modals/LoadingModal.tsx +1 -3
- package/src/ui/hooks/index.js +3 -3
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useKeyboard.ts +1 -3
- package/src/ui/hooks/useKeyboardHandler.js +9 -9
- package/src/ui/hooks/useKeyboardHandler.ts +9 -9
- package/src/ui/renderers/cliToolRenderers.js +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +153 -0
- package/src/ui/renderers/mcpRenderers.js +26 -0
- package/src/ui/renderers/mcpRenderers.tsx +145 -0
- package/src/ui/renderers/pluginRenderers.js +124 -0
- package/src/ui/renderers/pluginRenderers.tsx +362 -0
- package/src/ui/renderers/profileRenderers.js +172 -0
- package/src/ui/renderers/profileRenderers.tsx +410 -0
- package/src/ui/renderers/settingsRenderers.js +69 -0
- package/src/ui/renderers/settingsRenderers.tsx +205 -0
- package/src/ui/screens/CliToolsScreen.js +14 -58
- package/src/ui/screens/CliToolsScreen.tsx +36 -196
- package/src/ui/screens/EnvVarsScreen.js +12 -168
- package/src/ui/screens/EnvVarsScreen.tsx +16 -327
- package/src/ui/screens/McpScreen.js +12 -62
- package/src/ui/screens/McpScreen.tsx +21 -190
- package/src/ui/screens/PluginsScreen.js +52 -425
- package/src/ui/screens/PluginsScreen.tsx +70 -758
- package/src/ui/screens/ProfilesScreen.js +104 -68
- package/src/ui/screens/ProfilesScreen.tsx +147 -221
- package/src/ui/screens/SkillsScreen.js +16 -16
- package/src/ui/screens/SkillsScreen.tsx +20 -23
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
2
|
import { useEffect, useCallback, useMemo } from "react";
|
|
3
3
|
import { useApp, useModal, useNavigation } from "../state/AppContext.js";
|
|
4
4
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
@@ -7,20 +7,19 @@ import { ScreenLayout } from "../components/layout/index.js";
|
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
8
|
import { getMcpServersByCategory, getCategoryDisplayName, categoryOrder, } from "../../data/mcp-servers.js";
|
|
9
9
|
import { addMcpServer, removeMcpServer, getInstalledMcpServers, getEnabledMcpServers, } from "../../services/claude-settings.js";
|
|
10
|
+
import { renderMcpRow, renderMcpDetail, } from "../renderers/mcpRenderers.js";
|
|
10
11
|
export function McpScreen() {
|
|
11
12
|
const { state, dispatch } = useApp();
|
|
12
13
|
const { mcp } = state;
|
|
13
14
|
const modal = useModal();
|
|
14
15
|
const { navigateToScreen } = useNavigation();
|
|
15
16
|
const dimensions = useDimensions();
|
|
16
|
-
// Fetch data
|
|
17
17
|
const fetchData = useCallback(async () => {
|
|
18
18
|
dispatch({ type: "MCP_DATA_LOADING" });
|
|
19
19
|
try {
|
|
20
20
|
const serversByCategory = getMcpServersByCategory();
|
|
21
21
|
const installedServers = await getInstalledMcpServers(state.projectPath);
|
|
22
22
|
const enabledServers = await getEnabledMcpServers(state.projectPath);
|
|
23
|
-
// Build flat list of all servers
|
|
24
23
|
const servers = [];
|
|
25
24
|
for (const category of categoryOrder) {
|
|
26
25
|
const categoryServers = serversByCategory[category];
|
|
@@ -28,7 +27,6 @@ export function McpScreen() {
|
|
|
28
27
|
servers.push(...categoryServers);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
|
-
// Convert McpServerConfig to boolean - server exists = installed
|
|
32
30
|
const installedAsBooleans = {};
|
|
33
31
|
for (const name of Object.keys(installedServers)) {
|
|
34
32
|
installedAsBooleans[name] = true;
|
|
@@ -49,7 +47,6 @@ export function McpScreen() {
|
|
|
49
47
|
useEffect(() => {
|
|
50
48
|
fetchData();
|
|
51
49
|
}, [fetchData]);
|
|
52
|
-
// Build list items with categories
|
|
53
50
|
const allListItems = useMemo(() => {
|
|
54
51
|
if (mcp.servers.status !== "success")
|
|
55
52
|
return [];
|
|
@@ -59,36 +56,17 @@ export function McpScreen() {
|
|
|
59
56
|
const servers = serversByCategory[category];
|
|
60
57
|
if (!servers || servers.length === 0)
|
|
61
58
|
continue;
|
|
62
|
-
items.push({
|
|
63
|
-
label: getCategoryDisplayName(category),
|
|
64
|
-
isCategory: true,
|
|
65
|
-
});
|
|
59
|
+
items.push({ kind: "category", label: getCategoryDisplayName(category) });
|
|
66
60
|
for (const server of servers) {
|
|
67
61
|
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
68
|
-
|
|
69
|
-
let status = "○";
|
|
70
|
-
if (isInstalled && isEnabled) {
|
|
71
|
-
status = "●";
|
|
72
|
-
}
|
|
73
|
-
else if (isInstalled) {
|
|
74
|
-
status = "●";
|
|
75
|
-
}
|
|
76
|
-
const configTag = server.requiresConfig ? " *" : "";
|
|
77
|
-
items.push({
|
|
78
|
-
label: ` ${status} ${server.name}${configTag}`,
|
|
79
|
-
server,
|
|
80
|
-
});
|
|
62
|
+
items.push({ kind: "server", server, isInstalled });
|
|
81
63
|
}
|
|
82
64
|
}
|
|
83
65
|
return items;
|
|
84
66
|
}, [mcp.servers.status, mcp.installedServers]);
|
|
85
|
-
// Use all items - search goes to global registry, not local filter
|
|
86
|
-
const listItems = allListItems;
|
|
87
|
-
// Keyboard handling
|
|
88
67
|
useKeyboard((event) => {
|
|
89
68
|
if (state.isSearching || state.modal)
|
|
90
69
|
return;
|
|
91
|
-
// Start search - navigate to registry with search mode active
|
|
92
70
|
if (event.name === "/") {
|
|
93
71
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
94
72
|
navigateToScreen("mcp-registry");
|
|
@@ -99,7 +77,7 @@ export function McpScreen() {
|
|
|
99
77
|
dispatch({ type: "MCP_SELECT", index: newIndex });
|
|
100
78
|
}
|
|
101
79
|
else if (event.name === "down" || event.name === "j") {
|
|
102
|
-
const newIndex = Math.min(
|
|
80
|
+
const newIndex = Math.min(allListItems.length - 1, mcp.selectedIndex + 1);
|
|
103
81
|
dispatch({ type: "MCP_SELECT", index: newIndex });
|
|
104
82
|
}
|
|
105
83
|
else if (event.name === "r") {
|
|
@@ -110,11 +88,10 @@ export function McpScreen() {
|
|
|
110
88
|
}
|
|
111
89
|
});
|
|
112
90
|
const handleSelect = async () => {
|
|
113
|
-
const item =
|
|
114
|
-
if (!item || item.
|
|
91
|
+
const item = allListItems[mcp.selectedIndex];
|
|
92
|
+
if (!item || item.kind === "category")
|
|
115
93
|
return;
|
|
116
|
-
const server = item
|
|
117
|
-
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
94
|
+
const { server, isInstalled } = item;
|
|
118
95
|
if (isInstalled) {
|
|
119
96
|
const confirmed = await modal.confirm(`Remove ${server.name}?`, "This will remove the MCP server configuration.");
|
|
120
97
|
if (confirmed) {
|
|
@@ -132,7 +109,6 @@ export function McpScreen() {
|
|
|
132
109
|
}
|
|
133
110
|
}
|
|
134
111
|
else {
|
|
135
|
-
// Install server
|
|
136
112
|
await installMcpServer(server);
|
|
137
113
|
}
|
|
138
114
|
};
|
|
@@ -148,7 +124,6 @@ export function McpScreen() {
|
|
|
148
124
|
env: server.env ? { ...server.env } : undefined,
|
|
149
125
|
};
|
|
150
126
|
}
|
|
151
|
-
// Handle configuration fields if required
|
|
152
127
|
if (server.requiresConfig && server.configFields) {
|
|
153
128
|
for (const field of server.configFields) {
|
|
154
129
|
const envVarName = field.envVar || field.name;
|
|
@@ -163,7 +138,7 @@ export function McpScreen() {
|
|
|
163
138
|
}
|
|
164
139
|
const value = await modal.input(`Configure ${server.name}`, `${field.label}${field.required ? " (required)" : ""}:`, field.default);
|
|
165
140
|
if (value === null)
|
|
166
|
-
return;
|
|
141
|
+
return;
|
|
167
142
|
if (field.required && !value) {
|
|
168
143
|
await modal.message("Required Field", `${field.label} is required.`, "error");
|
|
169
144
|
return;
|
|
@@ -186,36 +161,11 @@ export function McpScreen() {
|
|
|
186
161
|
await modal.message("Error", `Failed to install: ${error}`, "error");
|
|
187
162
|
}
|
|
188
163
|
};
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
const renderDetail = () => {
|
|
192
|
-
if (mcp.servers.status === "loading") {
|
|
193
|
-
return _jsx("text", { fg: "gray", children: "Loading MCP servers..." });
|
|
194
|
-
}
|
|
195
|
-
if (!selectedItem || selectedItem.isCategory || !selectedItem.server) {
|
|
196
|
-
return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: "gray", children: "Select a server to see details" }) }));
|
|
197
|
-
}
|
|
198
|
-
const server = selectedItem.server;
|
|
199
|
-
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
200
|
-
const isEnabled = mcp.installedServers[server.name] === true;
|
|
201
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: ["\u26A1 ", server.name] }) }), server.requiresConfig && _jsx("text", { fg: "yellow", children: " \u2699" })] }), _jsx("text", { fg: "gray", children: server.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Status " }), isInstalled && isEnabled ? (_jsx("text", { fg: "green", children: "\u25CF Installed" })) : isInstalled ? (_jsx("text", { fg: "yellow", children: "\u25CF Disabled" })) : (_jsx("text", { fg: "gray", children: "\u25CB Not installed" }))] }), _jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Type " }), _jsx("text", { fg: "white", children: server.type === "http" ? "HTTP" : "Command" })] }), server.type === "http" ? (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "URL " }), _jsx("text", { fg: "#5c9aff", children: server.url })] })) : (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Command " }), _jsx("text", { fg: "cyan", children: server.command })] })), server.requiresConfig && (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Config " }), _jsxs("text", { fg: "yellow", children: [server.configFields?.length || 0, " fields required"] })] }))] }), _jsx("box", { marginTop: 2, children: isInstalled ? (_jsxs("box", { children: [_jsxs("text", { bg: "red", fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: "gray", children: " Remove server" })] })) : (_jsxs("box", { children: [_jsxs("text", { bg: "green", fg: "black", children: [" ", "Enter", " "] }), _jsx("text", { fg: "gray", children: " Install server" })] })) })] }));
|
|
202
|
-
};
|
|
203
|
-
const renderListItem = (item, _idx, isSelected) => {
|
|
204
|
-
// Category header
|
|
205
|
-
if (item.isCategory) {
|
|
206
|
-
return (_jsx("text", { fg: "magenta", children: _jsxs("strong", { children: ["\u25B8 ", item.label] }) }));
|
|
207
|
-
}
|
|
208
|
-
// Server item
|
|
209
|
-
const server = item.server;
|
|
210
|
-
const isInstalled = server && mcp.installedServers[server.name] !== undefined;
|
|
211
|
-
const icon = isInstalled ? "●" : "○";
|
|
212
|
-
const iconColor = isInstalled ? "green" : "gray";
|
|
213
|
-
return isSelected ? (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", icon, " ", server?.name || "", " "] })) : (_jsxs("text", { children: [_jsx("span", { fg: iconColor, children: icon }), _jsxs("span", { fg: "white", children: [" ", server?.name || ""] }), server?.requiresConfig && _jsx("span", { fg: "yellow", children: " \u2699" })] }));
|
|
214
|
-
};
|
|
215
|
-
// Calculate status counts
|
|
164
|
+
const selectedItem = allListItems[mcp.selectedIndex];
|
|
165
|
+
const isLoading = mcp.servers.status === "loading";
|
|
216
166
|
const installedCount = Object.keys(mcp.installedServers).length;
|
|
217
167
|
const enabledCount = Object.values(mcp.installedServers).filter((v) => v === true).length;
|
|
218
168
|
const subtitle = `${enabledCount} enabled │ ${installedCount} configured │ / to search`;
|
|
219
|
-
return (_jsx(ScreenLayout, { title: "claudeup MCP Servers", subtitle: subtitle, currentScreen: "mcp", footerHints: "\u2191\u2193:nav \u2502 Enter:toggle \u2502 /:search \u2502 r:registry", listPanel: _jsx(ScrollableList, { items:
|
|
169
|
+
return (_jsx(ScreenLayout, { title: "claudeup MCP Servers", subtitle: subtitle, currentScreen: "mcp", footerHints: "\u2191\u2193:nav \u2502 Enter:toggle \u2502 /:search \u2502 r:registry", listPanel: _jsx(ScrollableList, { items: allListItems, selectedIndex: mcp.selectedIndex, renderItem: renderMcpRow, maxHeight: dimensions.listPanelHeight }), detailPanel: renderMcpDetail(selectedItem, isLoading) }));
|
|
220
170
|
}
|
|
221
171
|
export default McpScreen;
|
|
@@ -16,12 +16,11 @@ import {
|
|
|
16
16
|
getEnabledMcpServers,
|
|
17
17
|
} from "../../services/claude-settings.js";
|
|
18
18
|
import type { McpServer, McpServerConfig } from "../../types/index.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
19
|
+
import {
|
|
20
|
+
renderMcpRow,
|
|
21
|
+
renderMcpDetail,
|
|
22
|
+
type McpListItem,
|
|
23
|
+
} from "../renderers/mcpRenderers.js";
|
|
25
24
|
|
|
26
25
|
export function McpScreen() {
|
|
27
26
|
const { state, dispatch } = useApp();
|
|
@@ -30,7 +29,6 @@ export function McpScreen() {
|
|
|
30
29
|
const { navigateToScreen } = useNavigation();
|
|
31
30
|
const dimensions = useDimensions();
|
|
32
31
|
|
|
33
|
-
// Fetch data
|
|
34
32
|
const fetchData = useCallback(async () => {
|
|
35
33
|
dispatch({ type: "MCP_DATA_LOADING" });
|
|
36
34
|
try {
|
|
@@ -38,7 +36,6 @@ export function McpScreen() {
|
|
|
38
36
|
const installedServers = await getInstalledMcpServers(state.projectPath);
|
|
39
37
|
const enabledServers = await getEnabledMcpServers(state.projectPath);
|
|
40
38
|
|
|
41
|
-
// Build flat list of all servers
|
|
42
39
|
const servers: McpServer[] = [];
|
|
43
40
|
for (const category of categoryOrder) {
|
|
44
41
|
const categoryServers = serversByCategory[category];
|
|
@@ -47,7 +44,6 @@ export function McpScreen() {
|
|
|
47
44
|
}
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
// Convert McpServerConfig to boolean - server exists = installed
|
|
51
47
|
const installedAsBooleans: Record<string, boolean> = {};
|
|
52
48
|
for (const name of Object.keys(installedServers)) {
|
|
53
49
|
installedAsBooleans[name] = true;
|
|
@@ -70,53 +66,30 @@ export function McpScreen() {
|
|
|
70
66
|
fetchData();
|
|
71
67
|
}, [fetchData]);
|
|
72
68
|
|
|
73
|
-
|
|
74
|
-
const allListItems = useMemo((): ListItem[] => {
|
|
69
|
+
const allListItems = useMemo((): McpListItem[] => {
|
|
75
70
|
if (mcp.servers.status !== "success") return [];
|
|
76
71
|
|
|
77
72
|
const serversByCategory = getMcpServersByCategory();
|
|
78
|
-
const items:
|
|
73
|
+
const items: McpListItem[] = [];
|
|
79
74
|
|
|
80
75
|
for (const category of categoryOrder) {
|
|
81
76
|
const servers = serversByCategory[category];
|
|
82
77
|
if (!servers || servers.length === 0) continue;
|
|
83
78
|
|
|
84
|
-
items.push({
|
|
85
|
-
label: getCategoryDisplayName(category),
|
|
86
|
-
isCategory: true,
|
|
87
|
-
});
|
|
79
|
+
items.push({ kind: "category", label: getCategoryDisplayName(category) });
|
|
88
80
|
|
|
89
81
|
for (const server of servers) {
|
|
90
82
|
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
let status = "○";
|
|
94
|
-
if (isInstalled && isEnabled) {
|
|
95
|
-
status = "●";
|
|
96
|
-
} else if (isInstalled) {
|
|
97
|
-
status = "●";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const configTag = server.requiresConfig ? " *" : "";
|
|
101
|
-
|
|
102
|
-
items.push({
|
|
103
|
-
label: ` ${status} ${server.name}${configTag}`,
|
|
104
|
-
server,
|
|
105
|
-
});
|
|
83
|
+
items.push({ kind: "server", server, isInstalled });
|
|
106
84
|
}
|
|
107
85
|
}
|
|
108
86
|
|
|
109
87
|
return items;
|
|
110
88
|
}, [mcp.servers.status, mcp.installedServers]);
|
|
111
89
|
|
|
112
|
-
// Use all items - search goes to global registry, not local filter
|
|
113
|
-
const listItems = allListItems;
|
|
114
|
-
|
|
115
|
-
// Keyboard handling
|
|
116
90
|
useKeyboard((event) => {
|
|
117
91
|
if (state.isSearching || state.modal) return;
|
|
118
92
|
|
|
119
|
-
// Start search - navigate to registry with search mode active
|
|
120
93
|
if (event.name === "/") {
|
|
121
94
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
122
95
|
navigateToScreen("mcp-registry");
|
|
@@ -127,7 +100,7 @@ export function McpScreen() {
|
|
|
127
100
|
const newIndex = Math.max(0, mcp.selectedIndex - 1);
|
|
128
101
|
dispatch({ type: "MCP_SELECT", index: newIndex });
|
|
129
102
|
} else if (event.name === "down" || event.name === "j") {
|
|
130
|
-
const newIndex = Math.min(
|
|
103
|
+
const newIndex = Math.min(allListItems.length - 1, mcp.selectedIndex + 1);
|
|
131
104
|
dispatch({ type: "MCP_SELECT", index: newIndex });
|
|
132
105
|
} else if (event.name === "r") {
|
|
133
106
|
navigateToScreen("mcp-registry");
|
|
@@ -137,11 +110,10 @@ export function McpScreen() {
|
|
|
137
110
|
});
|
|
138
111
|
|
|
139
112
|
const handleSelect = async () => {
|
|
140
|
-
const item =
|
|
141
|
-
if (!item || item.
|
|
113
|
+
const item = allListItems[mcp.selectedIndex];
|
|
114
|
+
if (!item || item.kind === "category") return;
|
|
142
115
|
|
|
143
|
-
const server = item
|
|
144
|
-
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
116
|
+
const { server, isInstalled } = item;
|
|
145
117
|
|
|
146
118
|
if (isInstalled) {
|
|
147
119
|
const confirmed = await modal.confirm(
|
|
@@ -153,11 +125,7 @@ export function McpScreen() {
|
|
|
153
125
|
try {
|
|
154
126
|
await removeMcpServer(server.name, state.projectPath);
|
|
155
127
|
modal.hideModal();
|
|
156
|
-
await modal.message(
|
|
157
|
-
"Removed",
|
|
158
|
-
`${server.name} has been removed.`,
|
|
159
|
-
"success",
|
|
160
|
-
);
|
|
128
|
+
await modal.message("Removed", `${server.name} has been removed.`, "success");
|
|
161
129
|
fetchData();
|
|
162
130
|
} catch (error) {
|
|
163
131
|
modal.hideModal();
|
|
@@ -165,7 +133,6 @@ export function McpScreen() {
|
|
|
165
133
|
}
|
|
166
134
|
}
|
|
167
135
|
} else {
|
|
168
|
-
// Install server
|
|
169
136
|
await installMcpServer(server);
|
|
170
137
|
}
|
|
171
138
|
};
|
|
@@ -183,7 +150,6 @@ export function McpScreen() {
|
|
|
183
150
|
};
|
|
184
151
|
}
|
|
185
152
|
|
|
186
|
-
// Handle configuration fields if required
|
|
187
153
|
if (server.requiresConfig && server.configFields) {
|
|
188
154
|
for (const field of server.configFields) {
|
|
189
155
|
const envVarName = field.envVar || field.name;
|
|
@@ -206,15 +172,10 @@ export function McpScreen() {
|
|
|
206
172
|
`${field.label}${field.required ? " (required)" : ""}:`,
|
|
207
173
|
field.default,
|
|
208
174
|
);
|
|
209
|
-
|
|
210
|
-
if (value === null) return; // User cancelled
|
|
175
|
+
if (value === null) return;
|
|
211
176
|
|
|
212
177
|
if (field.required && !value) {
|
|
213
|
-
await modal.message(
|
|
214
|
-
"Required Field",
|
|
215
|
-
`${field.label} is required.`,
|
|
216
|
-
"error",
|
|
217
|
-
);
|
|
178
|
+
await modal.message("Required Field", `${field.label} is required.`, "error");
|
|
218
179
|
return;
|
|
219
180
|
}
|
|
220
181
|
|
|
@@ -241,139 +202,9 @@ export function McpScreen() {
|
|
|
241
202
|
}
|
|
242
203
|
};
|
|
243
204
|
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
const renderDetail = () => {
|
|
248
|
-
if (mcp.servers.status === "loading") {
|
|
249
|
-
return <text fg="gray">Loading MCP servers...</text>;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (!selectedItem || selectedItem.isCategory || !selectedItem.server) {
|
|
253
|
-
return (
|
|
254
|
-
<box
|
|
255
|
-
flexDirection="column"
|
|
256
|
-
alignItems="center"
|
|
257
|
-
justifyContent="center"
|
|
258
|
-
flexGrow={1}
|
|
259
|
-
>
|
|
260
|
-
<text fg="gray">Select a server to see details</text>
|
|
261
|
-
</box>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const server = selectedItem.server;
|
|
266
|
-
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
267
|
-
const isEnabled = mcp.installedServers[server.name] === true;
|
|
268
|
-
|
|
269
|
-
return (
|
|
270
|
-
<box flexDirection="column">
|
|
271
|
-
<box marginBottom={1}>
|
|
272
|
-
<text fg="cyan">
|
|
273
|
-
<strong>⚡ {server.name}</strong>
|
|
274
|
-
</text>
|
|
275
|
-
{server.requiresConfig && <text fg="yellow"> ⚙</text>}
|
|
276
|
-
</box>
|
|
277
|
-
|
|
278
|
-
<text fg="gray">{server.description}</text>
|
|
279
|
-
|
|
280
|
-
<box marginTop={1} flexDirection="column">
|
|
281
|
-
<box>
|
|
282
|
-
<text fg="gray">Status </text>
|
|
283
|
-
{isInstalled && isEnabled ? (
|
|
284
|
-
<text fg="green">● Installed</text>
|
|
285
|
-
) : isInstalled ? (
|
|
286
|
-
<text fg="yellow">● Disabled</text>
|
|
287
|
-
) : (
|
|
288
|
-
<text fg="gray">○ Not installed</text>
|
|
289
|
-
)}
|
|
290
|
-
</box>
|
|
291
|
-
<box>
|
|
292
|
-
<text fg="gray">Type </text>
|
|
293
|
-
<text fg="white">
|
|
294
|
-
{server.type === "http" ? "HTTP" : "Command"}
|
|
295
|
-
</text>
|
|
296
|
-
</box>
|
|
297
|
-
{server.type === "http" ? (
|
|
298
|
-
<box>
|
|
299
|
-
<text fg="gray">URL </text>
|
|
300
|
-
<text fg="#5c9aff">{server.url}</text>
|
|
301
|
-
</box>
|
|
302
|
-
) : (
|
|
303
|
-
<box>
|
|
304
|
-
<text fg="gray">Command </text>
|
|
305
|
-
<text fg="cyan">{server.command}</text>
|
|
306
|
-
</box>
|
|
307
|
-
)}
|
|
308
|
-
{server.requiresConfig && (
|
|
309
|
-
<box>
|
|
310
|
-
<text fg="gray">Config </text>
|
|
311
|
-
<text fg="yellow">
|
|
312
|
-
{server.configFields?.length || 0} fields required
|
|
313
|
-
</text>
|
|
314
|
-
</box>
|
|
315
|
-
)}
|
|
316
|
-
</box>
|
|
317
|
-
|
|
318
|
-
<box marginTop={2}>
|
|
319
|
-
{isInstalled ? (
|
|
320
|
-
<box>
|
|
321
|
-
<text bg="red" fg="white">
|
|
322
|
-
{" "}
|
|
323
|
-
Enter{" "}
|
|
324
|
-
</text>
|
|
325
|
-
<text fg="gray"> Remove server</text>
|
|
326
|
-
</box>
|
|
327
|
-
) : (
|
|
328
|
-
<box>
|
|
329
|
-
<text bg="green" fg="black">
|
|
330
|
-
{" "}
|
|
331
|
-
Enter{" "}
|
|
332
|
-
</text>
|
|
333
|
-
<text fg="gray"> Install server</text>
|
|
334
|
-
</box>
|
|
335
|
-
)}
|
|
336
|
-
</box>
|
|
337
|
-
</box>
|
|
338
|
-
);
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const renderListItem = (
|
|
342
|
-
item: ListItem,
|
|
343
|
-
_idx: number,
|
|
344
|
-
isSelected: boolean,
|
|
345
|
-
) => {
|
|
346
|
-
// Category header
|
|
347
|
-
if (item.isCategory) {
|
|
348
|
-
return (
|
|
349
|
-
<text fg="magenta">
|
|
350
|
-
<strong>▸ {item.label}</strong>
|
|
351
|
-
</text>
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Server item
|
|
356
|
-
const server = item.server;
|
|
357
|
-
const isInstalled =
|
|
358
|
-
server && mcp.installedServers[server.name] !== undefined;
|
|
359
|
-
const icon = isInstalled ? "●" : "○";
|
|
360
|
-
const iconColor = isInstalled ? "green" : "gray";
|
|
361
|
-
|
|
362
|
-
return isSelected ? (
|
|
363
|
-
<text bg="magenta" fg="white">
|
|
364
|
-
{" "}
|
|
365
|
-
{icon} {server?.name || ""}{" "}
|
|
366
|
-
</text>
|
|
367
|
-
) : (
|
|
368
|
-
<text>
|
|
369
|
-
<span fg={iconColor}>{icon}</span>
|
|
370
|
-
<span fg="white"> {server?.name || ""}</span>
|
|
371
|
-
{server?.requiresConfig && <span fg="yellow"> ⚙</span>}
|
|
372
|
-
</text>
|
|
373
|
-
);
|
|
374
|
-
};
|
|
205
|
+
const selectedItem = allListItems[mcp.selectedIndex];
|
|
206
|
+
const isLoading = mcp.servers.status === "loading";
|
|
375
207
|
|
|
376
|
-
// Calculate status counts
|
|
377
208
|
const installedCount = Object.keys(mcp.installedServers).length;
|
|
378
209
|
const enabledCount = Object.values(mcp.installedServers).filter(
|
|
379
210
|
(v) => v === true,
|
|
@@ -388,13 +219,13 @@ export function McpScreen() {
|
|
|
388
219
|
footerHints="↑↓:nav │ Enter:toggle │ /:search │ r:registry"
|
|
389
220
|
listPanel={
|
|
390
221
|
<ScrollableList
|
|
391
|
-
items={
|
|
222
|
+
items={allListItems}
|
|
392
223
|
selectedIndex={mcp.selectedIndex}
|
|
393
|
-
renderItem={
|
|
224
|
+
renderItem={renderMcpRow}
|
|
394
225
|
maxHeight={dimensions.listPanelHeight}
|
|
395
226
|
/>
|
|
396
227
|
}
|
|
397
|
-
detailPanel={
|
|
228
|
+
detailPanel={renderMcpDetail(selectedItem, isLoading)}
|
|
398
229
|
/>
|
|
399
230
|
);
|
|
400
231
|
}
|