claudeup 4.18.0 → 4.22.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 (191) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/alias-adopt.test.ts +364 -0
  7. package/src/__tests__/alias-parser.test.ts +92 -0
  8. package/src/__tests__/alias-shell-writer.test.ts +7 -0
  9. package/src/__tests__/alias-store.test.ts +77 -0
  10. package/src/__tests__/cli-router.test.ts +65 -0
  11. package/src/__tests__/conventions-integration.test.ts +745 -0
  12. package/src/__tests__/conventions-manager.test.ts +1172 -0
  13. package/src/__tests__/doctor-bins.test.ts +35 -0
  14. package/src/__tests__/doctor.test.ts +450 -0
  15. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  16. package/src/__tests__/file-locking.test.ts +208 -0
  17. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  18. package/src/__tests__/install-command.test.ts +81 -0
  19. package/src/__tests__/install-plan.test.ts +98 -0
  20. package/src/__tests__/manifest.test.ts +136 -0
  21. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  22. package/src/__tests__/plugin-requires.test.ts +133 -0
  23. package/src/__tests__/plugin-setup.test.ts +118 -0
  24. package/src/__tests__/profile-command.test.ts +116 -0
  25. package/src/__tests__/profile-materializer.test.ts +82 -0
  26. package/src/__tests__/profile-sync.test.ts +136 -0
  27. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  28. package/src/__tests__/resolve-executable.test.ts +42 -0
  29. package/src/__tests__/resolver.test.ts +218 -0
  30. package/src/__tests__/symlink-manager.test.ts +99 -0
  31. package/src/__tests__/toolchain.test.ts +56 -0
  32. package/src/cli/claude.ts +21 -0
  33. package/src/cli/doctor.ts +132 -0
  34. package/src/cli/install.ts +360 -0
  35. package/src/cli/profile.ts +166 -0
  36. package/src/cli/router.ts +107 -0
  37. package/src/cli/update.ts +85 -0
  38. package/src/data/alias-flags.ts +11 -1
  39. package/src/data/cli-tools.ts +7 -7
  40. package/src/data/gitignore-defaults.ts +4 -0
  41. package/src/data/marketplaces.ts +12 -0
  42. package/src/data/predefined-profiles.ts +3 -4
  43. package/src/main.tsx +11 -154
  44. package/src/prerunner/index.ts +62 -27
  45. package/src/services/alias-shell-writer.ts +382 -8
  46. package/src/services/alias-store.ts +60 -0
  47. package/src/services/claude-cli.ts +19 -22
  48. package/src/services/claude-settings.ts +99 -26
  49. package/src/services/conventions-manager.ts +865 -0
  50. package/src/services/doctor-bins.ts +49 -0
  51. package/src/services/doctor.ts +509 -0
  52. package/src/services/install-plan.ts +107 -0
  53. package/src/services/manifest.ts +166 -0
  54. package/src/services/marketplace-refresh.ts +173 -0
  55. package/src/services/plugin-manager.ts +7 -1
  56. package/src/services/plugin-requires.ts +215 -0
  57. package/src/services/plugin-setup.ts +61 -4
  58. package/src/services/plugin-version-check.ts +9 -4
  59. package/src/services/profile-materializer.ts +61 -0
  60. package/src/services/profile-sync.ts +150 -0
  61. package/src/services/resolver.ts +293 -0
  62. package/src/services/symlink-manager.ts +146 -0
  63. package/src/services/toolchain.ts +97 -0
  64. package/src/services/version-check.ts +10 -11
  65. package/src/types/index.ts +138 -31
  66. package/src/ui/App.tsx +16 -11
  67. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  68. package/src/ui/components/modals/ConfirmModal.tsx +1 -1
  69. package/src/ui/screens/AliasScreen.tsx +491 -359
  70. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  71. package/src/ui/screens/PluginsScreen.tsx +3 -1
  72. package/src/ui/screens/index.ts +0 -1
  73. package/src/ui/state/reducer.ts +6 -102
  74. package/src/ui/state/types.ts +8 -35
  75. package/src/utils/command-utils.ts +23 -0
  76. package/src/utils/file-locking.ts +144 -0
  77. package/src/data/alias-flags.js +0 -196
  78. package/src/data/cli-tools.js +0 -123
  79. package/src/data/gitignore-defaults.js +0 -24
  80. package/src/data/gitignore-reasons.js +0 -97
  81. package/src/data/gitignore-templates.js +0 -21
  82. package/src/data/marketplaces.js +0 -138
  83. package/src/data/mcp-servers.js +0 -509
  84. package/src/data/predefined-profiles.js +0 -248
  85. package/src/data/settings-catalog.js +0 -625
  86. package/src/data/skill-repos.js +0 -160
  87. package/src/data/statuslines.js +0 -159
  88. package/src/data/statuslines.ts +0 -188
  89. package/src/index.js +0 -4
  90. package/src/index.ts +0 -5
  91. package/src/main.js +0 -170
  92. package/src/prerunner/index.js +0 -252
  93. package/src/services/alias-settings.js +0 -51
  94. package/src/services/alias-shell-writer.js +0 -764
  95. package/src/services/alias-store.js +0 -77
  96. package/src/services/claude-cli.js +0 -189
  97. package/src/services/claude-runner.js +0 -28
  98. package/src/services/claude-settings.js +0 -1223
  99. package/src/services/gitignore-detector.js +0 -155
  100. package/src/services/gitignore-fixer.js +0 -231
  101. package/src/services/gitignore-prerun.js +0 -46
  102. package/src/services/gitignore-resolver.js +0 -143
  103. package/src/services/gitignore-service.js +0 -117
  104. package/src/services/local-marketplace.js +0 -339
  105. package/src/services/marketplace-fetcher.js +0 -96
  106. package/src/services/marketplace-sync.js +0 -94
  107. package/src/services/mcp-registry.js +0 -87
  108. package/src/services/plugin-manager.js +0 -473
  109. package/src/services/plugin-mcp-config.js +0 -177
  110. package/src/services/plugin-setup.js +0 -444
  111. package/src/services/plugin-version-check.js +0 -262
  112. package/src/services/profiles.js +0 -161
  113. package/src/services/settings-manager.js +0 -243
  114. package/src/services/skills-manager.js +0 -393
  115. package/src/services/skillsmp-client.js +0 -87
  116. package/src/services/update-cache.js +0 -52
  117. package/src/services/version-check.js +0 -99
  118. package/src/types/gitignore.js +0 -6
  119. package/src/types/index.js +0 -1
  120. package/src/ui/App.js +0 -346
  121. package/src/ui/adapters/pluginsAdapter.js +0 -139
  122. package/src/ui/adapters/settingsAdapter.js +0 -111
  123. package/src/ui/adapters/skillsAdapter.js +0 -154
  124. package/src/ui/components/CategoryHeader.js +0 -9
  125. package/src/ui/components/EmptyFilterState.js +0 -4
  126. package/src/ui/components/ScopeIndicator.js +0 -30
  127. package/src/ui/components/ScrollableList.js +0 -36
  128. package/src/ui/components/SearchInput.js +0 -19
  129. package/src/ui/components/StyledText.js +0 -39
  130. package/src/ui/components/TabBar.js +0 -19
  131. package/src/ui/components/layout/FooterHints.js +0 -29
  132. package/src/ui/components/layout/Panel.js +0 -6
  133. package/src/ui/components/layout/ProgressBar.js +0 -14
  134. package/src/ui/components/layout/ScopeTabs.js +0 -6
  135. package/src/ui/components/layout/ScreenLayout.js +0 -16
  136. package/src/ui/components/layout/index.js +0 -5
  137. package/src/ui/components/modals/ConfirmModal.js +0 -14
  138. package/src/ui/components/modals/InputModal.js +0 -5
  139. package/src/ui/components/modals/LoadingModal.js +0 -17
  140. package/src/ui/components/modals/MessageModal.js +0 -16
  141. package/src/ui/components/modals/ModalContainer.js +0 -137
  142. package/src/ui/components/modals/SelectModal.js +0 -18
  143. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  144. package/src/ui/components/modals/index.js +0 -6
  145. package/src/ui/components/primitives/ActionHints.js +0 -13
  146. package/src/ui/components/primitives/DetailSection.js +0 -7
  147. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  148. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  149. package/src/ui/components/primitives/MetaText.js +0 -8
  150. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  151. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  152. package/src/ui/components/primitives/SelectableRow.js +0 -5
  153. package/src/ui/components/primitives/index.js +0 -8
  154. package/src/ui/hooks/index.js +0 -4
  155. package/src/ui/hooks/useAsyncData.js +0 -77
  156. package/src/ui/hooks/useGitignoreModal.js +0 -74
  157. package/src/ui/hooks/useKeyboard.js +0 -13
  158. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  159. package/src/ui/hooks/useMismatchModal.js +0 -77
  160. package/src/ui/registry.js +0 -1
  161. package/src/ui/renderers/cliToolRenderers.js +0 -54
  162. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  163. package/src/ui/renderers/mcpRenderers.js +0 -26
  164. package/src/ui/renderers/pluginRenderers.js +0 -124
  165. package/src/ui/renderers/profileRenderers.js +0 -177
  166. package/src/ui/renderers/settingsRenderers.js +0 -73
  167. package/src/ui/renderers/skillRenderers.js +0 -138
  168. package/src/ui/screens/AliasScreen.js +0 -1008
  169. package/src/ui/screens/CliToolsScreen.js +0 -338
  170. package/src/ui/screens/EnvVarsScreen.js +0 -152
  171. package/src/ui/screens/GitignoreScreen.js +0 -328
  172. package/src/ui/screens/McpRegistryScreen.js +0 -238
  173. package/src/ui/screens/McpScreen.js +0 -176
  174. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  175. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  176. package/src/ui/screens/PluginsScreen.js +0 -766
  177. package/src/ui/screens/ProfilesScreen.js +0 -298
  178. package/src/ui/screens/SkillsScreen.js +0 -549
  179. package/src/ui/screens/StatusLineScreen.js +0 -206
  180. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  181. package/src/ui/screens/index.js +0 -10
  182. package/src/ui/state/AnimationContext.js +0 -34
  183. package/src/ui/state/AppContext.js +0 -162
  184. package/src/ui/state/DimensionsContext.js +0 -71
  185. package/src/ui/state/reducer.js +0 -543
  186. package/src/ui/state/types.js +0 -1
  187. package/src/ui/theme.js +0 -47
  188. package/src/utils/clipboard.js +0 -56
  189. package/src/utils/command-utils.js +0 -19
  190. package/src/utils/fuzzy-search.js +0 -101
  191. package/src/utils/string-utils.js +0 -62
@@ -1,473 +0,0 @@
1
- import path from "node:path";
2
- import os from "node:os";
3
- import { execSync } from "node:child_process";
4
- import { getConfiguredMarketplaces, getEnabledPlugins, readSettings, writeSettings, getGlobalConfiguredMarketplaces, getGlobalEnabledPlugins, getGlobalInstalledPluginVersions, getLocalEnabledPlugins, getLocalInstalledPluginVersions, updateInstalledPluginsRegistry, removeFromInstalledPluginsRegistry, } from "./claude-settings.js";
5
- import { defaultMarketplaces } from "../data/marketplaces.js";
6
- import { scanLocalMarketplaces, repairAllMarketplaces, } from "./local-marketplace.js";
7
- import { formatMarketplaceName, parsePluginId, } from "../utils/string-utils.js";
8
- import { clearMarketplaceCache as clearFetcherCache, fetchMarketplacePlugins, resolveMarketplacePlugins, } from "./marketplace-fetcher.js";
9
- export { fetchMarketplacePlugins, resolveMarketplacePlugins, };
10
- // Cache for local marketplaces (session-level) - Promise-based to prevent race conditions
11
- let localMarketplacesPromise = null;
12
- export async function getAvailablePlugins(projectPath) {
13
- const configuredMarketplaces = await getConfiguredMarketplaces(projectPath);
14
- const enabledPlugins = await getEnabledPlugins(projectPath);
15
- const installedVersions = await getInstalledPluginVersions(projectPath);
16
- // Fetch all scopes for per-scope status
17
- const userEnabledPlugins = await getGlobalEnabledPlugins();
18
- const userInstalledVersions = await getGlobalInstalledPluginVersions();
19
- const projectEnabledPlugins = await getEnabledPlugins(projectPath);
20
- const projectInstalledVersions = await getInstalledPluginVersions(projectPath);
21
- const localEnabledPlugins = await getLocalEnabledPlugins(projectPath);
22
- const localInstalledVersions = await getLocalInstalledPluginVersions(projectPath);
23
- const plugins = [];
24
- const seenPluginIds = new Set();
25
- // Helper to build scope status
26
- const buildScopeStatus = (pluginId) => ({
27
- userScope: userEnabledPlugins[pluginId] !== undefined
28
- ? {
29
- enabled: userEnabledPlugins[pluginId],
30
- version: userInstalledVersions[pluginId],
31
- }
32
- : undefined,
33
- projectScope: projectEnabledPlugins[pluginId] !== undefined
34
- ? {
35
- enabled: projectEnabledPlugins[pluginId],
36
- version: projectInstalledVersions[pluginId],
37
- }
38
- : undefined,
39
- localScope: localEnabledPlugins[pluginId] !== undefined
40
- ? {
41
- enabled: localEnabledPlugins[pluginId],
42
- version: localInstalledVersions[pluginId],
43
- }
44
- : undefined,
45
- });
46
- // Get all marketplace names (configured + official/featured defaults)
47
- // Always include official and featured marketplaces so users can browse them
48
- const marketplaceNames = new Set();
49
- for (const mp of defaultMarketplaces) {
50
- if (configuredMarketplaces[mp.name] || mp.official || mp.featured) {
51
- marketplaceNames.add(mp.name);
52
- }
53
- }
54
- // Fetch local marketplace caches up front so we can detect stale caches
55
- let localMarketplaces = await getLocalMarketplaces();
56
- // Fetch plugins from each configured marketplace
57
- for (const mpName of marketplaceNames) {
58
- const marketplace = defaultMarketplaces.find((m) => m.name === mpName);
59
- if (!marketplace)
60
- continue;
61
- const marketplacePlugins = await resolveMarketplacePlugins(mpName, marketplace.source.repo, localMarketplaces);
62
- // Auto-sync local cache if remote has plugins the local cache doesn't
63
- localMarketplaces = await autoSyncIfStale(mpName, marketplacePlugins.map((p) => p.name), localMarketplaces);
64
- for (const plugin of marketplacePlugins) {
65
- const pluginId = `${plugin.name}@${mpName}`;
66
- const installedVersion = installedVersions[pluginId];
67
- const isEnabled = enabledPlugins[pluginId] === true;
68
- const scopeStatus = buildScopeStatus(pluginId);
69
- seenPluginIds.add(pluginId);
70
- plugins.push({
71
- id: pluginId,
72
- name: plugin.name,
73
- version: plugin.version,
74
- description: plugin.description,
75
- marketplace: mpName,
76
- marketplaceDisplay: marketplace.displayName,
77
- enabled: isEnabled,
78
- installedVersion: installedVersion,
79
- hasUpdate: installedVersion && plugin.version
80
- ? compareVersions(plugin.version, installedVersion) > 0
81
- : false,
82
- ...scopeStatus,
83
- category: plugin.category,
84
- author: plugin.author,
85
- homepage: plugin.homepage,
86
- tags: plugin.tags,
87
- });
88
- }
89
- }
90
- for (const [mpName, localMp] of localMarketplaces) {
91
- // Skip if already fetched from defaults
92
- if (marketplaceNames.has(mpName))
93
- continue;
94
- // Add ALL plugins from this local marketplace cache
95
- for (const localPlugin of localMp.plugins) {
96
- const pluginId = `${localPlugin.name}@${mpName}`;
97
- if (seenPluginIds.has(pluginId))
98
- continue;
99
- const installedVersion = installedVersions[pluginId];
100
- const isEnabled = enabledPlugins[pluginId] === true;
101
- const scopeStatus = buildScopeStatus(pluginId);
102
- seenPluginIds.add(pluginId);
103
- plugins.push({
104
- id: pluginId,
105
- name: localPlugin.name,
106
- version: localPlugin.version,
107
- description: localPlugin.description || "",
108
- marketplace: mpName,
109
- marketplaceDisplay: localMp.name || formatMarketplaceName(mpName),
110
- enabled: isEnabled,
111
- installedVersion: installedVersion,
112
- hasUpdate: installedVersion
113
- ? compareVersions(localPlugin.version, installedVersion) > 0
114
- : false,
115
- ...scopeStatus,
116
- category: localPlugin.category,
117
- author: localPlugin.author,
118
- agents: localPlugin.agents,
119
- commands: localPlugin.commands,
120
- skills: localPlugin.skills,
121
- mcpServers: localPlugin.mcpServers,
122
- lspServers: localPlugin.lspServers,
123
- });
124
- }
125
- }
126
- // Add orphaned plugins (enabled/installed but not in any cache)
127
- const allPluginIds = new Set([
128
- ...Object.keys(enabledPlugins),
129
- ...Object.keys(installedVersions),
130
- ]);
131
- for (const pluginId of allPluginIds) {
132
- if (seenPluginIds.has(pluginId))
133
- continue;
134
- const parsed = parsePluginId(pluginId);
135
- if (!parsed)
136
- continue;
137
- const { pluginName, marketplace: mpName } = parsed;
138
- const installedVersion = installedVersions[pluginId];
139
- const isEnabled = enabledPlugins[pluginId] === true;
140
- const scopeStatus = buildScopeStatus(pluginId);
141
- // Try to get plugin info from local marketplace cache (fallback)
142
- const localMp = localMarketplaces.get(mpName);
143
- const localPlugin = localMp?.plugins.find((p) => p.name === pluginName);
144
- const latestVersion = localPlugin?.version || installedVersion || "unknown";
145
- const description = localPlugin?.description || "Installed plugin";
146
- const hasUpdate = installedVersion && localPlugin?.version
147
- ? compareVersions(localPlugin.version, installedVersion) > 0
148
- : false;
149
- plugins.push({
150
- id: pluginId,
151
- name: pluginName,
152
- version: latestVersion,
153
- description,
154
- marketplace: mpName,
155
- marketplaceDisplay: localMp?.name || formatMarketplaceName(mpName),
156
- enabled: isEnabled,
157
- installedVersion: installedVersion,
158
- hasUpdate,
159
- isOrphaned: true,
160
- ...scopeStatus,
161
- });
162
- }
163
- return plugins;
164
- }
165
- export async function getGlobalAvailablePlugins() {
166
- const configuredMarketplaces = await getGlobalConfiguredMarketplaces();
167
- const enabledPlugins = await getGlobalEnabledPlugins();
168
- const installedVersions = await getGlobalInstalledPluginVersions();
169
- // Fetch all scopes for per-scope status display
170
- const userEnabledPlugins = await getGlobalEnabledPlugins();
171
- const userInstalledVersions = await getGlobalInstalledPluginVersions();
172
- // Also fetch project and local scope for complete status display
173
- const projectEnabledPlugins = await getEnabledPlugins();
174
- const projectInstalledVersions = await getInstalledPluginVersions();
175
- const localEnabledPlugins = await getLocalEnabledPlugins();
176
- const localInstalledVersions = await getLocalInstalledPluginVersions();
177
- const plugins = [];
178
- const seenPluginIds = new Set();
179
- // Helper to build scope status (show all scopes)
180
- const buildScopeStatus = (pluginId) => ({
181
- userScope: userEnabledPlugins[pluginId] !== undefined
182
- ? {
183
- enabled: userEnabledPlugins[pluginId],
184
- version: userInstalledVersions[pluginId],
185
- }
186
- : undefined,
187
- projectScope: projectEnabledPlugins[pluginId] !== undefined
188
- ? {
189
- enabled: projectEnabledPlugins[pluginId],
190
- version: projectInstalledVersions[pluginId],
191
- }
192
- : undefined,
193
- localScope: localEnabledPlugins[pluginId] !== undefined
194
- ? {
195
- enabled: localEnabledPlugins[pluginId],
196
- version: localInstalledVersions[pluginId],
197
- }
198
- : undefined,
199
- });
200
- // Get all marketplace names (configured + official/featured defaults)
201
- // Always include official and featured marketplaces so users can browse them
202
- const marketplaceNames = new Set();
203
- for (const mp of defaultMarketplaces) {
204
- if (configuredMarketplaces[mp.name] || mp.official || mp.featured) {
205
- marketplaceNames.add(mp.name);
206
- }
207
- }
208
- // Fetch local marketplace caches up front so we can detect stale caches
209
- let localMarketplaces = await getLocalMarketplaces();
210
- // Fetch plugins from each configured marketplace
211
- for (const mpName of marketplaceNames) {
212
- const marketplace = defaultMarketplaces.find((m) => m.name === mpName);
213
- if (!marketplace)
214
- continue;
215
- const marketplacePlugins = await resolveMarketplacePlugins(mpName, marketplace.source.repo, localMarketplaces);
216
- // Auto-sync local cache if remote has plugins the local cache doesn't
217
- localMarketplaces = await autoSyncIfStale(mpName, marketplacePlugins.map((p) => p.name), localMarketplaces);
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
- seenPluginIds.add(pluginId);
224
- plugins.push({
225
- id: pluginId,
226
- name: plugin.name,
227
- version: plugin.version,
228
- description: plugin.description,
229
- marketplace: mpName,
230
- marketplaceDisplay: marketplace.displayName,
231
- enabled: isEnabled,
232
- installedVersion: installedVersion,
233
- hasUpdate: installedVersion && plugin.version
234
- ? compareVersions(plugin.version, installedVersion) > 0
235
- : false,
236
- ...scopeStatus,
237
- category: plugin.category,
238
- author: plugin.author,
239
- homepage: plugin.homepage,
240
- tags: plugin.tags,
241
- });
242
- }
243
- }
244
- for (const [mpName, localMp] of localMarketplaces) {
245
- // Skip if already fetched from defaults
246
- if (marketplaceNames.has(mpName))
247
- continue;
248
- // Add ALL plugins from this local marketplace cache
249
- for (const localPlugin of localMp.plugins) {
250
- const pluginId = `${localPlugin.name}@${mpName}`;
251
- if (seenPluginIds.has(pluginId))
252
- continue;
253
- const installedVersion = installedVersions[pluginId];
254
- const isEnabled = enabledPlugins[pluginId] === true;
255
- const scopeStatus = buildScopeStatus(pluginId);
256
- seenPluginIds.add(pluginId);
257
- plugins.push({
258
- id: pluginId,
259
- name: localPlugin.name,
260
- version: localPlugin.version,
261
- description: localPlugin.description || "",
262
- marketplace: mpName,
263
- marketplaceDisplay: localMp.name || formatMarketplaceName(mpName),
264
- enabled: isEnabled,
265
- installedVersion: installedVersion,
266
- hasUpdate: installedVersion
267
- ? compareVersions(localPlugin.version, installedVersion) > 0
268
- : false,
269
- ...scopeStatus,
270
- category: localPlugin.category,
271
- author: localPlugin.author,
272
- agents: localPlugin.agents,
273
- commands: localPlugin.commands,
274
- skills: localPlugin.skills,
275
- mcpServers: localPlugin.mcpServers,
276
- lspServers: localPlugin.lspServers,
277
- });
278
- }
279
- }
280
- // Add orphaned plugins (enabled/installed but not in any cache)
281
- const allPluginIds = new Set([
282
- ...Object.keys(enabledPlugins),
283
- ...Object.keys(installedVersions),
284
- ]);
285
- for (const pluginId of allPluginIds) {
286
- if (seenPluginIds.has(pluginId))
287
- continue;
288
- const parsed = parsePluginId(pluginId);
289
- if (!parsed)
290
- continue;
291
- const { pluginName, marketplace: mpName } = parsed;
292
- const installedVersion = installedVersions[pluginId];
293
- const isEnabled = enabledPlugins[pluginId] === true;
294
- const scopeStatus = buildScopeStatus(pluginId);
295
- // Try to get plugin info from local marketplace cache (fallback)
296
- const localMp = localMarketplaces.get(mpName);
297
- const localPlugin = localMp?.plugins.find((p) => p.name === pluginName);
298
- const latestVersion = localPlugin?.version || installedVersion || "unknown";
299
- const description = localPlugin?.description || "Installed plugin";
300
- const hasUpdate = installedVersion && localPlugin?.version
301
- ? compareVersions(localPlugin.version, installedVersion) > 0
302
- : false;
303
- plugins.push({
304
- id: pluginId,
305
- name: pluginName,
306
- version: latestVersion,
307
- description,
308
- marketplace: mpName,
309
- marketplaceDisplay: localMp?.name || formatMarketplaceName(mpName),
310
- enabled: isEnabled,
311
- ...scopeStatus,
312
- installedVersion: installedVersion,
313
- hasUpdate,
314
- isOrphaned: true,
315
- });
316
- }
317
- return plugins;
318
- }
319
- // Simple version comparison (returns 1 if a > b, -1 if a < b, 0 if equal)
320
- function compareVersions(a, b) {
321
- if (!a || !b)
322
- return 0;
323
- const partsA = a.replace(/^v/, "").split(".").map(Number);
324
- const partsB = b.replace(/^v/, "").split(".").map(Number);
325
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
326
- const numA = partsA[i] || 0;
327
- const numB = partsB[i] || 0;
328
- if (numA > numB)
329
- return 1;
330
- if (numA < numB)
331
- return -1;
332
- }
333
- return 0;
334
- }
335
- // Get installed plugin versions from settings.json (shared, not secret)
336
- async function getInstalledPluginVersions(projectPath) {
337
- const settings = await readSettings(projectPath);
338
- return settings.installedPluginVersions || {};
339
- }
340
- // Save installed plugin version to settings.json
341
- export async function saveInstalledPluginVersion(pluginId, version, projectPath) {
342
- const settings = await readSettings(projectPath);
343
- settings.installedPluginVersions = settings.installedPluginVersions || {};
344
- settings.installedPluginVersions[pluginId] = version;
345
- await writeSettings(settings, projectPath);
346
- // Update installed_plugins.json registry
347
- await updateInstalledPluginsRegistry(pluginId, version, "project", projectPath ? path.resolve(projectPath) : undefined);
348
- }
349
- // Remove installed plugin version from settings.json
350
- export async function removeInstalledPluginVersion(pluginId, projectPath) {
351
- const settings = await readSettings(projectPath);
352
- if (settings.installedPluginVersions) {
353
- delete settings.installedPluginVersions[pluginId];
354
- }
355
- if (settings.enabledPlugins) {
356
- delete settings.enabledPlugins[pluginId];
357
- }
358
- await writeSettings(settings, projectPath);
359
- // Remove from installed_plugins.json registry
360
- await removeFromInstalledPluginsRegistry(pluginId, "project", projectPath ? path.resolve(projectPath) : undefined);
361
- }
362
- // Clear marketplace cache
363
- export function clearMarketplaceCache() {
364
- clearFetcherCache();
365
- localMarketplacesPromise = null;
366
- }
367
- // Get local marketplaces (with Promise-based caching to prevent race conditions)
368
- async function getLocalMarketplaces() {
369
- if (localMarketplacesPromise === null) {
370
- localMarketplacesPromise = scanLocalMarketplaces();
371
- }
372
- return localMarketplacesPromise;
373
- }
374
- // Export local marketplaces for use in other modules
375
- export async function getLocalMarketplacesInfo() {
376
- return getLocalMarketplaces();
377
- }
378
- // Track which marketplaces have already been auto-synced this session
379
- // so we only attempt the update once per marketplace per claudeup run.
380
- const autoSyncedMarketplaces = new Set();
381
- /**
382
- * If the remote manifest lists plugins that aren't in the local cache,
383
- * the local clone is stale. Log a warning but do NOT trigger a marketplace
384
- * update from claudeup.
385
- *
386
- * Previously this called `updateMarketplace()` which invokes Claude Code's
387
- * `cacheMarketplaceFromGit()` — a non-atomic delete-then-clone that can
388
- * permanently delete the marketplace directory on clone failure.
389
- * Claude Code's own background autoupdate handles marketplace refreshing
390
- * after session start. See: ai-docs/plugin-marketplace-bug-investigation.md
391
- */
392
- async function autoSyncIfStale(mpName, remotePluginNames, localMarketplaces) {
393
- if (autoSyncedMarketplaces.has(mpName))
394
- return localMarketplaces;
395
- const localMp = localMarketplaces.get(mpName);
396
- if (!localMp)
397
- return localMarketplaces;
398
- const localNames = new Set(localMp.plugins.map((p) => p.name));
399
- const missingPlugins = remotePluginNames.filter((name) => !localNames.has(name));
400
- if (missingPlugins.length === 0)
401
- return localMarketplaces;
402
- autoSyncedMarketplaces.add(mpName);
403
- // Log stale state but don't trigger update — Claude Code handles refresh
404
- console.log(`ℹ Marketplace ${mpName} is stale (missing: ${missingPlugins.join(", ")}). Claude Code will auto-update on next session start.`);
405
- return localMarketplaces;
406
- }
407
- /**
408
- * Refresh claudeup's internal cache
409
- * Note: Marketplace updates should be done via Claude Code's /plugin marketplace update command
410
- */
411
- export async function refreshAllMarketplaces(onProgress) {
412
- onProgress?.({ current: 1, total: 1, name: "Clearing cache..." });
413
- // Clear all caches to force fresh data
414
- clearMarketplaceCache();
415
- // Auto-repair plugin.json files with missing agents/commands/skills
416
- const repairResults = await repairAllMarketplaces();
417
- return {
418
- refresh: [],
419
- repair: repairResults,
420
- };
421
- }
422
- /**
423
- * Check if marketplaces have updates available on GitHub
424
- * Compares local git HEAD with remote HEAD
425
- */
426
- export async function checkMarketplaceUpdates() {
427
- const results = [];
428
- const localMarketplaces = await getLocalMarketplaces();
429
- for (const [name, marketplace] of localMarketplaces) {
430
- if (!marketplace.gitRepo)
431
- continue;
432
- try {
433
- const marketplacePath = path.join(os.homedir(), ".claude", "plugins", "marketplaces", name);
434
- // Get current HEAD from local repo
435
- let currentHead;
436
- try {
437
- currentHead = execSync("git rev-parse HEAD", {
438
- cwd: marketplacePath,
439
- encoding: "utf-8",
440
- timeout: 5000,
441
- }).trim();
442
- }
443
- catch {
444
- continue; // Skip if can't get HEAD
445
- }
446
- // Fetch latest commit from GitHub API
447
- const apiUrl = `https://api.github.com/repos/${marketplace.gitRepo}/commits/main`;
448
- const response = await fetch(apiUrl, {
449
- signal: AbortSignal.timeout(10000),
450
- headers: {
451
- Accept: "application/vnd.github.v3+json",
452
- },
453
- });
454
- if (response.ok) {
455
- const data = (await response.json());
456
- const latestCommit = data.sha;
457
- results.push({
458
- name,
459
- hasUpdate: currentHead !== latestCommit,
460
- currentCommit: currentHead.substring(0, 7),
461
- latestCommit: latestCommit.substring(0, 7),
462
- });
463
- }
464
- else {
465
- results.push({ name, hasUpdate: false });
466
- }
467
- }
468
- catch {
469
- results.push({ name, hasUpdate: false });
470
- }
471
- }
472
- return results;
473
- }
@@ -1,177 +0,0 @@
1
- import fs from "fs-extra";
2
- import path from "node:path";
3
- import os from "node:os";
4
- const CLAUDE_PLUGINS_DIR = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
5
- /**
6
- * Extract ${VAR_NAME} patterns from a value
7
- * @param value - The value to search for env var references
8
- * @returns Array of env var names found
9
- */
10
- function extractEnvVarReferences(value) {
11
- if (typeof value !== "string")
12
- return [];
13
- const matches = value.match(/\$\{([A-Z_][A-Z0-9_]*)\}/g);
14
- if (!matches)
15
- return [];
16
- return matches
17
- .map((match) => {
18
- // Extract VAR_NAME from ${VAR_NAME}
19
- const innerMatch = match.match(/\$\{([A-Z_][A-Z0-9_]*)\}/);
20
- return innerMatch ? innerMatch[1] : "";
21
- })
22
- .filter(Boolean);
23
- }
24
- /**
25
- * Recursively extract all env var references from an object
26
- */
27
- function extractAllEnvVarReferences(obj) {
28
- const vars = [];
29
- if (typeof obj === "string") {
30
- vars.push(...extractEnvVarReferences(obj));
31
- }
32
- else if (Array.isArray(obj)) {
33
- for (const item of obj) {
34
- vars.push(...extractAllEnvVarReferences(item));
35
- }
36
- }
37
- else if (obj && typeof obj === "object") {
38
- for (const value of Object.values(obj)) {
39
- vars.push(...extractAllEnvVarReferences(value));
40
- }
41
- }
42
- return vars;
43
- }
44
- /**
45
- * Get the path to a plugin's mcp-config.json in the marketplace cache
46
- * @param marketplaceName - Name of the marketplace (e.g., "magus")
47
- * @param pluginSource - Plugin source path from marketplace.json (e.g., "./plugins/frontend")
48
- * @returns Full path to mcp-config.json or undefined if not found
49
- */
50
- function getPluginMcpConfigPath(marketplaceName, pluginSource) {
51
- if (!pluginSource)
52
- return undefined;
53
- const marketplacePath = path.join(CLAUDE_PLUGINS_DIR, marketplaceName);
54
- const pluginPath = path.join(marketplacePath, pluginSource.replace(/^\.\//, ""));
55
- const mcpConfigPath = path.join(pluginPath, "mcp-servers", "mcp-config.json");
56
- return mcpConfigPath;
57
- }
58
- /**
59
- * Read and parse a plugin's MCP configuration
60
- * @param marketplaceName - Name of the marketplace
61
- * @param pluginSource - Plugin source path from marketplace.json
62
- * @returns Parsed MCP config or empty object if not found
63
- */
64
- export async function readPluginMcpConfig(marketplaceName, pluginSource) {
65
- const configPath = getPluginMcpConfigPath(marketplaceName, pluginSource);
66
- if (!configPath)
67
- return {};
68
- try {
69
- if (await fs.pathExists(configPath)) {
70
- const content = await fs.readJson(configPath);
71
- // Filter out comment fields (keys starting with _)
72
- const filtered = {};
73
- for (const [key, value] of Object.entries(content)) {
74
- if (!key.startsWith("_") && value && typeof value === "object") {
75
- filtered[key] = value;
76
- }
77
- }
78
- return filtered;
79
- }
80
- }
81
- catch (error) {
82
- console.error(`Failed to read plugin MCP config: ${error}`);
83
- }
84
- return {};
85
- }
86
- /**
87
- * Generate user-friendly label from env var name
88
- * @param varName - Environment variable name (e.g., "FIGMA_ACCESS_TOKEN")
89
- * @returns Human-readable label (e.g., "Figma Access Token")
90
- */
91
- function generateLabel(varName) {
92
- return varName
93
- .split("_")
94
- .map((word) => word.charAt(0) + word.slice(1).toLowerCase())
95
- .join(" ");
96
- }
97
- /**
98
- * Get all environment variable requirements for a plugin
99
- * Extracts ${VAR_NAME} patterns from the plugin's mcp-config.json
100
- *
101
- * @param marketplaceName - Name of the marketplace (e.g., "magus")
102
- * @param pluginSource - Plugin source path from marketplace.json (e.g., "./plugins/frontend")
103
- * @returns Array of required env vars with metadata
104
- */
105
- export async function getPluginEnvRequirements(marketplaceName, pluginSource) {
106
- const mcpConfig = await readPluginMcpConfig(marketplaceName, pluginSource);
107
- const requirements = [];
108
- const seenVars = new Set();
109
- for (const [serverName, config] of Object.entries(mcpConfig)) {
110
- // Extract from env object
111
- if (config.env) {
112
- for (const value of Object.values(config.env)) {
113
- const vars = extractEnvVarReferences(value);
114
- for (const varName of vars) {
115
- if (!seenVars.has(varName)) {
116
- seenVars.add(varName);
117
- requirements.push({
118
- name: varName,
119
- label: generateLabel(varName),
120
- serverName,
121
- required: true,
122
- });
123
- }
124
- }
125
- }
126
- }
127
- // Also check args array for env var references (e.g., sqlite with ${DATABASE_PATH})
128
- if (config.args) {
129
- const argsVars = extractAllEnvVarReferences(config.args);
130
- for (const varName of argsVars) {
131
- if (!seenVars.has(varName)) {
132
- seenVars.add(varName);
133
- requirements.push({
134
- name: varName,
135
- label: generateLabel(varName),
136
- serverName,
137
- required: true,
138
- });
139
- }
140
- }
141
- }
142
- }
143
- return requirements;
144
- }
145
- /**
146
- * Get plugin source path from local marketplace cache
147
- * @param marketplaceName - Name of the marketplace
148
- * @param pluginName - Name of the plugin
149
- * @returns Plugin source path or undefined
150
- */
151
- export async function getPluginSourcePath(marketplaceName, pluginName) {
152
- const marketplacePath = path.join(CLAUDE_PLUGINS_DIR, marketplaceName);
153
- const manifestPath = path.join(marketplacePath, ".claude-plugin", "marketplace.json");
154
- try {
155
- if (await fs.pathExists(manifestPath)) {
156
- const manifest = await fs.readJson(manifestPath);
157
- if (manifest.plugins && Array.isArray(manifest.plugins)) {
158
- const plugin = manifest.plugins.find((p) => p.name === pluginName);
159
- return plugin?.source;
160
- }
161
- }
162
- }
163
- catch {
164
- // Return undefined if can't read
165
- }
166
- return undefined;
167
- }
168
- /**
169
- * Get all MCP server names from a plugin's config
170
- * @param marketplaceName - Name of the marketplace
171
- * @param pluginSource - Plugin source path
172
- * @returns Array of MCP server names
173
- */
174
- export async function getPluginMcpServerNames(marketplaceName, pluginSource) {
175
- const mcpConfig = await readPluginMcpConfig(marketplaceName, pluginSource);
176
- return Object.keys(mcpConfig);
177
- }