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,693 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import { execSync } from "node:child_process";
4
+ import {
5
+ getConfiguredMarketplaces,
6
+ getEnabledPlugins,
7
+ readSettings,
8
+ writeSettings,
9
+ getGlobalConfiguredMarketplaces,
10
+ getGlobalEnabledPlugins,
11
+ getGlobalInstalledPluginVersions,
12
+ getLocalEnabledPlugins,
13
+ getLocalInstalledPluginVersions,
14
+ updateInstalledPluginsRegistry,
15
+ removeFromInstalledPluginsRegistry,
16
+ } from "./claude-settings.js";
17
+ import { defaultMarketplaces } from "../data/marketplaces.js";
18
+ import {
19
+ scanLocalMarketplaces,
20
+ repairAllMarketplaces,
21
+ type LocalMarketplace,
22
+ type ProgressCallback,
23
+ type RepairMarketplaceResult,
24
+ } from "./local-marketplace.js";
25
+ import {
26
+ formatMarketplaceName,
27
+ isValidGitHubRepo,
28
+ parsePluginId,
29
+ } from "../utils/string-utils.js";
30
+
31
+ // Cache for local marketplaces (session-level) - Promise-based to prevent race conditions
32
+ let localMarketplacesPromise: Promise<Map<string, LocalMarketplace>> | null =
33
+ null;
34
+
35
+ export interface ScopeStatus {
36
+ enabled: boolean;
37
+ version?: string;
38
+ }
39
+
40
+ export interface PluginInfo {
41
+ id: string;
42
+ name: string;
43
+ version: string | null;
44
+ description: string;
45
+ marketplace: string;
46
+ marketplaceDisplay: string;
47
+ enabled: boolean;
48
+ installedVersion?: string;
49
+ hasUpdate?: boolean;
50
+ // Per-scope installation status
51
+ userScope?: ScopeStatus;
52
+ projectScope?: ScopeStatus;
53
+ localScope?: ScopeStatus;
54
+ // Extended info
55
+ category?: string;
56
+ author?: { name: string; email?: string };
57
+ homepage?: string;
58
+ tags?: string[];
59
+ agents?: string[];
60
+ commands?: string[];
61
+ skills?: string[];
62
+ mcpServers?: string[];
63
+ lspServers?: Record<string, unknown>;
64
+ }
65
+
66
+ export interface MarketplacePlugin {
67
+ name: string;
68
+ version: string | null;
69
+ description: string;
70
+ category?: string;
71
+ author?: { name: string; email?: string };
72
+ homepage?: string;
73
+ tags?: string[];
74
+ }
75
+
76
+ // Session-level cache for fetched marketplace data (no TTL - persists until explicit refresh)
77
+ const marketplaceCache = new Map<string, MarketplacePlugin[]>();
78
+
79
+ export async function fetchMarketplacePlugins(
80
+ marketplaceName: string,
81
+ repo: string,
82
+ ): Promise<MarketplacePlugin[]> {
83
+ // Check cache first - session-level, no TTL
84
+ const cached = marketplaceCache.get(marketplaceName);
85
+ if (cached) {
86
+ return cached;
87
+ }
88
+
89
+ // Validate repo format to prevent SSRF
90
+ if (!isValidGitHubRepo(repo)) {
91
+ console.error(`Invalid GitHub repo format: ${repo}`);
92
+ return [];
93
+ }
94
+
95
+ try {
96
+ // Fetch marketplace.json from GitHub
97
+ const url = `https://raw.githubusercontent.com/${repo}/main/.claude-plugin/marketplace.json`;
98
+ const response = await fetch(url, {
99
+ signal: AbortSignal.timeout(10000), // 10s timeout
100
+ });
101
+
102
+ if (!response.ok) {
103
+ console.error(`Failed to fetch marketplace: ${response.status}`);
104
+ return [];
105
+ }
106
+
107
+ // Validate content-type
108
+ const contentType = response.headers.get("content-type");
109
+ if (
110
+ contentType &&
111
+ !contentType.includes("application/json") &&
112
+ !contentType.includes("text/plain")
113
+ ) {
114
+ console.error(`Invalid content-type for marketplace: ${contentType}`);
115
+ return [];
116
+ }
117
+
118
+ interface RawPlugin {
119
+ name: string;
120
+ version?: string | null;
121
+ description?: string;
122
+ category?: string;
123
+ author?: { name: string; email?: string };
124
+ homepage?: string;
125
+ tags?: string[];
126
+ }
127
+ const data = (await response.json()) as { plugins?: RawPlugin[] };
128
+ const plugins: MarketplacePlugin[] = [];
129
+
130
+ if (data.plugins && Array.isArray(data.plugins)) {
131
+ for (const plugin of data.plugins) {
132
+ plugins.push({
133
+ name: plugin.name,
134
+ version: plugin.version || null,
135
+ description: plugin.description || "",
136
+ category: plugin.category,
137
+ author: plugin.author,
138
+ homepage: plugin.homepage,
139
+ tags: plugin.tags,
140
+ });
141
+ }
142
+ }
143
+
144
+ // Cache the result (session-level)
145
+ marketplaceCache.set(marketplaceName, plugins);
146
+
147
+ return plugins;
148
+ } catch (error) {
149
+ console.error(`Error fetching marketplace ${marketplaceName}:`, error);
150
+ return [];
151
+ }
152
+ }
153
+
154
+ export async function getAvailablePlugins(
155
+ projectPath?: string,
156
+ ): Promise<PluginInfo[]> {
157
+ const configuredMarketplaces = await getConfiguredMarketplaces(projectPath);
158
+ const enabledPlugins = await getEnabledPlugins(projectPath);
159
+ const installedVersions = await getInstalledPluginVersions(projectPath);
160
+
161
+ // Fetch all scopes for per-scope status
162
+ const userEnabledPlugins = await getGlobalEnabledPlugins();
163
+ const userInstalledVersions = await getGlobalInstalledPluginVersions();
164
+ const projectEnabledPlugins = await getEnabledPlugins(projectPath);
165
+ const projectInstalledVersions =
166
+ await getInstalledPluginVersions(projectPath);
167
+ const localEnabledPlugins = await getLocalEnabledPlugins(projectPath);
168
+ const localInstalledVersions =
169
+ await getLocalInstalledPluginVersions(projectPath);
170
+
171
+ const plugins: PluginInfo[] = [];
172
+ const seenPluginIds = new Set<string>();
173
+
174
+ // Helper to build scope status
175
+ const buildScopeStatus = (pluginId: string) => ({
176
+ userScope:
177
+ userEnabledPlugins[pluginId] !== undefined
178
+ ? {
179
+ enabled: userEnabledPlugins[pluginId],
180
+ version: userInstalledVersions[pluginId],
181
+ }
182
+ : undefined,
183
+ projectScope:
184
+ projectEnabledPlugins[pluginId] !== undefined
185
+ ? {
186
+ enabled: projectEnabledPlugins[pluginId],
187
+ version: projectInstalledVersions[pluginId],
188
+ }
189
+ : undefined,
190
+ localScope:
191
+ localEnabledPlugins[pluginId] !== undefined
192
+ ? {
193
+ enabled: localEnabledPlugins[pluginId],
194
+ version: localInstalledVersions[pluginId],
195
+ }
196
+ : undefined,
197
+ });
198
+
199
+ // Get all marketplace names (configured + official/featured defaults)
200
+ // Always include official and featured marketplaces so users can browse them
201
+ const marketplaceNames = new Set<string>();
202
+ for (const mp of defaultMarketplaces) {
203
+ if (configuredMarketplaces[mp.name] || mp.official || mp.featured) {
204
+ marketplaceNames.add(mp.name);
205
+ }
206
+ }
207
+
208
+ // Fetch plugins from each configured marketplace
209
+ for (const mpName of marketplaceNames) {
210
+ const marketplace = defaultMarketplaces.find((m) => m.name === mpName);
211
+ if (!marketplace) continue;
212
+
213
+ const marketplacePlugins = await fetchMarketplacePlugins(
214
+ mpName,
215
+ marketplace.source.repo,
216
+ );
217
+
218
+ for (const plugin of marketplacePlugins) {
219
+ const pluginId = `${plugin.name}@${mpName}`;
220
+ const installedVersion = installedVersions[pluginId];
221
+ const isEnabled = enabledPlugins[pluginId] === true;
222
+ const scopeStatus = buildScopeStatus(pluginId);
223
+
224
+ seenPluginIds.add(pluginId);
225
+ plugins.push({
226
+ id: pluginId,
227
+ name: plugin.name,
228
+ version: plugin.version,
229
+ description: plugin.description,
230
+ marketplace: mpName,
231
+ marketplaceDisplay: marketplace.displayName,
232
+ enabled: isEnabled,
233
+ installedVersion: installedVersion,
234
+ hasUpdate:
235
+ installedVersion && plugin.version
236
+ ? compareVersions(plugin.version, installedVersion) > 0
237
+ : false,
238
+ ...scopeStatus,
239
+ category: plugin.category,
240
+ author: plugin.author,
241
+ homepage: plugin.homepage,
242
+ tags: plugin.tags,
243
+ });
244
+ }
245
+ }
246
+
247
+ // Fetch ALL plugins from local marketplace caches (for marketplaces not in defaults)
248
+ const localMarketplaces = await getLocalMarketplaces();
249
+
250
+ for (const [mpName, localMp] of localMarketplaces) {
251
+ // Skip if already fetched from defaults
252
+ if (marketplaceNames.has(mpName)) continue;
253
+
254
+ // Add ALL plugins from this local marketplace cache
255
+ for (const localPlugin of localMp.plugins) {
256
+ const pluginId = `${localPlugin.name}@${mpName}`;
257
+ if (seenPluginIds.has(pluginId)) continue;
258
+
259
+ const installedVersion = installedVersions[pluginId];
260
+ const isEnabled = enabledPlugins[pluginId] === true;
261
+ const scopeStatus = buildScopeStatus(pluginId);
262
+
263
+ seenPluginIds.add(pluginId);
264
+ plugins.push({
265
+ id: pluginId,
266
+ name: localPlugin.name,
267
+ version: localPlugin.version,
268
+ description: localPlugin.description || "",
269
+ marketplace: mpName,
270
+ marketplaceDisplay: localMp.name || formatMarketplaceName(mpName),
271
+ enabled: isEnabled,
272
+ installedVersion: installedVersion,
273
+ hasUpdate: installedVersion
274
+ ? compareVersions(localPlugin.version, installedVersion) > 0
275
+ : false,
276
+ ...scopeStatus,
277
+ category: localPlugin.category,
278
+ author: localPlugin.author,
279
+ agents: localPlugin.agents,
280
+ commands: localPlugin.commands,
281
+ skills: localPlugin.skills,
282
+ mcpServers: localPlugin.mcpServers,
283
+ lspServers: localPlugin.lspServers,
284
+ });
285
+ }
286
+ }
287
+
288
+ // Add orphaned plugins (enabled/installed but not in any cache)
289
+ const allPluginIds = new Set([
290
+ ...Object.keys(enabledPlugins),
291
+ ...Object.keys(installedVersions),
292
+ ]);
293
+
294
+ for (const pluginId of allPluginIds) {
295
+ if (seenPluginIds.has(pluginId)) continue;
296
+
297
+ const parsed = parsePluginId(pluginId);
298
+ if (!parsed) continue;
299
+
300
+ const { pluginName, marketplace: mpName } = parsed;
301
+ const installedVersion = installedVersions[pluginId];
302
+ const isEnabled = enabledPlugins[pluginId] === true;
303
+ const scopeStatus = buildScopeStatus(pluginId);
304
+
305
+ // Try to get plugin info from local marketplace cache (fallback)
306
+ const localMp = localMarketplaces.get(mpName);
307
+ const localPlugin = localMp?.plugins.find((p) => p.name === pluginName);
308
+
309
+ const latestVersion = localPlugin?.version || installedVersion || "unknown";
310
+ const description = localPlugin?.description || "Installed plugin";
311
+ const hasUpdate =
312
+ installedVersion && localPlugin?.version
313
+ ? compareVersions(localPlugin.version, installedVersion) > 0
314
+ : false;
315
+
316
+ plugins.push({
317
+ id: pluginId,
318
+ name: pluginName,
319
+ version: latestVersion,
320
+ description,
321
+ marketplace: mpName,
322
+ marketplaceDisplay: localMp?.name || formatMarketplaceName(mpName),
323
+ enabled: isEnabled,
324
+ installedVersion: installedVersion,
325
+ hasUpdate,
326
+ ...scopeStatus,
327
+ });
328
+ }
329
+
330
+ return plugins;
331
+ }
332
+
333
+ export async function getGlobalAvailablePlugins(): Promise<PluginInfo[]> {
334
+ const configuredMarketplaces = await getGlobalConfiguredMarketplaces();
335
+ const enabledPlugins = await getGlobalEnabledPlugins();
336
+ const installedVersions = await getGlobalInstalledPluginVersions();
337
+
338
+ // Fetch all scopes for per-scope status display
339
+ const userEnabledPlugins = await getGlobalEnabledPlugins();
340
+ const userInstalledVersions = await getGlobalInstalledPluginVersions();
341
+ // Also fetch project and local scope for complete status display
342
+ const projectEnabledPlugins = await getEnabledPlugins();
343
+ const projectInstalledVersions = await getInstalledPluginVersions();
344
+ const localEnabledPlugins = await getLocalEnabledPlugins();
345
+ const localInstalledVersions = await getLocalInstalledPluginVersions();
346
+
347
+ const plugins: PluginInfo[] = [];
348
+ const seenPluginIds = new Set<string>();
349
+
350
+ // Helper to build scope status (show all scopes)
351
+ const buildScopeStatus = (pluginId: string) => ({
352
+ userScope:
353
+ userEnabledPlugins[pluginId] !== undefined
354
+ ? {
355
+ enabled: userEnabledPlugins[pluginId],
356
+ version: userInstalledVersions[pluginId],
357
+ }
358
+ : undefined,
359
+ projectScope:
360
+ projectEnabledPlugins[pluginId] !== undefined
361
+ ? {
362
+ enabled: projectEnabledPlugins[pluginId],
363
+ version: projectInstalledVersions[pluginId],
364
+ }
365
+ : undefined,
366
+ localScope:
367
+ localEnabledPlugins[pluginId] !== undefined
368
+ ? {
369
+ enabled: localEnabledPlugins[pluginId],
370
+ version: localInstalledVersions[pluginId],
371
+ }
372
+ : undefined,
373
+ });
374
+
375
+ // Get all marketplace names (configured + official/featured defaults)
376
+ // Always include official and featured marketplaces so users can browse them
377
+ const marketplaceNames = new Set<string>();
378
+ for (const mp of defaultMarketplaces) {
379
+ if (configuredMarketplaces[mp.name] || mp.official || mp.featured) {
380
+ marketplaceNames.add(mp.name);
381
+ }
382
+ }
383
+
384
+ // Fetch plugins from each configured marketplace
385
+ for (const mpName of marketplaceNames) {
386
+ const marketplace = defaultMarketplaces.find((m) => m.name === mpName);
387
+ if (!marketplace) continue;
388
+
389
+ const marketplacePlugins = await fetchMarketplacePlugins(
390
+ mpName,
391
+ marketplace.source.repo,
392
+ );
393
+
394
+ for (const plugin of marketplacePlugins) {
395
+ const pluginId = `${plugin.name}@${mpName}`;
396
+ const installedVersion = installedVersions[pluginId];
397
+ const isEnabled = enabledPlugins[pluginId] === true;
398
+ const scopeStatus = buildScopeStatus(pluginId);
399
+
400
+ seenPluginIds.add(pluginId);
401
+ plugins.push({
402
+ id: pluginId,
403
+ name: plugin.name,
404
+ version: plugin.version,
405
+ description: plugin.description,
406
+ marketplace: mpName,
407
+ marketplaceDisplay: marketplace.displayName,
408
+ enabled: isEnabled,
409
+ installedVersion: installedVersion,
410
+ hasUpdate:
411
+ installedVersion && plugin.version
412
+ ? compareVersions(plugin.version, installedVersion) > 0
413
+ : false,
414
+ ...scopeStatus,
415
+ category: plugin.category,
416
+ author: plugin.author,
417
+ homepage: plugin.homepage,
418
+ tags: plugin.tags,
419
+ });
420
+ }
421
+ }
422
+
423
+ // Fetch ALL plugins from local marketplace caches (for marketplaces not in defaults)
424
+ const localMarketplaces = await getLocalMarketplaces();
425
+
426
+ for (const [mpName, localMp] of localMarketplaces) {
427
+ // Skip if already fetched from defaults
428
+ if (marketplaceNames.has(mpName)) continue;
429
+
430
+ // Add ALL plugins from this local marketplace cache
431
+ for (const localPlugin of localMp.plugins) {
432
+ const pluginId = `${localPlugin.name}@${mpName}`;
433
+ if (seenPluginIds.has(pluginId)) continue;
434
+
435
+ const installedVersion = installedVersions[pluginId];
436
+ const isEnabled = enabledPlugins[pluginId] === true;
437
+ const scopeStatus = buildScopeStatus(pluginId);
438
+
439
+ seenPluginIds.add(pluginId);
440
+ plugins.push({
441
+ id: pluginId,
442
+ name: localPlugin.name,
443
+ version: localPlugin.version,
444
+ description: localPlugin.description || "",
445
+ marketplace: mpName,
446
+ marketplaceDisplay: localMp.name || formatMarketplaceName(mpName),
447
+ enabled: isEnabled,
448
+ installedVersion: installedVersion,
449
+ hasUpdate: installedVersion
450
+ ? compareVersions(localPlugin.version, installedVersion) > 0
451
+ : false,
452
+ ...scopeStatus,
453
+ category: localPlugin.category,
454
+ author: localPlugin.author,
455
+ agents: localPlugin.agents,
456
+ commands: localPlugin.commands,
457
+ skills: localPlugin.skills,
458
+ mcpServers: localPlugin.mcpServers,
459
+ lspServers: localPlugin.lspServers,
460
+ });
461
+ }
462
+ }
463
+
464
+ // Add orphaned plugins (enabled/installed but not in any cache)
465
+ const allPluginIds = new Set([
466
+ ...Object.keys(enabledPlugins),
467
+ ...Object.keys(installedVersions),
468
+ ]);
469
+
470
+ for (const pluginId of allPluginIds) {
471
+ if (seenPluginIds.has(pluginId)) continue;
472
+
473
+ const parsed = parsePluginId(pluginId);
474
+ if (!parsed) continue;
475
+
476
+ const { pluginName, marketplace: mpName } = parsed;
477
+ const installedVersion = installedVersions[pluginId];
478
+ const isEnabled = enabledPlugins[pluginId] === true;
479
+ const scopeStatus = buildScopeStatus(pluginId);
480
+
481
+ // Try to get plugin info from local marketplace cache (fallback)
482
+ const localMp = localMarketplaces.get(mpName);
483
+ const localPlugin = localMp?.plugins.find((p) => p.name === pluginName);
484
+
485
+ const latestVersion = localPlugin?.version || installedVersion || "unknown";
486
+ const description = localPlugin?.description || "Installed plugin";
487
+ const hasUpdate =
488
+ installedVersion && localPlugin?.version
489
+ ? compareVersions(localPlugin.version, installedVersion) > 0
490
+ : false;
491
+
492
+ plugins.push({
493
+ id: pluginId,
494
+ name: pluginName,
495
+ version: latestVersion,
496
+ description,
497
+ marketplace: mpName,
498
+ marketplaceDisplay: localMp?.name || formatMarketplaceName(mpName),
499
+ enabled: isEnabled,
500
+ ...scopeStatus,
501
+ installedVersion: installedVersion,
502
+ hasUpdate,
503
+ });
504
+ }
505
+
506
+ return plugins;
507
+ }
508
+
509
+ // Simple version comparison (returns 1 if a > b, -1 if a < b, 0 if equal)
510
+ function compareVersions(
511
+ a: string | null | undefined,
512
+ b: string | null | undefined,
513
+ ): number {
514
+ if (!a || !b) return 0;
515
+ const partsA = a.replace(/^v/, "").split(".").map(Number);
516
+ const partsB = b.replace(/^v/, "").split(".").map(Number);
517
+
518
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
519
+ const numA = partsA[i] || 0;
520
+ const numB = partsB[i] || 0;
521
+ if (numA > numB) return 1;
522
+ if (numA < numB) return -1;
523
+ }
524
+ return 0;
525
+ }
526
+
527
+ // Get installed plugin versions from settings.json (shared, not secret)
528
+ async function getInstalledPluginVersions(
529
+ projectPath?: string,
530
+ ): Promise<Record<string, string>> {
531
+ const settings = await readSettings(projectPath);
532
+ return settings.installedPluginVersions || {};
533
+ }
534
+
535
+ // Save installed plugin version to settings.json
536
+ export async function saveInstalledPluginVersion(
537
+ pluginId: string,
538
+ version: string,
539
+ projectPath?: string,
540
+ ): Promise<void> {
541
+ const settings = await readSettings(projectPath);
542
+ settings.installedPluginVersions = settings.installedPluginVersions || {};
543
+ settings.installedPluginVersions[pluginId] = version;
544
+ await writeSettings(settings, projectPath);
545
+
546
+ // Update installed_plugins.json registry
547
+ await updateInstalledPluginsRegistry(
548
+ pluginId,
549
+ version,
550
+ "project",
551
+ projectPath ? path.resolve(projectPath) : undefined,
552
+ );
553
+ }
554
+
555
+ // Remove installed plugin version from settings.json
556
+ export async function removeInstalledPluginVersion(
557
+ pluginId: string,
558
+ projectPath?: string,
559
+ ): Promise<void> {
560
+ const settings = await readSettings(projectPath);
561
+ if (settings.installedPluginVersions) {
562
+ delete settings.installedPluginVersions[pluginId];
563
+ await writeSettings(settings, projectPath);
564
+ }
565
+
566
+ // Remove from installed_plugins.json registry
567
+ await removeFromInstalledPluginsRegistry(
568
+ pluginId,
569
+ "project",
570
+ projectPath ? path.resolve(projectPath) : undefined,
571
+ );
572
+ }
573
+
574
+ // Clear marketplace cache
575
+ export function clearMarketplaceCache(): void {
576
+ marketplaceCache.clear();
577
+ localMarketplacesPromise = null;
578
+ }
579
+
580
+ // Get local marketplaces (with Promise-based caching to prevent race conditions)
581
+ async function getLocalMarketplaces(): Promise<Map<string, LocalMarketplace>> {
582
+ if (localMarketplacesPromise === null) {
583
+ localMarketplacesPromise = scanLocalMarketplaces();
584
+ }
585
+ return localMarketplacesPromise;
586
+ }
587
+
588
+ // Export local marketplaces for use in other modules
589
+ export async function getLocalMarketplacesInfo(): Promise<
590
+ Map<string, LocalMarketplace>
591
+ > {
592
+ return getLocalMarketplaces();
593
+ }
594
+
595
+ export interface RefreshAndRepairResult {
596
+ refresh: never[];
597
+ repair: RepairMarketplaceResult[];
598
+ }
599
+
600
+ /**
601
+ * Refresh claudeup's internal cache
602
+ * Note: Marketplace updates should be done via Claude Code's /plugin marketplace update command
603
+ */
604
+ export async function refreshAllMarketplaces(
605
+ onProgress?: ProgressCallback,
606
+ ): Promise<RefreshAndRepairResult> {
607
+ onProgress?.({ current: 1, total: 1, name: "Clearing cache..." });
608
+
609
+ // Clear all caches to force fresh data
610
+ clearMarketplaceCache();
611
+
612
+ // Auto-repair plugin.json files with missing agents/commands/skills
613
+ const repairResults = await repairAllMarketplaces();
614
+
615
+ return {
616
+ refresh: [],
617
+ repair: repairResults,
618
+ };
619
+ }
620
+
621
+ export interface MarketplaceUpdateInfo {
622
+ name: string;
623
+ hasUpdate: boolean;
624
+ latestCommit?: string;
625
+ currentCommit?: string;
626
+ }
627
+
628
+ /**
629
+ * Check if marketplaces have updates available on GitHub
630
+ * Compares local git HEAD with remote HEAD
631
+ */
632
+ export async function checkMarketplaceUpdates(): Promise<
633
+ MarketplaceUpdateInfo[]
634
+ > {
635
+ const results: MarketplaceUpdateInfo[] = [];
636
+ const localMarketplaces = await getLocalMarketplaces();
637
+
638
+ for (const [name, marketplace] of localMarketplaces) {
639
+ if (!marketplace.gitRepo) continue;
640
+
641
+ try {
642
+ const marketplacePath = path.join(
643
+ os.homedir(),
644
+ ".claude",
645
+ "plugins",
646
+ "marketplaces",
647
+ name,
648
+ );
649
+
650
+ // Get current HEAD from local repo
651
+ let currentHead: string;
652
+ try {
653
+ currentHead = execSync("git rev-parse HEAD", {
654
+ cwd: marketplacePath,
655
+ encoding: "utf-8",
656
+ timeout: 5000,
657
+ }).trim();
658
+ } catch {
659
+ continue; // Skip if can't get HEAD
660
+ }
661
+
662
+ // Fetch latest commit from GitHub API
663
+ const apiUrl = `https://api.github.com/repos/${marketplace.gitRepo}/commits/main`;
664
+ const response = await fetch(apiUrl, {
665
+ signal: AbortSignal.timeout(10000),
666
+ headers: {
667
+ Accept: "application/vnd.github.v3+json",
668
+ },
669
+ });
670
+
671
+ if (response.ok) {
672
+ const data = (await response.json()) as { sha: string };
673
+ const latestCommit = data.sha;
674
+
675
+ results.push({
676
+ name,
677
+ hasUpdate: currentHead !== latestCommit,
678
+ currentCommit: currentHead.substring(0, 7),
679
+ latestCommit: latestCommit.substring(0, 7),
680
+ });
681
+ } else {
682
+ results.push({ name, hasUpdate: false });
683
+ }
684
+ } catch {
685
+ results.push({ name, hasUpdate: false });
686
+ }
687
+ }
688
+
689
+ return results;
690
+ }
691
+
692
+ // Re-export types for consumers
693
+ export type { ProgressCallback };