claudeup 4.19.0 → 4.23.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 (182) 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__/cli-router.test.ts +65 -0
  7. package/src/__tests__/conventions-integration.test.ts +745 -0
  8. package/src/__tests__/conventions-manager.test.ts +1172 -0
  9. package/src/__tests__/doctor-bins.test.ts +35 -0
  10. package/src/__tests__/doctor.test.ts +450 -0
  11. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  12. package/src/__tests__/file-locking.test.ts +208 -0
  13. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  14. package/src/__tests__/install-command.test.ts +81 -0
  15. package/src/__tests__/install-plan.test.ts +98 -0
  16. package/src/__tests__/manifest.test.ts +136 -0
  17. package/src/__tests__/marketplace-badge.test.ts +91 -0
  18. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  19. package/src/__tests__/plugin-requires.test.ts +133 -0
  20. package/src/__tests__/plugin-setup.test.ts +7 -0
  21. package/src/__tests__/profile-command.test.ts +116 -0
  22. package/src/__tests__/profile-materializer.test.ts +82 -0
  23. package/src/__tests__/profile-sync.test.ts +136 -0
  24. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  25. package/src/__tests__/resolve-executable.test.ts +42 -0
  26. package/src/__tests__/resolver.test.ts +218 -0
  27. package/src/__tests__/symlink-manager.test.ts +99 -0
  28. package/src/__tests__/toolchain.test.ts +56 -0
  29. package/src/cli/claude.ts +21 -0
  30. package/src/cli/doctor.ts +132 -0
  31. package/src/cli/install.ts +360 -0
  32. package/src/cli/profile.ts +166 -0
  33. package/src/cli/router.ts +107 -0
  34. package/src/cli/update.ts +85 -0
  35. package/src/data/cli-tools.ts +7 -7
  36. package/src/data/gitignore-defaults.ts +4 -0
  37. package/src/data/marketplaces.ts +12 -0
  38. package/src/data/predefined-profiles.ts +3 -4
  39. package/src/main.tsx +11 -154
  40. package/src/prerunner/index.ts +62 -27
  41. package/src/services/claude-cli.ts +19 -22
  42. package/src/services/claude-settings.ts +99 -26
  43. package/src/services/conventions-manager.ts +865 -0
  44. package/src/services/doctor-bins.ts +49 -0
  45. package/src/services/doctor.ts +509 -0
  46. package/src/services/install-plan.ts +107 -0
  47. package/src/services/manifest.ts +166 -0
  48. package/src/services/marketplace-refresh.ts +173 -0
  49. package/src/services/plugin-manager.ts +7 -1
  50. package/src/services/plugin-requires.ts +215 -0
  51. package/src/services/plugin-version-check.ts +9 -4
  52. package/src/services/profile-materializer.ts +61 -0
  53. package/src/services/profile-sync.ts +150 -0
  54. package/src/services/resolver.ts +293 -0
  55. package/src/services/symlink-manager.ts +146 -0
  56. package/src/services/toolchain.ts +97 -0
  57. package/src/services/version-check.ts +10 -11
  58. package/src/types/index.ts +138 -31
  59. package/src/ui/App.tsx +0 -4
  60. package/src/ui/adapters/pluginsAdapter.ts +13 -0
  61. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  62. package/src/ui/screens/index.ts +0 -1
  63. package/src/ui/state/reducer.ts +0 -101
  64. package/src/ui/state/types.ts +0 -35
  65. package/src/utils/command-utils.ts +23 -0
  66. package/src/utils/file-locking.ts +144 -0
  67. package/src/data/alias-flags.js +0 -205
  68. package/src/data/cli-tools.js +0 -123
  69. package/src/data/gitignore-defaults.js +0 -24
  70. package/src/data/gitignore-reasons.js +0 -97
  71. package/src/data/gitignore-templates.js +0 -21
  72. package/src/data/marketplaces.js +0 -138
  73. package/src/data/mcp-servers.js +0 -509
  74. package/src/data/predefined-profiles.js +0 -248
  75. package/src/data/settings-catalog.js +0 -625
  76. package/src/data/skill-repos.js +0 -160
  77. package/src/data/statuslines.js +0 -159
  78. package/src/data/statuslines.ts +0 -188
  79. package/src/index.js +0 -4
  80. package/src/index.ts +0 -5
  81. package/src/main.js +0 -170
  82. package/src/prerunner/index.js +0 -252
  83. package/src/services/alias-settings.js +0 -51
  84. package/src/services/alias-shell-writer.js +0 -1018
  85. package/src/services/alias-store.js +0 -129
  86. package/src/services/claude-cli.js +0 -189
  87. package/src/services/claude-runner.js +0 -28
  88. package/src/services/claude-settings.js +0 -1223
  89. package/src/services/gitignore-detector.js +0 -155
  90. package/src/services/gitignore-fixer.js +0 -231
  91. package/src/services/gitignore-prerun.js +0 -46
  92. package/src/services/gitignore-resolver.js +0 -143
  93. package/src/services/gitignore-service.js +0 -117
  94. package/src/services/local-marketplace.js +0 -339
  95. package/src/services/marketplace-fetcher.js +0 -96
  96. package/src/services/marketplace-sync.js +0 -94
  97. package/src/services/mcp-registry.js +0 -87
  98. package/src/services/plugin-manager.js +0 -473
  99. package/src/services/plugin-mcp-config.js +0 -177
  100. package/src/services/plugin-setup.js +0 -499
  101. package/src/services/plugin-version-check.js +0 -262
  102. package/src/services/profiles.js +0 -161
  103. package/src/services/settings-manager.js +0 -243
  104. package/src/services/skills-manager.js +0 -393
  105. package/src/services/skillsmp-client.js +0 -87
  106. package/src/services/update-cache.js +0 -52
  107. package/src/services/version-check.js +0 -99
  108. package/src/types/gitignore.js +0 -6
  109. package/src/types/index.js +0 -1
  110. package/src/ui/App.js +0 -355
  111. package/src/ui/adapters/pluginsAdapter.js +0 -139
  112. package/src/ui/adapters/settingsAdapter.js +0 -111
  113. package/src/ui/adapters/skillsAdapter.js +0 -154
  114. package/src/ui/components/CategoryHeader.js +0 -9
  115. package/src/ui/components/EmptyFilterState.js +0 -4
  116. package/src/ui/components/FlagDetailEditor.js +0 -0
  117. package/src/ui/components/ScopeIndicator.js +0 -30
  118. package/src/ui/components/ScrollableList.js +0 -36
  119. package/src/ui/components/SearchInput.js +0 -19
  120. package/src/ui/components/StyledText.js +0 -39
  121. package/src/ui/components/TabBar.js +0 -19
  122. package/src/ui/components/layout/FooterHints.js +0 -29
  123. package/src/ui/components/layout/Panel.js +0 -6
  124. package/src/ui/components/layout/ProgressBar.js +0 -14
  125. package/src/ui/components/layout/ScopeTabs.js +0 -6
  126. package/src/ui/components/layout/ScreenLayout.js +0 -16
  127. package/src/ui/components/layout/index.js +0 -5
  128. package/src/ui/components/modals/ConfirmModal.js +0 -14
  129. package/src/ui/components/modals/InputModal.js +0 -5
  130. package/src/ui/components/modals/LoadingModal.js +0 -17
  131. package/src/ui/components/modals/MessageModal.js +0 -16
  132. package/src/ui/components/modals/ModalContainer.js +0 -137
  133. package/src/ui/components/modals/SelectModal.js +0 -18
  134. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  135. package/src/ui/components/modals/index.js +0 -6
  136. package/src/ui/components/primitives/ActionHints.js +0 -13
  137. package/src/ui/components/primitives/DetailSection.js +0 -7
  138. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  139. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  140. package/src/ui/components/primitives/MetaText.js +0 -8
  141. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  142. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  143. package/src/ui/components/primitives/SelectableRow.js +0 -5
  144. package/src/ui/components/primitives/index.js +0 -8
  145. package/src/ui/hooks/index.js +0 -4
  146. package/src/ui/hooks/useAsyncData.js +0 -77
  147. package/src/ui/hooks/useGitignoreModal.js +0 -74
  148. package/src/ui/hooks/useKeyboard.js +0 -13
  149. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  150. package/src/ui/hooks/useMismatchModal.js +0 -77
  151. package/src/ui/registry.js +0 -1
  152. package/src/ui/renderers/cliToolRenderers.js +0 -54
  153. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  154. package/src/ui/renderers/mcpRenderers.js +0 -26
  155. package/src/ui/renderers/pluginRenderers.js +0 -124
  156. package/src/ui/renderers/profileRenderers.js +0 -177
  157. package/src/ui/renderers/settingsRenderers.js +0 -73
  158. package/src/ui/renderers/skillRenderers.js +0 -138
  159. package/src/ui/screens/AliasScreen.js +0 -1111
  160. package/src/ui/screens/CliToolsScreen.js +0 -338
  161. package/src/ui/screens/EnvVarsScreen.js +0 -152
  162. package/src/ui/screens/GitignoreScreen.js +0 -328
  163. package/src/ui/screens/McpRegistryScreen.js +0 -238
  164. package/src/ui/screens/McpScreen.js +0 -176
  165. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  166. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  167. package/src/ui/screens/PluginsScreen.js +0 -769
  168. package/src/ui/screens/ProfilesScreen.js +0 -298
  169. package/src/ui/screens/SkillsScreen.js +0 -549
  170. package/src/ui/screens/StatusLineScreen.js +0 -206
  171. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  172. package/src/ui/screens/index.js +0 -10
  173. package/src/ui/state/AnimationContext.js +0 -34
  174. package/src/ui/state/AppContext.js +0 -162
  175. package/src/ui/state/DimensionsContext.js +0 -71
  176. package/src/ui/state/reducer.js +0 -547
  177. package/src/ui/state/types.js +0 -1
  178. package/src/ui/theme.js +0 -47
  179. package/src/utils/clipboard.js +0 -56
  180. package/src/utils/command-utils.js +0 -19
  181. package/src/utils/fuzzy-search.js +0 -101
  182. package/src/utils/string-utils.js +0 -62
package/src/ui/App.js DELETED
@@ -1,355 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { useRenderer } from "@opentui/react";
4
- import fs from "node:fs";
5
- import { AppProvider, useApp, useNavigation, useModal, } from "./state/AppContext.js";
6
- import { DimensionsProvider, useDimensions, } from "./state/DimensionsContext.js";
7
- import { ModalContainer } from "./components/modals/index.js";
8
- import { PluginsScreen, McpScreen, McpRegistryScreen, SettingsScreen, CliToolsScreen, ModelSelectorScreen, ProfilesScreen, SkillsScreen, GitignoreScreen, AliasScreen, } from "./screens/index.js";
9
- import { checkGitignore } from "../services/gitignore-prerun.js";
10
- import { loadGitignoreState, applyAllSafeFixes, } from "../services/gitignore-service.js";
11
- import { useGitignoreModal } from "./hooks/useGitignoreModal.js";
12
- import { repairAllMarketplaces } from "../services/local-marketplace.js";
13
- import { migrateMarketplaceRename, recoverMarketplaceSettings, } from "../services/claude-settings.js";
14
- import { autoAddMissingMarketplaces } from "../services/marketplace-sync.js";
15
- import { isClaudeAvailable } from "../services/claude-cli.js";
16
- import { checkPluginVersionMismatches, } from "../services/plugin-version-check.js";
17
- import { useMismatchModal } from "./hooks/useMismatchModal.js";
18
- import { checkForUpdates, getCurrentVersion, } from "../services/version-check.js";
19
- import { useKeyboardHandler } from "./hooks/useKeyboardHandler.js";
20
- import { ProgressBar } from "./components/layout/ProgressBar.js";
21
- export const VERSION = getCurrentVersion();
22
- /**
23
- * Router Component
24
- * Renders the current screen based on route
25
- */
26
- function Router() {
27
- const { state } = useApp();
28
- const { currentRoute } = state;
29
- switch (currentRoute.screen) {
30
- case "plugins":
31
- return _jsx(PluginsScreen, {});
32
- case "mcp":
33
- return _jsx(McpScreen, {});
34
- case "mcp-registry":
35
- return _jsx(McpRegistryScreen, {});
36
- case "settings":
37
- return _jsx(SettingsScreen, {});
38
- case "cli-tools":
39
- return _jsx(CliToolsScreen, {});
40
- case "model-selector":
41
- return _jsx(ModelSelectorScreen, {});
42
- case "profiles":
43
- return _jsx(ProfilesScreen, {});
44
- case "skills":
45
- return _jsx(SkillsScreen, {});
46
- case "gitignore":
47
- return _jsx(GitignoreScreen, {});
48
- case "alias":
49
- return _jsx(AliasScreen, {});
50
- default:
51
- return _jsx(PluginsScreen, {});
52
- }
53
- }
54
- /**
55
- * GlobalKeyHandler Component
56
- * Handles global keyboard shortcuts (1-8, Tab, Escape, q, Ctrl+C, ?, Shift+D)
57
- * Does not render anything (returns null)
58
- */
59
- function GlobalKeyHandler({ onDebugToggle, onExit, }) {
60
- const { state } = useApp();
61
- const { navigateToScreen } = useNavigation();
62
- const modal = useModal();
63
- const renderer = useRenderer();
64
- useKeyboardHandler((input, key) => {
65
- // Debug key (Shift+D) - always available
66
- if (input === "D" && key.shift) {
67
- onDebugToggle();
68
- // Write debug info to file
69
- const debugInfo = {
70
- timestamp: new Date().toISOString(),
71
- terminal: { rows: renderer.height, columns: renderer.width },
72
- state: {
73
- currentRoute: state.currentRoute,
74
- isSearching: state.isSearching,
75
- modal: state.modal ? { type: state.modal.type } : null,
76
- plugins: {
77
- scope: state.plugins.scope,
78
- selectedIndex: state.plugins.selectedIndex,
79
- searchQuery: state.plugins.searchQuery,
80
- marketplacesStatus: state.plugins.marketplaces.status,
81
- pluginsStatus: state.plugins.plugins.status,
82
- },
83
- },
84
- };
85
- fs.writeFileSync("/tmp/claudeup-debug.json", JSON.stringify(debugInfo, null, 2));
86
- return;
87
- }
88
- // Don't handle keys when modal is open or searching
89
- if (state.modal || state.isSearching)
90
- return;
91
- // Global navigation shortcuts (1-8) - include mcp-registry as it's a sub-screen of mcp
92
- const isTopLevel = [
93
- "plugins",
94
- "mcp",
95
- "mcp-registry",
96
- "settings",
97
- "cli-tools",
98
- "model-selector",
99
- "profiles",
100
- "skills",
101
- "gitignore",
102
- "alias",
103
- ].includes(state.currentRoute.screen);
104
- if (isTopLevel) {
105
- if (input === "1")
106
- navigateToScreen("plugins");
107
- else if (input === "2")
108
- navigateToScreen("skills");
109
- else if (input === "3")
110
- navigateToScreen("mcp");
111
- else if (input === "4")
112
- navigateToScreen("settings");
113
- else if (input === "5")
114
- navigateToScreen("profiles");
115
- else if (input === "6")
116
- navigateToScreen("cli-tools");
117
- else if (input === "7")
118
- navigateToScreen("gitignore");
119
- else if (input === "8")
120
- navigateToScreen("alias");
121
- // Tab navigation cycling
122
- if (key.tab) {
123
- const screens = [
124
- "plugins",
125
- "skills",
126
- "mcp",
127
- "settings",
128
- "profiles",
129
- "cli-tools",
130
- "gitignore",
131
- "alias",
132
- ];
133
- const currentIndex = screens.indexOf(state.currentRoute.screen);
134
- if (currentIndex !== -1) {
135
- const nextIndex = key.shift
136
- ? (currentIndex - 1 + screens.length) % screens.length
137
- : (currentIndex + 1) % screens.length;
138
- navigateToScreen(screens[nextIndex]);
139
- }
140
- }
141
- }
142
- // q or Ctrl+C quits from anywhere — no need to bounce to home first.
143
- if (input === "q" || (key.ctrl && input === "c")) {
144
- onExit();
145
- return;
146
- }
147
- // Escape goes back: registry → mcp, any other tab → plugins home,
148
- // already-home → exit. But when a screen has a focused sub-view open
149
- // (e.g. the Alias flag-detail editor), that sub-view owns Escape and
150
- // backs out to itself — don't let the global handler jump to home.
151
- // Scoped to the Escape branch ONLY so q/Ctrl+C still quit from anywhere.
152
- if (key.escape) {
153
- if (state.detailActive)
154
- return;
155
- if (state.currentRoute.screen === "plugins") {
156
- onExit();
157
- }
158
- else if (state.currentRoute.screen === "mcp-registry") {
159
- navigateToScreen("mcp");
160
- }
161
- else {
162
- navigateToScreen("plugins");
163
- }
164
- }
165
- // ? for help
166
- if (input === "?") {
167
- modal.message("claudeup Help", `Navigation
168
- ↑/↓ or j/k Move selection
169
- Enter Select / Toggle
170
- Escape Back (to home)
171
- q / Ctrl+C Quit (from anywhere)
172
- ? This help
173
-
174
- Quick Navigation
175
- 1 Plugins 4 Settings 7 Git State
176
- 2 Skills 5 Profiles 8 Alias
177
- 3 MCP Servers 6 CLI Tools
178
-
179
- Plugin Actions
180
- u Update d Uninstall
181
- a Update All r Refresh
182
-
183
- MCP Servers
184
- / Search local + remote
185
- r Browse MCP registry`, "info");
186
- }
187
- });
188
- return null;
189
- }
190
- /**
191
- * UpdateBanner Component
192
- * Shows version update notification
193
- */
194
- function UpdateBanner({ result }) {
195
- if (!result.updateAvailable)
196
- return null;
197
- return (_jsxs("box", { paddingLeft: 1, paddingRight: 1, children: [_jsx("text", { bg: "yellow", fg: "black", children: _jsx("strong", { children: " UPDATE " }) }), _jsxs("text", { fg: "yellow", children: [" ", "v", result.currentVersion, " \u2192 v", result.latestVersion] }), _jsx("text", { fg: "gray", children: " Run: " }), _jsx("text", { fg: "cyan", children: "claudeup update" })] }));
198
- }
199
- /**
200
- * ProgressIndicator Component
201
- * Shows progress bar when operations are running
202
- */
203
- function ProgressIndicator({ message, current, total, }) {
204
- return (_jsx("box", { paddingLeft: 1, paddingRight: 1, children: _jsx(ProgressBar, { message: message, current: current, total: total }) }));
205
- }
206
- function AppContentInner({ showDebug, onDebugToggle, updateInfo, onExit, recoveryReport, }) {
207
- const { state } = useApp();
208
- const { progress } = state;
209
- const dimensions = useDimensions();
210
- return (_jsxs("box", { flexDirection: "column", height: dimensions.terminalHeight, children: [updateInfo?.updateAvailable && _jsx(UpdateBanner, { result: updateInfo }), recoveryReport && (_jsx("box", { paddingLeft: 1, paddingRight: 1, children: _jsxs("text", { fg: "green", children: ["\u2713 Fixed: ", recoveryReport] }) })), showDebug && (_jsx("box", { paddingLeft: 1, paddingRight: 1, children: _jsxs("text", { fg: "#888888", children: ["DEBUG: ", dimensions.terminalWidth, "x", dimensions.terminalHeight, " | content=", dimensions.contentHeight, " | screen=", state.currentRoute.screen] }) })), progress && _jsx(ProgressIndicator, { ...progress }), _jsx("box", { flexDirection: "column", height: dimensions.contentHeight, paddingLeft: 1, paddingRight: 1, children: _jsx(Router, {}) }), _jsx(GlobalKeyHandler, { onDebugToggle: onDebugToggle, onExit: onExit }), _jsx(ModalContainer, {})] }));
211
- }
212
- function AppContent({ onExit }) {
213
- const { state, dispatch } = useApp();
214
- const { progress } = state;
215
- const [showDebug, setShowDebug] = useState(false);
216
- const [updateInfo, setUpdateInfo] = useState(null);
217
- const [recoveryReport, setRecoveryReport] = useState(null);
218
- const [mismatchData, setMismatchData] = useState(null);
219
- const mismatchModal = useMismatchModal();
220
- const [gitignoreData, setGitignoreData] = useState(null);
221
- const gitignoreModal = useGitignoreModal();
222
- // Check for updates on startup (non-blocking)
223
- useEffect(() => {
224
- checkForUpdates()
225
- .then(setUpdateInfo)
226
- .catch(() => { });
227
- }, []);
228
- // Auto-dismiss recovery banner after 5 seconds
229
- useEffect(() => {
230
- if (!recoveryReport)
231
- return;
232
- const timer = setTimeout(() => setRecoveryReport(null), 5000);
233
- return () => clearTimeout(timer);
234
- }, [recoveryReport]);
235
- // Show mismatch modal when data arrives
236
- useEffect(() => {
237
- if (!mismatchData || mismatchData.length === 0)
238
- return;
239
- mismatchModal.show(mismatchData);
240
- setMismatchData(null);
241
- }, [mismatchData, mismatchModal]);
242
- // Show gitignore modal — wait for the modal slot to be free so we don't
243
- // stomp on the mismatch modal that may have just been shown.
244
- useEffect(() => {
245
- if (!gitignoreData)
246
- return;
247
- if (state.modal)
248
- return;
249
- gitignoreModal.show({
250
- violationCount: gitignoreData.violationCount,
251
- safeCount: gitignoreData.safeCount,
252
- onFixSafe: async () => {
253
- const s = await loadGitignoreState(state.projectPath);
254
- await applyAllSafeFixes(state.projectPath, s.violations);
255
- },
256
- });
257
- setGitignoreData(null);
258
- }, [gitignoreData, gitignoreModal, state.modal, state.projectPath]);
259
- // Auto-refresh marketplaces on startup
260
- useEffect(() => {
261
- const noRefresh = process.argv.includes("--no-refresh");
262
- if (noRefresh)
263
- return;
264
- dispatch({
265
- type: "SHOW_PROGRESS",
266
- state: { message: "Scanning marketplaces..." },
267
- });
268
- // Migrate old marketplace names -> magus (idempotent), then repair plugin.json files
269
- migrateMarketplaceRename().catch(() => { }); // non-blocking, best-effort
270
- // Auto-add marketplaces referenced by enabledPlugins but missing from
271
- // the local registry. Fixes the "fresh clone of a project whose
272
- // .claude/settings.json enables @magus plugins but this machine has
273
- // never run `claude plugin marketplace add`" case.
274
- (async () => {
275
- try {
276
- if (!(await isClaudeAvailable()))
277
- return;
278
- const added = await autoAddMissingMarketplaces(process.cwd());
279
- if (added.length > 0) {
280
- setRecoveryReport(`added marketplace(s): ${added.join(", ")}`);
281
- }
282
- }
283
- catch {
284
- // non-fatal: user can still install marketplaces manually
285
- }
286
- })();
287
- // Recover stale marketplace registry entries (e.g. "directory" -> "github")
288
- recoverMarketplaceSettings()
289
- .then(async (recovery) => {
290
- const parts = [];
291
- if (recovery.reregistered.length > 0) {
292
- // Update the marketplace clone now that the source is fixed
293
- const { updateMarketplace } = await import("../services/claude-cli.js");
294
- for (const mp of recovery.reregistered) {
295
- try {
296
- await updateMarketplace(mp);
297
- parts.push(`${mp} refreshed`);
298
- }
299
- catch {
300
- parts.push(`${mp} (update failed)`);
301
- }
302
- }
303
- }
304
- if (recovery.enabledAutoUpdate.length > 0) {
305
- parts.push(`auto-update: ${recovery.enabledAutoUpdate.join(", ")}`);
306
- }
307
- if (recovery.removed.length > 0) {
308
- parts.push(`removed: ${recovery.removed.join(", ")}`);
309
- }
310
- if (parts.length > 0) {
311
- setRecoveryReport(parts.join(" | "));
312
- }
313
- })
314
- .catch(() => { }); // non-fatal
315
- // Check for plugin version mismatches (the [0] index bug)
316
- checkPluginVersionMismatches(process.cwd())
317
- .then((mismatches) => {
318
- if (mismatches.length > 0) {
319
- setMismatchData(mismatches);
320
- }
321
- })
322
- .catch(() => { }); // non-fatal
323
- // Check for gitignore manifest violations and surface a popup if any.
324
- // Loads twice (once for count, once when user picks "fix safe") because
325
- // the second load happens after edits — keeps detection cheap on startup.
326
- (async () => {
327
- try {
328
- const result = await checkGitignore(process.cwd());
329
- if (result.violationCount > 0) {
330
- const s = await loadGitignoreState(process.cwd());
331
- const safeCount = s.violations.filter((v) => v.severity === "safe").length;
332
- setGitignoreData({ violationCount: result.violationCount, safeCount });
333
- }
334
- }
335
- catch {
336
- // non-fatal
337
- }
338
- })();
339
- repairAllMarketplaces()
340
- .then(async () => {
341
- dispatch({ type: "HIDE_PROGRESS" });
342
- dispatch({ type: "DATA_REFRESH_COMPLETE" });
343
- })
344
- .catch(() => {
345
- dispatch({ type: "HIDE_PROGRESS" });
346
- });
347
- }, [dispatch]);
348
- // Count transient banners for dimension calculation
349
- const transientBannerCount = recoveryReport ? 1 : 0;
350
- return (_jsx(DimensionsProvider, { showProgress: !!progress, showDebug: showDebug, showUpdateBanner: !!updateInfo?.updateAvailable, transientBannerCount: transientBannerCount, children: _jsx(AppContentInner, { showDebug: showDebug, onDebugToggle: () => setShowDebug((s) => !s), updateInfo: updateInfo, onExit: onExit, recoveryReport: recoveryReport }) }));
351
- }
352
- export function App({ onExit }) {
353
- return (_jsx(AppProvider, { children: _jsx(AppContent, { onExit: onExit }) }));
354
- }
355
- export default App;
@@ -1,139 +0,0 @@
1
- // Virtual marketplace name for the community sub-section of claude-plugins-official
2
- export const COMMUNITY_VIRTUAL_MARKETPLACE = "claude-plugins-official:community";
3
- // The marketplace that gets split into Anthropic Official + Community sections
4
- export const SPLIT_MARKETPLACE = "claude-plugins-official";
5
- /**
6
- * Derives tone and badge for a category item based on marketplace identity.
7
- */
8
- function categoryStyling(mp, isCommunitySection, marketplaceEnabled) {
9
- if (!marketplaceEnabled) {
10
- return { tone: "gray" };
11
- }
12
- if (isCommunitySection) {
13
- return { tone: "gray", badge: "3rd Party" };
14
- }
15
- if (mp.name === "claude-plugins-official") {
16
- return { tone: "yellow", badge: "★ Official" };
17
- }
18
- if (mp.name === "claude-code-plugins") {
19
- return { tone: "red", badge: "⚠ Deprecated" };
20
- }
21
- if (mp.official) {
22
- return { tone: "yellow", badge: "★ Official" };
23
- }
24
- return { tone: "green", badge: "✓ Added" };
25
- }
26
- /**
27
- * Builds the flat list of items for the PluginsScreen list panel.
28
- * Extracted from PluginsScreen so it can be tested independently.
29
- */
30
- export function buildPluginBrowserItems({ marketplaces, plugins, collapsedMarketplaces, }) {
31
- const pluginsByMarketplace = new Map();
32
- for (const plugin of plugins) {
33
- const existing = pluginsByMarketplace.get(plugin.marketplace) || [];
34
- existing.push(plugin);
35
- pluginsByMarketplace.set(plugin.marketplace, existing);
36
- }
37
- // Sort marketplaces: deprecated ones go to the bottom
38
- const sortedMarketplaces = [...marketplaces].sort((a, b) => {
39
- const aDeprecated = a.name === "claude-code-plugins" ? 1 : 0;
40
- const bDeprecated = b.name === "claude-code-plugins" ? 1 : 0;
41
- return aDeprecated - bDeprecated;
42
- });
43
- const items = [];
44
- for (const marketplace of sortedMarketplaces) {
45
- const marketplacePlugins = pluginsByMarketplace.get(marketplace.name) || [];
46
- const isCollapsed = collapsedMarketplaces.has(marketplace.name);
47
- const isEnabled = marketplacePlugins.length > 0 || !!marketplace.official;
48
- const hasPlugins = marketplacePlugins.length > 0;
49
- // Special handling: split claude-plugins-official into two sub-sections
50
- if (marketplace.name === SPLIT_MARKETPLACE && hasPlugins) {
51
- const anthropicPlugins = marketplacePlugins.filter((p) => p.author?.name?.toLowerCase() === "anthropic");
52
- const communityPlugins = marketplacePlugins.filter((p) => p.author?.name?.toLowerCase() !== "anthropic");
53
- // Sub-section 1: Anthropic Official (plugins by Anthropic)
54
- const anthropicCollapsed = collapsedMarketplaces.has(marketplace.name);
55
- const anthropicHasPlugins = anthropicPlugins.length > 0;
56
- const anthropicStyle = categoryStyling(marketplace, false, isEnabled);
57
- items.push({
58
- id: `mp:${marketplace.name}`,
59
- kind: "category",
60
- label: marketplace.displayName,
61
- marketplace,
62
- marketplaceEnabled: isEnabled,
63
- pluginCount: anthropicPlugins.length,
64
- isExpanded: !anthropicCollapsed && anthropicHasPlugins,
65
- tone: anthropicStyle.tone,
66
- badge: anthropicStyle.badge,
67
- });
68
- if (isEnabled && anthropicHasPlugins && !anthropicCollapsed) {
69
- for (const plugin of anthropicPlugins) {
70
- items.push({
71
- id: `pl:${plugin.id}`,
72
- kind: "plugin",
73
- label: plugin.name,
74
- plugin,
75
- });
76
- }
77
- }
78
- // Sub-section 2: Community (third-party plugins in same marketplace)
79
- if (communityPlugins.length > 0) {
80
- const communityVirtualMp = {
81
- name: COMMUNITY_VIRTUAL_MARKETPLACE,
82
- displayName: "Anthropic Official — 3rd Party",
83
- source: marketplace.source,
84
- description: "Third-party plugins in the Anthropic Official marketplace",
85
- };
86
- const communityCollapsed = collapsedMarketplaces.has(COMMUNITY_VIRTUAL_MARKETPLACE);
87
- const communityStyle = categoryStyling(communityVirtualMp, true, true);
88
- items.push({
89
- id: `mp:${COMMUNITY_VIRTUAL_MARKETPLACE}`,
90
- kind: "category",
91
- label: "Anthropic Official — 3rd Party",
92
- marketplace: communityVirtualMp,
93
- marketplaceEnabled: true,
94
- pluginCount: communityPlugins.length,
95
- isExpanded: !communityCollapsed,
96
- isCommunitySection: true,
97
- tone: communityStyle.tone,
98
- badge: communityStyle.badge,
99
- });
100
- if (!communityCollapsed) {
101
- for (const plugin of communityPlugins) {
102
- items.push({
103
- id: `pl:${plugin.id}`,
104
- kind: "plugin",
105
- label: plugin.name,
106
- plugin,
107
- });
108
- }
109
- }
110
- }
111
- continue;
112
- }
113
- // Category header (marketplace)
114
- const style = categoryStyling(marketplace, false, isEnabled);
115
- items.push({
116
- id: `mp:${marketplace.name}`,
117
- kind: "category",
118
- label: marketplace.displayName,
119
- marketplace,
120
- marketplaceEnabled: isEnabled,
121
- pluginCount: marketplacePlugins.length,
122
- isExpanded: !isCollapsed && hasPlugins,
123
- tone: style.tone,
124
- badge: style.badge,
125
- });
126
- // Plugins under this marketplace (if expanded)
127
- if (isEnabled && hasPlugins && !isCollapsed) {
128
- for (const plugin of marketplacePlugins) {
129
- items.push({
130
- id: `pl:${plugin.id}`,
131
- kind: "plugin",
132
- label: plugin.name,
133
- plugin,
134
- });
135
- }
136
- }
137
- }
138
- return items;
139
- }
@@ -1,111 +0,0 @@
1
- import { SETTINGS_CATALOG, } from "../../data/settings-catalog.js";
2
- // ─── Constants ─────────────────────────────────────────────────────────────────
3
- export const CATEGORY_LABELS = {
4
- recommended: "Recommended",
5
- agents: "Agents & Teams",
6
- models: "Models & Thinking",
7
- workflow: "Workflow",
8
- terminal: "Terminal & UI",
9
- performance: "Performance",
10
- advanced: "Advanced",
11
- };
12
- export const CATEGORY_ORDER = [
13
- "recommended",
14
- "agents",
15
- "models",
16
- "workflow",
17
- "terminal",
18
- "performance",
19
- "advanced",
20
- ];
21
- export const CATEGORY_BG = {
22
- recommended: "#2e7d32",
23
- agents: "#00838f",
24
- models: "#4527a0",
25
- workflow: "#1565c0",
26
- terminal: "#4e342e",
27
- performance: "#6a1b9a",
28
- advanced: "#e65100",
29
- };
30
- export const CATEGORY_COLOR = {
31
- recommended: "green",
32
- agents: "cyan",
33
- models: "cyan",
34
- workflow: "blue",
35
- terminal: "blue",
36
- performance: "magentaBright",
37
- advanced: "yellow",
38
- };
39
- export const CATEGORY_DESCRIPTIONS = {
40
- recommended: "Most impactful settings every user should know.",
41
- agents: "Agent teams, task lists, and subagent configuration.",
42
- models: "Model selection, extended thinking, and effort.",
43
- workflow: "Git, plans, permissions, output style, and languages.",
44
- terminal: "Shell, spinners, progress bars, voice, and UI behavior.",
45
- performance: "Compaction, token limits, timeouts, and caching.",
46
- advanced: "Telemetry, updates, debugging, and internal controls.",
47
- };
48
- // ─── Helpers ───────────────────────────────────────────────────────────────────
49
- /** Get the effective value (project overrides user) */
50
- export function getEffectiveValue(scoped) {
51
- return scoped.project !== undefined ? scoped.project : scoped.user;
52
- }
53
- export function formatValue(setting, value) {
54
- if (value === undefined || value === "") {
55
- if (setting.type === "boolean" && setting.defaultValue !== undefined) {
56
- return setting.defaultValue === "true" ? "on" : "off";
57
- }
58
- if (setting.type === "select" && setting.defaultValue !== undefined) {
59
- const opt = setting.options?.find((o) => o.value === setting.defaultValue);
60
- return opt ? opt.label : setting.defaultValue;
61
- }
62
- return "—";
63
- }
64
- if (setting.type === "boolean") {
65
- return value === "true" || value === "1" ? "on" : "off";
66
- }
67
- if (setting.type === "select" && setting.options) {
68
- const opt = setting.options.find((o) => o.value === value);
69
- return opt ? opt.label : value;
70
- }
71
- if (value.length > 12) {
72
- return value.slice(0, 12) + "…";
73
- }
74
- return value;
75
- }
76
- // ─── Adapter ───────────────────────────────────────────────────────────────────
77
- /**
78
- * Builds the flat list of items for the SettingsScreen list panel.
79
- * Extracted from SettingsScreen so it can be tested independently.
80
- */
81
- export function buildSettingsBrowserItems(values) {
82
- const items = [];
83
- for (const category of CATEGORY_ORDER) {
84
- items.push({
85
- id: `cat:${category}`,
86
- kind: "category",
87
- label: CATEGORY_LABELS[category],
88
- category,
89
- isDefault: true,
90
- });
91
- const categorySettings = SETTINGS_CATALOG.filter((s) => s.category === category);
92
- for (const setting of categorySettings) {
93
- const scoped = values.get(setting.id) || {
94
- user: undefined,
95
- project: undefined,
96
- };
97
- const effective = getEffectiveValue(scoped);
98
- items.push({
99
- id: `setting:${setting.id}`,
100
- kind: "setting",
101
- label: setting.name,
102
- category,
103
- setting,
104
- scopedValues: scoped,
105
- effectiveValue: effective,
106
- isDefault: effective === undefined || effective === "",
107
- });
108
- }
109
- }
110
- return items;
111
- }