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,338 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/react/jsx-runtime";
2
- import { useEffect, useCallback, useState, useRef } from "react";
3
- import { exec } from "child_process";
4
- import { promisify } from "util";
5
- import { useApp, useModal } from "../state/AppContext.js";
6
- import { useDimensions } from "../state/DimensionsContext.js";
7
- import { useKeyboard } from "../hooks/useKeyboard.js";
8
- import { ScreenLayout } from "../components/layout/index.js";
9
- import { cliTools } from "../../data/cli-tools.js";
10
- import { renderCliToolRow, renderCliToolDetail, } from "../renderers/cliToolRenderers.js";
11
- import { ScrollableList } from "../components/ScrollableList.js";
12
- const execAsync = promisify(exec);
13
- // ─── Version helpers ───────────────────────────────────────────────────────────
14
- // Session-level cache
15
- let cachedToolStatuses = null;
16
- let cacheInitialized = false;
17
- function clearCliToolsCache() {
18
- cachedToolStatuses = null;
19
- cacheInitialized = false;
20
- }
21
- function parseVersion(versionOutput) {
22
- const match = versionOutput.match(/v?(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
23
- return match ? match[1] : undefined;
24
- }
25
- function methodFromPath(binPath) {
26
- if (binPath.includes("/.bun/"))
27
- return "bun";
28
- if (binPath.includes("/homebrew/") || binPath.includes("/Cellar/"))
29
- return "brew";
30
- if (binPath.includes("/.local/share/claude") || binPath.includes("/.local/bin/claude"))
31
- return "npm";
32
- if (binPath.includes("/.nvm/") || binPath.includes("/node_modules/"))
33
- return "npm";
34
- if (binPath.includes("/.local/share/pnpm") || binPath.includes("/pnpm/"))
35
- return "pnpm";
36
- if (binPath.includes("/.yarn/"))
37
- return "yarn";
38
- return null;
39
- }
40
- /** Extract brew formula name from a Cellar symlink target like ../Cellar/gemini-cli/0.35.2/bin/gemini */
41
- function extractBrewFormula(binPath, linkTarget) {
42
- // Try symlink target first: ../Cellar/{formula}/{version}/...
43
- const cellarMatch = linkTarget.match(/Cellar\/([^/]+)\//);
44
- if (cellarMatch)
45
- return cellarMatch[1];
46
- // Try binary path: /opt/homebrew/Cellar/{formula}/...
47
- const pathMatch = binPath.match(/Cellar\/([^/]+)\//);
48
- if (pathMatch)
49
- return pathMatch[1];
50
- return undefined;
51
- }
52
- async function detectInstallMethods(tool) {
53
- const fallback = tool.packageManager === "pip" ? "pip" : "unknown";
54
- try {
55
- // which -a returns all matching binaries across PATH
56
- const { stdout } = await execAsync(`which -a ${tool.name} 2>/dev/null`, {
57
- timeout: 3000,
58
- shell: "/bin/bash",
59
- });
60
- const paths = stdout.trim().split("\n").filter((p) => p && !p.includes("aliased"));
61
- if (paths.length === 0)
62
- return { primary: fallback, all: [] };
63
- const methods = [];
64
- let brewFormula;
65
- for (const binPath of paths) {
66
- let method = methodFromPath(binPath);
67
- let linkTarget = "";
68
- // Check symlink target
69
- try {
70
- const { stdout: lt } = await execAsync(`readlink "${binPath}" 2>/dev/null || true`, { timeout: 2000, shell: "/bin/bash" });
71
- linkTarget = lt.trim();
72
- if (!method)
73
- method = methodFromPath(linkTarget);
74
- }
75
- catch {
76
- // ignore
77
- }
78
- // Detect brew formula name
79
- if (method === "brew" && !brewFormula) {
80
- brewFormula = extractBrewFormula(binPath, linkTarget);
81
- }
82
- if (method && !methods.includes(method))
83
- methods.push(method);
84
- }
85
- if (methods.length === 0)
86
- return { primary: fallback, all: [] };
87
- return { primary: methods[0], all: methods, brewFormula };
88
- }
89
- catch {
90
- return { primary: fallback, all: [] };
91
- }
92
- }
93
- function getUninstallCommand(tool, method, brewFormula) {
94
- switch (method) {
95
- case "bun": return `bun remove -g ${tool.packageName}`;
96
- case "npm": return `npm uninstall -g ${tool.packageName}`;
97
- case "pnpm": return `pnpm remove -g ${tool.packageName}`;
98
- case "yarn": return `yarn global remove ${tool.packageName}`;
99
- case "brew": return `brew uninstall ${brewFormula || tool.name}`;
100
- case "pip": return `pip uninstall -y ${tool.packageName}`;
101
- default: return "";
102
- }
103
- }
104
- function getUpdateCommand(tool, method, brewFormula) {
105
- switch (method) {
106
- case "bun": return `bun install -g ${tool.packageName}`;
107
- case "npm": return `npm install -g ${tool.packageName}`;
108
- case "pnpm": return `pnpm install -g ${tool.packageName}`;
109
- case "yarn": return `yarn global add ${tool.packageName}`;
110
- case "brew": return `brew upgrade ${brewFormula || tool.name}`;
111
- case "pip": return tool.installCommand;
112
- default: return tool.installCommand;
113
- }
114
- }
115
- async function getInstalledVersion(tool) {
116
- try {
117
- const { stdout } = await execAsync(tool.checkCommand, { timeout: 5000 });
118
- return parseVersion(stdout.trim());
119
- }
120
- catch {
121
- return undefined;
122
- }
123
- }
124
- async function getLatestNpmVersion(packageName) {
125
- try {
126
- const { stdout } = await execAsync(`npm view ${packageName} version 2>/dev/null`, { timeout: 10000 });
127
- return stdout.trim() || undefined;
128
- }
129
- catch {
130
- return undefined;
131
- }
132
- }
133
- async function getLatestPipVersion(packageName) {
134
- try {
135
- const { stdout } = await execAsync(`pip index versions ${packageName} 2>/dev/null | head -1`, { timeout: 10000, shell: "/bin/bash" });
136
- const match = stdout.trim().match(/\(([^)]+)\)/);
137
- return match ? match[1] : undefined;
138
- }
139
- catch {
140
- return undefined;
141
- }
142
- }
143
- async function getLatestVersion(tool) {
144
- return tool.packageManager === "npm"
145
- ? getLatestNpmVersion(tool.packageName)
146
- : getLatestPipVersion(tool.packageName);
147
- }
148
- function compareVersions(v1, v2) {
149
- const parts1 = v1.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
150
- const parts2 = v2.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
151
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
152
- const p1 = parts1[i] || 0;
153
- const p2 = parts2[i] || 0;
154
- if (p1 < p2)
155
- return -1;
156
- if (p1 > p2)
157
- return 1;
158
- }
159
- return 0;
160
- }
161
- // ─── Component ─────────────────────────────────────────────────────────────────
162
- export function CliToolsScreen() {
163
- const { state, dispatch } = useApp();
164
- const { cliTools: cliToolsState } = state;
165
- const modal = useModal();
166
- const dimensions = useDimensions();
167
- const [toolStatuses, setToolStatuses] = useState(() => {
168
- return (cachedToolStatuses ||
169
- cliTools.map((tool) => ({
170
- tool,
171
- installed: false,
172
- installedVersion: undefined,
173
- checking: true,
174
- })));
175
- });
176
- const statusesRef = useRef(toolStatuses);
177
- statusesRef.current = toolStatuses;
178
- const updateToolStatus = useCallback((index, updates) => {
179
- setToolStatuses((prev) => {
180
- const newStatuses = [...prev];
181
- newStatuses[index] = { ...newStatuses[index], ...updates };
182
- cachedToolStatuses = newStatuses;
183
- return newStatuses;
184
- });
185
- }, []);
186
- const fetchVersionInfo = useCallback(async () => {
187
- for (let i = 0; i < cliTools.length; i++) {
188
- const tool = cliTools[i];
189
- // Run all checks in parallel, then update with all results
190
- Promise.all([
191
- getInstalledVersion(tool),
192
- getLatestVersion(tool),
193
- detectInstallMethods(tool),
194
- ]).then(([version, latest, info]) => {
195
- updateToolStatus(i, {
196
- installedVersion: version,
197
- installed: version !== undefined,
198
- latestVersion: latest,
199
- checking: false,
200
- hasUpdate: version && latest
201
- ? compareVersions(version, latest) < 0
202
- : false,
203
- installMethod: version ? info.primary : undefined,
204
- allMethods: version && info.all.length > 1 ? info.all : undefined,
205
- updateCommand: version ? getUpdateCommand(tool, info.primary, info.brewFormula) : undefined,
206
- brewFormula: info.brewFormula,
207
- });
208
- });
209
- }
210
- }, [updateToolStatus]);
211
- useEffect(() => {
212
- if (!cacheInitialized) {
213
- cacheInitialized = true;
214
- cachedToolStatuses = toolStatuses;
215
- fetchVersionInfo();
216
- }
217
- }, [fetchVersionInfo, toolStatuses]);
218
- useKeyboard((event) => {
219
- if (state.isSearching || state.modal)
220
- return;
221
- if (event.name === "up" || event.name === "k") {
222
- const newIndex = Math.max(0, cliToolsState.selectedIndex - 1);
223
- dispatch({ type: "CLITOOLS_SELECT", index: newIndex });
224
- }
225
- else if (event.name === "down" || event.name === "j") {
226
- const newIndex = Math.min(toolStatuses.length - 1, cliToolsState.selectedIndex + 1);
227
- dispatch({ type: "CLITOOLS_SELECT", index: newIndex });
228
- }
229
- else if (event.name === "r") {
230
- handleRefresh();
231
- }
232
- else if (event.name === "a") {
233
- handleUpdateAll();
234
- }
235
- else if (event.name === "c") {
236
- handleResolveConflict();
237
- }
238
- else if (event.name === "enter" || event.name === "return") {
239
- handleInstall();
240
- }
241
- });
242
- const handleRefresh = () => {
243
- clearCliToolsCache();
244
- setToolStatuses(cliTools.map((tool) => ({
245
- tool,
246
- installed: false,
247
- installedVersion: undefined,
248
- checking: true,
249
- })));
250
- cacheInitialized = false;
251
- fetchVersionInfo();
252
- };
253
- const runCommand = async (command) => {
254
- try {
255
- await execAsync(command, {
256
- shell: "/bin/bash",
257
- timeout: 60000,
258
- });
259
- return { ok: true };
260
- }
261
- catch (err) {
262
- const msg = err instanceof Error ? err.message : String(err);
263
- return { ok: false, error: msg };
264
- }
265
- };
266
- const handleInstall = async () => {
267
- const status = toolStatuses[cliToolsState.selectedIndex];
268
- if (!status)
269
- return;
270
- const { tool, installed, hasUpdate, updateCommand } = status;
271
- const action = !installed ? "Installing" : hasUpdate ? "Updating" : "Reinstalling";
272
- const command = installed && updateCommand ? updateCommand : tool.installCommand;
273
- modal.loading(`${action} ${tool.displayName}...`);
274
- const result = await runCommand(command);
275
- modal.hideModal();
276
- if (result.ok) {
277
- handleRefresh();
278
- }
279
- else {
280
- await modal.message("Error", `Failed to ${action.toLowerCase()} ${tool.displayName}.\n\nTry running manually:\n${command}`, "error");
281
- }
282
- };
283
- const handleResolveConflict = async () => {
284
- const status = toolStatuses[cliToolsState.selectedIndex];
285
- if (!status || !status.allMethods || status.allMethods.length < 2)
286
- return;
287
- const { tool, allMethods, installMethod, brewFormula } = status;
288
- // Let user pick which install to keep
289
- const options = allMethods.map((method) => ({
290
- label: `Keep ${method}${method === installMethod ? " (active)" : ""}, remove others`,
291
- value: method,
292
- }));
293
- const keep = await modal.select(`Resolve ${tool.displayName} conflict`, `Installed via ${allMethods.join(" + ")}. Which to keep?`, options);
294
- if (keep === null)
295
- return;
296
- const toRemove = allMethods.filter((m) => m !== keep);
297
- modal.loading(`Removing ${toRemove.join(", ")} install(s)...`);
298
- const errors = [];
299
- for (const method of toRemove) {
300
- const cmd = getUninstallCommand(tool, method, brewFormula);
301
- if (!cmd)
302
- continue;
303
- const result = await runCommand(cmd);
304
- if (!result.ok)
305
- errors.push(`${method}: ${cmd}`);
306
- }
307
- modal.hideModal();
308
- if (errors.length > 0) {
309
- await modal.message("Partial", `Some removals failed. Try manually:\n\n${errors.join("\n")}`, "error");
310
- }
311
- handleRefresh();
312
- };
313
- const handleUpdateAll = async () => {
314
- const updatable = toolStatuses.filter((s) => s.hasUpdate);
315
- if (updatable.length === 0) {
316
- await modal.message("Up to Date", "All tools are already up to date.", "info");
317
- return;
318
- }
319
- modal.loading(`Updating ${updatable.length} tool(s)...`);
320
- for (const status of updatable) {
321
- const command = status.updateCommand || status.tool.installCommand;
322
- await runCommand(command);
323
- }
324
- modal.hideModal();
325
- handleRefresh();
326
- };
327
- const selectedStatus = toolStatuses[cliToolsState.selectedIndex];
328
- const installedCount = toolStatuses.filter((s) => s.installed).length;
329
- const updateCount = toolStatuses.filter((s) => s.hasUpdate).length;
330
- const statusContent = (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Installed: " }), _jsxs("span", { fg: "cyan", children: [installedCount, "/", toolStatuses.length] }), updateCount > 0 && (_jsxs(_Fragment, { children: [_jsx("span", { fg: "gray", children: " \u2502 Updates: " }), _jsx("span", { fg: "yellow", children: updateCount })] }))] }));
331
- return (_jsx(ScreenLayout, { title: "claudeup CLI Tools", currentScreen: "cli-tools", statusLine: statusContent, footerHints: [
332
- { keys: ["Enter"], label: "install" },
333
- { keys: ["a"], label: "update all" },
334
- { keys: ["c"], label: "fix conflict" },
335
- { keys: ["r"], label: "refresh" },
336
- ], listPanel: _jsx(ScrollableList, { items: toolStatuses, selectedIndex: cliToolsState.selectedIndex, renderItem: renderCliToolRow, maxHeight: dimensions.listPanelHeight, getKey: (status) => status.tool.name }), detailPanel: renderCliToolDetail(selectedStatus) }));
337
- }
338
- export default CliToolsScreen;
@@ -1,152 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
- import { useEffect, useCallback, useMemo } from "react";
3
- import { useApp, useModal } from "../state/AppContext.js";
4
- import { useDimensions } from "../state/DimensionsContext.js";
5
- import { useKeyboard } from "../hooks/useKeyboard.js";
6
- import { ScreenLayout } from "../components/layout/index.js";
7
- import { SETTINGS_CATALOG, } from "../../data/settings-catalog.js";
8
- import { readAllSettingsBothScopes, writeSettingValue, discoverOutputStyles, } from "../../services/settings-manager.js";
9
- import { buildSettingsBrowserItems, } from "../adapters/settingsAdapter.js";
10
- import { renderSettingRow, renderSettingDetail } from "../renderers/settingsRenderers.js";
11
- import { ScrollableList } from "../components/ScrollableList.js";
12
- export function SettingsScreen() {
13
- const { state, dispatch } = useApp();
14
- const { settings } = state;
15
- const modal = useModal();
16
- const dimensions = useDimensions();
17
- const fetchData = useCallback(async () => {
18
- dispatch({ type: "SETTINGS_DATA_LOADING" });
19
- try {
20
- // Populate dynamic output style options from installed plugins
21
- const outputStyleSetting = SETTINGS_CATALOG.find((s) => s.id === "output-style");
22
- if (outputStyleSetting && outputStyleSetting.type === "select") {
23
- outputStyleSetting.options = await discoverOutputStyles(state.projectPath);
24
- }
25
- const values = await readAllSettingsBothScopes(SETTINGS_CATALOG, state.projectPath);
26
- dispatch({ type: "SETTINGS_DATA_SUCCESS", values });
27
- }
28
- catch (error) {
29
- dispatch({
30
- type: "SETTINGS_DATA_ERROR",
31
- error: error instanceof Error ? error : new Error(String(error)),
32
- });
33
- }
34
- }, [dispatch, state.projectPath]);
35
- useEffect(() => {
36
- fetchData();
37
- }, [fetchData]);
38
- const listItems = useMemo(() => {
39
- if (settings.values.status !== "success")
40
- return [];
41
- return buildSettingsBrowserItems(settings.values.data);
42
- }, [settings.values]);
43
- const handleScopeChange = async (scope) => {
44
- const item = listItems[settings.selectedIndex];
45
- if (!item || item.kind !== "setting")
46
- return;
47
- const { setting, scopedValues } = item;
48
- const currentValue = scope === "user" ? scopedValues.user : scopedValues.project;
49
- if (setting.type === "boolean") {
50
- const currentBool = currentValue === "true" ||
51
- currentValue === "1" ||
52
- (currentValue === undefined && setting.defaultValue === "true");
53
- const newValue = currentBool ? "false" : "true";
54
- try {
55
- await writeSettingValue(setting, newValue, scope, state.projectPath);
56
- await fetchData();
57
- }
58
- catch (error) {
59
- await modal.message("Error", `Failed to update: ${error}`, "error");
60
- }
61
- }
62
- else if (setting.type === "select" && setting.options) {
63
- const options = setting.options.map((o) => ({
64
- label: o.label + (currentValue === o.value ? " (current)" : ""),
65
- value: o.value,
66
- }));
67
- const currentIndex = setting.options.findIndex((o) => o.value === currentValue);
68
- if (currentValue !== undefined) {
69
- options.push({ label: "Clear (use default)", value: "__clear__" });
70
- }
71
- const selected = await modal.select(`${setting.name} — ${scope}`, setting.description, options, currentIndex >= 0 ? currentIndex : undefined);
72
- if (selected === null)
73
- return;
74
- try {
75
- const val = selected === "__clear__" ? undefined : selected || undefined;
76
- await writeSettingValue(setting, val, scope, state.projectPath);
77
- await fetchData();
78
- }
79
- catch (error) {
80
- await modal.message("Error", `Failed to update: ${error}`, "error");
81
- }
82
- }
83
- else {
84
- // String type: if already set, clear it; if unset, show input modal
85
- if (currentValue !== undefined && currentValue !== "") {
86
- try {
87
- await writeSettingValue(setting, undefined, scope, state.projectPath);
88
- await fetchData();
89
- }
90
- catch (error) {
91
- await modal.message("Error", `Failed to update: ${error}`, "error");
92
- }
93
- }
94
- else {
95
- const newValue = await modal.input(`${setting.name} — ${scope}`, setting.description, currentValue || setting.defaultValue || "");
96
- if (newValue === null)
97
- return;
98
- try {
99
- await writeSettingValue(setting, newValue || undefined, scope, state.projectPath);
100
- await fetchData();
101
- }
102
- catch (error) {
103
- await modal.message("Error", `Failed to update: ${error}`, "error");
104
- }
105
- }
106
- }
107
- };
108
- useKeyboard((event) => {
109
- if (state.isSearching || state.modal)
110
- return;
111
- if (event.name === "up" || event.name === "k") {
112
- const newIndex = Math.max(0, settings.selectedIndex - 1);
113
- dispatch({ type: "SETTINGS_SELECT", index: newIndex });
114
- }
115
- else if (event.name === "down" || event.name === "j") {
116
- const newIndex = Math.min(Math.max(0, listItems.length - 1), settings.selectedIndex + 1);
117
- dispatch({ type: "SETTINGS_SELECT", index: newIndex });
118
- }
119
- else if (event.name === "u") {
120
- handleScopeChange("user");
121
- }
122
- else if (event.name === "p") {
123
- handleScopeChange("project");
124
- }
125
- else if (event.name === "enter") {
126
- handleScopeChange("project");
127
- }
128
- });
129
- const selectedItem = listItems[settings.selectedIndex];
130
- const renderDetail = () => {
131
- if (settings.values.status === "loading") {
132
- return _jsx("text", { fg: "gray", children: "Loading settings..." });
133
- }
134
- if (settings.values.status === "error") {
135
- return _jsx("text", { fg: "red", children: "Failed to load settings" });
136
- }
137
- return renderSettingDetail(selectedItem);
138
- };
139
- const totalSet = settings.values.status === "success"
140
- ? Array.from(settings.values.data.values()).filter((v) => v.user !== undefined || v.project !== undefined).length
141
- : 0;
142
- const statusContent = (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Settings: " }), _jsxs("span", { fg: "cyan", children: [totalSet, " configured"] }), _jsx("span", { fg: "gray", children: " \u2502 u:user p:project" })] }));
143
- return (_jsx(ScreenLayout, { title: "claudeup Settings", currentScreen: "settings", statusLine: statusContent, footerHints: [
144
- { keys: ["↑", "↓"], label: "nav" },
145
- { keys: ["u"], label: "user scope" },
146
- { keys: ["p"], label: "project scope" },
147
- { keys: ["Enter"], label: "project" },
148
- ], listPanel: settings.values.status !== "success" ? (_jsx("text", { fg: "gray", children: settings.values.status === "loading"
149
- ? "Loading..."
150
- : "Error loading settings" })) : (_jsx(ScrollableList, { items: listItems, selectedIndex: settings.selectedIndex, renderItem: renderSettingRow, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
151
- }
152
- export default SettingsScreen;