claudeup 0.6.4 → 1.1.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 (209) hide show
  1. package/bin/claudeup.js +1 -1
  2. package/dist/data/marketplaces.d.ts +2 -0
  3. package/dist/data/marketplaces.d.ts.map +1 -1
  4. package/dist/data/marketplaces.js +51 -8
  5. package/dist/data/marketplaces.js.map +1 -1
  6. package/dist/data/mcp-servers.d.ts.map +1 -1
  7. package/dist/data/mcp-servers.js +82 -0
  8. package/dist/data/mcp-servers.js.map +1 -1
  9. package/dist/index.js +8 -5
  10. package/dist/index.js.map +1 -1
  11. package/dist/main.d.ts +3 -0
  12. package/dist/main.d.ts.map +1 -0
  13. package/dist/main.js +79 -0
  14. package/dist/main.js.map +1 -0
  15. package/dist/services/claude-settings.d.ts +8 -1
  16. package/dist/services/claude-settings.d.ts.map +1 -1
  17. package/dist/services/claude-settings.js +79 -0
  18. package/dist/services/claude-settings.js.map +1 -1
  19. package/dist/services/local-marketplace.d.ts +76 -0
  20. package/dist/services/local-marketplace.d.ts.map +1 -0
  21. package/dist/services/local-marketplace.js +340 -0
  22. package/dist/services/local-marketplace.js.map +1 -0
  23. package/dist/services/plugin-manager.d.ts +39 -2
  24. package/dist/services/plugin-manager.d.ts.map +1 -1
  25. package/dist/services/plugin-manager.js +259 -9
  26. package/dist/services/plugin-manager.js.map +1 -1
  27. package/dist/services/plugin-mcp-config.d.ts +52 -0
  28. package/dist/services/plugin-mcp-config.d.ts.map +1 -0
  29. package/dist/services/plugin-mcp-config.js +176 -0
  30. package/dist/services/plugin-mcp-config.js.map +1 -0
  31. package/dist/types/index.d.ts +6 -1
  32. package/dist/types/index.d.ts.map +1 -1
  33. package/dist/ui/InkApp.d.ts +5 -0
  34. package/dist/ui/InkApp.d.ts.map +1 -0
  35. package/dist/ui/InkApp.js +175 -0
  36. package/dist/ui/InkApp.js.map +1 -0
  37. package/dist/ui/components/CategoryHeader.d.ts +16 -0
  38. package/dist/ui/components/CategoryHeader.d.ts.map +1 -0
  39. package/dist/ui/components/CategoryHeader.js +11 -0
  40. package/dist/ui/components/CategoryHeader.js.map +1 -0
  41. package/dist/ui/components/ScrollableList.d.ts +16 -0
  42. package/dist/ui/components/ScrollableList.d.ts.map +1 -0
  43. package/dist/ui/components/ScrollableList.js +35 -0
  44. package/dist/ui/components/ScrollableList.js.map +1 -0
  45. package/dist/ui/components/SearchInput.d.ts +18 -0
  46. package/dist/ui/components/SearchInput.d.ts.map +1 -0
  47. package/dist/ui/components/SearchInput.js +30 -0
  48. package/dist/ui/components/SearchInput.js.map +1 -0
  49. package/dist/ui/components/TabBar.d.ts +8 -0
  50. package/dist/ui/components/TabBar.d.ts.map +1 -0
  51. package/dist/ui/components/TabBar.js +18 -0
  52. package/dist/ui/components/TabBar.js.map +1 -0
  53. package/dist/ui/components/layout/Footer.d.ts +14 -0
  54. package/dist/ui/components/layout/Footer.d.ts.map +1 -0
  55. package/dist/ui/components/layout/Footer.js +23 -0
  56. package/dist/ui/components/layout/Footer.js.map +1 -0
  57. package/dist/ui/components/layout/Header.d.ts +4 -0
  58. package/dist/ui/components/layout/Header.d.ts.map +1 -0
  59. package/dist/ui/components/layout/Header.js +25 -0
  60. package/dist/ui/components/layout/Header.js.map +1 -0
  61. package/dist/ui/components/layout/Panel.d.ts +22 -0
  62. package/dist/ui/components/layout/Panel.d.ts.map +1 -0
  63. package/dist/ui/components/layout/Panel.js +8 -0
  64. package/dist/ui/components/layout/Panel.js.map +1 -0
  65. package/dist/ui/components/layout/ProgressBar.d.ts +12 -0
  66. package/dist/ui/components/layout/ProgressBar.d.ts.map +1 -0
  67. package/dist/ui/components/layout/ProgressBar.js +16 -0
  68. package/dist/ui/components/layout/ProgressBar.js.map +1 -0
  69. package/dist/ui/components/layout/ScopeTabs.d.ts +12 -0
  70. package/dist/ui/components/layout/ScopeTabs.d.ts.map +1 -0
  71. package/dist/ui/components/layout/ScopeTabs.js +8 -0
  72. package/dist/ui/components/layout/ScopeTabs.js.map +1 -0
  73. package/dist/ui/components/layout/ScreenLayout.d.ts +30 -0
  74. package/dist/ui/components/layout/ScreenLayout.d.ts.map +1 -0
  75. package/dist/ui/components/layout/ScreenLayout.js +23 -0
  76. package/dist/ui/components/layout/ScreenLayout.js.map +1 -0
  77. package/dist/ui/components/layout/index.d.ts +7 -0
  78. package/dist/ui/components/layout/index.d.ts.map +1 -0
  79. package/dist/ui/components/layout/index.js +7 -0
  80. package/dist/ui/components/layout/index.js.map +1 -0
  81. package/dist/ui/components/modals/ConfirmModal.d.ts +14 -0
  82. package/dist/ui/components/modals/ConfirmModal.d.ts.map +1 -0
  83. package/dist/ui/components/modals/ConfirmModal.js +15 -0
  84. package/dist/ui/components/modals/ConfirmModal.js.map +1 -0
  85. package/dist/ui/components/modals/InputModal.d.ts +16 -0
  86. package/dist/ui/components/modals/InputModal.d.ts.map +1 -0
  87. package/dist/ui/components/modals/InputModal.js +23 -0
  88. package/dist/ui/components/modals/InputModal.js.map +1 -0
  89. package/dist/ui/components/modals/LoadingModal.d.ts +8 -0
  90. package/dist/ui/components/modals/LoadingModal.d.ts.map +1 -0
  91. package/dist/ui/components/modals/LoadingModal.js +8 -0
  92. package/dist/ui/components/modals/LoadingModal.js.map +1 -0
  93. package/dist/ui/components/modals/MessageModal.d.ts +14 -0
  94. package/dist/ui/components/modals/MessageModal.d.ts.map +1 -0
  95. package/dist/ui/components/modals/MessageModal.js +17 -0
  96. package/dist/ui/components/modals/MessageModal.js.map +1 -0
  97. package/dist/ui/components/modals/ModalContainer.d.ts +7 -0
  98. package/dist/ui/components/modals/ModalContainer.d.ts.map +1 -0
  99. package/dist/ui/components/modals/ModalContainer.js +38 -0
  100. package/dist/ui/components/modals/ModalContainer.js.map +1 -0
  101. package/dist/ui/components/modals/SelectModal.d.ts +17 -0
  102. package/dist/ui/components/modals/SelectModal.d.ts.map +1 -0
  103. package/dist/ui/components/modals/SelectModal.js +33 -0
  104. package/dist/ui/components/modals/SelectModal.js.map +1 -0
  105. package/dist/ui/components/modals/index.d.ts +7 -0
  106. package/dist/ui/components/modals/index.d.ts.map +1 -0
  107. package/dist/ui/components/modals/index.js +7 -0
  108. package/dist/ui/components/modals/index.js.map +1 -0
  109. package/dist/ui/hooks/index.d.ts +3 -0
  110. package/dist/ui/hooks/index.d.ts.map +1 -0
  111. package/dist/ui/hooks/index.js +3 -0
  112. package/dist/ui/hooks/index.js.map +1 -0
  113. package/dist/ui/hooks/useAsyncData.d.ts +40 -0
  114. package/dist/ui/hooks/useAsyncData.d.ts.map +1 -0
  115. package/dist/ui/hooks/useAsyncData.js +78 -0
  116. package/dist/ui/hooks/useAsyncData.js.map +1 -0
  117. package/dist/ui/hooks/useKeyboardNavigation.d.ts +27 -0
  118. package/dist/ui/hooks/useKeyboardNavigation.d.ts.map +1 -0
  119. package/dist/ui/hooks/useKeyboardNavigation.js +82 -0
  120. package/dist/ui/hooks/useKeyboardNavigation.js.map +1 -0
  121. package/dist/ui/screens/CliToolsScreen.d.ts +4 -0
  122. package/dist/ui/screens/CliToolsScreen.d.ts.map +1 -0
  123. package/dist/ui/screens/CliToolsScreen.js +268 -0
  124. package/dist/ui/screens/CliToolsScreen.js.map +1 -0
  125. package/dist/ui/screens/EnvVarsScreen.d.ts +4 -0
  126. package/dist/ui/screens/EnvVarsScreen.d.ts.map +1 -0
  127. package/dist/ui/screens/EnvVarsScreen.js +145 -0
  128. package/dist/ui/screens/EnvVarsScreen.js.map +1 -0
  129. package/dist/ui/screens/McpRegistryScreen.d.ts +4 -0
  130. package/dist/ui/screens/McpRegistryScreen.d.ts.map +1 -0
  131. package/dist/ui/screens/McpRegistryScreen.js +226 -0
  132. package/dist/ui/screens/McpRegistryScreen.js.map +1 -0
  133. package/dist/ui/screens/McpScreen.d.ts +4 -0
  134. package/dist/ui/screens/McpScreen.d.ts.map +1 -0
  135. package/dist/ui/screens/McpScreen.js +222 -0
  136. package/dist/ui/screens/McpScreen.js.map +1 -0
  137. package/dist/ui/screens/ModelSelectorScreen.d.ts +4 -0
  138. package/dist/ui/screens/ModelSelectorScreen.d.ts.map +1 -0
  139. package/dist/ui/screens/ModelSelectorScreen.js +143 -0
  140. package/dist/ui/screens/ModelSelectorScreen.js.map +1 -0
  141. package/dist/ui/screens/PluginsScreen.d.ts +4 -0
  142. package/dist/ui/screens/PluginsScreen.d.ts.map +1 -0
  143. package/dist/ui/screens/PluginsScreen.js +818 -0
  144. package/dist/ui/screens/PluginsScreen.js.map +1 -0
  145. package/dist/ui/screens/StatusLineScreen.d.ts +4 -0
  146. package/dist/ui/screens/StatusLineScreen.d.ts.map +1 -0
  147. package/dist/ui/screens/StatusLineScreen.js +197 -0
  148. package/dist/ui/screens/StatusLineScreen.js.map +1 -0
  149. package/dist/ui/screens/index.d.ts +8 -0
  150. package/dist/ui/screens/index.d.ts.map +1 -0
  151. package/dist/ui/screens/index.js +8 -0
  152. package/dist/ui/screens/index.js.map +1 -0
  153. package/dist/ui/state/AppContext.d.ts +40 -0
  154. package/dist/ui/state/AppContext.d.ts.map +1 -0
  155. package/dist/ui/state/AppContext.js +162 -0
  156. package/dist/ui/state/AppContext.js.map +1 -0
  157. package/dist/ui/state/DimensionsContext.d.ts +25 -0
  158. package/dist/ui/state/DimensionsContext.d.ts.map +1 -0
  159. package/dist/ui/state/DimensionsContext.js +68 -0
  160. package/dist/ui/state/DimensionsContext.js.map +1 -0
  161. package/dist/ui/state/reducer.d.ts +4 -0
  162. package/dist/ui/state/reducer.d.ts.map +1 -0
  163. package/dist/ui/state/reducer.js +412 -0
  164. package/dist/ui/state/reducer.js.map +1 -0
  165. package/dist/ui/state/types.d.ts +266 -0
  166. package/dist/ui/state/types.d.ts.map +1 -0
  167. package/dist/ui/state/types.js +2 -0
  168. package/dist/ui/state/types.js.map +1 -0
  169. package/dist/utils/fuzzy-search.d.ts +33 -0
  170. package/dist/utils/fuzzy-search.d.ts.map +1 -0
  171. package/dist/utils/fuzzy-search.js +102 -0
  172. package/dist/utils/fuzzy-search.js.map +1 -0
  173. package/dist/utils/string-utils.d.ts +24 -0
  174. package/dist/utils/string-utils.d.ts.map +1 -0
  175. package/dist/utils/string-utils.js +62 -0
  176. package/dist/utils/string-utils.js.map +1 -0
  177. package/package.json +19 -7
  178. package/dist/ui/app.d.ts +0 -38
  179. package/dist/ui/app.d.ts.map +0 -1
  180. package/dist/ui/app.js +0 -590
  181. package/dist/ui/app.js.map +0 -1
  182. package/dist/ui/screens/cli-tools.d.ts +0 -4
  183. package/dist/ui/screens/cli-tools.d.ts.map +0 -1
  184. package/dist/ui/screens/cli-tools.js +0 -369
  185. package/dist/ui/screens/cli-tools.js.map +0 -1
  186. package/dist/ui/screens/env-vars.d.ts +0 -3
  187. package/dist/ui/screens/env-vars.d.ts.map +0 -1
  188. package/dist/ui/screens/env-vars.js +0 -119
  189. package/dist/ui/screens/env-vars.js.map +0 -1
  190. package/dist/ui/screens/main-menu.d.ts +0 -3
  191. package/dist/ui/screens/main-menu.d.ts.map +0 -1
  192. package/dist/ui/screens/main-menu.js +0 -110
  193. package/dist/ui/screens/main-menu.js.map +0 -1
  194. package/dist/ui/screens/mcp-registry.d.ts +0 -10
  195. package/dist/ui/screens/mcp-registry.d.ts.map +0 -1
  196. package/dist/ui/screens/mcp-registry.js +0 -310
  197. package/dist/ui/screens/mcp-registry.js.map +0 -1
  198. package/dist/ui/screens/mcp-setup.d.ts +0 -4
  199. package/dist/ui/screens/mcp-setup.d.ts.map +0 -1
  200. package/dist/ui/screens/mcp-setup.js +0 -492
  201. package/dist/ui/screens/mcp-setup.js.map +0 -1
  202. package/dist/ui/screens/plugins.d.ts +0 -3
  203. package/dist/ui/screens/plugins.d.ts.map +0 -1
  204. package/dist/ui/screens/plugins.js +0 -443
  205. package/dist/ui/screens/plugins.js.map +0 -1
  206. package/dist/ui/screens/statusline.d.ts +0 -5
  207. package/dist/ui/screens/statusline.d.ts.map +0 -1
  208. package/dist/ui/screens/statusline.js +0 -235
  209. package/dist/ui/screens/statusline.js.map +0 -1
@@ -0,0 +1,818 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useCallback, useMemo } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { useApp, useModal, useProgress } from '../state/AppContext.js';
5
+ import { useDimensions } from '../state/DimensionsContext.js';
6
+ import { ScreenLayout } from '../components/layout/index.js';
7
+ import { CategoryHeader } from '../components/CategoryHeader.js';
8
+ import { ScrollableList } from '../components/ScrollableList.js';
9
+ import { fuzzyFilter, highlightMatches } from '../../utils/fuzzy-search.js';
10
+ import { getAllMarketplaces } from '../../data/marketplaces.js';
11
+ import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, saveInstalledPluginVersion, removeInstalledPluginVersion, getLocalMarketplacesInfo, } from '../../services/plugin-manager.js';
12
+ import { addMarketplace, removeMarketplace, addGlobalMarketplace, removeGlobalMarketplace, enablePlugin, enableGlobalPlugin, enableLocalPlugin, saveGlobalInstalledPluginVersion, removeGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, removeLocalInstalledPluginVersion, setMcpEnvVar, getMcpEnvVars, } from '../../services/claude-settings.js';
13
+ import { getPluginEnvRequirements, getPluginSourcePath, } from '../../services/plugin-mcp-config.js';
14
+ import { cloneMarketplace, deleteMarketplace, addToKnownMarketplaces, removeFromKnownMarketplaces, } from '../../services/local-marketplace.js';
15
+ export function PluginsScreen() {
16
+ const { state, dispatch } = useApp();
17
+ const { plugins: pluginsState } = state;
18
+ const modal = useModal();
19
+ const progress = useProgress();
20
+ const dimensions = useDimensions();
21
+ const isSearchActive = state.isSearching && state.currentRoute.screen === 'plugins' && !state.modal;
22
+ // Fetch data (always fetches all scopes)
23
+ const fetchData = useCallback(async () => {
24
+ dispatch({ type: 'PLUGINS_DATA_LOADING' });
25
+ try {
26
+ const localMarketplaces = await getLocalMarketplacesInfo();
27
+ const allMarketplaces = getAllMarketplaces(localMarketplaces);
28
+ // Always use getAvailablePlugins which fetches all scope data
29
+ const pluginData = await getAvailablePlugins(state.projectPath);
30
+ dispatch({
31
+ type: 'PLUGINS_DATA_SUCCESS',
32
+ marketplaces: allMarketplaces,
33
+ plugins: pluginData,
34
+ });
35
+ }
36
+ catch (error) {
37
+ dispatch({
38
+ type: 'PLUGINS_DATA_ERROR',
39
+ error: error instanceof Error ? error : new Error(String(error)),
40
+ });
41
+ }
42
+ }, [dispatch, state.projectPath]);
43
+ // Load data on mount or when dataRefreshVersion changes
44
+ useEffect(() => {
45
+ fetchData();
46
+ }, [fetchData, state.dataRefreshVersion]);
47
+ // Build list items (categories + plugins)
48
+ const allItems = useMemo(() => {
49
+ if (pluginsState.marketplaces.status !== 'success' || pluginsState.plugins.status !== 'success') {
50
+ return [];
51
+ }
52
+ const marketplaces = pluginsState.marketplaces.data;
53
+ const plugins = pluginsState.plugins.data;
54
+ const collapsed = pluginsState.collapsedMarketplaces;
55
+ const pluginsByMarketplace = new Map();
56
+ for (const plugin of plugins) {
57
+ const existing = pluginsByMarketplace.get(plugin.marketplace) || [];
58
+ existing.push(plugin);
59
+ pluginsByMarketplace.set(plugin.marketplace, existing);
60
+ }
61
+ // Sort marketplaces: deprecated ones go to the bottom
62
+ const sortedMarketplaces = [...marketplaces].sort((a, b) => {
63
+ const aDeprecated = a.name === 'claude-code-plugins' ? 1 : 0;
64
+ const bDeprecated = b.name === 'claude-code-plugins' ? 1 : 0;
65
+ return aDeprecated - bDeprecated;
66
+ });
67
+ const items = [];
68
+ for (const marketplace of sortedMarketplaces) {
69
+ const marketplacePlugins = pluginsByMarketplace.get(marketplace.name) || [];
70
+ const isCollapsed = collapsed.has(marketplace.name);
71
+ const isEnabled = marketplacePlugins.length > 0 || marketplace.official;
72
+ const hasPlugins = marketplacePlugins.length > 0;
73
+ // Category header (marketplace)
74
+ items.push({
75
+ id: `mp:${marketplace.name}`,
76
+ type: 'category',
77
+ label: marketplace.displayName,
78
+ marketplace,
79
+ marketplaceEnabled: isEnabled,
80
+ pluginCount: marketplacePlugins.length,
81
+ isExpanded: !isCollapsed && hasPlugins,
82
+ });
83
+ // Plugins under this marketplace (if expanded)
84
+ if (isEnabled && hasPlugins && !isCollapsed) {
85
+ for (const plugin of marketplacePlugins) {
86
+ items.push({
87
+ id: `pl:${plugin.id}`,
88
+ type: 'plugin',
89
+ label: plugin.name,
90
+ plugin,
91
+ });
92
+ }
93
+ }
94
+ }
95
+ return items;
96
+ }, [pluginsState.marketplaces, pluginsState.plugins, pluginsState.collapsedMarketplaces]);
97
+ // Filter items by search query
98
+ const filteredItems = useMemo(() => {
99
+ const query = pluginsState.searchQuery.trim();
100
+ if (!query)
101
+ return allItems;
102
+ // Only search plugins, not categories
103
+ const pluginItems = allItems.filter(item => item.type === 'plugin');
104
+ const fuzzyResults = fuzzyFilter(pluginItems, query, item => item.label);
105
+ // Include parent categories for matched plugins
106
+ const matchedMarketplaces = new Set();
107
+ for (const result of fuzzyResults) {
108
+ if (result.item.plugin) {
109
+ matchedMarketplaces.add(result.item.plugin.marketplace);
110
+ }
111
+ }
112
+ const result = [];
113
+ let currentMarketplace = null;
114
+ for (const item of allItems) {
115
+ if (item.type === 'category' && item.marketplace) {
116
+ if (matchedMarketplaces.has(item.marketplace.name)) {
117
+ result.push(item);
118
+ currentMarketplace = item.marketplace.name;
119
+ }
120
+ else {
121
+ currentMarketplace = null;
122
+ }
123
+ }
124
+ else if (item.type === 'plugin' && item.plugin) {
125
+ if (currentMarketplace === item.plugin.marketplace) {
126
+ // Check if this plugin matched
127
+ const matched = fuzzyResults.find(r => r.item.id === item.id);
128
+ if (matched) {
129
+ result.push({ ...item, _matches: matched.matches });
130
+ }
131
+ }
132
+ }
133
+ }
134
+ return result;
135
+ }, [allItems, pluginsState.searchQuery]);
136
+ // Only selectable items (plugins, not categories)
137
+ const selectableItems = useMemo(() => {
138
+ return filteredItems.filter(item => item.type === 'plugin' || item.type === 'category');
139
+ }, [filteredItems]);
140
+ // Keyboard handling
141
+ useInput((input, key) => {
142
+ // Handle search mode
143
+ if (isSearchActive) {
144
+ if (key.escape) {
145
+ dispatch({ type: 'SET_SEARCHING', isSearching: false });
146
+ dispatch({ type: 'PLUGINS_SET_SEARCH', query: '' });
147
+ }
148
+ else if (key.return) {
149
+ dispatch({ type: 'SET_SEARCHING', isSearching: false });
150
+ // Keep the search query, just exit search mode
151
+ }
152
+ else if (key.backspace || key.delete) {
153
+ dispatch({ type: 'PLUGINS_SET_SEARCH', query: pluginsState.searchQuery.slice(0, -1) });
154
+ }
155
+ else if (input && !key.ctrl && !key.meta) {
156
+ dispatch({ type: 'PLUGINS_SET_SEARCH', query: pluginsState.searchQuery + input });
157
+ }
158
+ return;
159
+ }
160
+ if (state.modal)
161
+ return;
162
+ // Start search with /
163
+ if (input === '/') {
164
+ dispatch({ type: 'SET_SEARCHING', isSearching: true });
165
+ return;
166
+ }
167
+ // Navigation
168
+ if (key.upArrow || input === 'k') {
169
+ const newIndex = Math.max(0, pluginsState.selectedIndex - 1);
170
+ dispatch({ type: 'PLUGINS_SELECT', index: newIndex });
171
+ }
172
+ else if (key.downArrow || input === 'j') {
173
+ const newIndex = Math.min(selectableItems.length - 1, pluginsState.selectedIndex + 1);
174
+ dispatch({ type: 'PLUGINS_SELECT', index: newIndex });
175
+ }
176
+ // Collapse/expand marketplace
177
+ else if ((key.leftArrow || key.rightArrow || input === '<' || input === '>') && selectableItems[pluginsState.selectedIndex]?.marketplace) {
178
+ const item = selectableItems[pluginsState.selectedIndex];
179
+ if (item?.marketplace) {
180
+ dispatch({ type: 'PLUGINS_TOGGLE_MARKETPLACE', name: item.marketplace.name });
181
+ }
182
+ }
183
+ // Refresh
184
+ else if (input === 'r') {
185
+ handleRefresh();
186
+ }
187
+ // New marketplace
188
+ else if (input === 'n') {
189
+ handleAddMarketplace();
190
+ }
191
+ // Scope-specific toggle shortcuts (u/p/l)
192
+ else if (input === 'u') {
193
+ handleScopeToggle('user');
194
+ }
195
+ else if (input === 'p') {
196
+ handleScopeToggle('project');
197
+ }
198
+ else if (input === 'l') {
199
+ handleScopeToggle('local');
200
+ }
201
+ // Update plugin (Shift+U)
202
+ else if (input === 'U') {
203
+ handleUpdate();
204
+ }
205
+ // Update all
206
+ else if (input === 'a') {
207
+ handleUpdateAll();
208
+ }
209
+ // Delete/uninstall
210
+ else if (input === 'd') {
211
+ handleUninstall();
212
+ }
213
+ // Enter for selection
214
+ else if (key.return) {
215
+ handleSelect();
216
+ }
217
+ });
218
+ // Handle actions
219
+ const handleRefresh = async () => {
220
+ progress.show('Refreshing marketplaces...');
221
+ try {
222
+ await refreshAllMarketplaces((p) => {
223
+ progress.show(`Refreshing ${p.name}...`, p.current, p.total);
224
+ });
225
+ clearMarketplaceCache();
226
+ progress.hide();
227
+ await modal.message('Refreshed', 'Marketplaces refreshed successfully.', 'success');
228
+ fetchData();
229
+ }
230
+ catch (error) {
231
+ progress.hide();
232
+ await modal.message('Error', `Refresh failed: ${error}`, 'error');
233
+ }
234
+ };
235
+ const handleAddMarketplace = async () => {
236
+ const repo = await modal.input('Add Marketplace', 'GitHub repo (owner/repo):');
237
+ if (!repo || !repo.trim())
238
+ return;
239
+ progress.show('Cloning marketplace...');
240
+ try {
241
+ const result = await cloneMarketplace(repo.trim());
242
+ if (result.success) {
243
+ const normalizedRepo = repo.trim().replace(/^https:\/\/github\.com\//, '').replace(/\.git$/, '');
244
+ const marketplace = {
245
+ name: result.name,
246
+ displayName: result.name,
247
+ source: { source: 'github', repo: normalizedRepo },
248
+ description: '',
249
+ official: false,
250
+ };
251
+ if (pluginsState.scope === 'global') {
252
+ await addGlobalMarketplace(marketplace);
253
+ }
254
+ else {
255
+ await addMarketplace(marketplace, state.projectPath);
256
+ }
257
+ await addToKnownMarketplaces(result.name, normalizedRepo);
258
+ clearMarketplaceCache();
259
+ progress.hide();
260
+ await modal.message('Added', `${result.name} marketplace added.`, 'success');
261
+ fetchData();
262
+ }
263
+ else {
264
+ progress.hide();
265
+ await modal.message('Failed', result.error || 'Clone failed', 'error');
266
+ }
267
+ }
268
+ catch (error) {
269
+ progress.hide();
270
+ await modal.message('Error', `Failed to add marketplace: ${error}`, 'error');
271
+ }
272
+ };
273
+ /**
274
+ * Collect environment variables required by a plugin's MCP servers
275
+ * Prompts user for missing values and saves to local settings
276
+ */
277
+ const collectPluginEnvVars = async (pluginName, marketplace) => {
278
+ try {
279
+ // Get plugin source path from marketplace manifest
280
+ const pluginSource = await getPluginSourcePath(marketplace, pluginName);
281
+ if (!pluginSource)
282
+ return true; // No source path, nothing to configure
283
+ // Get env var requirements from plugin's MCP config
284
+ const requirements = await getPluginEnvRequirements(marketplace, pluginSource);
285
+ if (requirements.length === 0)
286
+ return true; // No env vars needed
287
+ // Get existing env vars
288
+ const existingEnvVars = await getMcpEnvVars(state.projectPath);
289
+ const missingVars = requirements.filter(req => !existingEnvVars[req.name] && !process.env[req.name]);
290
+ if (missingVars.length === 0)
291
+ return true; // All vars already configured
292
+ // Ask user if they want to configure MCP server env vars now
293
+ const serverNames = [...new Set(missingVars.map(v => v.serverName))];
294
+ 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?`);
295
+ if (!wantToConfigure) {
296
+ await modal.message('Skipped Configuration', 'You can configure these variables later in the Environment Variables screen (press 4).', 'info');
297
+ return true; // Still installed, just not configured
298
+ }
299
+ // Collect each missing env var
300
+ for (const req of missingVars) {
301
+ // Check if value exists in process.env
302
+ const existingProcessEnv = process.env[req.name];
303
+ if (existingProcessEnv) {
304
+ const useExisting = await modal.confirm(`Use ${req.name}?`, `${req.name} is already set in your environment.\n\nUse the existing value?`);
305
+ if (useExisting) {
306
+ // Store reference to env var instead of literal value
307
+ await setMcpEnvVar(req.name, `\${${req.name}}`, state.projectPath);
308
+ continue;
309
+ }
310
+ }
311
+ // Prompt for value
312
+ const value = await modal.input(`Configure ${req.serverName}`, `${req.label} (required):`, '');
313
+ if (value === null) {
314
+ // User cancelled
315
+ await modal.message('Configuration Incomplete', `Skipped remaining configuration.\nYou can configure these later in Environment Variables (press 4).`, 'info');
316
+ return true; // Still installed
317
+ }
318
+ if (value) {
319
+ await setMcpEnvVar(req.name, value, state.projectPath);
320
+ }
321
+ }
322
+ return true;
323
+ }
324
+ catch (error) {
325
+ console.error('Error collecting plugin env vars:', error);
326
+ return true; // Don't block installation on config errors
327
+ }
328
+ };
329
+ const handleSelect = async () => {
330
+ const item = selectableItems[pluginsState.selectedIndex];
331
+ if (!item)
332
+ return;
333
+ if (item.type === 'category' && item.marketplace) {
334
+ const mp = item.marketplace;
335
+ const isGlobal = pluginsState.scope === 'global';
336
+ if (item.marketplaceEnabled) {
337
+ const isCollapsed = pluginsState.collapsedMarketplaces.has(mp.name);
338
+ // If collapsed, expand first (even if no plugins - they might load)
339
+ if (isCollapsed) {
340
+ dispatch({ type: 'PLUGINS_TOGGLE_MARKETPLACE', name: mp.name });
341
+ }
342
+ else if (item.pluginCount && item.pluginCount > 0) {
343
+ // If expanded with plugins, collapse
344
+ dispatch({ type: 'PLUGINS_TOGGLE_MARKETPLACE', name: mp.name });
345
+ }
346
+ else {
347
+ // If expanded with no plugins, offer to remove
348
+ const confirmed = await modal.confirm(`Remove ${mp.displayName}?`, 'Plugins from this marketplace will no longer be available.');
349
+ if (confirmed) {
350
+ modal.loading(`Removing ${mp.displayName}...`);
351
+ try {
352
+ if (isGlobal) {
353
+ await removeGlobalMarketplace(mp.name);
354
+ }
355
+ else {
356
+ await removeMarketplace(mp.name, state.projectPath);
357
+ }
358
+ await deleteMarketplace(mp.name);
359
+ await removeFromKnownMarketplaces(mp.name);
360
+ clearMarketplaceCache();
361
+ modal.hideModal();
362
+ await modal.message('Removed', `${mp.displayName} removed.`, 'success');
363
+ fetchData();
364
+ }
365
+ catch (error) {
366
+ modal.hideModal();
367
+ await modal.message('Error', `Failed to remove: ${error}`, 'error');
368
+ }
369
+ }
370
+ }
371
+ }
372
+ else {
373
+ // Add marketplace
374
+ modal.loading(`Adding ${mp.displayName}...`);
375
+ try {
376
+ if (isGlobal) {
377
+ await addGlobalMarketplace(mp);
378
+ }
379
+ else {
380
+ await addMarketplace(mp, state.projectPath);
381
+ }
382
+ modal.hideModal();
383
+ fetchData();
384
+ }
385
+ catch (error) {
386
+ modal.hideModal();
387
+ await modal.message('Error', `Failed to add: ${error}`, 'error');
388
+ }
389
+ }
390
+ }
391
+ else if (item.type === 'plugin' && item.plugin) {
392
+ const plugin = item.plugin;
393
+ const latestVersion = plugin.version || '0.0.0';
394
+ // Build scope options with status info
395
+ const buildScopeLabel = (name, scope, desc) => {
396
+ const installed = scope?.enabled;
397
+ const ver = scope?.version;
398
+ const hasUpdate = ver && latestVersion && ver !== latestVersion && latestVersion !== '0.0.0';
399
+ let label = installed ? `● ${name}` : `○ ${name}`;
400
+ label += ` (${desc})`;
401
+ if (ver)
402
+ label += ` v${ver}`;
403
+ if (hasUpdate)
404
+ label += ` → v${latestVersion}`;
405
+ return label;
406
+ };
407
+ const scopeOptions = [
408
+ { label: buildScopeLabel('User', plugin.userScope, 'global'), value: 'user' },
409
+ { label: buildScopeLabel('Project', plugin.projectScope, 'team'), value: 'project' },
410
+ { label: buildScopeLabel('Local', plugin.localScope, 'private'), value: 'local' },
411
+ ];
412
+ const scopeValue = await modal.select(plugin.name, `Select scope to toggle:`, scopeOptions);
413
+ if (scopeValue === null)
414
+ return; // Cancelled
415
+ // Determine action based on selected scope's current state
416
+ const selectedScope = scopeValue === 'user' ? plugin.userScope :
417
+ scopeValue === 'project' ? plugin.projectScope :
418
+ plugin.localScope;
419
+ const isInstalledInScope = selectedScope?.enabled;
420
+ const installedVersion = selectedScope?.version;
421
+ const scopeLabel = scopeValue === 'user' ? 'User' : scopeValue === 'project' ? 'Project' : 'Local';
422
+ // Check if this scope has an update available
423
+ const hasUpdateInScope = isInstalledInScope && installedVersion &&
424
+ latestVersion !== '0.0.0' &&
425
+ installedVersion !== latestVersion;
426
+ // Determine action: update if available, otherwise toggle
427
+ let action;
428
+ if (isInstalledInScope && hasUpdateInScope) {
429
+ action = 'update';
430
+ }
431
+ else if (isInstalledInScope) {
432
+ action = 'uninstall';
433
+ }
434
+ else {
435
+ action = 'install';
436
+ }
437
+ const actionLabel = action === 'update' ? `Updating ${scopeLabel}` :
438
+ action === 'install' ? `Installing to ${scopeLabel}` :
439
+ `Uninstalling from ${scopeLabel}`;
440
+ modal.loading(`${actionLabel}...`);
441
+ try {
442
+ if (action === 'uninstall') {
443
+ // Uninstall from this scope
444
+ if (scopeValue === 'user') {
445
+ await enableGlobalPlugin(plugin.id, false);
446
+ await removeGlobalInstalledPluginVersion(plugin.id);
447
+ }
448
+ else if (scopeValue === 'project') {
449
+ await enablePlugin(plugin.id, false, state.projectPath);
450
+ await removeInstalledPluginVersion(plugin.id, state.projectPath);
451
+ }
452
+ else {
453
+ await enableLocalPlugin(plugin.id, false, state.projectPath);
454
+ await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
455
+ }
456
+ }
457
+ else {
458
+ // Install or update (both save the latest version)
459
+ if (scopeValue === 'user') {
460
+ await enableGlobalPlugin(plugin.id, true);
461
+ await saveGlobalInstalledPluginVersion(plugin.id, latestVersion);
462
+ }
463
+ else if (scopeValue === 'project') {
464
+ await enablePlugin(plugin.id, true, state.projectPath);
465
+ await saveInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
466
+ }
467
+ else {
468
+ await enableLocalPlugin(plugin.id, true, state.projectPath);
469
+ await saveLocalInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
470
+ }
471
+ // On fresh install, prompt for MCP server env vars if needed
472
+ if (action === 'install') {
473
+ modal.hideModal();
474
+ await collectPluginEnvVars(plugin.name, plugin.marketplace);
475
+ }
476
+ }
477
+ if (action !== 'install') {
478
+ modal.hideModal();
479
+ }
480
+ fetchData();
481
+ }
482
+ catch (error) {
483
+ modal.hideModal();
484
+ await modal.message('Error', `Failed: ${error}`, 'error');
485
+ }
486
+ }
487
+ };
488
+ const handleUpdate = async () => {
489
+ const item = selectableItems[pluginsState.selectedIndex];
490
+ if (!item || item.type !== 'plugin' || !item.plugin?.hasUpdate)
491
+ return;
492
+ const plugin = item.plugin;
493
+ const isGlobal = pluginsState.scope === 'global';
494
+ modal.loading(`Updating ${plugin.name}...`);
495
+ try {
496
+ const versionToSave = plugin.version || '0.0.0';
497
+ if (isGlobal) {
498
+ await saveGlobalInstalledPluginVersion(plugin.id, versionToSave);
499
+ }
500
+ else {
501
+ await saveInstalledPluginVersion(plugin.id, versionToSave, state.projectPath);
502
+ }
503
+ modal.hideModal();
504
+ fetchData();
505
+ }
506
+ catch (error) {
507
+ modal.hideModal();
508
+ await modal.message('Error', `Failed to update: ${error}`, 'error');
509
+ }
510
+ };
511
+ const handleUpdateAll = async () => {
512
+ if (pluginsState.plugins.status !== 'success')
513
+ return;
514
+ const updatable = pluginsState.plugins.data.filter((p) => p.hasUpdate);
515
+ if (updatable.length === 0)
516
+ return;
517
+ const isGlobal = pluginsState.scope === 'global';
518
+ modal.loading(`Updating ${updatable.length} plugin(s)...`);
519
+ try {
520
+ for (const plugin of updatable) {
521
+ const versionToSave = plugin.version || '0.0.0';
522
+ if (isGlobal) {
523
+ await saveGlobalInstalledPluginVersion(plugin.id, versionToSave);
524
+ }
525
+ else {
526
+ await saveInstalledPluginVersion(plugin.id, versionToSave, state.projectPath);
527
+ }
528
+ }
529
+ modal.hideModal();
530
+ fetchData();
531
+ }
532
+ catch (error) {
533
+ modal.hideModal();
534
+ await modal.message('Error', `Failed to update: ${error}`, 'error');
535
+ }
536
+ };
537
+ // Scope-specific toggle (install if not installed, uninstall if installed)
538
+ const handleScopeToggle = async (scope) => {
539
+ const item = selectableItems[pluginsState.selectedIndex];
540
+ if (!item || item.type !== 'plugin' || !item.plugin)
541
+ return;
542
+ const plugin = item.plugin;
543
+ const latestVersion = plugin.version || '0.0.0';
544
+ const scopeLabel = scope === 'user' ? 'User' : scope === 'project' ? 'Project' : 'Local';
545
+ // Check if installed in this scope
546
+ const scopeData = scope === 'user' ? plugin.userScope :
547
+ scope === 'project' ? plugin.projectScope :
548
+ plugin.localScope;
549
+ const isInstalledInScope = scopeData?.enabled;
550
+ const installedVersion = scopeData?.version;
551
+ // Check if this scope has an update available
552
+ const hasUpdateInScope = isInstalledInScope && installedVersion &&
553
+ latestVersion !== '0.0.0' &&
554
+ installedVersion !== latestVersion;
555
+ // Determine action: update if available, otherwise toggle install/uninstall
556
+ let action;
557
+ if (isInstalledInScope && hasUpdateInScope) {
558
+ action = 'update';
559
+ }
560
+ else if (isInstalledInScope) {
561
+ action = 'uninstall';
562
+ }
563
+ else {
564
+ action = 'install';
565
+ }
566
+ const actionLabel = action === 'update' ? `Updating ${scopeLabel}` :
567
+ action === 'install' ? `Installing to ${scopeLabel}` :
568
+ `Uninstalling from ${scopeLabel}`;
569
+ modal.loading(`${actionLabel}...`);
570
+ try {
571
+ if (action === 'uninstall') {
572
+ // Uninstall from this scope
573
+ if (scope === 'user') {
574
+ await enableGlobalPlugin(plugin.id, false);
575
+ await removeGlobalInstalledPluginVersion(plugin.id);
576
+ }
577
+ else if (scope === 'project') {
578
+ await enablePlugin(plugin.id, false, state.projectPath);
579
+ await removeInstalledPluginVersion(plugin.id, state.projectPath);
580
+ }
581
+ else {
582
+ await enableLocalPlugin(plugin.id, false, state.projectPath);
583
+ await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
584
+ }
585
+ }
586
+ else {
587
+ // Install or update to this scope (both save the latest version)
588
+ if (scope === 'user') {
589
+ await enableGlobalPlugin(plugin.id, true);
590
+ await saveGlobalInstalledPluginVersion(plugin.id, latestVersion);
591
+ }
592
+ else if (scope === 'project') {
593
+ await enablePlugin(plugin.id, true, state.projectPath);
594
+ await saveInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
595
+ }
596
+ else {
597
+ await enableLocalPlugin(plugin.id, true, state.projectPath);
598
+ await saveLocalInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
599
+ }
600
+ // On fresh install, prompt for MCP server env vars if needed
601
+ if (action === 'install') {
602
+ modal.hideModal();
603
+ await collectPluginEnvVars(plugin.name, plugin.marketplace);
604
+ }
605
+ }
606
+ if (action !== 'install') {
607
+ modal.hideModal();
608
+ }
609
+ fetchData();
610
+ }
611
+ catch (error) {
612
+ modal.hideModal();
613
+ await modal.message('Error', `Failed: ${error}`, 'error');
614
+ }
615
+ };
616
+ const handleUninstall = async () => {
617
+ const item = selectableItems[pluginsState.selectedIndex];
618
+ if (!item || item.type !== 'plugin' || !item.plugin)
619
+ return;
620
+ const plugin = item.plugin;
621
+ // Build list of scopes where plugin is installed
622
+ const installedScopes = [];
623
+ if (plugin.userScope?.enabled) {
624
+ const ver = plugin.userScope.version ? ` v${plugin.userScope.version}` : '';
625
+ installedScopes.push({ label: `User (global)${ver}`, value: 'user' });
626
+ }
627
+ if (plugin.projectScope?.enabled) {
628
+ const ver = plugin.projectScope.version ? ` v${plugin.projectScope.version}` : '';
629
+ installedScopes.push({ label: `Project${ver}`, value: 'project' });
630
+ }
631
+ if (plugin.localScope?.enabled) {
632
+ const ver = plugin.localScope.version ? ` v${plugin.localScope.version}` : '';
633
+ installedScopes.push({ label: `Local${ver}`, value: 'local' });
634
+ }
635
+ if (installedScopes.length === 0) {
636
+ await modal.message('Not Installed', `${plugin.name} is not installed in any scope.`, 'info');
637
+ return;
638
+ }
639
+ const scopeValue = await modal.select(`Uninstall ${plugin.name}`, `Installed in ${installedScopes.length} scope(s):`, installedScopes);
640
+ if (scopeValue === null)
641
+ return; // Cancelled
642
+ modal.loading(`Uninstalling ${plugin.name}...`);
643
+ try {
644
+ if (scopeValue === 'user') {
645
+ await enableGlobalPlugin(plugin.id, false);
646
+ await removeGlobalInstalledPluginVersion(plugin.id);
647
+ }
648
+ else if (scopeValue === 'project') {
649
+ await enablePlugin(plugin.id, false, state.projectPath);
650
+ await removeInstalledPluginVersion(plugin.id, state.projectPath);
651
+ }
652
+ else {
653
+ // local scope
654
+ await enableLocalPlugin(plugin.id, false, state.projectPath);
655
+ await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
656
+ }
657
+ modal.hideModal();
658
+ fetchData();
659
+ }
660
+ catch (error) {
661
+ modal.hideModal();
662
+ await modal.message('Error', `Failed to uninstall: ${error}`, 'error');
663
+ }
664
+ };
665
+ // Render loading state
666
+ if (pluginsState.marketplaces.status === 'loading' || pluginsState.plugins.status === 'loading') {
667
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "#7e57c2", bold: true, children: "claudeup Plugins" }), _jsx(Text, { color: "gray", children: "Loading..." })] }));
668
+ }
669
+ // Render error state
670
+ if (pluginsState.marketplaces.status === 'error' || pluginsState.plugins.status === 'error') {
671
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "#7e57c2", bold: true, children: "claudeup Plugins" }), _jsx(Text, { color: "red", children: "Error loading data" })] }));
672
+ }
673
+ // Get selected item for detail panel
674
+ const selectedItem = selectableItems[pluginsState.selectedIndex];
675
+ // Render item with fuzzy highlight support
676
+ const renderListItem = (item, _idx, isSelected) => {
677
+ if (item.type === 'category' && item.marketplace) {
678
+ const mp = item.marketplace;
679
+ // Differentiate marketplace types with appropriate badges
680
+ let statusText = '';
681
+ let statusColor = 'green';
682
+ if (item.marketplaceEnabled) {
683
+ if (mp.name === 'claude-plugins-official') {
684
+ statusText = '★ Official';
685
+ statusColor = 'yellow';
686
+ }
687
+ else if (mp.name === 'claude-code-plugins') {
688
+ statusText = '⚠ Deprecated';
689
+ statusColor = 'gray';
690
+ }
691
+ else if (mp.official) {
692
+ statusText = '★ Official';
693
+ statusColor = 'yellow';
694
+ }
695
+ else {
696
+ statusText = '✓ Added';
697
+ statusColor = 'green';
698
+ }
699
+ }
700
+ if (isSelected) {
701
+ const arrow = item.isExpanded ? '▼' : '▶';
702
+ const count = item.pluginCount !== undefined && item.pluginCount > 0 ? ` (${item.pluginCount})` : '';
703
+ return (_jsx(Text, { backgroundColor: "magenta", color: "white", bold: true, children: ` ${arrow} ${mp.displayName}${count} ` }));
704
+ }
705
+ return (_jsx(CategoryHeader, { title: mp.displayName, expanded: item.isExpanded, count: item.pluginCount, status: statusText, statusColor: statusColor }));
706
+ }
707
+ if (item.type === 'plugin' && item.plugin) {
708
+ const plugin = item.plugin;
709
+ let statusIcon = '○';
710
+ let statusColor = 'gray';
711
+ if (plugin.enabled) {
712
+ statusIcon = '●';
713
+ statusColor = 'green';
714
+ }
715
+ else if (plugin.installedVersion) {
716
+ statusIcon = '●';
717
+ statusColor = 'yellow';
718
+ }
719
+ // Build version string
720
+ let versionStr = '';
721
+ if (plugin.installedVersion && plugin.installedVersion !== '0.0.0') {
722
+ versionStr = ` v${plugin.installedVersion}`;
723
+ if (plugin.hasUpdate && plugin.version) {
724
+ versionStr += ` → v${plugin.version}`;
725
+ }
726
+ }
727
+ // Get fuzzy match highlights if available
728
+ const matches = item._matches;
729
+ const segments = matches ? highlightMatches(plugin.name, matches) : null;
730
+ if (isSelected) {
731
+ const displayText = ` ${statusIcon} ${plugin.name}${versionStr} `;
732
+ return (_jsx(Text, { backgroundColor: "magenta", color: "white", wrap: "truncate", children: displayText }));
733
+ }
734
+ // For non-selected, render with colors
735
+ const displayName = segments
736
+ ? segments.map(seg => seg.text).join('')
737
+ : plugin.name;
738
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: statusColor, children: [' ', statusIcon, " "] }), _jsx(Text, { children: displayName }), _jsx(Text, { color: plugin.hasUpdate ? 'yellow' : 'gray', children: versionStr })] }));
739
+ }
740
+ return _jsx(Text, { color: "gray", children: item.label });
741
+ };
742
+ // Render detail content - compact to fit in available space
743
+ const renderDetail = () => {
744
+ if (!selectedItem) {
745
+ return _jsx(Text, { color: "gray", children: "Select an item" });
746
+ }
747
+ if (selectedItem.type === 'category' && selectedItem.marketplace) {
748
+ const mp = selectedItem.marketplace;
749
+ const isEnabled = selectedItem.marketplaceEnabled;
750
+ // Get appropriate badge for marketplace type
751
+ const getBadge = () => {
752
+ if (mp.name === 'claude-plugins-official')
753
+ return ' ★';
754
+ if (mp.name === 'claude-code-plugins')
755
+ return ' ⚠';
756
+ if (mp.official)
757
+ return ' ★';
758
+ return '';
759
+ };
760
+ // Determine action hint based on state
761
+ const isCollapsed = pluginsState.collapsedMarketplaces.has(mp.name);
762
+ const hasPlugins = (selectedItem.pluginCount || 0) > 0;
763
+ let actionHint = 'Add';
764
+ if (isEnabled) {
765
+ if (isCollapsed) {
766
+ actionHint = 'Expand';
767
+ }
768
+ else if (hasPlugins) {
769
+ actionHint = 'Collapse';
770
+ }
771
+ else {
772
+ actionHint = 'Remove';
773
+ }
774
+ }
775
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "cyan", children: [mp.displayName, getBadge()] }), _jsx(Text, { color: "gray", wrap: "wrap", children: mp.description || 'No description' }), _jsx(Text, { color: isEnabled ? 'green' : 'gray', children: isEnabled ? '● Added' : '○ Not added' }), _jsxs(Text, { color: "blue", wrap: "wrap", children: ["github.com/", mp.source.repo] }), _jsxs(Text, { children: ["Plugins: ", selectedItem.pluginCount || 0] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { backgroundColor: isEnabled ? 'cyan' : 'green', color: "black", children: " Enter " }), _jsxs(Text, { color: "gray", children: [" ", actionHint] })] }), isEnabled && (_jsx(Box, { children: _jsx(Text, { color: "gray", children: "\u2190 \u2192 to expand/collapse" }) }))] }));
776
+ }
777
+ if (selectedItem.type === 'plugin' && selectedItem.plugin) {
778
+ const plugin = selectedItem.plugin;
779
+ const isInstalled = plugin.enabled || plugin.installedVersion;
780
+ // Build component counts
781
+ const components = [];
782
+ if (plugin.agents?.length)
783
+ components.push(`${plugin.agents.length} agents`);
784
+ if (plugin.commands?.length)
785
+ components.push(`${plugin.commands.length} commands`);
786
+ if (plugin.skills?.length)
787
+ components.push(`${plugin.skills.length} skills`);
788
+ if (plugin.mcpServers?.length)
789
+ components.push(`${plugin.mcpServers.length} MCP`);
790
+ if (plugin.lspServers && Object.keys(plugin.lspServers).length) {
791
+ components.push(`${Object.keys(plugin.lspServers).length} LSP`);
792
+ }
793
+ // Show version only if valid (not null, not 0.0.0)
794
+ const showVersion = plugin.version && plugin.version !== '0.0.0';
795
+ const showInstalledVersion = plugin.installedVersion && plugin.installedVersion !== '0.0.0';
796
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { justifyContent: "center", children: _jsx(Text, { backgroundColor: "magenta", color: "white", bold: true, children: ` ${plugin.name}${plugin.hasUpdate ? ' ⬆' : ''} ` }) }), _jsx(Box, { marginTop: 1, children: isInstalled ? (_jsx(Text, { color: plugin.enabled ? 'green' : 'yellow', children: plugin.enabled ? '● Enabled' : '● Disabled' })) : (_jsx(Text, { color: "gray", children: "\u25CB Not installed" })) }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: "white", wrap: "wrap", children: plugin.description }) }), showVersion && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Version " }), _jsxs(Text, { color: "blue", children: ["v", plugin.version] }), showInstalledVersion && plugin.installedVersion !== plugin.version && _jsxs(Text, { dimColor: true, children: [" (v", plugin.installedVersion, " installed)"] })] })), plugin.category && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Category " }), _jsx(Text, { color: "magenta", children: plugin.category })] })), plugin.author && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Author " }), _jsx(Text, { children: plugin.author.name })] })), components.length > 0 && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Contains " }), _jsx(Text, { color: "yellow", children: components.join(' · ') })] })), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, 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, { bold: true, children: "Scopes:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { backgroundColor: "cyan", color: "black", children: " u " }), _jsx(Text, { color: plugin.userScope?.enabled ? 'cyan' : 'gray', children: plugin.userScope?.enabled ? ' ● ' : ' ○ ' }), _jsx(Text, { color: "cyan", children: "User" }), _jsx(Text, { dimColor: true, children: " global" }), plugin.userScope?.version && _jsxs(Text, { color: "cyan", children: [" v", plugin.userScope.version] })] }), _jsxs(Box, { children: [_jsx(Text, { backgroundColor: "green", color: "black", children: " p " }), _jsx(Text, { color: plugin.projectScope?.enabled ? 'green' : 'gray', children: plugin.projectScope?.enabled ? ' ● ' : ' ○ ' }), _jsx(Text, { color: "green", children: "Project" }), _jsx(Text, { dimColor: true, children: " team" }), plugin.projectScope?.version && _jsxs(Text, { color: "green", children: [" v", plugin.projectScope.version] })] }), _jsxs(Box, { children: [_jsx(Text, { backgroundColor: "yellow", color: "black", children: " l " }), _jsx(Text, { color: plugin.localScope?.enabled ? 'yellow' : 'gray', children: plugin.localScope?.enabled ? ' ● ' : ' ○ ' }), _jsx(Text, { color: "yellow", children: "Local" }), _jsx(Text, { dimColor: true, children: " private" }), plugin.localScope?.version && _jsxs(Text, { color: "yellow", children: [" v", plugin.localScope.version] })] })] })] }), isInstalled && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [plugin.hasUpdate && (_jsxs(Box, { children: [_jsx(Text, { backgroundColor: "magenta", color: "white", children: " U " }), _jsxs(Text, { children: [" Update to v", plugin.version] })] })), _jsxs(Box, { children: [_jsx(Text, { backgroundColor: "red", color: "white", children: " d " }), _jsx(Text, { children: " Uninstall" })] })] }))] }));
797
+ }
798
+ return null;
799
+ };
800
+ const footerHints = isSearchActive
801
+ ? 'Type to search │ Enter Confirm │ Esc Cancel'
802
+ : 'u/p/l:scope │ U:update │ a:all │ d:remove │ /:search';
803
+ // Calculate status for subtitle
804
+ const scopeLabel = pluginsState.scope === 'global' ? 'Global' : 'Project';
805
+ const plugins = pluginsState.plugins.status === 'success' ? pluginsState.plugins.data : [];
806
+ const installedCount = plugins.filter(p => p.enabled).length;
807
+ const updateCount = plugins.filter(p => p.hasUpdate).length;
808
+ const subtitle = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} updates` : ''}`;
809
+ // Search placeholder shows status when not searching
810
+ const searchPlaceholder = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} ⬆` : ''} │ / to search`;
811
+ return (_jsx(ScreenLayout, { title: "claudeup Plugins", subtitle: subtitle, currentScreen: "plugins", search: {
812
+ isActive: isSearchActive,
813
+ query: pluginsState.searchQuery,
814
+ placeholder: searchPlaceholder,
815
+ }, footerHints: footerHints, listPanel: _jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderDetail() }));
816
+ }
817
+ export default PluginsScreen;
818
+ //# sourceMappingURL=PluginsScreen.js.map