claudeup 1.8.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 (301) 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/{dist → src}/prerunner/index.js +31 -41
  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 +0 -1
  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.map +0 -1
  137. package/dist/services/claude-runner.d.ts +0 -7
  138. package/dist/services/claude-runner.d.ts.map +0 -1
  139. package/dist/services/claude-runner.js.map +0 -1
  140. package/dist/services/claude-settings.d.ts +0 -73
  141. package/dist/services/claude-settings.d.ts.map +0 -1
  142. package/dist/services/claude-settings.js.map +0 -1
  143. package/dist/services/local-marketplace.d.ts +0 -111
  144. package/dist/services/local-marketplace.d.ts.map +0 -1
  145. package/dist/services/local-marketplace.js +0 -599
  146. package/dist/services/local-marketplace.js.map +0 -1
  147. package/dist/services/mcp-registry.d.ts +0 -10
  148. package/dist/services/mcp-registry.d.ts.map +0 -1
  149. package/dist/services/mcp-registry.js.map +0 -1
  150. package/dist/services/plugin-manager.d.ts +0 -65
  151. package/dist/services/plugin-manager.d.ts.map +0 -1
  152. package/dist/services/plugin-manager.js.map +0 -1
  153. package/dist/services/plugin-mcp-config.d.ts +0 -52
  154. package/dist/services/plugin-mcp-config.d.ts.map +0 -1
  155. package/dist/services/plugin-mcp-config.js.map +0 -1
  156. package/dist/services/update-cache.d.ts +0 -21
  157. package/dist/services/update-cache.d.ts.map +0 -1
  158. package/dist/services/update-cache.js.map +0 -1
  159. package/dist/services/version-check.d.ts +0 -20
  160. package/dist/services/version-check.d.ts.map +0 -1
  161. package/dist/services/version-check.js.map +0 -1
  162. package/dist/types/index.d.ts +0 -105
  163. package/dist/types/index.d.ts.map +0 -1
  164. package/dist/types/index.js +0 -2
  165. package/dist/types/index.js.map +0 -1
  166. package/dist/ui/InkApp.d.ts +0 -5
  167. package/dist/ui/InkApp.d.ts.map +0 -1
  168. package/dist/ui/InkApp.js +0 -188
  169. package/dist/ui/InkApp.js.map +0 -1
  170. package/dist/ui/components/CategoryHeader.d.ts +0 -16
  171. package/dist/ui/components/CategoryHeader.d.ts.map +0 -1
  172. package/dist/ui/components/CategoryHeader.js +0 -11
  173. package/dist/ui/components/CategoryHeader.js.map +0 -1
  174. package/dist/ui/components/ScrollableList.d.ts +0 -16
  175. package/dist/ui/components/ScrollableList.d.ts.map +0 -1
  176. package/dist/ui/components/ScrollableList.js.map +0 -1
  177. package/dist/ui/components/SearchInput.d.ts +0 -18
  178. package/dist/ui/components/SearchInput.d.ts.map +0 -1
  179. package/dist/ui/components/SearchInput.js +0 -30
  180. package/dist/ui/components/SearchInput.js.map +0 -1
  181. package/dist/ui/components/TabBar.d.ts +0 -8
  182. package/dist/ui/components/TabBar.d.ts.map +0 -1
  183. package/dist/ui/components/TabBar.js +0 -18
  184. package/dist/ui/components/TabBar.js.map +0 -1
  185. package/dist/ui/components/layout/Footer.d.ts +0 -14
  186. package/dist/ui/components/layout/Footer.d.ts.map +0 -1
  187. package/dist/ui/components/layout/Footer.js +0 -23
  188. package/dist/ui/components/layout/Footer.js.map +0 -1
  189. package/dist/ui/components/layout/Header.d.ts +0 -4
  190. package/dist/ui/components/layout/Header.d.ts.map +0 -1
  191. package/dist/ui/components/layout/Header.js +0 -25
  192. package/dist/ui/components/layout/Header.js.map +0 -1
  193. package/dist/ui/components/layout/Panel.d.ts +0 -22
  194. package/dist/ui/components/layout/Panel.d.ts.map +0 -1
  195. package/dist/ui/components/layout/Panel.js +0 -8
  196. package/dist/ui/components/layout/Panel.js.map +0 -1
  197. package/dist/ui/components/layout/ProgressBar.d.ts +0 -12
  198. package/dist/ui/components/layout/ProgressBar.d.ts.map +0 -1
  199. package/dist/ui/components/layout/ProgressBar.js +0 -16
  200. package/dist/ui/components/layout/ProgressBar.js.map +0 -1
  201. package/dist/ui/components/layout/ScopeTabs.d.ts +0 -12
  202. package/dist/ui/components/layout/ScopeTabs.d.ts.map +0 -1
  203. package/dist/ui/components/layout/ScopeTabs.js +0 -8
  204. package/dist/ui/components/layout/ScopeTabs.js.map +0 -1
  205. package/dist/ui/components/layout/ScreenLayout.d.ts +0 -30
  206. package/dist/ui/components/layout/ScreenLayout.d.ts.map +0 -1
  207. package/dist/ui/components/layout/ScreenLayout.js +0 -23
  208. package/dist/ui/components/layout/ScreenLayout.js.map +0 -1
  209. package/dist/ui/components/layout/index.d.ts +0 -7
  210. package/dist/ui/components/layout/index.d.ts.map +0 -1
  211. package/dist/ui/components/layout/index.js +0 -7
  212. package/dist/ui/components/layout/index.js.map +0 -1
  213. package/dist/ui/components/modals/ConfirmModal.d.ts +0 -14
  214. package/dist/ui/components/modals/ConfirmModal.d.ts.map +0 -1
  215. package/dist/ui/components/modals/ConfirmModal.js +0 -15
  216. package/dist/ui/components/modals/ConfirmModal.js.map +0 -1
  217. package/dist/ui/components/modals/InputModal.d.ts +0 -16
  218. package/dist/ui/components/modals/InputModal.d.ts.map +0 -1
  219. package/dist/ui/components/modals/InputModal.js +0 -23
  220. package/dist/ui/components/modals/InputModal.js.map +0 -1
  221. package/dist/ui/components/modals/LoadingModal.d.ts +0 -8
  222. package/dist/ui/components/modals/LoadingModal.d.ts.map +0 -1
  223. package/dist/ui/components/modals/LoadingModal.js +0 -8
  224. package/dist/ui/components/modals/LoadingModal.js.map +0 -1
  225. package/dist/ui/components/modals/MessageModal.d.ts +0 -14
  226. package/dist/ui/components/modals/MessageModal.d.ts.map +0 -1
  227. package/dist/ui/components/modals/MessageModal.js +0 -17
  228. package/dist/ui/components/modals/MessageModal.js.map +0 -1
  229. package/dist/ui/components/modals/ModalContainer.d.ts +0 -7
  230. package/dist/ui/components/modals/ModalContainer.d.ts.map +0 -1
  231. package/dist/ui/components/modals/ModalContainer.js +0 -38
  232. package/dist/ui/components/modals/ModalContainer.js.map +0 -1
  233. package/dist/ui/components/modals/SelectModal.d.ts +0 -17
  234. package/dist/ui/components/modals/SelectModal.d.ts.map +0 -1
  235. package/dist/ui/components/modals/SelectModal.js +0 -33
  236. package/dist/ui/components/modals/SelectModal.js.map +0 -1
  237. package/dist/ui/components/modals/index.d.ts +0 -7
  238. package/dist/ui/components/modals/index.d.ts.map +0 -1
  239. package/dist/ui/components/modals/index.js +0 -7
  240. package/dist/ui/components/modals/index.js.map +0 -1
  241. package/dist/ui/hooks/index.d.ts +0 -3
  242. package/dist/ui/hooks/index.d.ts.map +0 -1
  243. package/dist/ui/hooks/index.js +0 -3
  244. package/dist/ui/hooks/index.js.map +0 -1
  245. package/dist/ui/hooks/useAsyncData.d.ts +0 -40
  246. package/dist/ui/hooks/useAsyncData.d.ts.map +0 -1
  247. package/dist/ui/hooks/useAsyncData.js.map +0 -1
  248. package/dist/ui/hooks/useKeyboardNavigation.d.ts +0 -27
  249. package/dist/ui/hooks/useKeyboardNavigation.d.ts.map +0 -1
  250. package/dist/ui/hooks/useKeyboardNavigation.js +0 -82
  251. package/dist/ui/hooks/useKeyboardNavigation.js.map +0 -1
  252. package/dist/ui/screens/CliToolsScreen.d.ts +0 -4
  253. package/dist/ui/screens/CliToolsScreen.d.ts.map +0 -1
  254. package/dist/ui/screens/CliToolsScreen.js.map +0 -1
  255. package/dist/ui/screens/EnvVarsScreen.d.ts +0 -4
  256. package/dist/ui/screens/EnvVarsScreen.d.ts.map +0 -1
  257. package/dist/ui/screens/EnvVarsScreen.js +0 -145
  258. package/dist/ui/screens/EnvVarsScreen.js.map +0 -1
  259. package/dist/ui/screens/McpRegistryScreen.d.ts +0 -4
  260. package/dist/ui/screens/McpRegistryScreen.d.ts.map +0 -1
  261. package/dist/ui/screens/McpRegistryScreen.js.map +0 -1
  262. package/dist/ui/screens/McpScreen.d.ts +0 -4
  263. package/dist/ui/screens/McpScreen.d.ts.map +0 -1
  264. package/dist/ui/screens/McpScreen.js.map +0 -1
  265. package/dist/ui/screens/ModelSelectorScreen.d.ts +0 -4
  266. package/dist/ui/screens/ModelSelectorScreen.d.ts.map +0 -1
  267. package/dist/ui/screens/ModelSelectorScreen.js +0 -143
  268. package/dist/ui/screens/ModelSelectorScreen.js.map +0 -1
  269. package/dist/ui/screens/PluginsScreen.d.ts +0 -4
  270. package/dist/ui/screens/PluginsScreen.d.ts.map +0 -1
  271. package/dist/ui/screens/PluginsScreen.js.map +0 -1
  272. package/dist/ui/screens/StatusLineScreen.d.ts +0 -4
  273. package/dist/ui/screens/StatusLineScreen.d.ts.map +0 -1
  274. package/dist/ui/screens/StatusLineScreen.js +0 -197
  275. package/dist/ui/screens/StatusLineScreen.js.map +0 -1
  276. package/dist/ui/screens/index.d.ts +0 -8
  277. package/dist/ui/screens/index.d.ts.map +0 -1
  278. package/dist/ui/screens/index.js +0 -8
  279. package/dist/ui/screens/index.js.map +0 -1
  280. package/dist/ui/state/AppContext.d.ts +0 -40
  281. package/dist/ui/state/AppContext.d.ts.map +0 -1
  282. package/dist/ui/state/AppContext.js.map +0 -1
  283. package/dist/ui/state/DimensionsContext.d.ts +0 -27
  284. package/dist/ui/state/DimensionsContext.d.ts.map +0 -1
  285. package/dist/ui/state/DimensionsContext.js.map +0 -1
  286. package/dist/ui/state/reducer.d.ts +0 -4
  287. package/dist/ui/state/reducer.d.ts.map +0 -1
  288. package/dist/ui/state/reducer.js.map +0 -1
  289. package/dist/ui/state/types.d.ts +0 -266
  290. package/dist/ui/state/types.d.ts.map +0 -1
  291. package/dist/ui/state/types.js +0 -2
  292. package/dist/ui/state/types.js.map +0 -1
  293. package/dist/utils/command-utils.d.ts +0 -8
  294. package/dist/utils/command-utils.d.ts.map +0 -1
  295. package/dist/utils/command-utils.js.map +0 -1
  296. package/dist/utils/fuzzy-search.d.ts +0 -33
  297. package/dist/utils/fuzzy-search.d.ts.map +0 -1
  298. package/dist/utils/fuzzy-search.js.map +0 -1
  299. package/dist/utils/string-utils.d.ts +0 -24
  300. package/dist/utils/string-utils.d.ts.map +0 -1
  301. package/dist/utils/string-utils.js.map +0 -1
@@ -0,0 +1,489 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { execSync } from "node:child_process";
5
+
6
+ export interface LocalMarketplacePlugin {
7
+ name: string;
8
+ version: string;
9
+ description: string;
10
+ source?: string;
11
+ category?: string;
12
+ author?: { name: string; email?: string };
13
+ // Extended info from plugin.json or marketplace.json
14
+ strict?: boolean;
15
+ lspServers?: Record<string, unknown>;
16
+ agents?: string[];
17
+ commands?: string[];
18
+ skills?: string[];
19
+ mcpServers?: string[];
20
+ }
21
+
22
+ export interface LocalMarketplace {
23
+ name: string;
24
+ description: string;
25
+ plugins: LocalMarketplacePlugin[];
26
+ gitRepo?: string; // Extracted from git remote
27
+ }
28
+
29
+ const CLAUDE_PLUGINS_DIR = path.join(
30
+ os.homedir(),
31
+ ".claude",
32
+ "plugins",
33
+ "marketplaces",
34
+ );
35
+ const KNOWN_MARKETPLACES_FILE = path.join(
36
+ os.homedir(),
37
+ ".claude",
38
+ "plugins",
39
+ "known_marketplaces.json",
40
+ );
41
+
42
+ interface KnownMarketplaceEntry {
43
+ source: { source: string; url?: string; repo?: string };
44
+ installLocation: string;
45
+ lastUpdated: string;
46
+ }
47
+
48
+ type KnownMarketplaces = Record<string, KnownMarketplaceEntry>;
49
+
50
+ /**
51
+ * Read known_marketplaces.json (Claude Code's internal marketplace tracking)
52
+ */
53
+ async function readKnownMarketplaces(): Promise<KnownMarketplaces> {
54
+ try {
55
+ if (await fs.pathExists(KNOWN_MARKETPLACES_FILE)) {
56
+ return await fs.readJson(KNOWN_MARKETPLACES_FILE);
57
+ }
58
+ } catch {
59
+ // Return empty if can't read
60
+ }
61
+ return {};
62
+ }
63
+
64
+ /**
65
+ * Get git remote URL from a marketplace directory
66
+ */
67
+ function getGitRemote(marketplacePath: string): string | undefined {
68
+ try {
69
+ const gitDir = path.join(marketplacePath, ".git");
70
+ if (!fs.existsSync(gitDir)) return undefined;
71
+
72
+ const result = execSync("git remote get-url origin", {
73
+ cwd: marketplacePath,
74
+ encoding: "utf-8",
75
+ timeout: 5000,
76
+ }).trim();
77
+
78
+ // Convert SSH URL to HTTPS format: git@github.com:user/repo.git -> user/repo
79
+ if (result.startsWith("git@github.com:")) {
80
+ return result.replace("git@github.com:", "").replace(/\.git$/, "");
81
+ }
82
+ // HTTPS URL: https://github.com/user/repo -> user/repo
83
+ if (result.includes("github.com")) {
84
+ const match = result.match(/github\.com[/:]([^/]+\/[^/\s.]+)/);
85
+ if (match) return match[1].replace(/\.git$/, "");
86
+ }
87
+ return undefined;
88
+ } catch {
89
+ return undefined;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Scan a single marketplace directory and return marketplace info
95
+ */
96
+ async function scanSingleMarketplace(
97
+ marketplacePath: string,
98
+ marketplaceName: string,
99
+ ): Promise<LocalMarketplace | null> {
100
+ const manifestPath = path.join(
101
+ marketplacePath,
102
+ ".claude-plugin",
103
+ "marketplace.json",
104
+ );
105
+
106
+ if (!(await fs.pathExists(manifestPath))) return null;
107
+
108
+ try {
109
+ const manifest = await fs.readJson(manifestPath);
110
+ const gitRepo = getGitRemote(marketplacePath);
111
+
112
+ const plugins: LocalMarketplacePlugin[] = [];
113
+ const validManifestPlugins: typeof manifest.plugins = [];
114
+ let manifestModified = false;
115
+
116
+ if (manifest.plugins && Array.isArray(manifest.plugins)) {
117
+ for (const plugin of manifest.plugins) {
118
+ // Try to scan plugin directory for component counts
119
+ let agents: string[] = [];
120
+ let commands: string[] = [];
121
+ let skills: string[] = [];
122
+ let mcpServers: string[] = [];
123
+
124
+ // Handle both string sources (local paths) and object sources (remote URLs)
125
+ const sourceStr =
126
+ typeof plugin.source === "string" ? plugin.source : null;
127
+
128
+ if (sourceStr) {
129
+ const pluginPath = path.join(
130
+ marketplacePath,
131
+ sourceStr.replace("./", ""),
132
+ );
133
+
134
+ // Remove plugins whose source directory doesn't exist
135
+ if (!(await fs.pathExists(pluginPath))) {
136
+ manifestModified = true;
137
+ continue;
138
+ }
139
+
140
+ try {
141
+ // Scan for agents
142
+ const agentsDir = path.join(pluginPath, "agents");
143
+ if (await fs.pathExists(agentsDir)) {
144
+ const agentFiles = await fs.readdir(agentsDir);
145
+ agents = agentFiles
146
+ .filter((f) => f.endsWith(".md"))
147
+ .map((f) => f.replace(".md", ""));
148
+ }
149
+ // Scan for commands
150
+ const commandsDir = path.join(pluginPath, "commands");
151
+ if (await fs.pathExists(commandsDir)) {
152
+ const cmdFiles = await fs.readdir(commandsDir);
153
+ commands = cmdFiles
154
+ .filter((f) => f.endsWith(".md"))
155
+ .map((f) => f.replace(".md", ""));
156
+ }
157
+ // Scan for skills
158
+ const skillsDir = path.join(pluginPath, "skills");
159
+ if (await fs.pathExists(skillsDir)) {
160
+ const skillFiles = await fs.readdir(skillsDir);
161
+ skills = skillFiles.filter(
162
+ (f) =>
163
+ f.endsWith(".md") ||
164
+ fs.statSync(path.join(skillsDir, f)).isDirectory(),
165
+ );
166
+ }
167
+ // Scan for MCP servers
168
+ const mcpDir = path.join(pluginPath, "mcp-servers");
169
+ if (await fs.pathExists(mcpDir)) {
170
+ const mcpFiles = await fs.readdir(mcpDir);
171
+ mcpServers = mcpFiles
172
+ .filter((f) => f.endsWith(".json"))
173
+ .map((f) => f.replace(".json", ""));
174
+ }
175
+ } catch {
176
+ // Ignore scan errors
177
+ }
178
+ }
179
+
180
+ validManifestPlugins.push(plugin);
181
+ plugins.push({
182
+ name: plugin.name,
183
+ version: plugin.version || "0.0.0",
184
+ description: plugin.description || "",
185
+ source: plugin.source,
186
+ category: plugin.category,
187
+ author: plugin.author,
188
+ strict: plugin.strict,
189
+ lspServers: plugin.lspServers,
190
+ agents,
191
+ commands,
192
+ skills,
193
+ mcpServers,
194
+ });
195
+ }
196
+ }
197
+
198
+ // Update marketplace.json if we removed invalid plugins
199
+ if (manifestModified) {
200
+ manifest.plugins = validManifestPlugins;
201
+ await fs.writeJson(manifestPath, manifest, { spaces: 2 });
202
+ }
203
+
204
+ return {
205
+ name: manifest.name || marketplaceName,
206
+ description: manifest.description || manifest.metadata?.description || "",
207
+ plugins,
208
+ gitRepo,
209
+ };
210
+ } catch {
211
+ return null;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Scan local marketplace cache and return marketplace info
217
+ * Includes both:
218
+ * - Marketplaces in ~/.claude/plugins/marketplaces/
219
+ * - Directory-based marketplaces from known_marketplaces.json
220
+ */
221
+ export async function scanLocalMarketplaces(): Promise<
222
+ Map<string, LocalMarketplace>
223
+ > {
224
+ const marketplaces = new Map<string, LocalMarketplace>();
225
+
226
+ // 1. Scan marketplaces from the standard directory
227
+ if (await fs.pathExists(CLAUDE_PLUGINS_DIR)) {
228
+ try {
229
+ const entries = await fs.readdir(CLAUDE_PLUGINS_DIR, {
230
+ withFileTypes: true,
231
+ });
232
+
233
+ for (const entry of entries) {
234
+ if (!entry.isDirectory()) continue;
235
+
236
+ const marketplacePath = path.join(CLAUDE_PLUGINS_DIR, entry.name);
237
+ const marketplace = await scanSingleMarketplace(
238
+ marketplacePath,
239
+ entry.name,
240
+ );
241
+ if (marketplace) {
242
+ marketplaces.set(entry.name, marketplace);
243
+ }
244
+ }
245
+ } catch {
246
+ // Ignore read errors
247
+ }
248
+ }
249
+
250
+ // 2. Add directory-based marketplaces from known_marketplaces.json
251
+ try {
252
+ const known = await readKnownMarketplaces();
253
+ for (const [name, entry] of Object.entries(known)) {
254
+ // Only add if it's a directory source AND not already in the map
255
+ if (
256
+ entry.source.source === "directory" &&
257
+ entry.installLocation &&
258
+ !marketplaces.has(name)
259
+ ) {
260
+ if (await fs.pathExists(entry.installLocation)) {
261
+ const marketplace = await scanSingleMarketplace(
262
+ entry.installLocation,
263
+ name,
264
+ );
265
+ if (marketplace) {
266
+ marketplaces.set(name, marketplace);
267
+ }
268
+ }
269
+ }
270
+ }
271
+ } catch {
272
+ // Ignore errors reading known_marketplaces
273
+ }
274
+
275
+ return marketplaces;
276
+ }
277
+
278
+ /**
279
+ * Get a specific local marketplace by name
280
+ */
281
+ export async function getLocalMarketplace(
282
+ name: string,
283
+ ): Promise<LocalMarketplace | undefined> {
284
+ const marketplaces = await scanLocalMarketplaces();
285
+ return marketplaces.get(name);
286
+ }
287
+
288
+ /**
289
+ * Check if a marketplace exists in local cache
290
+ */
291
+ export async function hasLocalMarketplace(name: string): Promise<boolean> {
292
+ const marketplacePath = path.join(CLAUDE_PLUGINS_DIR, name);
293
+ const manifestPath = path.join(
294
+ marketplacePath,
295
+ ".claude-plugin",
296
+ "marketplace.json",
297
+ );
298
+ return fs.pathExists(manifestPath);
299
+ }
300
+
301
+ export interface RefreshResult {
302
+ name: string;
303
+ success: boolean;
304
+ updated: boolean;
305
+ error?: string;
306
+ }
307
+
308
+ export interface RefreshProgress {
309
+ current: number;
310
+ total: number;
311
+ name: string;
312
+ }
313
+
314
+ export type ProgressCallback = (progress: RefreshProgress) => void;
315
+
316
+ export interface RepairResult {
317
+ repaired: boolean;
318
+ pluginName: string;
319
+ added: {
320
+ agents: string[];
321
+ commands: string[];
322
+ skills: string[];
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Repair a plugin's plugin.json by adding missing agents/commands/skills arrays
328
+ * based on what files exist on disk.
329
+ *
330
+ * This fixes plugins where the author forgot to add the arrays to plugin.json,
331
+ * causing Claude Code to not load the agents/commands even though files exist.
332
+ */
333
+ export async function repairPluginJson(
334
+ pluginPath: string,
335
+ ): Promise<RepairResult> {
336
+ const pluginJsonPath = path.join(pluginPath, ".claude-plugin", "plugin.json");
337
+ const pluginName = path.basename(pluginPath);
338
+
339
+ const result: RepairResult = {
340
+ repaired: false,
341
+ pluginName,
342
+ added: { agents: [], commands: [], skills: [] },
343
+ };
344
+
345
+ if (!(await fs.pathExists(pluginJsonPath))) {
346
+ return result;
347
+ }
348
+
349
+ try {
350
+ const pluginJson = await fs.readJson(pluginJsonPath);
351
+ let modified = false;
352
+
353
+ // Scan and add agents if array is missing
354
+ const agentsDir = path.join(pluginPath, "agents");
355
+ if (await fs.pathExists(agentsDir)) {
356
+ const files = (await fs.readdir(agentsDir))
357
+ .filter((f) => f.endsWith(".md"))
358
+ .map((f) => `./agents/${f}`);
359
+
360
+ if (files.length > 0 && !pluginJson.agents) {
361
+ pluginJson.agents = files;
362
+ result.added.agents = files;
363
+ modified = true;
364
+ }
365
+ }
366
+
367
+ // Scan and add commands if array is missing
368
+ const commandsDir = path.join(pluginPath, "commands");
369
+ if (await fs.pathExists(commandsDir)) {
370
+ const files = (await fs.readdir(commandsDir))
371
+ .filter((f) => f.endsWith(".md"))
372
+ .map((f) => `./commands/${f}`);
373
+
374
+ if (files.length > 0 && !pluginJson.commands) {
375
+ pluginJson.commands = files;
376
+ result.added.commands = files;
377
+ modified = true;
378
+ }
379
+ }
380
+
381
+ // Scan and add skills if array is missing
382
+ const skillsDir = path.join(pluginPath, "skills");
383
+ if (await fs.pathExists(skillsDir)) {
384
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true });
385
+ const skills = entries
386
+ .filter((e) => e.isDirectory() || e.name.endsWith(".md"))
387
+ .map((e) => `./skills/${e.name}`);
388
+
389
+ if (skills.length > 0 && !pluginJson.skills) {
390
+ pluginJson.skills = skills;
391
+ result.added.skills = skills;
392
+ modified = true;
393
+ }
394
+ }
395
+
396
+ if (modified) {
397
+ await fs.writeJson(pluginJsonPath, pluginJson, { spaces: 2 });
398
+ result.repaired = true;
399
+ }
400
+ } catch {
401
+ // Ignore repair errors for individual plugins
402
+ }
403
+
404
+ return result;
405
+ }
406
+
407
+ export interface RepairMarketplaceResult {
408
+ marketplace: string;
409
+ repaired: RepairResult[];
410
+ }
411
+
412
+ /**
413
+ * Repair all plugins in a marketplace
414
+ */
415
+ export async function repairMarketplacePlugins(
416
+ marketplaceName: string,
417
+ ): Promise<RepairMarketplaceResult> {
418
+ const result: RepairMarketplaceResult = {
419
+ marketplace: marketplaceName,
420
+ repaired: [],
421
+ };
422
+
423
+ const marketplacePath = path.join(CLAUDE_PLUGINS_DIR, marketplaceName);
424
+ const manifestPath = path.join(
425
+ marketplacePath,
426
+ ".claude-plugin",
427
+ "marketplace.json",
428
+ );
429
+
430
+ if (!(await fs.pathExists(manifestPath))) {
431
+ return result;
432
+ }
433
+
434
+ try {
435
+ const manifest = await fs.readJson(manifestPath);
436
+
437
+ if (manifest.plugins && Array.isArray(manifest.plugins)) {
438
+ for (const plugin of manifest.plugins) {
439
+ if (plugin.source) {
440
+ const pluginPath = path.join(
441
+ marketplacePath,
442
+ plugin.source.replace("./", ""),
443
+ );
444
+ const repairResult = await repairPluginJson(pluginPath);
445
+
446
+ if (repairResult.repaired) {
447
+ result.repaired.push(repairResult);
448
+ }
449
+ }
450
+ }
451
+ }
452
+ } catch {
453
+ // Ignore errors
454
+ }
455
+
456
+ return result;
457
+ }
458
+
459
+ /**
460
+ * Repair all plugins in all marketplaces
461
+ */
462
+ export async function repairAllMarketplaces(): Promise<
463
+ RepairMarketplaceResult[]
464
+ > {
465
+ const results: RepairMarketplaceResult[] = [];
466
+
467
+ if (!(await fs.pathExists(CLAUDE_PLUGINS_DIR))) {
468
+ return results;
469
+ }
470
+
471
+ try {
472
+ const entries = await fs.readdir(CLAUDE_PLUGINS_DIR, {
473
+ withFileTypes: true,
474
+ });
475
+
476
+ for (const entry of entries) {
477
+ if (!entry.isDirectory()) continue;
478
+
479
+ const repairResult = await repairMarketplacePlugins(entry.name);
480
+ if (repairResult.repaired.length > 0) {
481
+ results.push(repairResult);
482
+ }
483
+ }
484
+ } catch {
485
+ // Return empty on error
486
+ }
487
+
488
+ return results;
489
+ }
@@ -1,18 +1,18 @@
1
1
  // Official Model Context Protocol Registry
2
2
  // API Docs: https://registry.modelcontextprotocol.io
3
- const MCP_REGISTRY_API = 'https://registry.modelcontextprotocol.io/v0';
3
+ const MCP_REGISTRY_API = "https://registry.modelcontextprotocol.io/v0";
4
4
  export async function searchMcpServers(options = {}) {
5
- const { query = '', limit = 20, cursor } = options;
5
+ const { query = "", limit = 20, cursor } = options;
6
6
  const params = new URLSearchParams();
7
7
  if (query)
8
- params.set('query', query);
9
- params.set('limit', String(limit));
8
+ params.set("query", query);
9
+ params.set("limit", String(limit));
10
10
  if (cursor)
11
- params.set('cursor', cursor);
11
+ params.set("cursor", cursor);
12
12
  const url = `${MCP_REGISTRY_API}/servers?${params.toString()}`;
13
13
  const response = await fetch(url, {
14
14
  headers: {
15
- 'Accept': 'application/json',
15
+ Accept: "application/json",
16
16
  },
17
17
  });
18
18
  if (!response.ok) {
@@ -25,9 +25,9 @@ export async function searchMcpServers(options = {}) {
25
25
  const servers = (data.servers || [])
26
26
  .map((item) => {
27
27
  const server = item.server || item; // Handle both nested and flat structures
28
- const meta = item._meta?.['io.modelcontextprotocol.registry/official'];
28
+ const meta = item._meta?.["io.modelcontextprotocol.registry/official"];
29
29
  // Get URL from remotes (HTTP) or construct from packages
30
- let url = '';
30
+ let url = "";
31
31
  if (server.remotes?.length > 0) {
32
32
  url = server.remotes[0].url;
33
33
  }
@@ -39,7 +39,7 @@ export async function searchMcpServers(options = {}) {
39
39
  return {
40
40
  name: server.name,
41
41
  url,
42
- short_description: server.description || 'No description',
42
+ short_description: server.description || "No description",
43
43
  version: server.version,
44
44
  source_code_url: server.repository?.url,
45
45
  package_registry: server.packages?.[0]?.registryType,
@@ -54,7 +54,7 @@ export async function searchMcpServers(options = {}) {
54
54
  return 1;
55
55
  if (!b.published_at)
56
56
  return -1;
57
- return new Date(b.published_at).getTime() - new Date(a.published_at).getTime();
57
+ return (new Date(b.published_at).getTime() - new Date(a.published_at).getTime());
58
58
  });
59
59
  return {
60
60
  servers,
@@ -68,15 +68,15 @@ export async function getPopularServers(limit = 20) {
68
68
  }
69
69
  export function formatDate(dateStr) {
70
70
  if (!dateStr)
71
- return '';
71
+ return "";
72
72
  const date = new Date(dateStr);
73
73
  const now = new Date();
74
74
  const diffMs = now.getTime() - date.getTime();
75
75
  const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
76
76
  if (diffDays === 0)
77
- return 'today';
77
+ return "today";
78
78
  if (diffDays === 1)
79
- return 'yesterday';
79
+ return "yesterday";
80
80
  if (diffDays < 7)
81
81
  return `${diffDays}d ago`;
82
82
  if (diffDays < 30)
@@ -85,4 +85,3 @@ export function formatDate(dateStr) {
85
85
  return `${Math.floor(diffDays / 30)}mo ago`;
86
86
  return `${Math.floor(diffDays / 365)}y ago`;
87
87
  }
88
- //# sourceMappingURL=mcp-registry.js.map
@@ -0,0 +1,105 @@
1
+ import type { McpRegistryServer, McpRegistryResponse } from "../types/index.js";
2
+
3
+ // Official Model Context Protocol Registry
4
+ // API Docs: https://registry.modelcontextprotocol.io
5
+ const MCP_REGISTRY_API = "https://registry.modelcontextprotocol.io/v0";
6
+
7
+ export interface SearchOptions {
8
+ query?: string;
9
+ limit?: number;
10
+ cursor?: string;
11
+ }
12
+
13
+ export async function searchMcpServers(
14
+ options: SearchOptions = {},
15
+ ): Promise<McpRegistryResponse> {
16
+ const { query = "", limit = 20, cursor } = options;
17
+
18
+ const params = new URLSearchParams();
19
+ if (query) params.set("query", query);
20
+ params.set("limit", String(limit));
21
+ if (cursor) params.set("cursor", cursor);
22
+
23
+ const url = `${MCP_REGISTRY_API}/servers?${params.toString()}`;
24
+
25
+ const response = await fetch(url, {
26
+ headers: {
27
+ Accept: "application/json",
28
+ },
29
+ });
30
+
31
+ if (!response.ok) {
32
+ throw new Error(
33
+ `MCP Registry API error: ${response.status} ${response.statusText}`,
34
+ );
35
+ }
36
+
37
+ const data: any = await response.json();
38
+
39
+ // The official registry returns data in a nested format:
40
+ // { servers: [{ server: {...}, _meta: {...} }] }
41
+ // Convert it to match our expected McpRegistryResponse format
42
+ const servers: McpRegistryServer[] = (data.servers || [])
43
+ .map((item: any) => {
44
+ const server = item.server || item; // Handle both nested and flat structures
45
+ const meta = item._meta?.["io.modelcontextprotocol.registry/official"];
46
+
47
+ // Get URL from remotes (HTTP) or construct from packages
48
+ let url = "";
49
+ if (server.remotes?.length > 0) {
50
+ url = server.remotes[0].url;
51
+ } else if (server.packages?.length > 0) {
52
+ // For package-based servers, use the package identifier as reference
53
+ const pkg = server.packages[0];
54
+ url = `${pkg.registryType}:${pkg.identifier}`;
55
+ }
56
+
57
+ return {
58
+ name: server.name,
59
+ url,
60
+ short_description: server.description || "No description",
61
+ version: server.version,
62
+ source_code_url: server.repository?.url,
63
+ package_registry: server.packages?.[0]?.registryType,
64
+ published_at: meta?.publishedAt,
65
+ };
66
+ })
67
+ // Filter out servers without name or URL
68
+ .filter((s: McpRegistryServer) => s.name && s.url)
69
+ // Sort by publish date (newest first)
70
+ .sort((a: McpRegistryServer, b: McpRegistryServer) => {
71
+ if (!a.published_at) return 1;
72
+ if (!b.published_at) return -1;
73
+ return (
74
+ new Date(b.published_at).getTime() - new Date(a.published_at).getTime()
75
+ );
76
+ });
77
+
78
+ return {
79
+ servers,
80
+ next_cursor: data.metadata?.nextCursor || data.next_cursor,
81
+ };
82
+ }
83
+
84
+ export async function getPopularServers(
85
+ limit = 20,
86
+ ): Promise<McpRegistryServer[]> {
87
+ const response = await searchMcpServers({ limit });
88
+ // Already sorted by publish date in searchMcpServers
89
+ return response.servers;
90
+ }
91
+
92
+ export function formatDate(dateStr?: string): string {
93
+ if (!dateStr) return "";
94
+ const date = new Date(dateStr);
95
+ const now = new Date();
96
+ const diffMs = now.getTime() - date.getTime();
97
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
98
+
99
+ if (diffDays === 0) return "today";
100
+ if (diffDays === 1) return "yesterday";
101
+ if (diffDays < 7) return `${diffDays}d ago`;
102
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
103
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)}mo ago`;
104
+ return `${Math.floor(diffDays / 365)}y ago`;
105
+ }