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
|
@@ -4,10 +4,9 @@ import { useApp, useModal, useProgress } from "../state/AppContext.js";
|
|
|
4
4
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
5
5
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
6
6
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
|
-
import { CategoryHeader } from "../components/CategoryHeader.js";
|
|
8
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
9
8
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
10
|
-
import { fuzzyFilter
|
|
9
|
+
import { fuzzyFilter } from "../../utils/fuzzy-search.js";
|
|
11
10
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
12
11
|
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
|
|
13
12
|
import { setMcpEnvVar, getMcpEnvVars, readSettings, saveGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, } from "../../services/claude-settings.js";
|
|
@@ -16,10 +15,8 @@ import { saveInstalledPluginVersion } from "../../services/plugin-manager.js";
|
|
|
16
15
|
import { installPlugin as cliInstallPlugin, uninstallPlugin as cliUninstallPlugin, updatePlugin as cliUpdatePlugin, } from "../../services/claude-cli.js";
|
|
17
16
|
import { getPluginEnvRequirements, getPluginSourcePath, } from "../../services/plugin-mcp-config.js";
|
|
18
17
|
import { getPluginSetupFromSource, checkMissingDeps, installPluginDeps, } from "../../services/plugin-setup.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// The marketplace that gets split into Anthropic Official + Community sections
|
|
22
|
-
const SPLIT_MARKETPLACE = "claude-plugins-official";
|
|
18
|
+
import { buildPluginBrowserItems, } from "../adapters/pluginsAdapter.js";
|
|
19
|
+
import { renderPluginRow, renderPluginDetail } from "../renderers/pluginRenderers.js";
|
|
23
20
|
export function PluginsScreen() {
|
|
24
21
|
const { state, dispatch } = useApp();
|
|
25
22
|
const { plugins: pluginsState } = state;
|
|
@@ -35,7 +32,6 @@ export function PluginsScreen() {
|
|
|
35
32
|
try {
|
|
36
33
|
const localMarketplaces = await getLocalMarketplacesInfo();
|
|
37
34
|
const allMarketplaces = getAllMarketplaces(localMarketplaces);
|
|
38
|
-
// Always use getAvailablePlugins which fetches all scope data
|
|
39
35
|
const pluginData = await getAvailablePlugins(state.projectPath);
|
|
40
36
|
dispatch({
|
|
41
37
|
type: "PLUGINS_DATA_SUCCESS",
|
|
@@ -60,108 +56,11 @@ export function PluginsScreen() {
|
|
|
60
56
|
pluginsState.plugins.status !== "success") {
|
|
61
57
|
return [];
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
for (const plugin of plugins) {
|
|
68
|
-
const existing = pluginsByMarketplace.get(plugin.marketplace) || [];
|
|
69
|
-
existing.push(plugin);
|
|
70
|
-
pluginsByMarketplace.set(plugin.marketplace, existing);
|
|
71
|
-
}
|
|
72
|
-
// Sort marketplaces: deprecated ones go to the bottom
|
|
73
|
-
const sortedMarketplaces = [...marketplaces].sort((a, b) => {
|
|
74
|
-
const aDeprecated = a.name === "claude-code-plugins" ? 1 : 0;
|
|
75
|
-
const bDeprecated = b.name === "claude-code-plugins" ? 1 : 0;
|
|
76
|
-
return aDeprecated - bDeprecated;
|
|
59
|
+
return buildPluginBrowserItems({
|
|
60
|
+
marketplaces: pluginsState.marketplaces.data,
|
|
61
|
+
plugins: pluginsState.plugins.data,
|
|
62
|
+
collapsedMarketplaces: pluginsState.collapsedMarketplaces,
|
|
77
63
|
});
|
|
78
|
-
const items = [];
|
|
79
|
-
for (const marketplace of sortedMarketplaces) {
|
|
80
|
-
const marketplacePlugins = pluginsByMarketplace.get(marketplace.name) || [];
|
|
81
|
-
const isCollapsed = collapsed.has(marketplace.name);
|
|
82
|
-
const isEnabled = marketplacePlugins.length > 0 || marketplace.official;
|
|
83
|
-
const hasPlugins = marketplacePlugins.length > 0;
|
|
84
|
-
// Special handling: split claude-plugins-official into two sub-sections
|
|
85
|
-
if (marketplace.name === SPLIT_MARKETPLACE && hasPlugins) {
|
|
86
|
-
const anthropicPlugins = marketplacePlugins.filter((p) => p.author?.name?.toLowerCase() === "anthropic");
|
|
87
|
-
const communityPlugins = marketplacePlugins.filter((p) => p.author?.name?.toLowerCase() !== "anthropic");
|
|
88
|
-
// Sub-section 1: Anthropic Official (plugins by Anthropic)
|
|
89
|
-
const anthropicCollapsed = collapsed.has(marketplace.name);
|
|
90
|
-
const anthropicHasPlugins = anthropicPlugins.length > 0;
|
|
91
|
-
items.push({
|
|
92
|
-
id: `mp:${marketplace.name}`,
|
|
93
|
-
type: "category",
|
|
94
|
-
label: marketplace.displayName,
|
|
95
|
-
marketplace,
|
|
96
|
-
marketplaceEnabled: isEnabled,
|
|
97
|
-
pluginCount: anthropicPlugins.length,
|
|
98
|
-
isExpanded: !anthropicCollapsed && anthropicHasPlugins,
|
|
99
|
-
});
|
|
100
|
-
if (isEnabled && anthropicHasPlugins && !anthropicCollapsed) {
|
|
101
|
-
for (const plugin of anthropicPlugins) {
|
|
102
|
-
items.push({
|
|
103
|
-
id: `pl:${plugin.id}`,
|
|
104
|
-
type: "plugin",
|
|
105
|
-
label: plugin.name,
|
|
106
|
-
plugin,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// Sub-section 2: Community (third-party plugins in same marketplace)
|
|
111
|
-
if (communityPlugins.length > 0) {
|
|
112
|
-
const communityVirtualMp = {
|
|
113
|
-
name: COMMUNITY_VIRTUAL_MARKETPLACE,
|
|
114
|
-
displayName: "Anthropic Official — 3rd Party",
|
|
115
|
-
source: marketplace.source,
|
|
116
|
-
description: "Third-party plugins in the Anthropic Official marketplace",
|
|
117
|
-
};
|
|
118
|
-
const communityCollapsed = collapsed.has(COMMUNITY_VIRTUAL_MARKETPLACE);
|
|
119
|
-
items.push({
|
|
120
|
-
id: `mp:${COMMUNITY_VIRTUAL_MARKETPLACE}`,
|
|
121
|
-
type: "category",
|
|
122
|
-
label: "Anthropic Official — 3rd Party",
|
|
123
|
-
marketplace: communityVirtualMp,
|
|
124
|
-
marketplaceEnabled: true,
|
|
125
|
-
pluginCount: communityPlugins.length,
|
|
126
|
-
isExpanded: !communityCollapsed,
|
|
127
|
-
isCommunitySection: true,
|
|
128
|
-
});
|
|
129
|
-
if (!communityCollapsed) {
|
|
130
|
-
for (const plugin of communityPlugins) {
|
|
131
|
-
items.push({
|
|
132
|
-
id: `pl:${plugin.id}`,
|
|
133
|
-
type: "plugin",
|
|
134
|
-
label: plugin.name,
|
|
135
|
-
plugin,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
// Category header (marketplace)
|
|
143
|
-
items.push({
|
|
144
|
-
id: `mp:${marketplace.name}`,
|
|
145
|
-
type: "category",
|
|
146
|
-
label: marketplace.displayName,
|
|
147
|
-
marketplace,
|
|
148
|
-
marketplaceEnabled: isEnabled,
|
|
149
|
-
pluginCount: marketplacePlugins.length,
|
|
150
|
-
isExpanded: !isCollapsed && hasPlugins,
|
|
151
|
-
});
|
|
152
|
-
// Plugins under this marketplace (if expanded)
|
|
153
|
-
if (isEnabled && hasPlugins && !isCollapsed) {
|
|
154
|
-
for (const plugin of marketplacePlugins) {
|
|
155
|
-
items.push({
|
|
156
|
-
id: `pl:${plugin.id}`,
|
|
157
|
-
type: "plugin",
|
|
158
|
-
label: plugin.name,
|
|
159
|
-
plugin,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return items;
|
|
165
64
|
}, [
|
|
166
65
|
pluginsState.marketplaces,
|
|
167
66
|
pluginsState.plugins,
|
|
@@ -173,26 +72,23 @@ export function PluginsScreen() {
|
|
|
173
72
|
if (!query)
|
|
174
73
|
return allItems;
|
|
175
74
|
// Only search plugins, not categories
|
|
176
|
-
const pluginItems = allItems.filter((item) => item.
|
|
75
|
+
const pluginItems = allItems.filter((item) => item.kind === "plugin");
|
|
177
76
|
const fuzzyResults = fuzzyFilter(pluginItems, query, (item) => item.label);
|
|
178
|
-
// Build a set of matched plugin item ids for O(1) lookup
|
|
179
77
|
const matchedPluginIds = new Set();
|
|
180
78
|
for (const result of fuzzyResults) {
|
|
181
79
|
matchedPluginIds.add(result.item.id);
|
|
182
80
|
}
|
|
183
|
-
// Walk allItems sequentially:
|
|
184
|
-
// For each category, include it only if any plugin under it matched.
|
|
185
|
-
// We build a map from category item id -> whether any plugin below matched.
|
|
81
|
+
// Walk allItems sequentially: include a category only if any plugin below matched
|
|
186
82
|
const categoryHasMatch = new Map();
|
|
187
83
|
let currentCategoryId = null;
|
|
188
84
|
for (const item of allItems) {
|
|
189
|
-
if (item.
|
|
85
|
+
if (item.kind === "category") {
|
|
190
86
|
currentCategoryId = item.id;
|
|
191
87
|
if (!categoryHasMatch.has(item.id)) {
|
|
192
88
|
categoryHasMatch.set(item.id, false);
|
|
193
89
|
}
|
|
194
90
|
}
|
|
195
|
-
else if (item.
|
|
91
|
+
else if (item.kind === "plugin" && currentCategoryId) {
|
|
196
92
|
if (matchedPluginIds.has(item.id)) {
|
|
197
93
|
categoryHasMatch.set(currentCategoryId, true);
|
|
198
94
|
}
|
|
@@ -202,96 +98,80 @@ export function PluginsScreen() {
|
|
|
202
98
|
let currentCatIncluded = false;
|
|
203
99
|
currentCategoryId = null;
|
|
204
100
|
for (const item of allItems) {
|
|
205
|
-
if (item.
|
|
101
|
+
if (item.kind === "category") {
|
|
206
102
|
currentCategoryId = item.id;
|
|
207
103
|
currentCatIncluded = categoryHasMatch.get(item.id) === true;
|
|
208
|
-
if (currentCatIncluded)
|
|
104
|
+
if (currentCatIncluded)
|
|
209
105
|
result.push(item);
|
|
210
|
-
}
|
|
211
106
|
}
|
|
212
|
-
else if (item.
|
|
107
|
+
else if (item.kind === "plugin" && currentCatIncluded) {
|
|
213
108
|
if (matchedPluginIds.has(item.id)) {
|
|
214
109
|
const matched = fuzzyResults.find((r) => r.item.id === item.id);
|
|
215
|
-
result.push({ ...item,
|
|
110
|
+
result.push({ ...item, matches: matched?.matches });
|
|
216
111
|
}
|
|
217
112
|
}
|
|
218
113
|
}
|
|
219
114
|
return result;
|
|
220
115
|
}, [allItems, pluginsState.searchQuery]);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return filteredItems.filter((item) => item.type === "plugin" || item.type === "category");
|
|
224
|
-
}, [filteredItems]);
|
|
225
|
-
// Keyboard handling — inline search with live filtering
|
|
116
|
+
const selectableItems = useMemo(() => filteredItems, [filteredItems]);
|
|
117
|
+
// Keyboard handling
|
|
226
118
|
useKeyboard((event) => {
|
|
227
119
|
if (state.modal)
|
|
228
120
|
return;
|
|
229
121
|
const hasQuery = pluginsState.searchQuery.length > 0;
|
|
230
|
-
// Escape: always clear search state fully
|
|
231
122
|
if (event.name === "escape") {
|
|
232
123
|
if (hasQuery || isSearchActive) {
|
|
233
124
|
dispatch({ type: "PLUGINS_SET_SEARCH", query: "" });
|
|
234
125
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
235
126
|
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
236
127
|
}
|
|
237
|
-
// Don't return — let GlobalKeyHandler handle Escape too (for quit)
|
|
238
128
|
return;
|
|
239
129
|
}
|
|
240
|
-
// Backspace: remove last char from search query
|
|
241
130
|
if (event.name === "backspace" || event.name === "delete") {
|
|
242
131
|
if (hasQuery) {
|
|
243
132
|
const newQuery = pluginsState.searchQuery.slice(0, -1);
|
|
244
133
|
dispatch({ type: "PLUGINS_SET_SEARCH", query: newQuery });
|
|
245
134
|
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
246
|
-
// If query becomes empty, exit search mode
|
|
247
135
|
if (newQuery.length === 0) {
|
|
248
136
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
249
137
|
}
|
|
250
138
|
}
|
|
251
139
|
return;
|
|
252
140
|
}
|
|
253
|
-
// Navigation — always works; exits search mode on navigate
|
|
254
141
|
if (event.name === "up" || event.name === "k") {
|
|
255
142
|
if (isSearchActive)
|
|
256
143
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
257
|
-
|
|
258
|
-
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
144
|
+
dispatch({ type: "PLUGINS_SELECT", index: Math.max(0, pluginsState.selectedIndex - 1) });
|
|
259
145
|
return;
|
|
260
146
|
}
|
|
261
147
|
if (event.name === "down" || event.name === "j") {
|
|
262
148
|
if (isSearchActive)
|
|
263
149
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
264
|
-
|
|
265
|
-
|
|
150
|
+
dispatch({
|
|
151
|
+
type: "PLUGINS_SELECT",
|
|
152
|
+
index: Math.min(selectableItems.length - 1, pluginsState.selectedIndex + 1),
|
|
153
|
+
});
|
|
266
154
|
return;
|
|
267
155
|
}
|
|
268
|
-
|
|
269
|
-
if (event.name === "enter") {
|
|
156
|
+
if (event.name === "enter" || event.name === "return") {
|
|
270
157
|
if (isSearchActive) {
|
|
271
158
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
272
|
-
// Keep the query — filter stays active, shortcuts resume
|
|
273
159
|
return;
|
|
274
160
|
}
|
|
275
161
|
handleSelect();
|
|
276
162
|
return;
|
|
277
163
|
}
|
|
278
|
-
// Collapse/expand marketplace — always works
|
|
279
164
|
if ((event.name === "left" ||
|
|
280
165
|
event.name === "right" ||
|
|
281
166
|
event.name === "<" ||
|
|
282
167
|
event.name === ">") &&
|
|
283
|
-
selectableItems[pluginsState.selectedIndex]?.
|
|
168
|
+
selectableItems[pluginsState.selectedIndex]?.kind === "category") {
|
|
284
169
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
285
|
-
if (item?.
|
|
286
|
-
dispatch({
|
|
287
|
-
type: "PLUGINS_TOGGLE_MARKETPLACE",
|
|
288
|
-
name: item.marketplace.name,
|
|
289
|
-
});
|
|
170
|
+
if (item?.kind === "category") {
|
|
171
|
+
dispatch({ type: "PLUGINS_TOGGLE_MARKETPLACE", name: item.marketplace.name });
|
|
290
172
|
}
|
|
291
173
|
return;
|
|
292
174
|
}
|
|
293
|
-
// When actively typing in search, letters go to the query
|
|
294
|
-
// After Enter (isSearchActive=false, hasQuery=true), shortcuts resume
|
|
295
175
|
if (isSearchActive) {
|
|
296
176
|
if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
|
|
297
177
|
dispatch({
|
|
@@ -302,13 +182,10 @@ export function PluginsScreen() {
|
|
|
302
182
|
}
|
|
303
183
|
return;
|
|
304
184
|
}
|
|
305
|
-
// Action shortcuts work when not actively typing (even with filter visible)
|
|
306
|
-
// Start explicit search mode with /
|
|
307
185
|
if (event.name === "/") {
|
|
308
186
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
309
187
|
return;
|
|
310
188
|
}
|
|
311
|
-
// Action shortcuts (only when query is empty)
|
|
312
189
|
if (event.name === "r")
|
|
313
190
|
handleRefresh();
|
|
314
191
|
else if (event.name === "n")
|
|
@@ -327,12 +204,8 @@ export function PluginsScreen() {
|
|
|
327
204
|
handleUpdateAll();
|
|
328
205
|
else if (event.name === "s")
|
|
329
206
|
handleSaveAsProfile();
|
|
330
|
-
// "/" to enter search mode
|
|
331
|
-
else if (event.name === "/") {
|
|
332
|
-
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
333
|
-
}
|
|
334
207
|
});
|
|
335
|
-
//
|
|
208
|
+
// ── Action handlers ────────────────────────────────────────────────────────
|
|
336
209
|
const handleRefresh = async () => {
|
|
337
210
|
progress.show("Refreshing cache...");
|
|
338
211
|
try {
|
|
@@ -341,7 +214,6 @@ export function PluginsScreen() {
|
|
|
341
214
|
});
|
|
342
215
|
clearMarketplaceCache();
|
|
343
216
|
progress.hide();
|
|
344
|
-
// Build message
|
|
345
217
|
let message = "Cache refreshed.\n\n" +
|
|
346
218
|
"To update marketplaces from GitHub, run in Claude Code:\n" +
|
|
347
219
|
" /plugin marketplace update\n\n" +
|
|
@@ -393,49 +265,39 @@ export function PluginsScreen() {
|
|
|
393
265
|
await modal.message("Team Configuration", helpText, "info");
|
|
394
266
|
};
|
|
395
267
|
/**
|
|
396
|
-
* Collect environment variables required by a plugin's MCP servers
|
|
397
|
-
* Prompts user for missing values and saves to local settings
|
|
268
|
+
* Collect environment variables required by a plugin's MCP servers.
|
|
398
269
|
*/
|
|
399
270
|
const collectPluginEnvVars = async (pluginName, marketplace) => {
|
|
400
271
|
try {
|
|
401
|
-
// Get plugin source path from marketplace manifest
|
|
402
272
|
const pluginSource = await getPluginSourcePath(marketplace, pluginName);
|
|
403
273
|
if (!pluginSource)
|
|
404
|
-
return true;
|
|
405
|
-
// Get env var requirements from plugin's MCP config
|
|
274
|
+
return true;
|
|
406
275
|
const requirements = await getPluginEnvRequirements(marketplace, pluginSource);
|
|
407
276
|
if (requirements.length === 0)
|
|
408
|
-
return true;
|
|
409
|
-
// Get existing env vars
|
|
277
|
+
return true;
|
|
410
278
|
const existingEnvVars = await getMcpEnvVars(state.projectPath);
|
|
411
279
|
const missingVars = requirements.filter((req) => !existingEnvVars[req.name] && !process.env[req.name]);
|
|
412
280
|
if (missingVars.length === 0)
|
|
413
|
-
return true;
|
|
414
|
-
// Ask user if they want to configure MCP server env vars now
|
|
281
|
+
return true;
|
|
415
282
|
const serverNames = [...new Set(missingVars.map((v) => v.serverName))];
|
|
416
283
|
const wantToConfigure = await modal.confirm("Configure MCP Servers?", `This plugin includes MCP servers (${serverNames.join(", ")}) that need ${missingVars.length} environment variable(s).\n\nConfigure now?`);
|
|
417
284
|
if (!wantToConfigure) {
|
|
418
285
|
await modal.message("Skipped Configuration", "You can configure these variables later in the Environment Variables screen (press 4).", "info");
|
|
419
|
-
return true;
|
|
286
|
+
return true;
|
|
420
287
|
}
|
|
421
|
-
// Collect each missing env var
|
|
422
288
|
for (const req of missingVars) {
|
|
423
|
-
// Check if value exists in process.env
|
|
424
289
|
const existingProcessEnv = process.env[req.name];
|
|
425
290
|
if (existingProcessEnv) {
|
|
426
291
|
const useExisting = await modal.confirm(`Use ${req.name}?`, `${req.name} is already set in your environment.\n\nUse the existing value?`);
|
|
427
292
|
if (useExisting) {
|
|
428
|
-
// Store reference to env var instead of literal value
|
|
429
293
|
await setMcpEnvVar(req.name, `\${${req.name}}`, state.projectPath);
|
|
430
294
|
continue;
|
|
431
295
|
}
|
|
432
296
|
}
|
|
433
|
-
// Prompt for value
|
|
434
297
|
const value = await modal.input(`Configure ${req.serverName}`, `${req.label} (required):`, "");
|
|
435
298
|
if (value === null) {
|
|
436
|
-
// User cancelled
|
|
437
299
|
await modal.message("Configuration Incomplete", `Skipped remaining configuration.\nYou can configure these later in Environment Variables (press 4).`, "info");
|
|
438
|
-
return true;
|
|
300
|
+
return true;
|
|
439
301
|
}
|
|
440
302
|
if (value) {
|
|
441
303
|
await setMcpEnvVar(req.name, value, state.projectPath);
|
|
@@ -445,12 +307,11 @@ export function PluginsScreen() {
|
|
|
445
307
|
}
|
|
446
308
|
catch (error) {
|
|
447
309
|
console.error("Error collecting plugin env vars:", error);
|
|
448
|
-
return true;
|
|
310
|
+
return true;
|
|
449
311
|
}
|
|
450
312
|
};
|
|
451
313
|
/**
|
|
452
|
-
* Install system dependencies required by a plugin's MCP servers
|
|
453
|
-
* Auto-detects available package managers (uv/pip, brew, npm, cargo)
|
|
314
|
+
* Install system dependencies required by a plugin's MCP servers.
|
|
454
315
|
*/
|
|
455
316
|
const installPluginSystemDeps = async (pluginName, marketplace) => {
|
|
456
317
|
try {
|
|
@@ -465,7 +326,6 @@ export function PluginsScreen() {
|
|
|
465
326
|
0;
|
|
466
327
|
if (!hasMissing)
|
|
467
328
|
return;
|
|
468
|
-
// Build description of what will be installed
|
|
469
329
|
const parts = [];
|
|
470
330
|
if (missing.pip?.length)
|
|
471
331
|
parts.push(`pip: ${missing.pip.join(", ")}`);
|
|
@@ -482,9 +342,7 @@ export function PluginsScreen() {
|
|
|
482
342
|
const result = await installPluginDeps(missing);
|
|
483
343
|
modal.hideModal();
|
|
484
344
|
if (result.failed.length > 0) {
|
|
485
|
-
const failMsg = result.failed
|
|
486
|
-
.map((f) => `${f.pkg}: ${f.error}`)
|
|
487
|
-
.join("\n");
|
|
345
|
+
const failMsg = result.failed.map((f) => `${f.pkg}: ${f.error}`).join("\n");
|
|
488
346
|
await modal.message("Partial Install", `Installed: ${result.installed.length}\nFailed:\n${failMsg}`, "error");
|
|
489
347
|
}
|
|
490
348
|
else if (result.installed.length > 0) {
|
|
@@ -496,9 +354,7 @@ export function PluginsScreen() {
|
|
|
496
354
|
}
|
|
497
355
|
};
|
|
498
356
|
/**
|
|
499
|
-
* Save
|
|
500
|
-
* Claude CLI doesn't update installedPluginVersions in settings.json,
|
|
501
|
-
* so we do it ourselves to keep the TUI version display accurate.
|
|
357
|
+
* Save installed plugin version to settings after CLI install/update.
|
|
502
358
|
*/
|
|
503
359
|
const saveVersionAfterInstall = async (pluginId, version, scope) => {
|
|
504
360
|
try {
|
|
@@ -520,44 +376,36 @@ export function PluginsScreen() {
|
|
|
520
376
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
521
377
|
if (!item)
|
|
522
378
|
return;
|
|
523
|
-
if (item.
|
|
379
|
+
if (item.kind === "category") {
|
|
524
380
|
const mp = item.marketplace;
|
|
525
381
|
if (item.marketplaceEnabled) {
|
|
526
382
|
const isCollapsed = pluginsState.collapsedMarketplaces.has(mp.name);
|
|
527
|
-
// If collapsed, expand first (even if no plugins - they might load)
|
|
528
383
|
if (isCollapsed) {
|
|
529
384
|
dispatch({ type: "PLUGINS_TOGGLE_MARKETPLACE", name: mp.name });
|
|
530
385
|
}
|
|
531
|
-
else if (item.pluginCount
|
|
532
|
-
// If expanded with plugins, collapse
|
|
386
|
+
else if (item.pluginCount > 0) {
|
|
533
387
|
dispatch({ type: "PLUGINS_TOGGLE_MARKETPLACE", name: mp.name });
|
|
534
388
|
}
|
|
535
389
|
else {
|
|
536
|
-
// If expanded with no plugins, show removal instructions
|
|
537
390
|
await modal.message(`Remove ${mp.displayName}?`, `To remove this marketplace, run in Claude Code:\n\n` +
|
|
538
391
|
` /plugin marketplace remove ${mp.name}\n\n` +
|
|
539
392
|
`After removing, refresh claudeup with 'r' to update the display.`, "info");
|
|
540
393
|
}
|
|
541
394
|
}
|
|
542
395
|
else {
|
|
543
|
-
// Show add marketplace instructions
|
|
544
396
|
await modal.message(`Add ${mp.displayName}?`, `To add this marketplace, run in your terminal:\n\n` +
|
|
545
397
|
` claude plugin marketplace add ${mp.source.repo || mp.name}\n\n` +
|
|
546
398
|
`Auto-update is enabled by default.\n\n` +
|
|
547
399
|
`After adding, refresh claudeup with 'r' to see it.`, "info");
|
|
548
400
|
}
|
|
549
401
|
}
|
|
550
|
-
else if (item.
|
|
402
|
+
else if (item.kind === "plugin") {
|
|
551
403
|
const plugin = item.plugin;
|
|
552
404
|
const latestVersion = plugin.version || "0.0.0";
|
|
553
|
-
// Build scope options with status info
|
|
554
405
|
const buildScopeLabel = (name, scope, desc) => {
|
|
555
406
|
const installed = scope?.enabled;
|
|
556
407
|
const ver = scope?.version;
|
|
557
|
-
const hasUpdate = ver &&
|
|
558
|
-
latestVersion &&
|
|
559
|
-
ver !== latestVersion &&
|
|
560
|
-
latestVersion !== "0.0.0";
|
|
408
|
+
const hasUpdate = ver && latestVersion && ver !== latestVersion && latestVersion !== "0.0.0";
|
|
561
409
|
let label = installed ? `● ${name}` : `○ ${name}`;
|
|
562
410
|
label += ` (${desc})`;
|
|
563
411
|
if (ver)
|
|
@@ -567,23 +415,13 @@ export function PluginsScreen() {
|
|
|
567
415
|
return label;
|
|
568
416
|
};
|
|
569
417
|
const scopeOptions = [
|
|
570
|
-
{
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
label: buildScopeLabel("Project", plugin.projectScope, "team"),
|
|
576
|
-
value: "project",
|
|
577
|
-
},
|
|
578
|
-
{
|
|
579
|
-
label: buildScopeLabel("Local", plugin.localScope, "private"),
|
|
580
|
-
value: "local",
|
|
581
|
-
},
|
|
418
|
+
{ label: buildScopeLabel("User", plugin.userScope, "global"), value: "user" },
|
|
419
|
+
{ label: buildScopeLabel("Project", plugin.projectScope, "team"), value: "project" },
|
|
420
|
+
{ label: buildScopeLabel("Local", plugin.localScope, "private"), value: "local" },
|
|
582
421
|
];
|
|
583
422
|
const scopeValue = await modal.select(plugin.name, `Select scope to toggle:`, scopeOptions);
|
|
584
423
|
if (scopeValue === null)
|
|
585
|
-
return;
|
|
586
|
-
// Determine action based on selected scope's current state
|
|
424
|
+
return;
|
|
587
425
|
const selectedScope = scopeValue === "user"
|
|
588
426
|
? plugin.userScope
|
|
589
427
|
: scopeValue === "project"
|
|
@@ -591,17 +429,11 @@ export function PluginsScreen() {
|
|
|
591
429
|
: plugin.localScope;
|
|
592
430
|
const isInstalledInScope = selectedScope?.enabled;
|
|
593
431
|
const installedVersion = selectedScope?.version;
|
|
594
|
-
const scopeLabel = scopeValue === "user"
|
|
595
|
-
? "User"
|
|
596
|
-
: scopeValue === "project"
|
|
597
|
-
? "Project"
|
|
598
|
-
: "Local";
|
|
599
|
-
// Check if this scope has an update available
|
|
432
|
+
const scopeLabel = scopeValue === "user" ? "User" : scopeValue === "project" ? "Project" : "Local";
|
|
600
433
|
const hasUpdateInScope = isInstalledInScope &&
|
|
601
434
|
installedVersion &&
|
|
602
435
|
latestVersion !== "0.0.0" &&
|
|
603
436
|
installedVersion !== latestVersion;
|
|
604
|
-
// Determine action: update if available, otherwise toggle
|
|
605
437
|
let action;
|
|
606
438
|
if (isInstalledInScope && hasUpdateInScope) {
|
|
607
439
|
action = "update";
|
|
@@ -630,7 +462,6 @@ export function PluginsScreen() {
|
|
|
630
462
|
else {
|
|
631
463
|
await cliInstallPlugin(plugin.id, scope);
|
|
632
464
|
await saveVersionAfterInstall(plugin.id, latestVersion, scope);
|
|
633
|
-
// On fresh install, configure env vars and install system deps
|
|
634
465
|
modal.hideModal();
|
|
635
466
|
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
636
467
|
await installPluginSystemDeps(plugin.name, plugin.marketplace);
|
|
@@ -648,7 +479,7 @@ export function PluginsScreen() {
|
|
|
648
479
|
};
|
|
649
480
|
const handleUpdate = async () => {
|
|
650
481
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
651
|
-
if (!item || item.
|
|
482
|
+
if (!item || item.kind !== "plugin" || !item.plugin.hasUpdate)
|
|
652
483
|
return;
|
|
653
484
|
const plugin = item.plugin;
|
|
654
485
|
const scope = pluginsState.scope === "global" ? "user" : "project";
|
|
@@ -685,15 +516,13 @@ export function PluginsScreen() {
|
|
|
685
516
|
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
686
517
|
}
|
|
687
518
|
};
|
|
688
|
-
// Scope-specific toggle (install if not installed, uninstall if installed)
|
|
689
519
|
const handleScopeToggle = async (scope) => {
|
|
690
520
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
691
|
-
if (!item || item.
|
|
521
|
+
if (!item || item.kind !== "plugin")
|
|
692
522
|
return;
|
|
693
523
|
const plugin = item.plugin;
|
|
694
524
|
const latestVersion = plugin.version || "0.0.0";
|
|
695
525
|
const scopeLabel = scope === "user" ? "User" : scope === "project" ? "Project" : "Local";
|
|
696
|
-
// Check if installed in this specific scope
|
|
697
526
|
const scopeData = scope === "user"
|
|
698
527
|
? plugin.userScope
|
|
699
528
|
: scope === "project"
|
|
@@ -701,17 +530,10 @@ export function PluginsScreen() {
|
|
|
701
530
|
: plugin.localScope;
|
|
702
531
|
const isInstalledInScope = scopeData?.enabled;
|
|
703
532
|
const installedVersion = scopeData?.version;
|
|
704
|
-
// Also check if installed in ANY scope (for the toggle behavior)
|
|
705
|
-
const isInstalledAnywhere = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
706
|
-
// Check if this scope has an update available
|
|
707
533
|
const hasUpdateInScope = isInstalledInScope &&
|
|
708
534
|
installedVersion &&
|
|
709
535
|
latestVersion !== "0.0.0" &&
|
|
710
536
|
installedVersion !== latestVersion;
|
|
711
|
-
// Determine action for THIS scope:
|
|
712
|
-
// - installed in this scope + has update → update
|
|
713
|
-
// - installed in this scope → uninstall from this scope
|
|
714
|
-
// - not installed in this scope → install to this scope
|
|
715
537
|
let action;
|
|
716
538
|
if (isInstalledInScope && hasUpdateInScope) {
|
|
717
539
|
action = "update";
|
|
@@ -739,7 +561,6 @@ export function PluginsScreen() {
|
|
|
739
561
|
else {
|
|
740
562
|
await cliInstallPlugin(plugin.id, scope);
|
|
741
563
|
await saveVersionAfterInstall(plugin.id, latestVersion, scope);
|
|
742
|
-
// On fresh install, configure env vars and install system deps
|
|
743
564
|
modal.hideModal();
|
|
744
565
|
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
745
566
|
await installPluginSystemDeps(plugin.name, plugin.marketplace);
|
|
@@ -755,21 +576,14 @@ export function PluginsScreen() {
|
|
|
755
576
|
}
|
|
756
577
|
};
|
|
757
578
|
const handleSaveAsProfile = async () => {
|
|
758
|
-
// Read current enabledPlugins from project settings
|
|
759
579
|
const settings = await readSettings(state.projectPath);
|
|
760
580
|
const enabledPlugins = settings.enabledPlugins ?? {};
|
|
761
581
|
const name = await modal.input("Save Profile", "Profile name:");
|
|
762
582
|
if (name === null || !name.trim())
|
|
763
583
|
return;
|
|
764
584
|
const scopeChoice = await modal.select("Save to scope", "Where should this profile be saved?", [
|
|
765
|
-
{
|
|
766
|
-
|
|
767
|
-
value: "user",
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
label: "Project — .claude/profiles.json (shared with team via git)",
|
|
771
|
-
value: "project",
|
|
772
|
-
},
|
|
585
|
+
{ label: "User — ~/.claude/profiles.json (available everywhere)", value: "user" },
|
|
586
|
+
{ label: "Project — .claude/profiles.json (shared with team via git)", value: "project" },
|
|
773
587
|
]);
|
|
774
588
|
if (scopeChoice === null)
|
|
775
589
|
return;
|
|
@@ -785,216 +599,29 @@ export function PluginsScreen() {
|
|
|
785
599
|
await modal.message("Error", `Failed to save profile: ${error}`, "error");
|
|
786
600
|
}
|
|
787
601
|
};
|
|
788
|
-
|
|
789
|
-
const item = selectableItems[pluginsState.selectedIndex];
|
|
790
|
-
if (!item || item.type !== "plugin" || !item.plugin)
|
|
791
|
-
return;
|
|
792
|
-
const plugin = item.plugin;
|
|
793
|
-
// Build list of scopes where plugin is installed
|
|
794
|
-
const installedScopes = [];
|
|
795
|
-
if (plugin.userScope?.enabled) {
|
|
796
|
-
const ver = plugin.userScope.version
|
|
797
|
-
? ` v${plugin.userScope.version}`
|
|
798
|
-
: "";
|
|
799
|
-
installedScopes.push({ label: `User (global)${ver}`, value: "user" });
|
|
800
|
-
}
|
|
801
|
-
if (plugin.projectScope?.enabled) {
|
|
802
|
-
const ver = plugin.projectScope.version
|
|
803
|
-
? ` v${plugin.projectScope.version}`
|
|
804
|
-
: "";
|
|
805
|
-
installedScopes.push({ label: `Project${ver}`, value: "project" });
|
|
806
|
-
}
|
|
807
|
-
if (plugin.localScope?.enabled) {
|
|
808
|
-
const ver = plugin.localScope.version
|
|
809
|
-
? ` v${plugin.localScope.version}`
|
|
810
|
-
: "";
|
|
811
|
-
installedScopes.push({ label: `Local${ver}`, value: "local" });
|
|
812
|
-
}
|
|
813
|
-
if (installedScopes.length === 0) {
|
|
814
|
-
await modal.message("Not Installed", `${plugin.name} is not installed in any scope.`, "info");
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
const scopeValue = await modal.select(`Uninstall ${plugin.name}`, `Installed in ${installedScopes.length} scope(s):`, installedScopes);
|
|
818
|
-
if (scopeValue === null)
|
|
819
|
-
return; // Cancelled
|
|
820
|
-
modal.loading(`Uninstalling ${plugin.name}...`);
|
|
821
|
-
try {
|
|
822
|
-
await cliUninstallPlugin(plugin.id, scopeValue, state.projectPath);
|
|
823
|
-
modal.hideModal();
|
|
824
|
-
fetchData();
|
|
825
|
-
}
|
|
826
|
-
catch (error) {
|
|
827
|
-
modal.hideModal();
|
|
828
|
-
await modal.message("Error", `Failed to uninstall: ${error}`, "error");
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
// Render loading state
|
|
602
|
+
// ── Render ─────────────────────────────────────────────────────────────────
|
|
832
603
|
if (pluginsState.marketplaces.status === "loading" ||
|
|
833
604
|
pluginsState.plugins.status === "loading") {
|
|
834
605
|
return (_jsxs("box", { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [_jsx("text", { fg: "#7e57c2", children: _jsx("strong", { children: "claudeup Plugins" }) }), _jsx("text", { fg: "gray", children: "Loading..." })] }));
|
|
835
606
|
}
|
|
836
|
-
// Render error state
|
|
837
607
|
if (pluginsState.marketplaces.status === "error" ||
|
|
838
608
|
pluginsState.plugins.status === "error") {
|
|
839
609
|
return (_jsxs("box", { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [_jsx("text", { fg: "#7e57c2", children: _jsx("strong", { children: "claudeup Plugins" }) }), _jsx("text", { fg: "red", children: "Error loading data" })] }));
|
|
840
610
|
}
|
|
841
|
-
// Get selected item for detail panel
|
|
842
611
|
const selectedItem = selectableItems[pluginsState.selectedIndex];
|
|
843
|
-
// Render item with fuzzy highlight support
|
|
844
|
-
const renderListItem = (item, _idx, isSelected) => {
|
|
845
|
-
if (item.type === "category" && item.marketplace) {
|
|
846
|
-
const mp = item.marketplace;
|
|
847
|
-
// Differentiate marketplace types with appropriate badges
|
|
848
|
-
let statusText = "";
|
|
849
|
-
let statusColor = "green";
|
|
850
|
-
if (item.marketplaceEnabled) {
|
|
851
|
-
if (item.isCommunitySection) {
|
|
852
|
-
statusText = "3rd Party";
|
|
853
|
-
statusColor = "gray";
|
|
854
|
-
}
|
|
855
|
-
else if (mp.name === "claude-plugins-official") {
|
|
856
|
-
statusText = "★ Official";
|
|
857
|
-
statusColor = "yellow";
|
|
858
|
-
}
|
|
859
|
-
else if (mp.name === "claude-code-plugins") {
|
|
860
|
-
statusText = "⚠ Deprecated";
|
|
861
|
-
statusColor = "gray";
|
|
862
|
-
}
|
|
863
|
-
else if (mp.official) {
|
|
864
|
-
statusText = "★ Official";
|
|
865
|
-
statusColor = "yellow";
|
|
866
|
-
}
|
|
867
|
-
else {
|
|
868
|
-
statusText = "✓ Added";
|
|
869
|
-
statusColor = "green";
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
if (isSelected) {
|
|
873
|
-
const arrow = item.isExpanded ? "▼" : "▶";
|
|
874
|
-
const count = item.pluginCount !== undefined && item.pluginCount > 0
|
|
875
|
-
? ` (${item.pluginCount})`
|
|
876
|
-
: "";
|
|
877
|
-
return (_jsx("text", { bg: "magenta", fg: "white", children: _jsxs("strong", { children: [" ", arrow, " ", mp.displayName, count, " "] }) }));
|
|
878
|
-
}
|
|
879
|
-
return (_jsx(CategoryHeader, { title: mp.displayName, expanded: item.isExpanded, count: item.pluginCount, status: statusText, statusColor: statusColor }));
|
|
880
|
-
}
|
|
881
|
-
if (item.type === "plugin" && item.plugin) {
|
|
882
|
-
const plugin = item.plugin;
|
|
883
|
-
const isAnyScope = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
884
|
-
// Build scope parts for colored rendering
|
|
885
|
-
const hasUser = plugin.userScope?.enabled;
|
|
886
|
-
const hasProject = plugin.projectScope?.enabled;
|
|
887
|
-
const hasLocal = plugin.localScope?.enabled;
|
|
888
|
-
const hasAnyScope = hasUser || hasProject || hasLocal;
|
|
889
|
-
// Build version string
|
|
890
|
-
let versionStr = "";
|
|
891
|
-
if (plugin.isOrphaned) {
|
|
892
|
-
versionStr = " deprecated";
|
|
893
|
-
}
|
|
894
|
-
else if (plugin.installedVersion && plugin.installedVersion !== "0.0.0") {
|
|
895
|
-
versionStr = ` v${plugin.installedVersion}`;
|
|
896
|
-
if (plugin.hasUpdate && plugin.version) {
|
|
897
|
-
versionStr += ` → v${plugin.version}`;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
// Get fuzzy match highlights if available
|
|
901
|
-
const matches = item._matches;
|
|
902
|
-
const segments = matches ? highlightMatches(plugin.name, matches) : null;
|
|
903
|
-
if (isSelected) {
|
|
904
|
-
return (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", _jsx("span", { children: hasUser ? "■" : "□" }), _jsx("span", { children: hasProject ? "■" : "□" }), _jsx("span", { children: hasLocal ? "■" : "□" }), " ", plugin.name, versionStr, " "] }));
|
|
905
|
-
}
|
|
906
|
-
const displayName = segments
|
|
907
|
-
? segments.map((seg) => seg.text).join("")
|
|
908
|
-
: plugin.name;
|
|
909
|
-
if (plugin.isOrphaned) {
|
|
910
|
-
const ver = plugin.installedVersion && plugin.installedVersion !== "0.0.0"
|
|
911
|
-
? ` v${plugin.installedVersion}` : "";
|
|
912
|
-
return (_jsxs("text", { children: [_jsx("span", { fg: "red", children: " \u25A0\u25A0\u25A0 " }), _jsx("span", { fg: "gray", children: displayName }), ver && _jsx("span", { fg: "yellow", children: ver }), _jsx("span", { fg: "red", children: " deprecated" })] }));
|
|
913
|
-
}
|
|
914
|
-
return (_jsxs("text", { children: [_jsx("span", { children: " " }), _jsx("span", { fg: hasUser ? "cyan" : "#333333", children: "\u25A0" }), _jsx("span", { fg: hasProject ? "green" : "#333333", children: "\u25A0" }), _jsx("span", { fg: hasLocal ? "yellow" : "#333333", children: "\u25A0" }), _jsx("span", { children: " " }), _jsx("span", { fg: hasAnyScope ? "white" : "gray", children: displayName }), _jsx("span", { fg: plugin.hasUpdate ? "yellow" : "gray", children: versionStr })] }));
|
|
915
|
-
}
|
|
916
|
-
return _jsx("text", { fg: "gray", children: item.label });
|
|
917
|
-
};
|
|
918
|
-
// Render detail content - compact to fit in available space
|
|
919
|
-
const renderDetail = () => {
|
|
920
|
-
if (!selectedItem) {
|
|
921
|
-
return _jsx("text", { fg: "gray", children: "Select an item" });
|
|
922
|
-
}
|
|
923
|
-
if (selectedItem.type === "category" && selectedItem.marketplace) {
|
|
924
|
-
const mp = selectedItem.marketplace;
|
|
925
|
-
const isEnabled = selectedItem.marketplaceEnabled;
|
|
926
|
-
// Get appropriate badge for marketplace type
|
|
927
|
-
const getBadge = () => {
|
|
928
|
-
if (selectedItem.isCommunitySection)
|
|
929
|
-
return " 3rd Party";
|
|
930
|
-
if (mp.name === "claude-plugins-official")
|
|
931
|
-
return " ★";
|
|
932
|
-
if (mp.name === "claude-code-plugins")
|
|
933
|
-
return " ⚠";
|
|
934
|
-
if (mp.official)
|
|
935
|
-
return " ★";
|
|
936
|
-
return "";
|
|
937
|
-
};
|
|
938
|
-
// Determine action hint based on state
|
|
939
|
-
const isCollapsed = pluginsState.collapsedMarketplaces.has(mp.name);
|
|
940
|
-
const hasPlugins = (selectedItem.pluginCount || 0) > 0;
|
|
941
|
-
let actionHint = "Add";
|
|
942
|
-
if (isEnabled) {
|
|
943
|
-
if (isCollapsed) {
|
|
944
|
-
actionHint = "Expand";
|
|
945
|
-
}
|
|
946
|
-
else if (hasPlugins) {
|
|
947
|
-
actionHint = "Collapse";
|
|
948
|
-
}
|
|
949
|
-
else {
|
|
950
|
-
actionHint = "Remove";
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: [mp.displayName, getBadge()] }) }), _jsx("text", { fg: "gray", children: mp.description || "No description" }), _jsx("text", { fg: isEnabled ? "green" : "gray", children: isEnabled ? "● Added" : "○ Not added" }), _jsxs("text", { fg: "#5c9aff", children: ["github.com/", mp.source.repo] }), _jsxs("text", { children: ["Plugins: ", selectedItem.pluginCount || 0] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: isEnabled ? "cyan" : "green", fg: "black", children: [" ", "Enter", " "] }), _jsxs("text", { fg: "gray", children: [" ", actionHint] })] }), isEnabled && (_jsx("box", { children: _jsx("text", { fg: "gray", children: "\u2190 \u2192 to expand/collapse" }) }))] }));
|
|
954
|
-
}
|
|
955
|
-
if (selectedItem.type === "plugin" && selectedItem.plugin) {
|
|
956
|
-
const plugin = selectedItem.plugin;
|
|
957
|
-
const isInstalled = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
958
|
-
// Orphaned/deprecated plugin
|
|
959
|
-
if (plugin.isOrphaned) {
|
|
960
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: "yellow", fg: "black", children: _jsxs("strong", { children: [" ", plugin.name, " \u2014 DEPRECATED "] }) }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "yellow", children: "This plugin is no longer in the marketplace." }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "It was removed from the marketplace but still referenced in your settings. Press d to uninstall and clean up." }) }), isInstalled && (_jsx("box", { flexDirection: "column", marginTop: 2, children: _jsxs("box", { children: [_jsx("text", { bg: "red", fg: "white", children: " d " }), _jsx("text", { children: " Uninstall (recommended)" })] }) }))] }));
|
|
961
|
-
}
|
|
962
|
-
// Build component counts
|
|
963
|
-
const components = [];
|
|
964
|
-
if (plugin.agents?.length)
|
|
965
|
-
components.push(`${plugin.agents.length} agents`);
|
|
966
|
-
if (plugin.commands?.length)
|
|
967
|
-
components.push(`${plugin.commands.length} commands`);
|
|
968
|
-
if (plugin.skills?.length)
|
|
969
|
-
components.push(`${plugin.skills.length} skills`);
|
|
970
|
-
if (plugin.mcpServers?.length)
|
|
971
|
-
components.push(`${plugin.mcpServers.length} MCP`);
|
|
972
|
-
if (plugin.lspServers && Object.keys(plugin.lspServers).length) {
|
|
973
|
-
components.push(`${Object.keys(plugin.lspServers).length} LSP`);
|
|
974
|
-
}
|
|
975
|
-
// Show version only if valid (not null, not 0.0.0)
|
|
976
|
-
const showVersion = plugin.version && plugin.version !== "0.0.0";
|
|
977
|
-
const showInstalledVersion = plugin.installedVersion && plugin.installedVersion !== "0.0.0";
|
|
978
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: "magenta", fg: "white", children: _jsxs("strong", { children: [" ", plugin.name, plugin.hasUpdate ? " ⬆" : "", " "] }) }) }), _jsx("box", { marginTop: 1, children: isInstalled ? (_jsx("text", { fg: "green", children: "\u25CF Installed" })) : (_jsx("text", { fg: "gray", children: "\u25CB Not installed" })) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { fg: "white", children: plugin.description }) }), showVersion && (_jsxs("text", { children: [_jsx("span", { children: "Version " }), _jsxs("span", { fg: "#5c9aff", children: ["v", plugin.version] }), showInstalledVersion &&
|
|
979
|
-
plugin.installedVersion !== plugin.version && (_jsxs("span", { children: [" (v", plugin.installedVersion, " installed)"] }))] })), plugin.category && (_jsxs("text", { children: [_jsx("span", { children: "Category " }), _jsx("span", { fg: "magenta", children: plugin.category })] })), plugin.author && (_jsxs("text", { children: [_jsx("span", { children: "Author " }), _jsx("span", { children: plugin.author.name })] })), components.length > 0 && (_jsxs("text", { children: [_jsx("span", { children: "Contains " }), _jsx("span", { fg: "yellow", children: components.join(" · ") })] })), _jsxs("box", { flexDirection: "column", marginTop: 1, children: [_jsx("text", { children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx("text", { children: _jsx("strong", { children: "Scopes:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsxs("span", { bg: "cyan", fg: "black", children: [" ", "u", " "] }), _jsx("span", { fg: plugin.userScope?.enabled ? "cyan" : "gray", children: plugin.userScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "cyan", children: "User" }), _jsx("span", { children: " global" }), plugin.userScope?.version && (_jsxs("span", { fg: "cyan", children: [" v", plugin.userScope.version] }))] }), _jsxs("text", { children: [_jsxs("span", { bg: "green", fg: "black", children: [" ", "p", " "] }), _jsx("span", { fg: plugin.projectScope?.enabled ? "green" : "gray", children: plugin.projectScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { children: " team" }), plugin.projectScope?.version && (_jsxs("span", { fg: "green", children: [" v", plugin.projectScope.version] }))] }), _jsxs("text", { children: [_jsxs("span", { bg: "yellow", fg: "black", children: [" ", "l", " "] }), _jsx("span", { fg: plugin.localScope?.enabled ? "yellow" : "gray", children: plugin.localScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "yellow", children: "Local" }), _jsx("span", { children: " private" }), plugin.localScope?.version && (_jsxs("span", { fg: "yellow", children: [" v", plugin.localScope.version] }))] })] })] }), isInstalled && (_jsx("box", { flexDirection: "column", marginTop: 1, children: plugin.hasUpdate && (_jsxs("box", { children: [_jsxs("text", { bg: "magenta", fg: "white", children: [" ", "U", " "] }), _jsxs("text", { children: [" Update to v", plugin.version] })] })) }))] }));
|
|
980
|
-
}
|
|
981
|
-
return null;
|
|
982
|
-
};
|
|
983
612
|
const footerHints = isSearchActive
|
|
984
613
|
? "type to filter │ Enter:done │ Esc:clear"
|
|
985
614
|
: "u/p/l:toggle │ U:update │ a:all │ s:profile │ /:search";
|
|
986
|
-
// Calculate status for subtitle
|
|
987
615
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
988
616
|
const plugins = pluginsState.plugins.status === "success" ? pluginsState.plugins.data : [];
|
|
989
617
|
const installedCount = plugins.filter((p) => p.enabled).length;
|
|
990
618
|
const updateCount = plugins.filter((p) => p.hasUpdate).length;
|
|
991
619
|
const subtitle = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} updates` : ""}`;
|
|
992
|
-
// Search placeholder shows status when not searching
|
|
993
620
|
const searchPlaceholder = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} ⬆` : ""} │ / to search`;
|
|
994
621
|
return (_jsx(ScreenLayout, { title: "claudeup Plugins", subtitle: subtitle, currentScreen: "plugins", search: {
|
|
995
622
|
isActive: isSearchActive,
|
|
996
623
|
query: pluginsState.searchQuery,
|
|
997
624
|
placeholder: searchPlaceholder,
|
|
998
|
-
}, footerHints: footerHints, listPanel: _jsxs("box", { flexDirection: "column", children: [_jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem:
|
|
625
|
+
}, footerHints: footerHints, listPanel: _jsxs("box", { flexDirection: "column", children: [_jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem: renderPluginRow, maxHeight: dimensions.listPanelHeight }), pluginsState.searchQuery && selectableItems.length === 0 && (_jsx(EmptyFilterState, { query: pluginsState.searchQuery, entityName: "plugins" }))] }), detailPanel: renderPluginDetail(selectedItem, pluginsState.collapsedMarketplaces) }));
|
|
999
626
|
}
|
|
1000
627
|
export default PluginsScreen;
|