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
@@ -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;