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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/data/predefined-profiles.js +191 -0
  3. package/src/data/predefined-profiles.ts +205 -0
  4. package/src/ui/adapters/pluginsAdapter.js +139 -0
  5. package/src/ui/adapters/pluginsAdapter.ts +202 -0
  6. package/src/ui/adapters/settingsAdapter.js +111 -0
  7. package/src/ui/adapters/settingsAdapter.ts +165 -0
  8. package/src/ui/components/ScrollableList.js +4 -4
  9. package/src/ui/components/ScrollableList.tsx +4 -4
  10. package/src/ui/components/SearchInput.js +2 -2
  11. package/src/ui/components/SearchInput.tsx +3 -3
  12. package/src/ui/components/StyledText.js +1 -1
  13. package/src/ui/components/StyledText.tsx +5 -1
  14. package/src/ui/components/layout/ProgressBar.js +1 -1
  15. package/src/ui/components/layout/ProgressBar.tsx +1 -5
  16. package/src/ui/components/modals/InputModal.tsx +1 -6
  17. package/src/ui/components/modals/LoadingModal.js +1 -1
  18. package/src/ui/components/modals/LoadingModal.tsx +1 -3
  19. package/src/ui/hooks/index.js +3 -3
  20. package/src/ui/hooks/index.ts +3 -3
  21. package/src/ui/hooks/useKeyboard.ts +1 -3
  22. package/src/ui/hooks/useKeyboardHandler.js +9 -9
  23. package/src/ui/hooks/useKeyboardHandler.ts +9 -9
  24. package/src/ui/renderers/cliToolRenderers.js +33 -0
  25. package/src/ui/renderers/cliToolRenderers.tsx +153 -0
  26. package/src/ui/renderers/mcpRenderers.js +26 -0
  27. package/src/ui/renderers/mcpRenderers.tsx +145 -0
  28. package/src/ui/renderers/pluginRenderers.js +124 -0
  29. package/src/ui/renderers/pluginRenderers.tsx +362 -0
  30. package/src/ui/renderers/profileRenderers.js +172 -0
  31. package/src/ui/renderers/profileRenderers.tsx +410 -0
  32. package/src/ui/renderers/settingsRenderers.js +69 -0
  33. package/src/ui/renderers/settingsRenderers.tsx +205 -0
  34. package/src/ui/screens/CliToolsScreen.js +14 -58
  35. package/src/ui/screens/CliToolsScreen.tsx +36 -196
  36. package/src/ui/screens/EnvVarsScreen.js +12 -168
  37. package/src/ui/screens/EnvVarsScreen.tsx +16 -327
  38. package/src/ui/screens/McpScreen.js +12 -62
  39. package/src/ui/screens/McpScreen.tsx +21 -190
  40. package/src/ui/screens/PluginsScreen.js +52 -425
  41. package/src/ui/screens/PluginsScreen.tsx +70 -758
  42. package/src/ui/screens/ProfilesScreen.js +104 -68
  43. package/src/ui/screens/ProfilesScreen.tsx +147 -221
  44. package/src/ui/screens/SkillsScreen.js +16 -16
  45. package/src/ui/screens/SkillsScreen.tsx +20 -23
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
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
- const isEnabled = mcp.installedServers[server.name] === true;
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(listItems.length - 1, mcp.selectedIndex + 1);
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 = listItems[mcp.selectedIndex];
114
- if (!item || item.isCategory || !item.server)
91
+ const item = allListItems[mcp.selectedIndex];
92
+ if (!item || item.kind === "category")
115
93
  return;
116
- const server = item.server;
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; // User cancelled
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
- // Get selected item
190
- const selectedItem = listItems[mcp.selectedIndex];
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: listItems, selectedIndex: mcp.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderDetail() }));
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
- interface ListItem {
21
- label: string;
22
- server?: McpServer;
23
- isCategory?: boolean;
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
- // Build list items with categories
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: ListItem[] = [];
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
- const isEnabled = mcp.installedServers[server.name] === true;
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(listItems.length - 1, mcp.selectedIndex + 1);
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 = listItems[mcp.selectedIndex];
141
- if (!item || item.isCategory || !item.server) return;
113
+ const item = allListItems[mcp.selectedIndex];
114
+ if (!item || item.kind === "category") return;
142
115
 
143
- const server = item.server;
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
- // Get selected item
245
- const selectedItem = listItems[mcp.selectedIndex];
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={listItems}
222
+ items={allListItems}
392
223
  selectedIndex={mcp.selectedIndex}
393
- renderItem={renderListItem}
224
+ renderItem={renderMcpRow}
394
225
  maxHeight={dimensions.listPanelHeight}
395
226
  />
396
227
  }
397
- detailPanel={renderDetail()}
228
+ detailPanel={renderMcpDetail(selectedItem, isLoading)}
398
229
  />
399
230
  );
400
231
  }