claudeup 1.7.0 → 3.3.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 (302) hide show
  1. package/bin/claudeup.js +20 -2
  2. package/package.json +10 -19
  3. package/src/data/cli-tools.js +123 -0
  4. package/src/data/cli-tools.ts +140 -0
  5. package/{dist → src}/data/marketplaces.js +23 -24
  6. package/src/data/marketplaces.ts +95 -0
  7. package/src/data/mcp-servers.js +509 -0
  8. package/src/data/mcp-servers.ts +526 -0
  9. package/src/data/statuslines.js +159 -0
  10. package/src/data/statuslines.ts +188 -0
  11. package/src/index.js +4 -0
  12. package/src/index.ts +5 -0
  13. package/{dist → src}/main.js +46 -47
  14. package/src/main.tsx +145 -0
  15. package/src/opentui.d.ts +191 -0
  16. package/src/prerunner/index.js +87 -0
  17. package/src/prerunner/index.ts +124 -0
  18. package/{dist → src}/services/claude-runner.js +9 -10
  19. package/src/services/claude-runner.ts +31 -0
  20. package/{dist → src}/services/claude-settings.js +72 -33
  21. package/src/services/claude-settings.ts +934 -0
  22. package/src/services/local-marketplace.js +339 -0
  23. package/src/services/local-marketplace.ts +489 -0
  24. package/{dist → src}/services/mcp-registry.js +13 -14
  25. package/src/services/mcp-registry.ts +105 -0
  26. package/{dist → src}/services/plugin-manager.js +61 -13
  27. package/src/services/plugin-manager.ts +693 -0
  28. package/{dist → src}/services/plugin-mcp-config.js +19 -18
  29. package/src/services/plugin-mcp-config.ts +242 -0
  30. package/{dist → src}/services/update-cache.js +7 -8
  31. package/src/services/update-cache.ts +78 -0
  32. package/{dist → src}/services/version-check.js +15 -14
  33. package/src/services/version-check.ts +122 -0
  34. package/src/types/index.js +1 -0
  35. package/src/types/index.ts +141 -0
  36. package/src/ui/App.js +213 -0
  37. package/src/ui/App.tsx +359 -0
  38. package/src/ui/components/CategoryHeader.js +9 -0
  39. package/src/ui/components/CategoryHeader.tsx +41 -0
  40. package/{dist → src}/ui/components/ScrollableList.js +19 -6
  41. package/src/ui/components/ScrollableList.tsx +98 -0
  42. package/src/ui/components/SearchInput.js +19 -0
  43. package/src/ui/components/SearchInput.tsx +56 -0
  44. package/src/ui/components/StyledText.js +39 -0
  45. package/src/ui/components/StyledText.tsx +70 -0
  46. package/src/ui/components/TabBar.js +38 -0
  47. package/src/ui/components/TabBar.tsx +88 -0
  48. package/src/ui/components/layout/Panel.js +6 -0
  49. package/src/ui/components/layout/Panel.tsx +62 -0
  50. package/src/ui/components/layout/ProgressBar.js +14 -0
  51. package/src/ui/components/layout/ProgressBar.tsx +47 -0
  52. package/src/ui/components/layout/ScopeTabs.js +6 -0
  53. package/src/ui/components/layout/ScopeTabs.tsx +53 -0
  54. package/src/ui/components/layout/ScreenLayout.js +21 -0
  55. package/src/ui/components/layout/ScreenLayout.tsx +147 -0
  56. package/src/ui/components/layout/index.js +4 -0
  57. package/src/ui/components/layout/index.ts +4 -0
  58. package/src/ui/components/modals/ConfirmModal.js +14 -0
  59. package/src/ui/components/modals/ConfirmModal.tsx +59 -0
  60. package/src/ui/components/modals/InputModal.js +16 -0
  61. package/src/ui/components/modals/InputModal.tsx +68 -0
  62. package/src/ui/components/modals/LoadingModal.js +14 -0
  63. package/src/ui/components/modals/LoadingModal.tsx +40 -0
  64. package/src/ui/components/modals/MessageModal.js +16 -0
  65. package/src/ui/components/modals/MessageModal.tsx +64 -0
  66. package/src/ui/components/modals/ModalContainer.js +56 -0
  67. package/src/ui/components/modals/ModalContainer.tsx +104 -0
  68. package/src/ui/components/modals/SelectModal.js +26 -0
  69. package/src/ui/components/modals/SelectModal.tsx +82 -0
  70. package/src/ui/components/modals/index.js +6 -0
  71. package/src/ui/components/modals/index.ts +6 -0
  72. package/src/ui/hooks/index.js +3 -0
  73. package/src/ui/hooks/index.ts +3 -0
  74. package/{dist → src}/ui/hooks/useAsyncData.js +21 -22
  75. package/src/ui/hooks/useAsyncData.ts +127 -0
  76. package/src/ui/hooks/useKeyboard.js +13 -0
  77. package/src/ui/hooks/useKeyboard.ts +26 -0
  78. package/src/ui/hooks/useKeyboardHandler.js +39 -0
  79. package/src/ui/hooks/useKeyboardHandler.ts +63 -0
  80. package/{dist → src}/ui/screens/CliToolsScreen.js +60 -54
  81. package/src/ui/screens/CliToolsScreen.tsx +468 -0
  82. package/src/ui/screens/EnvVarsScreen.js +154 -0
  83. package/src/ui/screens/EnvVarsScreen.tsx +269 -0
  84. package/{dist → src}/ui/screens/McpRegistryScreen.js +56 -55
  85. package/src/ui/screens/McpRegistryScreen.tsx +331 -0
  86. package/{dist → src}/ui/screens/McpScreen.js +46 -47
  87. package/src/ui/screens/McpScreen.tsx +392 -0
  88. package/src/ui/screens/ModelSelectorScreen.js +292 -0
  89. package/src/ui/screens/ModelSelectorScreen.tsx +441 -0
  90. package/{dist → src}/ui/screens/PluginsScreen.js +305 -293
  91. package/src/ui/screens/PluginsScreen.tsx +1231 -0
  92. package/src/ui/screens/StatusLineScreen.js +200 -0
  93. package/src/ui/screens/StatusLineScreen.tsx +411 -0
  94. package/src/ui/screens/index.js +7 -0
  95. package/src/ui/screens/index.ts +7 -0
  96. package/src/ui/state/AnimationContext.js +34 -0
  97. package/src/ui/state/AnimationContext.tsx +76 -0
  98. package/{dist → src}/ui/state/AppContext.js +31 -32
  99. package/src/ui/state/AppContext.tsx +235 -0
  100. package/{dist → src}/ui/state/DimensionsContext.js +16 -17
  101. package/src/ui/state/DimensionsContext.tsx +144 -0
  102. package/{dist → src}/ui/state/reducer.js +89 -90
  103. package/src/ui/state/reducer.ts +467 -0
  104. package/src/ui/state/types.js +1 -0
  105. package/src/ui/state/types.ts +273 -0
  106. package/{dist → src}/utils/command-utils.js +3 -4
  107. package/src/utils/command-utils.ts +20 -0
  108. package/{dist → src}/utils/fuzzy-search.js +2 -3
  109. package/src/utils/fuzzy-search.ts +138 -0
  110. package/{dist → src}/utils/string-utils.js +6 -6
  111. package/src/utils/string-utils.ts +88 -0
  112. package/dist/data/cli-tools.d.ts +0 -13
  113. package/dist/data/cli-tools.d.ts.map +0 -1
  114. package/dist/data/cli-tools.js +0 -124
  115. package/dist/data/cli-tools.js.map +0 -1
  116. package/dist/data/marketplaces.d.ts +0 -6
  117. package/dist/data/marketplaces.d.ts.map +0 -1
  118. package/dist/data/marketplaces.js.map +0 -1
  119. package/dist/data/mcp-servers.d.ts +0 -8
  120. package/dist/data/mcp-servers.d.ts.map +0 -1
  121. package/dist/data/mcp-servers.js +0 -503
  122. package/dist/data/mcp-servers.js.map +0 -1
  123. package/dist/data/statuslines.d.ts +0 -10
  124. package/dist/data/statuslines.d.ts.map +0 -1
  125. package/dist/data/statuslines.js +0 -160
  126. package/dist/data/statuslines.js.map +0 -1
  127. package/dist/index.d.ts +0 -3
  128. package/dist/index.d.ts.map +0 -1
  129. package/dist/index.js +0 -90
  130. package/dist/index.js.map +0 -1
  131. package/dist/main.d.ts +0 -3
  132. package/dist/main.d.ts.map +0 -1
  133. package/dist/main.js.map +0 -1
  134. package/dist/prerunner/index.d.ts +0 -7
  135. package/dist/prerunner/index.d.ts.map +0 -1
  136. package/dist/prerunner/index.js +0 -64
  137. package/dist/prerunner/index.js.map +0 -1
  138. package/dist/services/claude-runner.d.ts +0 -7
  139. package/dist/services/claude-runner.d.ts.map +0 -1
  140. package/dist/services/claude-runner.js.map +0 -1
  141. package/dist/services/claude-settings.d.ts +0 -73
  142. package/dist/services/claude-settings.d.ts.map +0 -1
  143. package/dist/services/claude-settings.js.map +0 -1
  144. package/dist/services/local-marketplace.d.ts +0 -111
  145. package/dist/services/local-marketplace.d.ts.map +0 -1
  146. package/dist/services/local-marketplace.js +0 -599
  147. package/dist/services/local-marketplace.js.map +0 -1
  148. package/dist/services/mcp-registry.d.ts +0 -10
  149. package/dist/services/mcp-registry.d.ts.map +0 -1
  150. package/dist/services/mcp-registry.js.map +0 -1
  151. package/dist/services/plugin-manager.d.ts +0 -65
  152. package/dist/services/plugin-manager.d.ts.map +0 -1
  153. package/dist/services/plugin-manager.js.map +0 -1
  154. package/dist/services/plugin-mcp-config.d.ts +0 -52
  155. package/dist/services/plugin-mcp-config.d.ts.map +0 -1
  156. package/dist/services/plugin-mcp-config.js.map +0 -1
  157. package/dist/services/update-cache.d.ts +0 -16
  158. package/dist/services/update-cache.d.ts.map +0 -1
  159. package/dist/services/update-cache.js.map +0 -1
  160. package/dist/services/version-check.d.ts +0 -20
  161. package/dist/services/version-check.d.ts.map +0 -1
  162. package/dist/services/version-check.js.map +0 -1
  163. package/dist/types/index.d.ts +0 -105
  164. package/dist/types/index.d.ts.map +0 -1
  165. package/dist/types/index.js +0 -2
  166. package/dist/types/index.js.map +0 -1
  167. package/dist/ui/InkApp.d.ts +0 -5
  168. package/dist/ui/InkApp.d.ts.map +0 -1
  169. package/dist/ui/InkApp.js +0 -188
  170. package/dist/ui/InkApp.js.map +0 -1
  171. package/dist/ui/components/CategoryHeader.d.ts +0 -16
  172. package/dist/ui/components/CategoryHeader.d.ts.map +0 -1
  173. package/dist/ui/components/CategoryHeader.js +0 -11
  174. package/dist/ui/components/CategoryHeader.js.map +0 -1
  175. package/dist/ui/components/ScrollableList.d.ts +0 -16
  176. package/dist/ui/components/ScrollableList.d.ts.map +0 -1
  177. package/dist/ui/components/ScrollableList.js.map +0 -1
  178. package/dist/ui/components/SearchInput.d.ts +0 -18
  179. package/dist/ui/components/SearchInput.d.ts.map +0 -1
  180. package/dist/ui/components/SearchInput.js +0 -30
  181. package/dist/ui/components/SearchInput.js.map +0 -1
  182. package/dist/ui/components/TabBar.d.ts +0 -8
  183. package/dist/ui/components/TabBar.d.ts.map +0 -1
  184. package/dist/ui/components/TabBar.js +0 -18
  185. package/dist/ui/components/TabBar.js.map +0 -1
  186. package/dist/ui/components/layout/Footer.d.ts +0 -14
  187. package/dist/ui/components/layout/Footer.d.ts.map +0 -1
  188. package/dist/ui/components/layout/Footer.js +0 -23
  189. package/dist/ui/components/layout/Footer.js.map +0 -1
  190. package/dist/ui/components/layout/Header.d.ts +0 -4
  191. package/dist/ui/components/layout/Header.d.ts.map +0 -1
  192. package/dist/ui/components/layout/Header.js +0 -25
  193. package/dist/ui/components/layout/Header.js.map +0 -1
  194. package/dist/ui/components/layout/Panel.d.ts +0 -22
  195. package/dist/ui/components/layout/Panel.d.ts.map +0 -1
  196. package/dist/ui/components/layout/Panel.js +0 -8
  197. package/dist/ui/components/layout/Panel.js.map +0 -1
  198. package/dist/ui/components/layout/ProgressBar.d.ts +0 -12
  199. package/dist/ui/components/layout/ProgressBar.d.ts.map +0 -1
  200. package/dist/ui/components/layout/ProgressBar.js +0 -16
  201. package/dist/ui/components/layout/ProgressBar.js.map +0 -1
  202. package/dist/ui/components/layout/ScopeTabs.d.ts +0 -12
  203. package/dist/ui/components/layout/ScopeTabs.d.ts.map +0 -1
  204. package/dist/ui/components/layout/ScopeTabs.js +0 -8
  205. package/dist/ui/components/layout/ScopeTabs.js.map +0 -1
  206. package/dist/ui/components/layout/ScreenLayout.d.ts +0 -30
  207. package/dist/ui/components/layout/ScreenLayout.d.ts.map +0 -1
  208. package/dist/ui/components/layout/ScreenLayout.js +0 -23
  209. package/dist/ui/components/layout/ScreenLayout.js.map +0 -1
  210. package/dist/ui/components/layout/index.d.ts +0 -7
  211. package/dist/ui/components/layout/index.d.ts.map +0 -1
  212. package/dist/ui/components/layout/index.js +0 -7
  213. package/dist/ui/components/layout/index.js.map +0 -1
  214. package/dist/ui/components/modals/ConfirmModal.d.ts +0 -14
  215. package/dist/ui/components/modals/ConfirmModal.d.ts.map +0 -1
  216. package/dist/ui/components/modals/ConfirmModal.js +0 -15
  217. package/dist/ui/components/modals/ConfirmModal.js.map +0 -1
  218. package/dist/ui/components/modals/InputModal.d.ts +0 -16
  219. package/dist/ui/components/modals/InputModal.d.ts.map +0 -1
  220. package/dist/ui/components/modals/InputModal.js +0 -23
  221. package/dist/ui/components/modals/InputModal.js.map +0 -1
  222. package/dist/ui/components/modals/LoadingModal.d.ts +0 -8
  223. package/dist/ui/components/modals/LoadingModal.d.ts.map +0 -1
  224. package/dist/ui/components/modals/LoadingModal.js +0 -8
  225. package/dist/ui/components/modals/LoadingModal.js.map +0 -1
  226. package/dist/ui/components/modals/MessageModal.d.ts +0 -14
  227. package/dist/ui/components/modals/MessageModal.d.ts.map +0 -1
  228. package/dist/ui/components/modals/MessageModal.js +0 -17
  229. package/dist/ui/components/modals/MessageModal.js.map +0 -1
  230. package/dist/ui/components/modals/ModalContainer.d.ts +0 -7
  231. package/dist/ui/components/modals/ModalContainer.d.ts.map +0 -1
  232. package/dist/ui/components/modals/ModalContainer.js +0 -38
  233. package/dist/ui/components/modals/ModalContainer.js.map +0 -1
  234. package/dist/ui/components/modals/SelectModal.d.ts +0 -17
  235. package/dist/ui/components/modals/SelectModal.d.ts.map +0 -1
  236. package/dist/ui/components/modals/SelectModal.js +0 -33
  237. package/dist/ui/components/modals/SelectModal.js.map +0 -1
  238. package/dist/ui/components/modals/index.d.ts +0 -7
  239. package/dist/ui/components/modals/index.d.ts.map +0 -1
  240. package/dist/ui/components/modals/index.js +0 -7
  241. package/dist/ui/components/modals/index.js.map +0 -1
  242. package/dist/ui/hooks/index.d.ts +0 -3
  243. package/dist/ui/hooks/index.d.ts.map +0 -1
  244. package/dist/ui/hooks/index.js +0 -3
  245. package/dist/ui/hooks/index.js.map +0 -1
  246. package/dist/ui/hooks/useAsyncData.d.ts +0 -40
  247. package/dist/ui/hooks/useAsyncData.d.ts.map +0 -1
  248. package/dist/ui/hooks/useAsyncData.js.map +0 -1
  249. package/dist/ui/hooks/useKeyboardNavigation.d.ts +0 -27
  250. package/dist/ui/hooks/useKeyboardNavigation.d.ts.map +0 -1
  251. package/dist/ui/hooks/useKeyboardNavigation.js +0 -82
  252. package/dist/ui/hooks/useKeyboardNavigation.js.map +0 -1
  253. package/dist/ui/screens/CliToolsScreen.d.ts +0 -4
  254. package/dist/ui/screens/CliToolsScreen.d.ts.map +0 -1
  255. package/dist/ui/screens/CliToolsScreen.js.map +0 -1
  256. package/dist/ui/screens/EnvVarsScreen.d.ts +0 -4
  257. package/dist/ui/screens/EnvVarsScreen.d.ts.map +0 -1
  258. package/dist/ui/screens/EnvVarsScreen.js +0 -145
  259. package/dist/ui/screens/EnvVarsScreen.js.map +0 -1
  260. package/dist/ui/screens/McpRegistryScreen.d.ts +0 -4
  261. package/dist/ui/screens/McpRegistryScreen.d.ts.map +0 -1
  262. package/dist/ui/screens/McpRegistryScreen.js.map +0 -1
  263. package/dist/ui/screens/McpScreen.d.ts +0 -4
  264. package/dist/ui/screens/McpScreen.d.ts.map +0 -1
  265. package/dist/ui/screens/McpScreen.js.map +0 -1
  266. package/dist/ui/screens/ModelSelectorScreen.d.ts +0 -4
  267. package/dist/ui/screens/ModelSelectorScreen.d.ts.map +0 -1
  268. package/dist/ui/screens/ModelSelectorScreen.js +0 -143
  269. package/dist/ui/screens/ModelSelectorScreen.js.map +0 -1
  270. package/dist/ui/screens/PluginsScreen.d.ts +0 -4
  271. package/dist/ui/screens/PluginsScreen.d.ts.map +0 -1
  272. package/dist/ui/screens/PluginsScreen.js.map +0 -1
  273. package/dist/ui/screens/StatusLineScreen.d.ts +0 -4
  274. package/dist/ui/screens/StatusLineScreen.d.ts.map +0 -1
  275. package/dist/ui/screens/StatusLineScreen.js +0 -197
  276. package/dist/ui/screens/StatusLineScreen.js.map +0 -1
  277. package/dist/ui/screens/index.d.ts +0 -8
  278. package/dist/ui/screens/index.d.ts.map +0 -1
  279. package/dist/ui/screens/index.js +0 -8
  280. package/dist/ui/screens/index.js.map +0 -1
  281. package/dist/ui/state/AppContext.d.ts +0 -40
  282. package/dist/ui/state/AppContext.d.ts.map +0 -1
  283. package/dist/ui/state/AppContext.js.map +0 -1
  284. package/dist/ui/state/DimensionsContext.d.ts +0 -27
  285. package/dist/ui/state/DimensionsContext.d.ts.map +0 -1
  286. package/dist/ui/state/DimensionsContext.js.map +0 -1
  287. package/dist/ui/state/reducer.d.ts +0 -4
  288. package/dist/ui/state/reducer.d.ts.map +0 -1
  289. package/dist/ui/state/reducer.js.map +0 -1
  290. package/dist/ui/state/types.d.ts +0 -266
  291. package/dist/ui/state/types.d.ts.map +0 -1
  292. package/dist/ui/state/types.js +0 -2
  293. package/dist/ui/state/types.js.map +0 -1
  294. package/dist/utils/command-utils.d.ts +0 -8
  295. package/dist/utils/command-utils.d.ts.map +0 -1
  296. package/dist/utils/command-utils.js.map +0 -1
  297. package/dist/utils/fuzzy-search.d.ts +0 -33
  298. package/dist/utils/fuzzy-search.d.ts.map +0 -1
  299. package/dist/utils/fuzzy-search.js.map +0 -1
  300. package/dist/utils/string-utils.d.ts +0 -24
  301. package/dist/utils/string-utils.d.ts.map +0 -1
  302. package/dist/utils/string-utils.js.map +0 -1
@@ -0,0 +1,331 @@
1
+ import React, { useEffect, useCallback, useState, useRef } from "react";
2
+ import { useApp, useModal, useNavigation } from "../state/AppContext.js";
3
+ import { useDimensions } from "../state/DimensionsContext.js";
4
+ import { useKeyboard } from "../hooks/useKeyboard.js";
5
+ import { ScreenLayout } from "../components/layout/index.js";
6
+ import { ScrollableList } from "../components/ScrollableList.js";
7
+ import { searchMcpServers, formatDate } from "../../services/mcp-registry.js";
8
+ import { addMcpServer, setAllowMcp } from "../../services/claude-settings.js";
9
+ import type { McpRegistryServer, McpServerConfig } from "../../types/index.js";
10
+
11
+ /**
12
+ * Deduplicate servers by name, keeping only the latest version.
13
+ * Uses version string comparison, falling back to published_at date.
14
+ */
15
+ function deduplicateServers(servers: McpRegistryServer[]): McpRegistryServer[] {
16
+ const serverMap = new Map<string, McpRegistryServer>();
17
+
18
+ for (const server of servers) {
19
+ const existing = serverMap.get(server.name);
20
+ if (!existing) {
21
+ serverMap.set(server.name, server);
22
+ continue;
23
+ }
24
+
25
+ // Compare versions - keep the newer one
26
+ const isNewer = compareVersions(server, existing) > 0;
27
+ if (isNewer) {
28
+ serverMap.set(server.name, server);
29
+ }
30
+ }
31
+
32
+ return Array.from(serverMap.values());
33
+ }
34
+
35
+ /**
36
+ * Compare two servers by version. Returns:
37
+ * > 0 if a is newer
38
+ * < 0 if b is newer
39
+ * 0 if equal or cannot determine
40
+ */
41
+ function compareVersions(a: McpRegistryServer, b: McpRegistryServer): number {
42
+ // Try semver comparison first
43
+ if (a.version && b.version) {
44
+ const aParts = a.version.split(".").map((n) => parseInt(n, 10) || 0);
45
+ const bParts = b.version.split(".").map((n) => parseInt(n, 10) || 0);
46
+
47
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
48
+ const aVal = aParts[i] || 0;
49
+ const bVal = bParts[i] || 0;
50
+ if (aVal !== bVal) {
51
+ return aVal - bVal;
52
+ }
53
+ }
54
+ }
55
+
56
+ // Fall back to published_at date
57
+ if (a.published_at && b.published_at) {
58
+ return (
59
+ new Date(a.published_at).getTime() - new Date(b.published_at).getTime()
60
+ );
61
+ }
62
+
63
+ // If one has a date and other doesn't, prefer the one with date
64
+ if (a.published_at && !b.published_at) return 1;
65
+ if (!a.published_at && b.published_at) return -1;
66
+
67
+ return 0;
68
+ }
69
+
70
+ export function McpRegistryScreen() {
71
+ const { state, dispatch } = useApp();
72
+ const { mcpRegistry } = state;
73
+ const modal = useModal();
74
+ const { navigateToScreen } = useNavigation();
75
+ const dimensions = useDimensions();
76
+
77
+ const [servers, setServers] = useState<McpRegistryServer[]>([]);
78
+ const [isLoading, setIsLoading] = useState(true);
79
+ const [error, setError] = useState<string | null>(null);
80
+ const [searchQuery, setSearchQuery] = useState(mcpRegistry.searchQuery || "");
81
+
82
+ const isSearchActive =
83
+ state.isSearching &&
84
+ state.currentRoute.screen === "mcp-registry" &&
85
+ !state.modal;
86
+
87
+ const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
88
+
89
+ // Load servers
90
+ const loadServers = useCallback(async (query: string) => {
91
+ setIsLoading(true);
92
+ setError(null);
93
+ try {
94
+ const response = await searchMcpServers({ query, limit: 50 });
95
+ if (!response || !Array.isArray(response.servers)) {
96
+ throw new Error("Invalid response from MCP Registry API");
97
+ }
98
+ // Deduplicate to show only latest version of each server
99
+ const deduplicated = deduplicateServers(response.servers);
100
+ setServers(deduplicated);
101
+ } catch (err) {
102
+ setError(err instanceof Error ? err.message : "Failed to load servers");
103
+ setServers([]);
104
+ }
105
+ setIsLoading(false);
106
+ }, []);
107
+
108
+ // Debounced search
109
+ const debouncedSearch = useCallback(
110
+ (query: string) => {
111
+ if (searchTimeoutRef.current) {
112
+ clearTimeout(searchTimeoutRef.current);
113
+ }
114
+ searchTimeoutRef.current = setTimeout(() => {
115
+ loadServers(query);
116
+ }, 300);
117
+ },
118
+ [loadServers],
119
+ );
120
+
121
+ useEffect(() => {
122
+ loadServers(searchQuery);
123
+ return () => {
124
+ if (searchTimeoutRef.current) {
125
+ clearTimeout(searchTimeoutRef.current);
126
+ }
127
+ };
128
+ }, []);
129
+
130
+ // Keyboard handling
131
+ useKeyboard((event) => {
132
+ // Handle search mode
133
+ if (isSearchActive) {
134
+ if (event.name === "escape") {
135
+ dispatch({ type: "SET_SEARCHING", isSearching: false });
136
+ } else if (event.name === "enter") {
137
+ // Exit search mode and stay on list for navigation
138
+ dispatch({ type: "SET_SEARCHING", isSearching: false });
139
+ } else if (event.name === "up") {
140
+ // Allow navigation while searching
141
+ const newIndex = Math.max(0, mcpRegistry.selectedIndex - 1);
142
+ dispatch({ type: "MCPREGISTRY_SELECT", index: newIndex });
143
+ } else if (event.name === "down") {
144
+ // Allow navigation while searching
145
+ const newIndex = Math.min(
146
+ Math.max(0, servers.length - 1),
147
+ mcpRegistry.selectedIndex + 1,
148
+ );
149
+ dispatch({ type: "MCPREGISTRY_SELECT", index: newIndex });
150
+ } else if (event.name === "backspace" || event.name === "delete") {
151
+ const newQuery = searchQuery.slice(0, -1);
152
+ setSearchQuery(newQuery);
153
+ dispatch({ type: "MCPREGISTRY_SEARCH", query: newQuery });
154
+ debouncedSearch(newQuery);
155
+ } else if (event.name.length === 1 && !event.ctrl && !event.meta) {
156
+ const newQuery = searchQuery + event.name;
157
+ setSearchQuery(newQuery);
158
+ dispatch({ type: "MCPREGISTRY_SEARCH", query: newQuery });
159
+ debouncedSearch(newQuery);
160
+ }
161
+ return;
162
+ }
163
+
164
+ if (state.modal) return;
165
+
166
+ // Start search with /
167
+ if (event.name === "/") {
168
+ dispatch({ type: "SET_SEARCHING", isSearching: true });
169
+ return;
170
+ }
171
+
172
+ // Navigation
173
+ if (event.name === "up" || event.name === "k") {
174
+ const newIndex = Math.max(0, mcpRegistry.selectedIndex - 1);
175
+ dispatch({ type: "MCPREGISTRY_SELECT", index: newIndex });
176
+ } else if (event.name === "down" || event.name === "j") {
177
+ const newIndex = Math.min(
178
+ Math.max(0, servers.length - 1),
179
+ mcpRegistry.selectedIndex + 1,
180
+ );
181
+ dispatch({ type: "MCPREGISTRY_SELECT", index: newIndex });
182
+ } else if (event.name === "l") {
183
+ navigateToScreen("mcp");
184
+ } else if (event.name === "R") {
185
+ loadServers(searchQuery);
186
+ } else if (event.name === "enter") {
187
+ handleInstall();
188
+ }
189
+ });
190
+
191
+ const handleInstall = async () => {
192
+ const server = servers[mcpRegistry.selectedIndex];
193
+ if (!server) return;
194
+
195
+ const config: McpServerConfig = {
196
+ type: "http",
197
+ url: server.url,
198
+ };
199
+
200
+ modal.loading(`Installing ${server.name}...`);
201
+ try {
202
+ await setAllowMcp(true, state.projectPath);
203
+ await addMcpServer(server.name, config, state.projectPath);
204
+ modal.hideModal();
205
+ await modal.message(
206
+ "Installed",
207
+ `${server.name} has been configured.\n\nRestart Claude Code to activate.`,
208
+ "success",
209
+ );
210
+ } catch (error) {
211
+ modal.hideModal();
212
+ await modal.message("Error", `Failed to install: ${error}`, "error");
213
+ }
214
+ };
215
+
216
+ // Get selected server
217
+ const selectedServer = servers[mcpRegistry.selectedIndex];
218
+
219
+ const renderDetail = () => {
220
+ if (isLoading) {
221
+ return <text fg="gray">Loading...</text>;
222
+ }
223
+
224
+ if (error) {
225
+ return <text fg="red">Error: {error}</text>;
226
+ }
227
+
228
+ if (!selectedServer) {
229
+ return <text fg="gray">Select a server to see details</text>;
230
+ }
231
+
232
+ const dateDisplay = selectedServer.published_at
233
+ ? formatDate(selectedServer.published_at)
234
+ : "unknown";
235
+
236
+ const versionDisplay = selectedServer.version
237
+ ? `v${selectedServer.version}`
238
+ : "unknown";
239
+
240
+ return (
241
+ <box flexDirection="column">
242
+ <text fg="magenta"><strong>{selectedServer.name}</strong></text>
243
+ <box marginTop={1}>
244
+ <text>{selectedServer.short_description}</text>
245
+ </box>
246
+ <box marginTop={1}>
247
+ <text><strong>Version: </strong></text>
248
+ <text fg="green">{versionDisplay}</text>
249
+ </box>
250
+ <box>
251
+ <text><strong>Published: </strong></text>
252
+ <text fg="cyan">{dateDisplay}</text>
253
+ </box>
254
+ <box marginTop={1} flexDirection="column">
255
+ <text><strong>URL:</strong></text>
256
+ <text fg="cyan">{selectedServer.url}</text>
257
+ </box>
258
+ {selectedServer.source_code_url && (
259
+ <box marginTop={1} flexDirection="column">
260
+ <text><strong>Source:</strong></text>
261
+ <text fg="gray">{selectedServer.source_code_url}</text>
262
+ </box>
263
+ )}
264
+ <box marginTop={1}>
265
+ <text fg="green">Press Enter to install</text>
266
+ </box>
267
+ </box>
268
+ );
269
+ };
270
+
271
+ const renderListItem = (
272
+ server: McpRegistryServer,
273
+ _idx: number,
274
+ isSelected: boolean,
275
+ ) => {
276
+ const version = server.version ? ` v${server.version}` : "";
277
+ return isSelected ? (
278
+ <text bg="magenta" fg="white">
279
+ {" "}
280
+ {server.name}
281
+ {version}{" "}
282
+ </text>
283
+ ) : (
284
+ <text>
285
+ <span><strong>{server.name}</strong></span>
286
+ <span fg="green">{version}</span>
287
+ </text>
288
+ );
289
+ };
290
+
291
+ // Footer hints
292
+ const footerHints = isSearchActive
293
+ ? "Type to search │ ↑↓:nav │ Enter:done │ Esc:cancel"
294
+ : "↑↓:nav │ Enter:install │ /:search │ R:refresh │ l:local";
295
+
296
+ // Status for search placeholder
297
+ const searchPlaceholder = `${servers.length} servers │ / to search`;
298
+
299
+ return (
300
+ <ScreenLayout
301
+ title="claudeup MCP Registry"
302
+ subtitle="Powered by MCP Registry"
303
+ currentScreen="mcp-registry"
304
+ search={{
305
+ isActive: isSearchActive,
306
+ query: searchQuery,
307
+ placeholder: searchPlaceholder,
308
+ }}
309
+ footerHints={footerHints}
310
+ listPanel={
311
+ isLoading ? (
312
+ <text fg="gray">Loading...</text>
313
+ ) : error ? (
314
+ <text fg="red">Error: {error}</text>
315
+ ) : servers.length === 0 ? (
316
+ <text fg="gray">No servers found</text>
317
+ ) : (
318
+ <ScrollableList
319
+ items={servers}
320
+ selectedIndex={mcpRegistry.selectedIndex}
321
+ renderItem={renderListItem}
322
+ maxHeight={dimensions.listPanelHeight}
323
+ />
324
+ )
325
+ }
326
+ detailPanel={renderDetail()}
327
+ />
328
+ );
329
+ }
330
+
331
+ export default McpRegistryScreen;
@@ -1,12 +1,12 @@
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, useNavigation } from '../state/AppContext.js';
5
- import { useDimensions } from '../state/DimensionsContext.js';
6
- import { ScreenLayout } from '../components/layout/index.js';
7
- import { ScrollableList } from '../components/ScrollableList.js';
8
- import { getMcpServersByCategory, getCategoryDisplayName, categoryOrder, } from '../../data/mcp-servers.js';
9
- import { addMcpServer, removeMcpServer, getInstalledMcpServers, getEnabledMcpServers, } from '../../services/claude-settings.js';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useEffect, useCallback, useMemo } from "react";
3
+ import { useApp, useModal, useNavigation } from "../state/AppContext.js";
4
+ import { useDimensions } from "../state/DimensionsContext.js";
5
+ import { useKeyboard } from "../hooks/useKeyboard.js";
6
+ import { ScreenLayout } from "../components/layout/index.js";
7
+ import { ScrollableList } from "../components/ScrollableList.js";
8
+ import { getMcpServersByCategory, getCategoryDisplayName, categoryOrder, } from "../../data/mcp-servers.js";
9
+ import { addMcpServer, removeMcpServer, getInstalledMcpServers, getEnabledMcpServers, } from "../../services/claude-settings.js";
10
10
  export function McpScreen() {
11
11
  const { state, dispatch } = useApp();
12
12
  const { mcp } = state;
@@ -15,7 +15,7 @@ export function McpScreen() {
15
15
  const dimensions = useDimensions();
16
16
  // Fetch data
17
17
  const fetchData = useCallback(async () => {
18
- dispatch({ type: 'MCP_DATA_LOADING' });
18
+ dispatch({ type: "MCP_DATA_LOADING" });
19
19
  try {
20
20
  const serversByCategory = getMcpServersByCategory();
21
21
  const installedServers = await getInstalledMcpServers(state.projectPath);
@@ -34,14 +34,14 @@ export function McpScreen() {
34
34
  installedAsBooleans[name] = true;
35
35
  }
36
36
  dispatch({
37
- type: 'MCP_DATA_SUCCESS',
37
+ type: "MCP_DATA_SUCCESS",
38
38
  servers,
39
39
  installedServers: { ...installedAsBooleans, ...enabledServers },
40
40
  });
41
41
  }
42
42
  catch (error) {
43
43
  dispatch({
44
- type: 'MCP_DATA_ERROR',
44
+ type: "MCP_DATA_ERROR",
45
45
  error: error instanceof Error ? error : new Error(String(error)),
46
46
  });
47
47
  }
@@ -51,7 +51,7 @@ export function McpScreen() {
51
51
  }, [fetchData]);
52
52
  // Build list items with categories
53
53
  const allListItems = useMemo(() => {
54
- if (mcp.servers.status !== 'success')
54
+ if (mcp.servers.status !== "success")
55
55
  return [];
56
56
  const serversByCategory = getMcpServersByCategory();
57
57
  const items = [];
@@ -66,14 +66,14 @@ export function McpScreen() {
66
66
  for (const server of servers) {
67
67
  const isInstalled = mcp.installedServers[server.name] !== undefined;
68
68
  const isEnabled = mcp.installedServers[server.name] === true;
69
- let status = '';
69
+ let status = "";
70
70
  if (isInstalled && isEnabled) {
71
- status = '';
71
+ status = "";
72
72
  }
73
73
  else if (isInstalled) {
74
- status = '';
74
+ status = "";
75
75
  }
76
- const configTag = server.requiresConfig ? ' *' : '';
76
+ const configTag = server.requiresConfig ? " *" : "";
77
77
  items.push({
78
78
  label: ` ${status} ${server.name}${configTag}`,
79
79
  server,
@@ -85,27 +85,27 @@ export function McpScreen() {
85
85
  // Use all items - search goes to global registry, not local filter
86
86
  const listItems = allListItems;
87
87
  // Keyboard handling
88
- useInput((input, key) => {
88
+ useKeyboard((event) => {
89
89
  if (state.isSearching || state.modal)
90
90
  return;
91
91
  // Start search - navigate to registry with search mode active
92
- if (input === '/') {
93
- dispatch({ type: 'SET_SEARCHING', isSearching: true });
94
- navigateToScreen('mcp-registry');
92
+ if (event.name === "/") {
93
+ dispatch({ type: "SET_SEARCHING", isSearching: true });
94
+ navigateToScreen("mcp-registry");
95
95
  return;
96
96
  }
97
- if (key.upArrow || input === 'k') {
97
+ if (event.name === "up" || event.name === "k") {
98
98
  const newIndex = Math.max(0, mcp.selectedIndex - 1);
99
- dispatch({ type: 'MCP_SELECT', index: newIndex });
99
+ dispatch({ type: "MCP_SELECT", index: newIndex });
100
100
  }
101
- else if (key.downArrow || input === 'j') {
101
+ else if (event.name === "down" || event.name === "j") {
102
102
  const newIndex = Math.min(listItems.length - 1, mcp.selectedIndex + 1);
103
- dispatch({ type: 'MCP_SELECT', index: newIndex });
103
+ dispatch({ type: "MCP_SELECT", index: newIndex });
104
104
  }
105
- else if (input === 'r') {
106
- navigateToScreen('mcp-registry');
105
+ else if (event.name === "r") {
106
+ navigateToScreen("mcp-registry");
107
107
  }
108
- else if (key.return) {
108
+ else if (event.name === "enter") {
109
109
  handleSelect();
110
110
  }
111
111
  });
@@ -116,18 +116,18 @@ export function McpScreen() {
116
116
  const server = item.server;
117
117
  const isInstalled = mcp.installedServers[server.name] !== undefined;
118
118
  if (isInstalled) {
119
- const confirmed = await modal.confirm(`Remove ${server.name}?`, 'This will remove the MCP server configuration.');
119
+ const confirmed = await modal.confirm(`Remove ${server.name}?`, "This will remove the MCP server configuration.");
120
120
  if (confirmed) {
121
121
  modal.loading(`Removing ${server.name}...`);
122
122
  try {
123
123
  await removeMcpServer(server.name, state.projectPath);
124
124
  modal.hideModal();
125
- await modal.message('Removed', `${server.name} has been removed.`, 'success');
125
+ await modal.message("Removed", `${server.name} has been removed.`, "success");
126
126
  fetchData();
127
127
  }
128
128
  catch (error) {
129
129
  modal.hideModal();
130
- await modal.message('Error', `Failed to remove: ${error}`, 'error');
130
+ await modal.message("Error", `Failed to remove: ${error}`, "error");
131
131
  }
132
132
  }
133
133
  }
@@ -138,8 +138,8 @@ export function McpScreen() {
138
138
  };
139
139
  const installMcpServer = async (server) => {
140
140
  let config;
141
- if (server.type === 'http') {
142
- config = { type: 'http', url: server.url };
141
+ if (server.type === "http") {
142
+ config = { type: "http", url: server.url };
143
143
  }
144
144
  else {
145
145
  config = {
@@ -161,11 +161,11 @@ export function McpScreen() {
161
161
  continue;
162
162
  }
163
163
  }
164
- const value = await modal.input(`Configure ${server.name}`, `${field.label}${field.required ? ' (required)' : ''}:`, field.default);
164
+ const value = await modal.input(`Configure ${server.name}`, `${field.label}${field.required ? " (required)" : ""}:`, field.default);
165
165
  if (value === null)
166
166
  return; // User cancelled
167
167
  if (field.required && !value) {
168
- await modal.message('Required Field', `${field.label} is required.`, 'error');
168
+ await modal.message("Required Field", `${field.label} is required.`, "error");
169
169
  return;
170
170
  }
171
171
  if (value) {
@@ -178,45 +178,44 @@ export function McpScreen() {
178
178
  try {
179
179
  await addMcpServer(server.name, config, state.projectPath);
180
180
  modal.hideModal();
181
- await modal.message('Installed', `${server.name} has been configured.\n\nRestart Claude Code to activate.`, 'success');
181
+ await modal.message("Installed", `${server.name} has been configured.\n\nRestart Claude Code to activate.`, "success");
182
182
  fetchData();
183
183
  }
184
184
  catch (error) {
185
185
  modal.hideModal();
186
- await modal.message('Error', `Failed to install: ${error}`, 'error');
186
+ await modal.message("Error", `Failed to install: ${error}`, "error");
187
187
  }
188
188
  };
189
189
  // Get selected item
190
190
  const selectedItem = listItems[mcp.selectedIndex];
191
191
  const renderDetail = () => {
192
- if (mcp.servers.status === 'loading') {
193
- return _jsx(Text, { color: "gray", children: "Loading MCP servers..." });
192
+ if (mcp.servers.status === "loading") {
193
+ return _jsx("text", { fg: "gray", children: "Loading MCP servers..." });
194
194
  }
195
195
  if (!selectedItem || selectedItem.isCategory || !selectedItem.server) {
196
- return (_jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx(Text, { color: "gray", children: "Select a server to see details" }) }));
196
+ return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: "gray", children: "Select a server to see details" }) }));
197
197
  }
198
198
  const server = selectedItem.server;
199
199
  const isInstalled = mcp.installedServers[server.name] !== undefined;
200
200
  const isEnabled = mcp.installedServers[server.name] === true;
201
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["\u26A1 ", server.name] }), server.requiresConfig && _jsx(Text, { color: "yellow", children: " \u2699" })] }), _jsx(Text, { color: "gray", children: server.description }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "Status " }), isInstalled && isEnabled ? (_jsx(Text, { color: "green", children: "\u25CF Installed" })) : isInstalled ? (_jsx(Text, { color: "yellow", children: "\u25CF Disabled" })) : (_jsx(Text, { color: "gray", children: "\u25CB Not installed" }))] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "Type " }), _jsx(Text, { color: "white", children: server.type === 'http' ? 'HTTP' : 'Command' })] }), server.type === 'http' ? (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "URL " }), _jsx(Text, { color: "blue", children: server.url })] })) : (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "Command " }), _jsx(Text, { color: "cyan", children: server.command })] })), server.requiresConfig && (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "Config " }), _jsxs(Text, { color: "yellow", children: [server.configFields?.length || 0, " fields required"] })] }))] }), _jsx(Box, { marginTop: 2, children: isInstalled ? (_jsxs(Box, { children: [_jsxs(Text, { backgroundColor: "red", color: "white", children: [' ', "Enter", ' '] }), _jsx(Text, { color: "gray", children: " Remove server" })] })) : (_jsxs(Box, { children: [_jsxs(Text, { backgroundColor: "green", color: "black", children: [' ', "Enter", ' '] }), _jsx(Text, { color: "gray", children: " Install server" })] })) })] }));
201
+ return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: ["\u26A1 ", server.name] }) }), server.requiresConfig && _jsx("text", { fg: "yellow", children: " \u2699" })] }), _jsx("text", { fg: "gray", children: server.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Status " }), isInstalled && isEnabled ? (_jsx("text", { fg: "green", children: "\u25CF Installed" })) : isInstalled ? (_jsx("text", { fg: "yellow", children: "\u25CF Disabled" })) : (_jsx("text", { fg: "gray", children: "\u25CB Not installed" }))] }), _jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Type " }), _jsx("text", { fg: "white", children: server.type === "http" ? "HTTP" : "Command" })] }), server.type === "http" ? (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "URL " }), _jsx("text", { fg: "blue", children: server.url })] })) : (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Command " }), _jsx("text", { fg: "cyan", children: server.command })] })), server.requiresConfig && (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Config " }), _jsxs("text", { fg: "yellow", children: [server.configFields?.length || 0, " fields required"] })] }))] }), _jsx("box", { marginTop: 2, children: isInstalled ? (_jsxs("box", { children: [_jsx("text", { bg: "red", fg: "white", children: " Enter " }), _jsx("text", { fg: "gray", children: " Remove server" })] })) : (_jsxs("box", { children: [_jsx("text", { bg: "green", fg: "black", children: " Enter " }), _jsx("text", { fg: "gray", children: " Install server" })] })) })] }));
202
202
  };
203
203
  const renderListItem = (item, _idx, isSelected) => {
204
204
  // Category header
205
205
  if (item.isCategory) {
206
- return (_jsxs(Text, { bold: true, color: "magenta", children: ["\u25B8 ", item.label] }));
206
+ return (_jsx("text", { fg: "magenta", children: _jsxs("strong", { children: ["\u25B8 ", item.label] }) }));
207
207
  }
208
208
  // Server item
209
209
  const server = item.server;
210
210
  const isInstalled = server && mcp.installedServers[server.name] !== undefined;
211
- const icon = isInstalled ? '' : '';
212
- const iconColor = isInstalled ? 'green' : 'gray';
213
- return isSelected ? (_jsxs(Text, { backgroundColor: "magenta", color: "white", wrap: "truncate", children: [' ', icon, " ", server?.name || '', ' '] })) : (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: iconColor, children: icon }), _jsxs(Text, { color: "white", children: [' ', server?.name || ''] }), server?.requiresConfig && _jsx(Text, { color: "yellow", children: " \u2699" })] }));
211
+ const icon = isInstalled ? "" : "";
212
+ const iconColor = isInstalled ? "green" : "gray";
213
+ return isSelected ? (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", icon, " ", server?.name || "", " "] })) : (_jsxs("text", { children: [_jsx("span", { fg: iconColor, children: icon }), _jsxs("span", { fg: "white", children: [" ", server?.name || ""] }), server?.requiresConfig && _jsx("span", { fg: "yellow", children: " \u2699" })] }));
214
214
  };
215
215
  // Calculate status counts
216
216
  const installedCount = Object.keys(mcp.installedServers).length;
217
- const enabledCount = Object.values(mcp.installedServers).filter(v => v === true).length;
217
+ const enabledCount = Object.values(mcp.installedServers).filter((v) => v === true).length;
218
218
  const subtitle = `${enabledCount} enabled │ ${installedCount} configured │ / to search`;
219
219
  return (_jsx(ScreenLayout, { title: "claudeup MCP Servers", subtitle: subtitle, currentScreen: "mcp", footerHints: "\u2191\u2193:nav \u2502 Enter:toggle \u2502 /:search \u2502 r:registry", listPanel: _jsx(ScrollableList, { items: listItems, selectedIndex: mcp.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderDetail() }));
220
220
  }
221
221
  export default McpScreen;
222
- //# sourceMappingURL=McpScreen.js.map