claudeup 3.17.0 → 4.0.1

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 (53) hide show
  1. package/package.json +1 -1
  2. package/src/data/predefined-profiles.js +69 -12
  3. package/src/data/predefined-profiles.ts +73 -14
  4. package/src/services/claude-cli.js +8 -1
  5. package/src/services/claude-cli.ts +10 -7
  6. package/src/services/plugin-manager.js +40 -4
  7. package/src/services/plugin-manager.ts +57 -6
  8. package/src/ui/adapters/pluginsAdapter.js +139 -0
  9. package/src/ui/adapters/pluginsAdapter.ts +202 -0
  10. package/src/ui/adapters/settingsAdapter.js +111 -0
  11. package/src/ui/adapters/settingsAdapter.ts +165 -0
  12. package/src/ui/components/ScrollableDetail.js +23 -0
  13. package/src/ui/components/ScrollableDetail.tsx +55 -0
  14. package/src/ui/components/ScrollableList.js +4 -4
  15. package/src/ui/components/ScrollableList.tsx +4 -4
  16. package/src/ui/components/SearchInput.js +2 -2
  17. package/src/ui/components/SearchInput.tsx +3 -3
  18. package/src/ui/components/StyledText.js +1 -1
  19. package/src/ui/components/StyledText.tsx +5 -1
  20. package/src/ui/components/layout/ProgressBar.js +1 -1
  21. package/src/ui/components/layout/ProgressBar.tsx +1 -5
  22. package/src/ui/components/layout/ScreenLayout.js +1 -1
  23. package/src/ui/components/layout/ScreenLayout.tsx +11 -8
  24. package/src/ui/components/modals/InputModal.tsx +1 -6
  25. package/src/ui/components/modals/LoadingModal.js +1 -1
  26. package/src/ui/components/modals/LoadingModal.tsx +1 -3
  27. package/src/ui/hooks/index.js +3 -3
  28. package/src/ui/hooks/index.ts +3 -3
  29. package/src/ui/hooks/useKeyboard.ts +1 -3
  30. package/src/ui/hooks/useKeyboardHandler.js +9 -9
  31. package/src/ui/hooks/useKeyboardHandler.ts +9 -9
  32. package/src/ui/renderers/cliToolRenderers.js +33 -0
  33. package/src/ui/renderers/cliToolRenderers.tsx +153 -0
  34. package/src/ui/renderers/mcpRenderers.js +26 -0
  35. package/src/ui/renderers/mcpRenderers.tsx +145 -0
  36. package/src/ui/renderers/pluginRenderers.js +124 -0
  37. package/src/ui/renderers/pluginRenderers.tsx +362 -0
  38. package/src/ui/renderers/profileRenderers.js +177 -0
  39. package/src/ui/renderers/profileRenderers.tsx +361 -0
  40. package/src/ui/renderers/settingsRenderers.js +69 -0
  41. package/src/ui/renderers/settingsRenderers.tsx +205 -0
  42. package/src/ui/screens/CliToolsScreen.js +14 -58
  43. package/src/ui/screens/CliToolsScreen.tsx +36 -196
  44. package/src/ui/screens/EnvVarsScreen.js +12 -168
  45. package/src/ui/screens/EnvVarsScreen.tsx +16 -327
  46. package/src/ui/screens/McpScreen.js +12 -62
  47. package/src/ui/screens/McpScreen.tsx +21 -190
  48. package/src/ui/screens/PluginsScreen.js +52 -425
  49. package/src/ui/screens/PluginsScreen.tsx +70 -758
  50. package/src/ui/screens/ProfilesScreen.js +32 -97
  51. package/src/ui/screens/ProfilesScreen.tsx +58 -328
  52. package/src/ui/screens/SkillsScreen.js +16 -16
  53. package/src/ui/screens/SkillsScreen.tsx +20 -23
@@ -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
  }