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,53 @@
1
+ import React from "react";
2
+
3
+ interface ScopeTabsProps {
4
+ /** Current scope */
5
+ scope: "project" | "global";
6
+ /** Callback when scope changes */
7
+ onToggle?: () => void;
8
+ /** Hint text for toggle key */
9
+ toggleHint?: string;
10
+ }
11
+
12
+ export function ScopeTabs({
13
+ scope,
14
+ onToggle: _onToggle,
15
+ toggleHint,
16
+ }: ScopeTabsProps) {
17
+ const isProject = scope === "project";
18
+
19
+ return (
20
+ <box marginBottom={1} flexDirection="row" gap={1}>
21
+ {/* Project tab */}
22
+ <box>
23
+ {isProject ? (
24
+ <text bg="cyan" fg="black">
25
+ <strong> ◆ Project </strong>
26
+ </text>
27
+ ) : (
28
+ <text fg="gray"> ○ Project </text>
29
+ )}
30
+ </box>
31
+
32
+ {/* Global tab */}
33
+ <box>
34
+ {!isProject ? (
35
+ <text bg="magenta" fg="white">
36
+ <strong> ◆ Global </strong>
37
+ </text>
38
+ ) : (
39
+ <text fg="gray"> ○ Global </text>
40
+ )}
41
+ </box>
42
+
43
+ {/* Toggle hint */}
44
+ {toggleHint && (
45
+ <box marginLeft={2}>
46
+ <text fg="#666666">({toggleHint})</text>
47
+ </box>
48
+ )}
49
+ </box>
50
+ );
51
+ }
52
+
53
+ export default ScopeTabs;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/react/jsx-runtime";
2
+ import { useDimensions } from "../../state/DimensionsContext.js";
3
+ import { TabBar } from "../TabBar.js";
4
+ const HEADER_COLOR = "#7e57c2";
5
+ export function ScreenLayout({ title, subtitle, currentScreen, search, statusLine, footerHints, listPanel, detailPanel, }) {
6
+ const dimensions = useDimensions();
7
+ // Calculate panel heights
8
+ // Header: 4 lines (border + title + status/search + border)
9
+ // Footer: 2 lines (border-top + content)
10
+ const headerHeight = 4;
11
+ const footerHeight = 2;
12
+ const panelHeight = Math.max(5, dimensions.contentHeight - headerHeight - footerHeight);
13
+ return (_jsxs("box", { flexDirection: "column", height: dimensions.contentHeight, children: [_jsxs("box", { flexDirection: "column", border: true, borderStyle: "single", borderColor: HEADER_COLOR, padding: 1, marginBottom: 0, children: [_jsxs("box", { flexDirection: "row", justifyContent: "space-between", children: [_jsx("text", { fg: HEADER_COLOR, children: _jsx("strong", { children: title }) }), subtitle && _jsx("text", { fg: "gray", children: subtitle })] }), _jsx("box", { flexDirection: "row", marginTop: 0, children: search ? (
14
+ // Search mode
15
+ _jsxs(_Fragment, { children: [_jsx("text", { fg: "green", children: "> " }), search.isActive ? (_jsxs(_Fragment, { children: [_jsx("text", { fg: "white", children: search.query }), _jsx("text", { bg: "white", fg: "black", children: " " })] })) : (_jsx("text", { fg: "gray", children: search.query || search.placeholder || "/" }))] })) : statusLine ? (
16
+ // Custom status line
17
+ statusLine) : (
18
+ // Default empty status
19
+ _jsx("text", { fg: "gray", children: "\u2500" })) })] }), _jsxs("box", { flexDirection: "row", height: panelHeight, children: [_jsx("box", { flexDirection: "column", width: "49%", height: panelHeight, paddingRight: 1, children: listPanel }), _jsx("box", { flexDirection: "column", width: 1, height: panelHeight, children: _jsx("text", { fg: "#444444", children: "│".repeat(panelHeight) }) }), _jsx("box", { flexDirection: "column", width: "50%", height: panelHeight, paddingLeft: 1, children: detailPanel })] }), _jsxs("box", { height: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsx("text", { fg: "gray", children: footerHints }), _jsx(TabBar, { currentScreen: currentScreen })] })] }));
20
+ }
21
+ export default ScreenLayout;
@@ -0,0 +1,147 @@
1
+ import React from "react";
2
+ import { useDimensions } from "../../state/DimensionsContext.js";
3
+ import { TabBar } from "../TabBar.js";
4
+ import type { Screen } from "../../state/types.js";
5
+
6
+ interface ScreenLayoutProps {
7
+ /** Screen title (e.g., "claudeup Plugins") */
8
+ title: string;
9
+ /** Optional subtitle shown to the right of title */
10
+ subtitle?: string;
11
+ /** Current screen for tab highlighting */
12
+ currentScreen: Screen;
13
+ /** Search bar configuration (for screens with search) */
14
+ search?: {
15
+ /** Is search currently active */
16
+ isActive: boolean;
17
+ /** Current search query */
18
+ query: string;
19
+ /** Placeholder when not searching (default: "/") */
20
+ placeholder?: string;
21
+ };
22
+ /** Status line content (for screens without search) - shown in second row */
23
+ statusLine?: React.ReactNode;
24
+ /** Footer hints (left side) */
25
+ footerHints: string;
26
+ /** Left panel content */
27
+ listPanel: React.ReactNode;
28
+ /** Right panel content (detail view) */
29
+ detailPanel: React.ReactNode;
30
+ }
31
+
32
+ const HEADER_COLOR = "#7e57c2";
33
+
34
+ export function ScreenLayout({
35
+ title,
36
+ subtitle,
37
+ currentScreen,
38
+ search,
39
+ statusLine,
40
+ footerHints,
41
+ listPanel,
42
+ detailPanel,
43
+ }: ScreenLayoutProps) {
44
+ const dimensions = useDimensions();
45
+
46
+ // Calculate panel heights
47
+ // Header: 4 lines (border + title + status/search + border)
48
+ // Footer: 2 lines (border-top + content)
49
+ const headerHeight = 4;
50
+ const footerHeight = 2;
51
+ const panelHeight = Math.max(
52
+ 5,
53
+ dimensions.contentHeight - headerHeight - footerHeight,
54
+ );
55
+
56
+ return (
57
+ <box flexDirection="column" height={dimensions.contentHeight}>
58
+ {/* Header */}
59
+ <box
60
+ flexDirection="column"
61
+ border
62
+ borderStyle="single"
63
+ borderColor={HEADER_COLOR}
64
+ padding={1}
65
+ marginBottom={0}
66
+ >
67
+ {/* Title row */}
68
+ <box flexDirection="row" justifyContent="space-between">
69
+ <text fg={HEADER_COLOR}>
70
+ <strong>{title}</strong>
71
+ </text>
72
+ {subtitle && <text fg="gray">{subtitle}</text>}
73
+ </box>
74
+
75
+ {/* Status/Search row - always present */}
76
+ <box flexDirection="row" marginTop={0}>
77
+ {search ? (
78
+ // Search mode
79
+ <>
80
+ <text fg="green">{"> "}</text>
81
+ {search.isActive ? (
82
+ <>
83
+ <text fg="white">{search.query}</text>
84
+ <text bg="white" fg="black"> </text>
85
+ </>
86
+ ) : (
87
+ <text fg="gray">
88
+ {search.query || search.placeholder || "/"}
89
+ </text>
90
+ )}
91
+ </>
92
+ ) : statusLine ? (
93
+ // Custom status line
94
+ statusLine
95
+ ) : (
96
+ // Default empty status
97
+ <text fg="gray">─</text>
98
+ )}
99
+ </box>
100
+ </box>
101
+
102
+ {/* Main content area */}
103
+ <box flexDirection="row" height={panelHeight}>
104
+ {/* List panel */}
105
+ <box
106
+ flexDirection="column"
107
+ width="49%"
108
+ height={panelHeight}
109
+ paddingRight={1}
110
+ >
111
+ {listPanel}
112
+ </box>
113
+
114
+ {/* Vertical separator */}
115
+ <box
116
+ flexDirection="column"
117
+ width={1}
118
+ height={panelHeight}
119
+ >
120
+ <text fg="#444444">{"│".repeat(panelHeight)}</text>
121
+ </box>
122
+
123
+ {/* Detail panel */}
124
+ <box
125
+ flexDirection="column"
126
+ width="50%"
127
+ height={panelHeight}
128
+ paddingLeft={1}
129
+ >
130
+ {detailPanel}
131
+ </box>
132
+ </box>
133
+
134
+ {/* Footer */}
135
+ <box
136
+ height={1}
137
+ flexDirection="row"
138
+ justifyContent="space-between"
139
+ >
140
+ <text fg="gray">{footerHints}</text>
141
+ <TabBar currentScreen={currentScreen} />
142
+ </box>
143
+ </box>
144
+ );
145
+ }
146
+
147
+ export default ScreenLayout;
@@ -0,0 +1,4 @@
1
+ export { Panel } from "./Panel.js";
2
+ export { ScopeTabs } from "./ScopeTabs.js";
3
+ export { ProgressBar } from "./ProgressBar.js";
4
+ export { ScreenLayout } from "./ScreenLayout.js";
@@ -0,0 +1,4 @@
1
+ export { Panel } from "./Panel.js";
2
+ export { ScopeTabs } from "./ScopeTabs.js";
3
+ export { ProgressBar } from "./ProgressBar.js";
4
+ export { ScreenLayout } from "./ScreenLayout.js";
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+ export function ConfirmModal({ title, message, onConfirm, onCancel, }) {
4
+ useKeyboard((key) => {
5
+ if (key.name === "y" || key.name === "Y") {
6
+ onConfirm();
7
+ }
8
+ else if (key.name === "n" || key.name === "N" || key.name === "escape") {
9
+ onCancel();
10
+ }
11
+ });
12
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "yellow", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "green", children: "[Y]" }), _jsx("span", { children: "es " }), _jsx("span", { fg: "red", children: "[N]" }), _jsx("span", { children: "o" })] }) })] }));
13
+ }
14
+ export default ConfirmModal;
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+
4
+ interface ConfirmModalProps {
5
+ /** Modal title */
6
+ title: string;
7
+ /** Modal message */
8
+ message: string;
9
+ /** Callback when confirmed */
10
+ onConfirm: () => void;
11
+ /** Callback when cancelled */
12
+ onCancel: () => void;
13
+ }
14
+
15
+ export function ConfirmModal({
16
+ title,
17
+ message,
18
+ onConfirm,
19
+ onCancel,
20
+ }: ConfirmModalProps) {
21
+ useKeyboard((key) => {
22
+ if (key.name === "y" || key.name === "Y") {
23
+ onConfirm();
24
+ } else if (key.name === "n" || key.name === "N" || key.name === "escape") {
25
+ onCancel();
26
+ }
27
+ });
28
+
29
+ return (
30
+ <box
31
+ flexDirection="column"
32
+ border
33
+ borderStyle="rounded"
34
+ borderColor="yellow"
35
+ paddingLeft={2}
36
+ paddingRight={2}
37
+ paddingTop={1}
38
+ paddingBottom={1}
39
+ width={60}
40
+ >
41
+ <text>
42
+ <strong>{title}</strong>
43
+ </text>
44
+ <box marginTop={1} marginBottom={1}>
45
+ <text>{message}</text>
46
+ </box>
47
+ <box>
48
+ <text>
49
+ <span fg="green">[Y]</span>
50
+ <span>es </span>
51
+ <span fg="red">[N]</span>
52
+ <span>o</span>
53
+ </text>
54
+ </box>
55
+ </box>
56
+ );
57
+ }
58
+
59
+ export default ConfirmModal;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
4
+ export function InputModal({ title, label, defaultValue = "", onSubmit, onCancel, }) {
5
+ const [value, setValue] = useState(defaultValue);
6
+ useKeyboard((key) => {
7
+ if (key.name === "enter") {
8
+ onSubmit(value);
9
+ }
10
+ else if (key.name === "escape") {
11
+ onCancel();
12
+ }
13
+ });
14
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: label }) }), _jsx("input", { value: value, onChange: setValue, focused: true, width: 54 }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#666666", children: "Enter to confirm \u2022 Escape to cancel" }) })] }));
15
+ }
16
+ export default InputModal;
@@ -0,0 +1,68 @@
1
+ import React, { useState } from "react";
2
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+
4
+ interface InputModalProps {
5
+ /** Modal title */
6
+ title: string;
7
+ /** Input label */
8
+ label: string;
9
+ /** Default input value */
10
+ defaultValue?: string;
11
+ /** Callback when submitted */
12
+ onSubmit: (value: string) => void;
13
+ /** Callback when cancelled */
14
+ onCancel: () => void;
15
+ }
16
+
17
+ export function InputModal({
18
+ title,
19
+ label,
20
+ defaultValue = "",
21
+ onSubmit,
22
+ onCancel,
23
+ }: InputModalProps) {
24
+ const [value, setValue] = useState(defaultValue);
25
+
26
+ useKeyboard((key) => {
27
+ if (key.name === "enter") {
28
+ onSubmit(value);
29
+ } else if (key.name === "escape") {
30
+ onCancel();
31
+ }
32
+ });
33
+
34
+ return (
35
+ <box
36
+ flexDirection="column"
37
+ border
38
+ borderStyle="rounded"
39
+ borderColor="cyan"
40
+ paddingLeft={2}
41
+ paddingRight={2}
42
+ paddingTop={1}
43
+ paddingBottom={1}
44
+ width={60}
45
+ >
46
+ <text>
47
+ <strong>{title}</strong>
48
+ </text>
49
+
50
+ <box marginTop={1} marginBottom={1}>
51
+ <text>{label}</text>
52
+ </box>
53
+
54
+ <input
55
+ value={value}
56
+ onChange={setValue}
57
+ focused
58
+ width={54}
59
+ />
60
+
61
+ <box marginTop={1}>
62
+ <text fg="#666666">Enter to confirm • Escape to cancel</text>
63
+ </box>
64
+ </box>
65
+ );
66
+ }
67
+
68
+ export default InputModal;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
4
+ export function LoadingModal({ message, }) {
5
+ const [frame, setFrame] = useState(0);
6
+ useEffect(() => {
7
+ const interval = setInterval(() => {
8
+ setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
9
+ }, 80);
10
+ return () => clearInterval(interval);
11
+ }, []);
12
+ return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs("text", { children: [" ", message] })] }));
13
+ }
14
+ export default LoadingModal;
@@ -0,0 +1,40 @@
1
+ import React, { useState, useEffect } from "react";
2
+
3
+ interface LoadingModalProps {
4
+ /** Loading message */
5
+ message: string;
6
+ }
7
+
8
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
9
+
10
+ export function LoadingModal({
11
+ message,
12
+ }: LoadingModalProps) {
13
+ const [frame, setFrame] = useState(0);
14
+
15
+ useEffect(() => {
16
+ const interval = setInterval(() => {
17
+ setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
18
+ }, 80);
19
+
20
+ return () => clearInterval(interval);
21
+ }, []);
22
+
23
+ return (
24
+ <box
25
+ flexDirection="row"
26
+ border
27
+ borderStyle="rounded"
28
+ borderColor="cyan"
29
+ paddingLeft={2}
30
+ paddingRight={2}
31
+ paddingTop={1}
32
+ paddingBottom={1}
33
+ >
34
+ <text fg="cyan">{SPINNER_FRAMES[frame]}</text>
35
+ <text> {message}</text>
36
+ </box>
37
+ );
38
+ }
39
+
40
+ export default LoadingModal;
@@ -0,0 +1,16 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+ const variantConfig = {
4
+ info: { icon: "ℹ", color: "cyan" },
5
+ success: { icon: "✓", color: "green" },
6
+ error: { icon: "✗", color: "red" },
7
+ };
8
+ export function MessageModal({ title, message, variant, onDismiss, }) {
9
+ const config = variantConfig[variant];
10
+ useKeyboard(() => {
11
+ // Any key dismisses
12
+ onDismiss();
13
+ });
14
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: config.color, paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsxs("box", { children: [_jsxs("text", { fg: config.color, children: [config.icon, " "] }), _jsx("text", { children: _jsx("strong", { children: title }) })] }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsx("text", { fg: "#666666", children: "Press any key to continue" }) })] }));
15
+ }
16
+ export default MessageModal;
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
3
+
4
+ interface MessageModalProps {
5
+ /** Modal title */
6
+ title: string;
7
+ /** Modal message */
8
+ message: string;
9
+ /** Message variant */
10
+ variant: "info" | "success" | "error";
11
+ /** Callback when dismissed */
12
+ onDismiss: () => void;
13
+ }
14
+
15
+ const variantConfig = {
16
+ info: { icon: "ℹ", color: "cyan" },
17
+ success: { icon: "✓", color: "green" },
18
+ error: { icon: "✗", color: "red" },
19
+ } as const;
20
+
21
+ export function MessageModal({
22
+ title,
23
+ message,
24
+ variant,
25
+ onDismiss,
26
+ }: MessageModalProps) {
27
+ const config = variantConfig[variant];
28
+
29
+ useKeyboard(() => {
30
+ // Any key dismisses
31
+ onDismiss();
32
+ });
33
+
34
+ return (
35
+ <box
36
+ flexDirection="column"
37
+ border
38
+ borderStyle="rounded"
39
+ borderColor={config.color}
40
+ paddingLeft={2}
41
+ paddingRight={2}
42
+ paddingTop={1}
43
+ paddingBottom={1}
44
+ width={60}
45
+ >
46
+ <box>
47
+ <text fg={config.color}>{config.icon} </text>
48
+ <text>
49
+ <strong>{title}</strong>
50
+ </text>
51
+ </box>
52
+
53
+ <box marginTop={1} marginBottom={1}>
54
+ <text>{message}</text>
55
+ </box>
56
+
57
+ <box>
58
+ <text fg="#666666">Press any key to continue</text>
59
+ </box>
60
+ </box>
61
+ );
62
+ }
63
+
64
+ export default MessageModal;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { useApp } from "../../state/AppContext.js";
3
+ import { useKeyboard } from "../../hooks/useKeyboard.js";
4
+ import { ConfirmModal } from "./ConfirmModal.js";
5
+ import { InputModal } from "./InputModal.js";
6
+ import { SelectModal } from "./SelectModal.js";
7
+ import { MessageModal } from "./MessageModal.js";
8
+ import { LoadingModal } from "./LoadingModal.js";
9
+ /**
10
+ * Container that renders the active modal as an overlay
11
+ * Handles global Escape key to close modals
12
+ */
13
+ export function ModalContainer() {
14
+ const { state } = useApp();
15
+ const { modal } = state;
16
+ // Handle Escape key to close modal (except loading)
17
+ useKeyboard((key) => {
18
+ if (key.name === "escape" && modal && modal.type !== "loading") {
19
+ // Loading modal cannot be dismissed with Escape
20
+ if (modal.type === "confirm") {
21
+ modal.onCancel();
22
+ }
23
+ else if (modal.type === "input") {
24
+ modal.onCancel();
25
+ }
26
+ else if (modal.type === "select") {
27
+ modal.onCancel();
28
+ }
29
+ else if (modal.type === "message") {
30
+ modal.onDismiss();
31
+ }
32
+ }
33
+ });
34
+ if (!modal) {
35
+ return null;
36
+ }
37
+ const renderModal = () => {
38
+ switch (modal.type) {
39
+ case "confirm":
40
+ return (_jsx(ConfirmModal, { title: modal.title, message: modal.message, onConfirm: modal.onConfirm, onCancel: modal.onCancel }));
41
+ case "input":
42
+ return (_jsx(InputModal, { title: modal.title, label: modal.label, defaultValue: modal.defaultValue, onSubmit: modal.onSubmit, onCancel: modal.onCancel }));
43
+ case "select":
44
+ return (_jsx(SelectModal, { title: modal.title, message: modal.message, options: modal.options, onSelect: modal.onSelect, onCancel: modal.onCancel }));
45
+ case "message":
46
+ return (_jsx(MessageModal, { title: modal.title, message: modal.message, variant: modal.variant, onDismiss: modal.onDismiss }));
47
+ case "loading":
48
+ return _jsx(LoadingModal, { message: modal.message });
49
+ default:
50
+ return null;
51
+ }
52
+ };
53
+ // Center the modal on screen
54
+ return (_jsx("box", { position: "absolute", width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: renderModal() }));
55
+ }
56
+ export default ModalContainer;