claudeup 1.7.0 → 3.3.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 (302) hide show
  1. package/bin/claudeup.js +20 -2
  2. package/package.json +10 -19
  3. package/src/data/cli-tools.js +123 -0
  4. package/src/data/cli-tools.ts +140 -0
  5. package/{dist → src}/data/marketplaces.js +23 -24
  6. package/src/data/marketplaces.ts +95 -0
  7. package/src/data/mcp-servers.js +509 -0
  8. package/src/data/mcp-servers.ts +526 -0
  9. package/src/data/statuslines.js +159 -0
  10. package/src/data/statuslines.ts +188 -0
  11. package/src/index.js +4 -0
  12. package/src/index.ts +5 -0
  13. package/{dist → src}/main.js +46 -47
  14. package/src/main.tsx +145 -0
  15. package/src/opentui.d.ts +191 -0
  16. package/src/prerunner/index.js +87 -0
  17. package/src/prerunner/index.ts +124 -0
  18. package/{dist → src}/services/claude-runner.js +9 -10
  19. package/src/services/claude-runner.ts +31 -0
  20. package/{dist → src}/services/claude-settings.js +72 -33
  21. package/src/services/claude-settings.ts +934 -0
  22. package/src/services/local-marketplace.js +339 -0
  23. package/src/services/local-marketplace.ts +489 -0
  24. package/{dist → src}/services/mcp-registry.js +13 -14
  25. package/src/services/mcp-registry.ts +105 -0
  26. package/{dist → src}/services/plugin-manager.js +61 -13
  27. package/src/services/plugin-manager.ts +693 -0
  28. package/{dist → src}/services/plugin-mcp-config.js +19 -18
  29. package/src/services/plugin-mcp-config.ts +242 -0
  30. package/{dist → src}/services/update-cache.js +7 -8
  31. package/src/services/update-cache.ts +78 -0
  32. package/{dist → src}/services/version-check.js +15 -14
  33. package/src/services/version-check.ts +122 -0
  34. package/src/types/index.js +1 -0
  35. package/src/types/index.ts +141 -0
  36. package/src/ui/App.js +213 -0
  37. package/src/ui/App.tsx +359 -0
  38. package/src/ui/components/CategoryHeader.js +9 -0
  39. package/src/ui/components/CategoryHeader.tsx +41 -0
  40. package/{dist → src}/ui/components/ScrollableList.js +19 -6
  41. package/src/ui/components/ScrollableList.tsx +98 -0
  42. package/src/ui/components/SearchInput.js +19 -0
  43. package/src/ui/components/SearchInput.tsx +56 -0
  44. package/src/ui/components/StyledText.js +39 -0
  45. package/src/ui/components/StyledText.tsx +70 -0
  46. package/src/ui/components/TabBar.js +38 -0
  47. package/src/ui/components/TabBar.tsx +88 -0
  48. package/src/ui/components/layout/Panel.js +6 -0
  49. package/src/ui/components/layout/Panel.tsx +62 -0
  50. package/src/ui/components/layout/ProgressBar.js +14 -0
  51. package/src/ui/components/layout/ProgressBar.tsx +47 -0
  52. package/src/ui/components/layout/ScopeTabs.js +6 -0
  53. package/src/ui/components/layout/ScopeTabs.tsx +53 -0
  54. package/src/ui/components/layout/ScreenLayout.js +21 -0
  55. package/src/ui/components/layout/ScreenLayout.tsx +147 -0
  56. package/src/ui/components/layout/index.js +4 -0
  57. package/src/ui/components/layout/index.ts +4 -0
  58. package/src/ui/components/modals/ConfirmModal.js +14 -0
  59. package/src/ui/components/modals/ConfirmModal.tsx +59 -0
  60. package/src/ui/components/modals/InputModal.js +16 -0
  61. package/src/ui/components/modals/InputModal.tsx +68 -0
  62. package/src/ui/components/modals/LoadingModal.js +14 -0
  63. package/src/ui/components/modals/LoadingModal.tsx +40 -0
  64. package/src/ui/components/modals/MessageModal.js +16 -0
  65. package/src/ui/components/modals/MessageModal.tsx +64 -0
  66. package/src/ui/components/modals/ModalContainer.js +56 -0
  67. package/src/ui/components/modals/ModalContainer.tsx +104 -0
  68. package/src/ui/components/modals/SelectModal.js +26 -0
  69. package/src/ui/components/modals/SelectModal.tsx +82 -0
  70. package/src/ui/components/modals/index.js +6 -0
  71. package/src/ui/components/modals/index.ts +6 -0
  72. package/src/ui/hooks/index.js +3 -0
  73. package/src/ui/hooks/index.ts +3 -0
  74. package/{dist → src}/ui/hooks/useAsyncData.js +21 -22
  75. package/src/ui/hooks/useAsyncData.ts +127 -0
  76. package/src/ui/hooks/useKeyboard.js +13 -0
  77. package/src/ui/hooks/useKeyboard.ts +26 -0
  78. package/src/ui/hooks/useKeyboardHandler.js +39 -0
  79. package/src/ui/hooks/useKeyboardHandler.ts +63 -0
  80. package/{dist → src}/ui/screens/CliToolsScreen.js +60 -54
  81. package/src/ui/screens/CliToolsScreen.tsx +468 -0
  82. package/src/ui/screens/EnvVarsScreen.js +154 -0
  83. package/src/ui/screens/EnvVarsScreen.tsx +269 -0
  84. package/{dist → src}/ui/screens/McpRegistryScreen.js +56 -55
  85. package/src/ui/screens/McpRegistryScreen.tsx +331 -0
  86. package/{dist → src}/ui/screens/McpScreen.js +46 -47
  87. package/src/ui/screens/McpScreen.tsx +392 -0
  88. package/src/ui/screens/ModelSelectorScreen.js +292 -0
  89. package/src/ui/screens/ModelSelectorScreen.tsx +441 -0
  90. package/{dist → src}/ui/screens/PluginsScreen.js +305 -293
  91. package/src/ui/screens/PluginsScreen.tsx +1231 -0
  92. package/src/ui/screens/StatusLineScreen.js +200 -0
  93. package/src/ui/screens/StatusLineScreen.tsx +411 -0
  94. package/src/ui/screens/index.js +7 -0
  95. package/src/ui/screens/index.ts +7 -0
  96. package/src/ui/state/AnimationContext.js +34 -0
  97. package/src/ui/state/AnimationContext.tsx +76 -0
  98. package/{dist → src}/ui/state/AppContext.js +31 -32
  99. package/src/ui/state/AppContext.tsx +235 -0
  100. package/{dist → src}/ui/state/DimensionsContext.js +16 -17
  101. package/src/ui/state/DimensionsContext.tsx +144 -0
  102. package/{dist → src}/ui/state/reducer.js +89 -90
  103. package/src/ui/state/reducer.ts +467 -0
  104. package/src/ui/state/types.js +1 -0
  105. package/src/ui/state/types.ts +273 -0
  106. package/{dist → src}/utils/command-utils.js +3 -4
  107. package/src/utils/command-utils.ts +20 -0
  108. package/{dist → src}/utils/fuzzy-search.js +2 -3
  109. package/src/utils/fuzzy-search.ts +138 -0
  110. package/{dist → src}/utils/string-utils.js +6 -6
  111. package/src/utils/string-utils.ts +88 -0
  112. package/dist/data/cli-tools.d.ts +0 -13
  113. package/dist/data/cli-tools.d.ts.map +0 -1
  114. package/dist/data/cli-tools.js +0 -124
  115. package/dist/data/cli-tools.js.map +0 -1
  116. package/dist/data/marketplaces.d.ts +0 -6
  117. package/dist/data/marketplaces.d.ts.map +0 -1
  118. package/dist/data/marketplaces.js.map +0 -1
  119. package/dist/data/mcp-servers.d.ts +0 -8
  120. package/dist/data/mcp-servers.d.ts.map +0 -1
  121. package/dist/data/mcp-servers.js +0 -503
  122. package/dist/data/mcp-servers.js.map +0 -1
  123. package/dist/data/statuslines.d.ts +0 -10
  124. package/dist/data/statuslines.d.ts.map +0 -1
  125. package/dist/data/statuslines.js +0 -160
  126. package/dist/data/statuslines.js.map +0 -1
  127. package/dist/index.d.ts +0 -3
  128. package/dist/index.d.ts.map +0 -1
  129. package/dist/index.js +0 -90
  130. package/dist/index.js.map +0 -1
  131. package/dist/main.d.ts +0 -3
  132. package/dist/main.d.ts.map +0 -1
  133. package/dist/main.js.map +0 -1
  134. package/dist/prerunner/index.d.ts +0 -7
  135. package/dist/prerunner/index.d.ts.map +0 -1
  136. package/dist/prerunner/index.js +0 -64
  137. package/dist/prerunner/index.js.map +0 -1
  138. package/dist/services/claude-runner.d.ts +0 -7
  139. package/dist/services/claude-runner.d.ts.map +0 -1
  140. package/dist/services/claude-runner.js.map +0 -1
  141. package/dist/services/claude-settings.d.ts +0 -73
  142. package/dist/services/claude-settings.d.ts.map +0 -1
  143. package/dist/services/claude-settings.js.map +0 -1
  144. package/dist/services/local-marketplace.d.ts +0 -111
  145. package/dist/services/local-marketplace.d.ts.map +0 -1
  146. package/dist/services/local-marketplace.js +0 -599
  147. package/dist/services/local-marketplace.js.map +0 -1
  148. package/dist/services/mcp-registry.d.ts +0 -10
  149. package/dist/services/mcp-registry.d.ts.map +0 -1
  150. package/dist/services/mcp-registry.js.map +0 -1
  151. package/dist/services/plugin-manager.d.ts +0 -65
  152. package/dist/services/plugin-manager.d.ts.map +0 -1
  153. package/dist/services/plugin-manager.js.map +0 -1
  154. package/dist/services/plugin-mcp-config.d.ts +0 -52
  155. package/dist/services/plugin-mcp-config.d.ts.map +0 -1
  156. package/dist/services/plugin-mcp-config.js.map +0 -1
  157. package/dist/services/update-cache.d.ts +0 -16
  158. package/dist/services/update-cache.d.ts.map +0 -1
  159. package/dist/services/update-cache.js.map +0 -1
  160. package/dist/services/version-check.d.ts +0 -20
  161. package/dist/services/version-check.d.ts.map +0 -1
  162. package/dist/services/version-check.js.map +0 -1
  163. package/dist/types/index.d.ts +0 -105
  164. package/dist/types/index.d.ts.map +0 -1
  165. package/dist/types/index.js +0 -2
  166. package/dist/types/index.js.map +0 -1
  167. package/dist/ui/InkApp.d.ts +0 -5
  168. package/dist/ui/InkApp.d.ts.map +0 -1
  169. package/dist/ui/InkApp.js +0 -188
  170. package/dist/ui/InkApp.js.map +0 -1
  171. package/dist/ui/components/CategoryHeader.d.ts +0 -16
  172. package/dist/ui/components/CategoryHeader.d.ts.map +0 -1
  173. package/dist/ui/components/CategoryHeader.js +0 -11
  174. package/dist/ui/components/CategoryHeader.js.map +0 -1
  175. package/dist/ui/components/ScrollableList.d.ts +0 -16
  176. package/dist/ui/components/ScrollableList.d.ts.map +0 -1
  177. package/dist/ui/components/ScrollableList.js.map +0 -1
  178. package/dist/ui/components/SearchInput.d.ts +0 -18
  179. package/dist/ui/components/SearchInput.d.ts.map +0 -1
  180. package/dist/ui/components/SearchInput.js +0 -30
  181. package/dist/ui/components/SearchInput.js.map +0 -1
  182. package/dist/ui/components/TabBar.d.ts +0 -8
  183. package/dist/ui/components/TabBar.d.ts.map +0 -1
  184. package/dist/ui/components/TabBar.js +0 -18
  185. package/dist/ui/components/TabBar.js.map +0 -1
  186. package/dist/ui/components/layout/Footer.d.ts +0 -14
  187. package/dist/ui/components/layout/Footer.d.ts.map +0 -1
  188. package/dist/ui/components/layout/Footer.js +0 -23
  189. package/dist/ui/components/layout/Footer.js.map +0 -1
  190. package/dist/ui/components/layout/Header.d.ts +0 -4
  191. package/dist/ui/components/layout/Header.d.ts.map +0 -1
  192. package/dist/ui/components/layout/Header.js +0 -25
  193. package/dist/ui/components/layout/Header.js.map +0 -1
  194. package/dist/ui/components/layout/Panel.d.ts +0 -22
  195. package/dist/ui/components/layout/Panel.d.ts.map +0 -1
  196. package/dist/ui/components/layout/Panel.js +0 -8
  197. package/dist/ui/components/layout/Panel.js.map +0 -1
  198. package/dist/ui/components/layout/ProgressBar.d.ts +0 -12
  199. package/dist/ui/components/layout/ProgressBar.d.ts.map +0 -1
  200. package/dist/ui/components/layout/ProgressBar.js +0 -16
  201. package/dist/ui/components/layout/ProgressBar.js.map +0 -1
  202. package/dist/ui/components/layout/ScopeTabs.d.ts +0 -12
  203. package/dist/ui/components/layout/ScopeTabs.d.ts.map +0 -1
  204. package/dist/ui/components/layout/ScopeTabs.js +0 -8
  205. package/dist/ui/components/layout/ScopeTabs.js.map +0 -1
  206. package/dist/ui/components/layout/ScreenLayout.d.ts +0 -30
  207. package/dist/ui/components/layout/ScreenLayout.d.ts.map +0 -1
  208. package/dist/ui/components/layout/ScreenLayout.js +0 -23
  209. package/dist/ui/components/layout/ScreenLayout.js.map +0 -1
  210. package/dist/ui/components/layout/index.d.ts +0 -7
  211. package/dist/ui/components/layout/index.d.ts.map +0 -1
  212. package/dist/ui/components/layout/index.js +0 -7
  213. package/dist/ui/components/layout/index.js.map +0 -1
  214. package/dist/ui/components/modals/ConfirmModal.d.ts +0 -14
  215. package/dist/ui/components/modals/ConfirmModal.d.ts.map +0 -1
  216. package/dist/ui/components/modals/ConfirmModal.js +0 -15
  217. package/dist/ui/components/modals/ConfirmModal.js.map +0 -1
  218. package/dist/ui/components/modals/InputModal.d.ts +0 -16
  219. package/dist/ui/components/modals/InputModal.d.ts.map +0 -1
  220. package/dist/ui/components/modals/InputModal.js +0 -23
  221. package/dist/ui/components/modals/InputModal.js.map +0 -1
  222. package/dist/ui/components/modals/LoadingModal.d.ts +0 -8
  223. package/dist/ui/components/modals/LoadingModal.d.ts.map +0 -1
  224. package/dist/ui/components/modals/LoadingModal.js +0 -8
  225. package/dist/ui/components/modals/LoadingModal.js.map +0 -1
  226. package/dist/ui/components/modals/MessageModal.d.ts +0 -14
  227. package/dist/ui/components/modals/MessageModal.d.ts.map +0 -1
  228. package/dist/ui/components/modals/MessageModal.js +0 -17
  229. package/dist/ui/components/modals/MessageModal.js.map +0 -1
  230. package/dist/ui/components/modals/ModalContainer.d.ts +0 -7
  231. package/dist/ui/components/modals/ModalContainer.d.ts.map +0 -1
  232. package/dist/ui/components/modals/ModalContainer.js +0 -38
  233. package/dist/ui/components/modals/ModalContainer.js.map +0 -1
  234. package/dist/ui/components/modals/SelectModal.d.ts +0 -17
  235. package/dist/ui/components/modals/SelectModal.d.ts.map +0 -1
  236. package/dist/ui/components/modals/SelectModal.js +0 -33
  237. package/dist/ui/components/modals/SelectModal.js.map +0 -1
  238. package/dist/ui/components/modals/index.d.ts +0 -7
  239. package/dist/ui/components/modals/index.d.ts.map +0 -1
  240. package/dist/ui/components/modals/index.js +0 -7
  241. package/dist/ui/components/modals/index.js.map +0 -1
  242. package/dist/ui/hooks/index.d.ts +0 -3
  243. package/dist/ui/hooks/index.d.ts.map +0 -1
  244. package/dist/ui/hooks/index.js +0 -3
  245. package/dist/ui/hooks/index.js.map +0 -1
  246. package/dist/ui/hooks/useAsyncData.d.ts +0 -40
  247. package/dist/ui/hooks/useAsyncData.d.ts.map +0 -1
  248. package/dist/ui/hooks/useAsyncData.js.map +0 -1
  249. package/dist/ui/hooks/useKeyboardNavigation.d.ts +0 -27
  250. package/dist/ui/hooks/useKeyboardNavigation.d.ts.map +0 -1
  251. package/dist/ui/hooks/useKeyboardNavigation.js +0 -82
  252. package/dist/ui/hooks/useKeyboardNavigation.js.map +0 -1
  253. package/dist/ui/screens/CliToolsScreen.d.ts +0 -4
  254. package/dist/ui/screens/CliToolsScreen.d.ts.map +0 -1
  255. package/dist/ui/screens/CliToolsScreen.js.map +0 -1
  256. package/dist/ui/screens/EnvVarsScreen.d.ts +0 -4
  257. package/dist/ui/screens/EnvVarsScreen.d.ts.map +0 -1
  258. package/dist/ui/screens/EnvVarsScreen.js +0 -145
  259. package/dist/ui/screens/EnvVarsScreen.js.map +0 -1
  260. package/dist/ui/screens/McpRegistryScreen.d.ts +0 -4
  261. package/dist/ui/screens/McpRegistryScreen.d.ts.map +0 -1
  262. package/dist/ui/screens/McpRegistryScreen.js.map +0 -1
  263. package/dist/ui/screens/McpScreen.d.ts +0 -4
  264. package/dist/ui/screens/McpScreen.d.ts.map +0 -1
  265. package/dist/ui/screens/McpScreen.js.map +0 -1
  266. package/dist/ui/screens/ModelSelectorScreen.d.ts +0 -4
  267. package/dist/ui/screens/ModelSelectorScreen.d.ts.map +0 -1
  268. package/dist/ui/screens/ModelSelectorScreen.js +0 -143
  269. package/dist/ui/screens/ModelSelectorScreen.js.map +0 -1
  270. package/dist/ui/screens/PluginsScreen.d.ts +0 -4
  271. package/dist/ui/screens/PluginsScreen.d.ts.map +0 -1
  272. package/dist/ui/screens/PluginsScreen.js.map +0 -1
  273. package/dist/ui/screens/StatusLineScreen.d.ts +0 -4
  274. package/dist/ui/screens/StatusLineScreen.d.ts.map +0 -1
  275. package/dist/ui/screens/StatusLineScreen.js +0 -197
  276. package/dist/ui/screens/StatusLineScreen.js.map +0 -1
  277. package/dist/ui/screens/index.d.ts +0 -8
  278. package/dist/ui/screens/index.d.ts.map +0 -1
  279. package/dist/ui/screens/index.js +0 -8
  280. package/dist/ui/screens/index.js.map +0 -1
  281. package/dist/ui/state/AppContext.d.ts +0 -40
  282. package/dist/ui/state/AppContext.d.ts.map +0 -1
  283. package/dist/ui/state/AppContext.js.map +0 -1
  284. package/dist/ui/state/DimensionsContext.d.ts +0 -27
  285. package/dist/ui/state/DimensionsContext.d.ts.map +0 -1
  286. package/dist/ui/state/DimensionsContext.js.map +0 -1
  287. package/dist/ui/state/reducer.d.ts +0 -4
  288. package/dist/ui/state/reducer.d.ts.map +0 -1
  289. package/dist/ui/state/reducer.js.map +0 -1
  290. package/dist/ui/state/types.d.ts +0 -266
  291. package/dist/ui/state/types.d.ts.map +0 -1
  292. package/dist/ui/state/types.js +0 -2
  293. package/dist/ui/state/types.js.map +0 -1
  294. package/dist/utils/command-utils.d.ts +0 -8
  295. package/dist/utils/command-utils.d.ts.map +0 -1
  296. package/dist/utils/command-utils.js.map +0 -1
  297. package/dist/utils/fuzzy-search.d.ts +0 -33
  298. package/dist/utils/fuzzy-search.d.ts.map +0 -1
  299. package/dist/utils/fuzzy-search.js.map +0 -1
  300. package/dist/utils/string-utils.d.ts +0 -24
  301. package/dist/utils/string-utils.d.ts.map +0 -1
  302. package/dist/utils/string-utils.js.map +0 -1
@@ -0,0 +1,468 @@
1
+ import React, { useEffect, useCallback, useState, useRef } from "react";
2
+ import { exec, execSync } from "child_process";
3
+ import { promisify } from "util";
4
+ import { useApp, useModal } from "../state/AppContext.js";
5
+ import { useDimensions } from "../state/DimensionsContext.js";
6
+ import { useKeyboard } from "../hooks/useKeyboard.js";
7
+ import { ScreenLayout } from "../components/layout/index.js";
8
+ import { ScrollableList } from "../components/ScrollableList.js";
9
+ import { cliTools, type CliTool } from "../../data/cli-tools.js";
10
+
11
+ const execAsync = promisify(exec);
12
+
13
+ interface ToolStatus {
14
+ tool: CliTool;
15
+ installed: boolean;
16
+ installedVersion?: string;
17
+ latestVersion?: string;
18
+ hasUpdate?: boolean;
19
+ checking: boolean;
20
+ }
21
+
22
+ // Session-level cache
23
+ let cachedToolStatuses: ToolStatus[] | null = null;
24
+ let cacheInitialized = false;
25
+
26
+ function clearCliToolsCache(): void {
27
+ cachedToolStatuses = null;
28
+ cacheInitialized = false;
29
+ }
30
+
31
+ function parseVersion(versionOutput: string): string | undefined {
32
+ const match = versionOutput.match(/v?(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
33
+ return match ? match[1] : undefined;
34
+ }
35
+
36
+ async function isInstalledViaHomebrew(toolName: string): Promise<boolean> {
37
+ try {
38
+ const { stdout } = await execAsync("brew list --formula 2>/dev/null", {
39
+ timeout: 2000,
40
+ shell: "/bin/bash",
41
+ });
42
+ const formulas = stdout.trim().split("\n");
43
+ return formulas.some((f) => f.trim() === toolName);
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ async function getInstalledVersion(tool: CliTool): Promise<string | undefined> {
50
+ try {
51
+ const { stdout } = await execAsync(tool.checkCommand, { timeout: 5000 });
52
+ return parseVersion(stdout.trim());
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ async function getLatestNpmVersion(
59
+ packageName: string,
60
+ ): Promise<string | undefined> {
61
+ try {
62
+ const { stdout } = await execAsync(
63
+ `npm view ${packageName} version 2>/dev/null`,
64
+ { timeout: 10000 },
65
+ );
66
+ return stdout.trim() || undefined;
67
+ } catch {
68
+ return undefined;
69
+ }
70
+ }
71
+
72
+ async function getLatestPipVersion(
73
+ packageName: string,
74
+ ): Promise<string | undefined> {
75
+ try {
76
+ const { stdout } = await execAsync(
77
+ `pip index versions ${packageName} 2>/dev/null | head -1`,
78
+ {
79
+ timeout: 10000,
80
+ shell: "/bin/bash",
81
+ },
82
+ );
83
+ const match = stdout.trim().match(/\(([^)]+)\)/);
84
+ return match ? match[1] : undefined;
85
+ } catch {
86
+ return undefined;
87
+ }
88
+ }
89
+
90
+ async function getLatestVersion(tool: CliTool): Promise<string | undefined> {
91
+ if (tool.packageManager === "npm") {
92
+ return getLatestNpmVersion(tool.packageName);
93
+ } else {
94
+ return getLatestPipVersion(tool.packageName);
95
+ }
96
+ }
97
+
98
+ function compareVersions(v1: string, v2: string): number {
99
+ const parts1 = v1.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
100
+ const parts2 = v2.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
101
+
102
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
103
+ const p1 = parts1[i] || 0;
104
+ const p2 = parts2[i] || 0;
105
+ if (p1 < p2) return -1;
106
+ if (p1 > p2) return 1;
107
+ }
108
+ return 0;
109
+ }
110
+
111
+ export function CliToolsScreen() {
112
+ const { state, dispatch } = useApp();
113
+ const { cliTools: cliToolsState } = state;
114
+ const modal = useModal();
115
+ const dimensions = useDimensions();
116
+
117
+ const [toolStatuses, setToolStatuses] = useState<ToolStatus[]>(() => {
118
+ return (
119
+ cachedToolStatuses ||
120
+ cliTools.map((tool) => ({
121
+ tool,
122
+ installed: false,
123
+ installedVersion: undefined,
124
+ checking: true,
125
+ }))
126
+ );
127
+ });
128
+
129
+ const statusesRef = useRef(toolStatuses);
130
+ statusesRef.current = toolStatuses;
131
+
132
+ // Update single tool status
133
+ const updateToolStatus = useCallback(
134
+ (index: number, updates: Partial<ToolStatus>) => {
135
+ setToolStatuses((prev) => {
136
+ const newStatuses = [...prev];
137
+ newStatuses[index] = { ...newStatuses[index], ...updates };
138
+ cachedToolStatuses = newStatuses;
139
+ return newStatuses;
140
+ });
141
+ },
142
+ [],
143
+ );
144
+
145
+ // Fetch all version info
146
+ const fetchVersionInfo = useCallback(async () => {
147
+ // Check installed versions
148
+ for (let i = 0; i < cliTools.length; i++) {
149
+ const tool = cliTools[i];
150
+ getInstalledVersion(tool).then((version) => {
151
+ updateToolStatus(i, {
152
+ installedVersion: version,
153
+ installed: version !== undefined,
154
+ });
155
+ });
156
+
157
+ getLatestVersion(tool).then((latest) => {
158
+ const current = statusesRef.current[i];
159
+ updateToolStatus(i, {
160
+ latestVersion: latest,
161
+ checking: false,
162
+ hasUpdate:
163
+ current.installedVersion && latest
164
+ ? compareVersions(current.installedVersion, latest) < 0
165
+ : false,
166
+ });
167
+ });
168
+ }
169
+ }, [updateToolStatus]);
170
+
171
+ useEffect(() => {
172
+ if (!cacheInitialized) {
173
+ cacheInitialized = true;
174
+ cachedToolStatuses = toolStatuses;
175
+ fetchVersionInfo();
176
+ }
177
+ }, [fetchVersionInfo, toolStatuses]);
178
+
179
+ // Keyboard handling
180
+ useKeyboard((event) => {
181
+ if (state.isSearching || state.modal) return;
182
+
183
+ if (event.name === "up" || event.name === "k") {
184
+ const newIndex = Math.max(0, cliToolsState.selectedIndex - 1);
185
+ dispatch({ type: "CLITOOLS_SELECT", index: newIndex });
186
+ } else if (event.name === "down" || event.name === "j") {
187
+ const newIndex = Math.min(
188
+ toolStatuses.length - 1,
189
+ cliToolsState.selectedIndex + 1,
190
+ );
191
+ dispatch({ type: "CLITOOLS_SELECT", index: newIndex });
192
+ } else if (event.name === "r") {
193
+ handleRefresh();
194
+ } else if (event.name === "a") {
195
+ handleUpdateAll();
196
+ } else if (event.name === "enter") {
197
+ handleInstall();
198
+ }
199
+ });
200
+
201
+ const handleRefresh = () => {
202
+ clearCliToolsCache();
203
+ setToolStatuses(
204
+ cliTools.map((tool) => ({
205
+ tool,
206
+ installed: false,
207
+ installedVersion: undefined,
208
+ checking: true,
209
+ })),
210
+ );
211
+ cacheInitialized = false;
212
+ fetchVersionInfo();
213
+ };
214
+
215
+ const handleInstall = async () => {
216
+ const status = toolStatuses[cliToolsState.selectedIndex];
217
+ if (!status) return;
218
+
219
+ const { tool, installed, hasUpdate } = status;
220
+ const action = !installed
221
+ ? "Installing"
222
+ : hasUpdate
223
+ ? "Updating"
224
+ : "Reinstalling";
225
+
226
+ const viaHomebrew = installed
227
+ ? await isInstalledViaHomebrew(tool.name)
228
+ : false;
229
+
230
+ let command: string;
231
+ if (!installed) {
232
+ command = tool.installCommand;
233
+ } else if (viaHomebrew) {
234
+ command = `brew upgrade ${tool.name}`;
235
+ } else {
236
+ command = tool.installCommand;
237
+ }
238
+
239
+ modal.loading(`${action} ${tool.displayName}...`);
240
+
241
+ try {
242
+ execSync(command, {
243
+ encoding: "utf-8",
244
+ stdio: "pipe",
245
+ shell: "/bin/bash",
246
+ });
247
+ modal.hideModal();
248
+ handleRefresh();
249
+ } catch (error) {
250
+ modal.hideModal();
251
+ await modal.message(
252
+ "Error",
253
+ `Failed to ${action.toLowerCase()} ${tool.displayName}.\n\nTry running manually:\n${command}`,
254
+ "error",
255
+ );
256
+ }
257
+ };
258
+
259
+ const handleUpdateAll = async () => {
260
+ const updatable = toolStatuses.filter((s) => s.hasUpdate);
261
+ if (updatable.length === 0) {
262
+ await modal.message(
263
+ "Up to Date",
264
+ "All tools are already up to date.",
265
+ "info",
266
+ );
267
+ return;
268
+ }
269
+
270
+ modal.loading(`Updating ${updatable.length} tool(s)...`);
271
+
272
+ for (const status of updatable) {
273
+ const viaHomebrew = await isInstalledViaHomebrew(status.tool.name);
274
+ const command = viaHomebrew
275
+ ? `brew upgrade ${status.tool.name}`
276
+ : status.tool.installCommand;
277
+
278
+ try {
279
+ execSync(command, {
280
+ encoding: "utf-8",
281
+ stdio: "pipe",
282
+ shell: "/bin/bash",
283
+ });
284
+ } catch {
285
+ // Continue with other updates
286
+ }
287
+ }
288
+
289
+ modal.hideModal();
290
+ handleRefresh();
291
+ };
292
+
293
+ // Get selected item
294
+ const selectedStatus = toolStatuses[cliToolsState.selectedIndex];
295
+
296
+ const renderDetail = () => {
297
+ if (!selectedStatus) {
298
+ return (
299
+ <box
300
+ flexDirection="column"
301
+ alignItems="center"
302
+ justifyContent="center"
303
+ flexGrow={1}
304
+ >
305
+ <text fg="gray">Select a tool to see details</text>
306
+ </box>
307
+ );
308
+ }
309
+
310
+ const {
311
+ tool,
312
+ installed,
313
+ installedVersion,
314
+ latestVersion,
315
+ hasUpdate,
316
+ checking,
317
+ } = selectedStatus;
318
+
319
+ return (
320
+ <box flexDirection="column">
321
+ <box marginBottom={1}>
322
+ <text fg="cyan">
323
+ <strong>⚙ {tool.displayName}</strong>
324
+ </text>
325
+ {hasUpdate && <text fg="yellow"> ⬆</text>}
326
+ </box>
327
+
328
+ <text fg="gray">{tool.description}</text>
329
+
330
+ <box marginTop={1} flexDirection="column">
331
+ <box>
332
+ <text fg="gray">Status </text>
333
+ {!installed ? (
334
+ <text fg="gray">○ Not installed</text>
335
+ ) : checking ? (
336
+ <text fg="green">● Checking...</text>
337
+ ) : hasUpdate ? (
338
+ <text fg="yellow">● Update available</text>
339
+ ) : (
340
+ <text fg="green">● Up to date</text>
341
+ )}
342
+ </box>
343
+ {installedVersion && (
344
+ <box>
345
+ <text fg="gray">Installed </text>
346
+ <text fg="green">v{installedVersion}</text>
347
+ </box>
348
+ )}
349
+ {latestVersion && (
350
+ <box>
351
+ <text fg="gray">Latest </text>
352
+ <text fg="white">v{latestVersion}</text>
353
+ </box>
354
+ )}
355
+ <box>
356
+ <text fg="gray">Website </text>
357
+ <text fg="blue">{tool.website}</text>
358
+ </box>
359
+ </box>
360
+
361
+ <box marginTop={2}>
362
+ {!installed ? (
363
+ <box>
364
+ <text bg="green" fg="black">
365
+ {" "}
366
+ Enter{" "}
367
+ </text>
368
+ <text fg="gray"> Install</text>
369
+ </box>
370
+ ) : hasUpdate ? (
371
+ <box>
372
+ <text bg="yellow" fg="black">
373
+ {" "}
374
+ Enter{" "}
375
+ </text>
376
+ <text fg="gray"> Update to v{latestVersion}</text>
377
+ </box>
378
+ ) : (
379
+ <box>
380
+ <text bg="gray" fg="white">
381
+ {" "}
382
+ Enter{" "}
383
+ </text>
384
+ <text fg="gray"> Reinstall</text>
385
+ </box>
386
+ )}
387
+ </box>
388
+ </box>
389
+ );
390
+ };
391
+
392
+ const renderListItem = (
393
+ status: ToolStatus,
394
+ _idx: number,
395
+ isSelected: boolean,
396
+ ) => {
397
+ const { tool, installed, installedVersion, hasUpdate, checking } = status;
398
+
399
+ let icon: string;
400
+ let iconColor: string;
401
+
402
+ if (!installed) {
403
+ icon = "○";
404
+ iconColor = "gray";
405
+ } else if (hasUpdate) {
406
+ icon = "⬆";
407
+ iconColor = "yellow";
408
+ } else {
409
+ icon = "●";
410
+ iconColor = "green";
411
+ }
412
+
413
+ const versionText = installedVersion ? `v${installedVersion}` : "";
414
+
415
+ return isSelected ? (
416
+ <text bg="magenta" fg="white">
417
+ {" "}
418
+ {icon} {tool.displayName} {versionText}
419
+ {checking ? "..." : ""}{" "}
420
+ </text>
421
+ ) : (
422
+ <text>
423
+ <span fg={iconColor}>{icon}</span>
424
+ <span fg="white"> {tool.displayName}</span>
425
+ {versionText && <span fg="green"> {versionText}</span>}
426
+ {checking && <span fg="gray">...</span>}
427
+ </text>
428
+ );
429
+ };
430
+
431
+ // Calculate stats for status line
432
+ const installedCount = toolStatuses.filter((s) => s.installed).length;
433
+ const updateCount = toolStatuses.filter((s) => s.hasUpdate).length;
434
+ const statusContent = (
435
+ <>
436
+ <text fg="gray">Installed: </text>
437
+ <text fg="cyan">
438
+ {installedCount}/{toolStatuses.length}
439
+ </text>
440
+ {updateCount > 0 && (
441
+ <>
442
+ <text fg="gray"> │ Updates: </text>
443
+ <text fg="yellow">{updateCount}</text>
444
+ </>
445
+ )}
446
+ </>
447
+ );
448
+
449
+ return (
450
+ <ScreenLayout
451
+ title="claudeup CLI Tools"
452
+ currentScreen="cli-tools"
453
+ statusLine={statusContent}
454
+ footerHints="↑↓:nav │ Enter:install │ a:update all │ r:refresh"
455
+ listPanel={
456
+ <ScrollableList
457
+ items={toolStatuses}
458
+ selectedIndex={cliToolsState.selectedIndex}
459
+ renderItem={renderListItem}
460
+ maxHeight={dimensions.listPanelHeight}
461
+ />
462
+ }
463
+ detailPanel={renderDetail()}
464
+ />
465
+ );
466
+ }
467
+
468
+ export default CliToolsScreen;
@@ -0,0 +1,154 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/react/jsx-runtime";
2
+ import { useEffect, useCallback, useState } 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 { ScrollableList } from "../components/ScrollableList.js";
8
+ import { getMcpEnvVars, setMcpEnvVar, removeMcpEnvVar, } from "../../services/claude-settings.js";
9
+ export function EnvVarsScreen() {
10
+ const { state, dispatch } = useApp();
11
+ const { envVars } = state;
12
+ const modal = useModal();
13
+ const dimensions = useDimensions();
14
+ const [envVarList, setEnvVarList] = useState([]);
15
+ const [isLoading, setIsLoading] = useState(true);
16
+ // Fetch data
17
+ const fetchData = useCallback(async () => {
18
+ setIsLoading(true);
19
+ try {
20
+ const vars = await getMcpEnvVars(state.projectPath);
21
+ const list = Object.entries(vars).map(([name, value]) => ({
22
+ name,
23
+ value,
24
+ }));
25
+ setEnvVarList(list);
26
+ }
27
+ catch (error) {
28
+ setEnvVarList([]);
29
+ }
30
+ setIsLoading(false);
31
+ }, [state.projectPath]);
32
+ useEffect(() => {
33
+ fetchData();
34
+ }, [fetchData]);
35
+ // Keyboard handling
36
+ useKeyboard((event) => {
37
+ if (state.isSearching || state.modal)
38
+ return;
39
+ if (event.name === "up" || event.name === "k") {
40
+ const newIndex = Math.max(0, envVars.selectedIndex - 1);
41
+ dispatch({ type: "ENVVARS_SELECT", index: newIndex });
42
+ }
43
+ else if (event.name === "down" || event.name === "j") {
44
+ const newIndex = Math.min(Math.max(0, envVarList.length - 1), envVars.selectedIndex + 1);
45
+ dispatch({ type: "ENVVARS_SELECT", index: newIndex });
46
+ }
47
+ else if (event.name === "a") {
48
+ handleAdd();
49
+ }
50
+ else if (event.name === "e" || event.name === "enter") {
51
+ handleEdit();
52
+ }
53
+ else if (event.name === "d") {
54
+ handleDelete();
55
+ }
56
+ });
57
+ const handleAdd = async () => {
58
+ const varName = await modal.input("Add Variable", "Variable name:");
59
+ if (varName === null || !varName.trim())
60
+ return;
61
+ const cleanName = varName
62
+ .trim()
63
+ .toUpperCase()
64
+ .replace(/[^A-Z0-9_]/g, "_");
65
+ // Check if already exists
66
+ const existing = envVarList.find((v) => v.name === cleanName);
67
+ if (existing) {
68
+ const overwrite = await modal.confirm(`${cleanName} exists`, "Overwrite existing value?");
69
+ if (!overwrite)
70
+ return;
71
+ }
72
+ const value = await modal.input(`Set ${cleanName}`, "Value:");
73
+ if (value === null)
74
+ return;
75
+ modal.loading(`Adding ${cleanName}...`);
76
+ try {
77
+ await setMcpEnvVar(cleanName, value, state.projectPath);
78
+ modal.hideModal();
79
+ await modal.message("Added", `${cleanName} added.\nRestart Claude Code to apply.`, "success");
80
+ fetchData();
81
+ }
82
+ catch (error) {
83
+ modal.hideModal();
84
+ await modal.message("Error", `Failed to add: ${error}`, "error");
85
+ }
86
+ };
87
+ const handleEdit = async () => {
88
+ if (envVarList.length === 0)
89
+ return;
90
+ const envVar = envVarList[envVars.selectedIndex];
91
+ if (!envVar)
92
+ return;
93
+ const newValue = await modal.input(`Edit ${envVar.name}`, "New value:", envVar.value);
94
+ if (newValue === null)
95
+ return;
96
+ modal.loading(`Updating ${envVar.name}...`);
97
+ try {
98
+ await setMcpEnvVar(envVar.name, newValue, state.projectPath);
99
+ modal.hideModal();
100
+ await modal.message("Updated", `${envVar.name} updated.\nRestart Claude Code to apply.`, "success");
101
+ fetchData();
102
+ }
103
+ catch (error) {
104
+ modal.hideModal();
105
+ await modal.message("Error", `Failed to update: ${error}`, "error");
106
+ }
107
+ };
108
+ const handleDelete = async () => {
109
+ if (envVarList.length === 0)
110
+ return;
111
+ const envVar = envVarList[envVars.selectedIndex];
112
+ if (!envVar)
113
+ return;
114
+ const confirmed = await modal.confirm(`Delete ${envVar.name}?`, "This will remove the variable from configuration.");
115
+ if (confirmed) {
116
+ modal.loading(`Deleting ${envVar.name}...`);
117
+ try {
118
+ await removeMcpEnvVar(envVar.name, state.projectPath);
119
+ modal.hideModal();
120
+ await modal.message("Deleted", `${envVar.name} removed.`, "success");
121
+ fetchData();
122
+ }
123
+ catch (error) {
124
+ modal.hideModal();
125
+ await modal.message("Error", `Failed to delete: ${error}`, "error");
126
+ }
127
+ }
128
+ };
129
+ // Get selected item
130
+ const selectedVar = envVarList[envVars.selectedIndex];
131
+ const renderDetail = () => {
132
+ if (isLoading) {
133
+ return _jsx("text", { fg: "gray", children: "Loading environment variables..." });
134
+ }
135
+ if (envVarList.length === 0) {
136
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "gray", children: "No environment variables configured." }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "green", children: "Press 'a' to add a new variable" }) })] }));
137
+ }
138
+ if (!selectedVar) {
139
+ return _jsx("text", { fg: "gray", children: "Select a variable to see details" });
140
+ }
141
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: selectedVar.name }) }), _jsxs("box", { marginTop: 1, children: [_jsx("text", { fg: "gray", children: "Value: " }), _jsx("text", { children: selectedVar.value.length > 50
142
+ ? selectedVar.value.slice(0, 50) + "..."
143
+ : selectedVar.value })] }), _jsxs("box", { marginTop: 2, flexDirection: "column", children: [_jsxs("box", { children: [_jsxs("text", { bg: "magenta", fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: "gray", children: " Edit value" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: "red", fg: "white", children: [" ", "d", " "] }), _jsx("text", { fg: "gray", children: " Delete variable" })] })] })] }));
144
+ };
145
+ const renderListItem = (envVar, _idx, isSelected) => {
146
+ const masked = envVar.value.length > 20
147
+ ? envVar.value.slice(0, 20) + "..."
148
+ : envVar.value;
149
+ return isSelected ? (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", envVar.name, " = \"", masked, "\"", " "] })) : (_jsxs("text", { children: [_jsx("span", { fg: "cyan", children: envVar.name }), _jsxs("span", { fg: "gray", children: [" = \"", masked, "\""] })] }));
150
+ };
151
+ const statusContent = (_jsxs(_Fragment, { children: [_jsx("text", { fg: "gray", children: "Variables: " }), _jsx("text", { fg: "cyan", children: envVarList.length }), _jsx("text", { fg: "gray", children: " \u2502 Location: " }), _jsx("text", { fg: "green", children: ".claude/settings.local.json" })] }));
152
+ return (_jsx(ScreenLayout, { title: "claudeup Environment Variables", currentScreen: "env-vars", statusLine: statusContent, footerHints: "\u2191\u2193:nav \u2502 Enter/e:edit \u2502 a:add \u2502 d:delete", listPanel: envVarList.length === 0 ? (_jsx("text", { fg: "gray", children: "No environment variables configured" })) : (_jsx(ScrollableList, { items: envVarList, selectedIndex: envVars.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
153
+ }
154
+ export default EnvVarsScreen;