claudeup 4.19.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 (180) 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-refresh.test.ts +267 -0
  18. package/src/__tests__/plugin-requires.test.ts +133 -0
  19. package/src/__tests__/plugin-setup.test.ts +7 -0
  20. package/src/__tests__/profile-command.test.ts +116 -0
  21. package/src/__tests__/profile-materializer.test.ts +82 -0
  22. package/src/__tests__/profile-sync.test.ts +136 -0
  23. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  24. package/src/__tests__/resolve-executable.test.ts +42 -0
  25. package/src/__tests__/resolver.test.ts +218 -0
  26. package/src/__tests__/symlink-manager.test.ts +99 -0
  27. package/src/__tests__/toolchain.test.ts +56 -0
  28. package/src/cli/claude.ts +21 -0
  29. package/src/cli/doctor.ts +132 -0
  30. package/src/cli/install.ts +360 -0
  31. package/src/cli/profile.ts +166 -0
  32. package/src/cli/router.ts +107 -0
  33. package/src/cli/update.ts +85 -0
  34. package/src/data/cli-tools.ts +7 -7
  35. package/src/data/gitignore-defaults.ts +4 -0
  36. package/src/data/marketplaces.ts +12 -0
  37. package/src/data/predefined-profiles.ts +3 -4
  38. package/src/main.tsx +11 -154
  39. package/src/prerunner/index.ts +62 -27
  40. package/src/services/claude-cli.ts +19 -22
  41. package/src/services/claude-settings.ts +99 -26
  42. package/src/services/conventions-manager.ts +865 -0
  43. package/src/services/doctor-bins.ts +49 -0
  44. package/src/services/doctor.ts +509 -0
  45. package/src/services/install-plan.ts +107 -0
  46. package/src/services/manifest.ts +166 -0
  47. package/src/services/marketplace-refresh.ts +173 -0
  48. package/src/services/plugin-manager.ts +7 -1
  49. package/src/services/plugin-requires.ts +215 -0
  50. package/src/services/plugin-version-check.ts +9 -4
  51. package/src/services/profile-materializer.ts +61 -0
  52. package/src/services/profile-sync.ts +150 -0
  53. package/src/services/resolver.ts +293 -0
  54. package/src/services/symlink-manager.ts +146 -0
  55. package/src/services/toolchain.ts +97 -0
  56. package/src/services/version-check.ts +10 -11
  57. package/src/types/index.ts +138 -31
  58. package/src/ui/App.tsx +0 -4
  59. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  60. package/src/ui/screens/index.ts +0 -1
  61. package/src/ui/state/reducer.ts +0 -101
  62. package/src/ui/state/types.ts +0 -35
  63. package/src/utils/command-utils.ts +23 -0
  64. package/src/utils/file-locking.ts +144 -0
  65. package/src/data/alias-flags.js +0 -205
  66. package/src/data/cli-tools.js +0 -123
  67. package/src/data/gitignore-defaults.js +0 -24
  68. package/src/data/gitignore-reasons.js +0 -97
  69. package/src/data/gitignore-templates.js +0 -21
  70. package/src/data/marketplaces.js +0 -138
  71. package/src/data/mcp-servers.js +0 -509
  72. package/src/data/predefined-profiles.js +0 -248
  73. package/src/data/settings-catalog.js +0 -625
  74. package/src/data/skill-repos.js +0 -160
  75. package/src/data/statuslines.js +0 -159
  76. package/src/data/statuslines.ts +0 -188
  77. package/src/index.js +0 -4
  78. package/src/index.ts +0 -5
  79. package/src/main.js +0 -170
  80. package/src/prerunner/index.js +0 -252
  81. package/src/services/alias-settings.js +0 -51
  82. package/src/services/alias-shell-writer.js +0 -1018
  83. package/src/services/alias-store.js +0 -129
  84. package/src/services/claude-cli.js +0 -189
  85. package/src/services/claude-runner.js +0 -28
  86. package/src/services/claude-settings.js +0 -1223
  87. package/src/services/gitignore-detector.js +0 -155
  88. package/src/services/gitignore-fixer.js +0 -231
  89. package/src/services/gitignore-prerun.js +0 -46
  90. package/src/services/gitignore-resolver.js +0 -143
  91. package/src/services/gitignore-service.js +0 -117
  92. package/src/services/local-marketplace.js +0 -339
  93. package/src/services/marketplace-fetcher.js +0 -96
  94. package/src/services/marketplace-sync.js +0 -94
  95. package/src/services/mcp-registry.js +0 -87
  96. package/src/services/plugin-manager.js +0 -473
  97. package/src/services/plugin-mcp-config.js +0 -177
  98. package/src/services/plugin-setup.js +0 -499
  99. package/src/services/plugin-version-check.js +0 -262
  100. package/src/services/profiles.js +0 -161
  101. package/src/services/settings-manager.js +0 -243
  102. package/src/services/skills-manager.js +0 -393
  103. package/src/services/skillsmp-client.js +0 -87
  104. package/src/services/update-cache.js +0 -52
  105. package/src/services/version-check.js +0 -99
  106. package/src/types/gitignore.js +0 -6
  107. package/src/types/index.js +0 -1
  108. package/src/ui/App.js +0 -355
  109. package/src/ui/adapters/pluginsAdapter.js +0 -139
  110. package/src/ui/adapters/settingsAdapter.js +0 -111
  111. package/src/ui/adapters/skillsAdapter.js +0 -154
  112. package/src/ui/components/CategoryHeader.js +0 -9
  113. package/src/ui/components/EmptyFilterState.js +0 -4
  114. package/src/ui/components/FlagDetailEditor.js +0 -0
  115. package/src/ui/components/ScopeIndicator.js +0 -30
  116. package/src/ui/components/ScrollableList.js +0 -36
  117. package/src/ui/components/SearchInput.js +0 -19
  118. package/src/ui/components/StyledText.js +0 -39
  119. package/src/ui/components/TabBar.js +0 -19
  120. package/src/ui/components/layout/FooterHints.js +0 -29
  121. package/src/ui/components/layout/Panel.js +0 -6
  122. package/src/ui/components/layout/ProgressBar.js +0 -14
  123. package/src/ui/components/layout/ScopeTabs.js +0 -6
  124. package/src/ui/components/layout/ScreenLayout.js +0 -16
  125. package/src/ui/components/layout/index.js +0 -5
  126. package/src/ui/components/modals/ConfirmModal.js +0 -14
  127. package/src/ui/components/modals/InputModal.js +0 -5
  128. package/src/ui/components/modals/LoadingModal.js +0 -17
  129. package/src/ui/components/modals/MessageModal.js +0 -16
  130. package/src/ui/components/modals/ModalContainer.js +0 -137
  131. package/src/ui/components/modals/SelectModal.js +0 -18
  132. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  133. package/src/ui/components/modals/index.js +0 -6
  134. package/src/ui/components/primitives/ActionHints.js +0 -13
  135. package/src/ui/components/primitives/DetailSection.js +0 -7
  136. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  137. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  138. package/src/ui/components/primitives/MetaText.js +0 -8
  139. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  140. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  141. package/src/ui/components/primitives/SelectableRow.js +0 -5
  142. package/src/ui/components/primitives/index.js +0 -8
  143. package/src/ui/hooks/index.js +0 -4
  144. package/src/ui/hooks/useAsyncData.js +0 -77
  145. package/src/ui/hooks/useGitignoreModal.js +0 -74
  146. package/src/ui/hooks/useKeyboard.js +0 -13
  147. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  148. package/src/ui/hooks/useMismatchModal.js +0 -77
  149. package/src/ui/registry.js +0 -1
  150. package/src/ui/renderers/cliToolRenderers.js +0 -54
  151. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  152. package/src/ui/renderers/mcpRenderers.js +0 -26
  153. package/src/ui/renderers/pluginRenderers.js +0 -124
  154. package/src/ui/renderers/profileRenderers.js +0 -177
  155. package/src/ui/renderers/settingsRenderers.js +0 -73
  156. package/src/ui/renderers/skillRenderers.js +0 -138
  157. package/src/ui/screens/AliasScreen.js +0 -1111
  158. package/src/ui/screens/CliToolsScreen.js +0 -338
  159. package/src/ui/screens/EnvVarsScreen.js +0 -152
  160. package/src/ui/screens/GitignoreScreen.js +0 -328
  161. package/src/ui/screens/McpRegistryScreen.js +0 -238
  162. package/src/ui/screens/McpScreen.js +0 -176
  163. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  164. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  165. package/src/ui/screens/PluginsScreen.js +0 -769
  166. package/src/ui/screens/ProfilesScreen.js +0 -298
  167. package/src/ui/screens/SkillsScreen.js +0 -549
  168. package/src/ui/screens/StatusLineScreen.js +0 -206
  169. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  170. package/src/ui/screens/index.js +0 -10
  171. package/src/ui/state/AnimationContext.js +0 -34
  172. package/src/ui/state/AppContext.js +0 -162
  173. package/src/ui/state/DimensionsContext.js +0 -71
  174. package/src/ui/state/reducer.js +0 -547
  175. package/src/ui/state/types.js +0 -1
  176. package/src/ui/theme.js +0 -47
  177. package/src/utils/clipboard.js +0 -56
  178. package/src/utils/command-utils.js +0 -19
  179. package/src/utils/fuzzy-search.js +0 -101
  180. 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
- }