claudeup 1.8.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 (301) 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/{dist → src}/prerunner/index.js +31 -41
  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 +0 -1
  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.map +0 -1
  137. package/dist/services/claude-runner.d.ts +0 -7
  138. package/dist/services/claude-runner.d.ts.map +0 -1
  139. package/dist/services/claude-runner.js.map +0 -1
  140. package/dist/services/claude-settings.d.ts +0 -73
  141. package/dist/services/claude-settings.d.ts.map +0 -1
  142. package/dist/services/claude-settings.js.map +0 -1
  143. package/dist/services/local-marketplace.d.ts +0 -111
  144. package/dist/services/local-marketplace.d.ts.map +0 -1
  145. package/dist/services/local-marketplace.js +0 -599
  146. package/dist/services/local-marketplace.js.map +0 -1
  147. package/dist/services/mcp-registry.d.ts +0 -10
  148. package/dist/services/mcp-registry.d.ts.map +0 -1
  149. package/dist/services/mcp-registry.js.map +0 -1
  150. package/dist/services/plugin-manager.d.ts +0 -65
  151. package/dist/services/plugin-manager.d.ts.map +0 -1
  152. package/dist/services/plugin-manager.js.map +0 -1
  153. package/dist/services/plugin-mcp-config.d.ts +0 -52
  154. package/dist/services/plugin-mcp-config.d.ts.map +0 -1
  155. package/dist/services/plugin-mcp-config.js.map +0 -1
  156. package/dist/services/update-cache.d.ts +0 -21
  157. package/dist/services/update-cache.d.ts.map +0 -1
  158. package/dist/services/update-cache.js.map +0 -1
  159. package/dist/services/version-check.d.ts +0 -20
  160. package/dist/services/version-check.d.ts.map +0 -1
  161. package/dist/services/version-check.js.map +0 -1
  162. package/dist/types/index.d.ts +0 -105
  163. package/dist/types/index.d.ts.map +0 -1
  164. package/dist/types/index.js +0 -2
  165. package/dist/types/index.js.map +0 -1
  166. package/dist/ui/InkApp.d.ts +0 -5
  167. package/dist/ui/InkApp.d.ts.map +0 -1
  168. package/dist/ui/InkApp.js +0 -188
  169. package/dist/ui/InkApp.js.map +0 -1
  170. package/dist/ui/components/CategoryHeader.d.ts +0 -16
  171. package/dist/ui/components/CategoryHeader.d.ts.map +0 -1
  172. package/dist/ui/components/CategoryHeader.js +0 -11
  173. package/dist/ui/components/CategoryHeader.js.map +0 -1
  174. package/dist/ui/components/ScrollableList.d.ts +0 -16
  175. package/dist/ui/components/ScrollableList.d.ts.map +0 -1
  176. package/dist/ui/components/ScrollableList.js.map +0 -1
  177. package/dist/ui/components/SearchInput.d.ts +0 -18
  178. package/dist/ui/components/SearchInput.d.ts.map +0 -1
  179. package/dist/ui/components/SearchInput.js +0 -30
  180. package/dist/ui/components/SearchInput.js.map +0 -1
  181. package/dist/ui/components/TabBar.d.ts +0 -8
  182. package/dist/ui/components/TabBar.d.ts.map +0 -1
  183. package/dist/ui/components/TabBar.js +0 -18
  184. package/dist/ui/components/TabBar.js.map +0 -1
  185. package/dist/ui/components/layout/Footer.d.ts +0 -14
  186. package/dist/ui/components/layout/Footer.d.ts.map +0 -1
  187. package/dist/ui/components/layout/Footer.js +0 -23
  188. package/dist/ui/components/layout/Footer.js.map +0 -1
  189. package/dist/ui/components/layout/Header.d.ts +0 -4
  190. package/dist/ui/components/layout/Header.d.ts.map +0 -1
  191. package/dist/ui/components/layout/Header.js +0 -25
  192. package/dist/ui/components/layout/Header.js.map +0 -1
  193. package/dist/ui/components/layout/Panel.d.ts +0 -22
  194. package/dist/ui/components/layout/Panel.d.ts.map +0 -1
  195. package/dist/ui/components/layout/Panel.js +0 -8
  196. package/dist/ui/components/layout/Panel.js.map +0 -1
  197. package/dist/ui/components/layout/ProgressBar.d.ts +0 -12
  198. package/dist/ui/components/layout/ProgressBar.d.ts.map +0 -1
  199. package/dist/ui/components/layout/ProgressBar.js +0 -16
  200. package/dist/ui/components/layout/ProgressBar.js.map +0 -1
  201. package/dist/ui/components/layout/ScopeTabs.d.ts +0 -12
  202. package/dist/ui/components/layout/ScopeTabs.d.ts.map +0 -1
  203. package/dist/ui/components/layout/ScopeTabs.js +0 -8
  204. package/dist/ui/components/layout/ScopeTabs.js.map +0 -1
  205. package/dist/ui/components/layout/ScreenLayout.d.ts +0 -30
  206. package/dist/ui/components/layout/ScreenLayout.d.ts.map +0 -1
  207. package/dist/ui/components/layout/ScreenLayout.js +0 -23
  208. package/dist/ui/components/layout/ScreenLayout.js.map +0 -1
  209. package/dist/ui/components/layout/index.d.ts +0 -7
  210. package/dist/ui/components/layout/index.d.ts.map +0 -1
  211. package/dist/ui/components/layout/index.js +0 -7
  212. package/dist/ui/components/layout/index.js.map +0 -1
  213. package/dist/ui/components/modals/ConfirmModal.d.ts +0 -14
  214. package/dist/ui/components/modals/ConfirmModal.d.ts.map +0 -1
  215. package/dist/ui/components/modals/ConfirmModal.js +0 -15
  216. package/dist/ui/components/modals/ConfirmModal.js.map +0 -1
  217. package/dist/ui/components/modals/InputModal.d.ts +0 -16
  218. package/dist/ui/components/modals/InputModal.d.ts.map +0 -1
  219. package/dist/ui/components/modals/InputModal.js +0 -23
  220. package/dist/ui/components/modals/InputModal.js.map +0 -1
  221. package/dist/ui/components/modals/LoadingModal.d.ts +0 -8
  222. package/dist/ui/components/modals/LoadingModal.d.ts.map +0 -1
  223. package/dist/ui/components/modals/LoadingModal.js +0 -8
  224. package/dist/ui/components/modals/LoadingModal.js.map +0 -1
  225. package/dist/ui/components/modals/MessageModal.d.ts +0 -14
  226. package/dist/ui/components/modals/MessageModal.d.ts.map +0 -1
  227. package/dist/ui/components/modals/MessageModal.js +0 -17
  228. package/dist/ui/components/modals/MessageModal.js.map +0 -1
  229. package/dist/ui/components/modals/ModalContainer.d.ts +0 -7
  230. package/dist/ui/components/modals/ModalContainer.d.ts.map +0 -1
  231. package/dist/ui/components/modals/ModalContainer.js +0 -38
  232. package/dist/ui/components/modals/ModalContainer.js.map +0 -1
  233. package/dist/ui/components/modals/SelectModal.d.ts +0 -17
  234. package/dist/ui/components/modals/SelectModal.d.ts.map +0 -1
  235. package/dist/ui/components/modals/SelectModal.js +0 -33
  236. package/dist/ui/components/modals/SelectModal.js.map +0 -1
  237. package/dist/ui/components/modals/index.d.ts +0 -7
  238. package/dist/ui/components/modals/index.d.ts.map +0 -1
  239. package/dist/ui/components/modals/index.js +0 -7
  240. package/dist/ui/components/modals/index.js.map +0 -1
  241. package/dist/ui/hooks/index.d.ts +0 -3
  242. package/dist/ui/hooks/index.d.ts.map +0 -1
  243. package/dist/ui/hooks/index.js +0 -3
  244. package/dist/ui/hooks/index.js.map +0 -1
  245. package/dist/ui/hooks/useAsyncData.d.ts +0 -40
  246. package/dist/ui/hooks/useAsyncData.d.ts.map +0 -1
  247. package/dist/ui/hooks/useAsyncData.js.map +0 -1
  248. package/dist/ui/hooks/useKeyboardNavigation.d.ts +0 -27
  249. package/dist/ui/hooks/useKeyboardNavigation.d.ts.map +0 -1
  250. package/dist/ui/hooks/useKeyboardNavigation.js +0 -82
  251. package/dist/ui/hooks/useKeyboardNavigation.js.map +0 -1
  252. package/dist/ui/screens/CliToolsScreen.d.ts +0 -4
  253. package/dist/ui/screens/CliToolsScreen.d.ts.map +0 -1
  254. package/dist/ui/screens/CliToolsScreen.js.map +0 -1
  255. package/dist/ui/screens/EnvVarsScreen.d.ts +0 -4
  256. package/dist/ui/screens/EnvVarsScreen.d.ts.map +0 -1
  257. package/dist/ui/screens/EnvVarsScreen.js +0 -145
  258. package/dist/ui/screens/EnvVarsScreen.js.map +0 -1
  259. package/dist/ui/screens/McpRegistryScreen.d.ts +0 -4
  260. package/dist/ui/screens/McpRegistryScreen.d.ts.map +0 -1
  261. package/dist/ui/screens/McpRegistryScreen.js.map +0 -1
  262. package/dist/ui/screens/McpScreen.d.ts +0 -4
  263. package/dist/ui/screens/McpScreen.d.ts.map +0 -1
  264. package/dist/ui/screens/McpScreen.js.map +0 -1
  265. package/dist/ui/screens/ModelSelectorScreen.d.ts +0 -4
  266. package/dist/ui/screens/ModelSelectorScreen.d.ts.map +0 -1
  267. package/dist/ui/screens/ModelSelectorScreen.js +0 -143
  268. package/dist/ui/screens/ModelSelectorScreen.js.map +0 -1
  269. package/dist/ui/screens/PluginsScreen.d.ts +0 -4
  270. package/dist/ui/screens/PluginsScreen.d.ts.map +0 -1
  271. package/dist/ui/screens/PluginsScreen.js.map +0 -1
  272. package/dist/ui/screens/StatusLineScreen.d.ts +0 -4
  273. package/dist/ui/screens/StatusLineScreen.d.ts.map +0 -1
  274. package/dist/ui/screens/StatusLineScreen.js +0 -197
  275. package/dist/ui/screens/StatusLineScreen.js.map +0 -1
  276. package/dist/ui/screens/index.d.ts +0 -8
  277. package/dist/ui/screens/index.d.ts.map +0 -1
  278. package/dist/ui/screens/index.js +0 -8
  279. package/dist/ui/screens/index.js.map +0 -1
  280. package/dist/ui/state/AppContext.d.ts +0 -40
  281. package/dist/ui/state/AppContext.d.ts.map +0 -1
  282. package/dist/ui/state/AppContext.js.map +0 -1
  283. package/dist/ui/state/DimensionsContext.d.ts +0 -27
  284. package/dist/ui/state/DimensionsContext.d.ts.map +0 -1
  285. package/dist/ui/state/DimensionsContext.js.map +0 -1
  286. package/dist/ui/state/reducer.d.ts +0 -4
  287. package/dist/ui/state/reducer.d.ts.map +0 -1
  288. package/dist/ui/state/reducer.js.map +0 -1
  289. package/dist/ui/state/types.d.ts +0 -266
  290. package/dist/ui/state/types.d.ts.map +0 -1
  291. package/dist/ui/state/types.js +0 -2
  292. package/dist/ui/state/types.js.map +0 -1
  293. package/dist/utils/command-utils.d.ts +0 -8
  294. package/dist/utils/command-utils.d.ts.map +0 -1
  295. package/dist/utils/command-utils.js.map +0 -1
  296. package/dist/utils/fuzzy-search.d.ts +0 -33
  297. package/dist/utils/fuzzy-search.d.ts.map +0 -1
  298. package/dist/utils/fuzzy-search.js.map +0 -1
  299. package/dist/utils/string-utils.d.ts +0 -24
  300. package/dist/utils/string-utils.d.ts.map +0 -1
  301. package/dist/utils/string-utils.js.map +0 -1
@@ -0,0 +1,200 @@
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 { statusLineCategories } from "../../data/statuslines.js";
9
+ import { setStatusLine, getStatusLine, setGlobalStatusLine, getGlobalStatusLine, } from "../../services/claude-settings.js";
10
+ export function StatusLineScreen() {
11
+ const { state, dispatch } = useApp();
12
+ const { statusline: statusLine } = state;
13
+ const modal = useModal();
14
+ const dimensions = useDimensions();
15
+ const [projectStatusLine, setProjectStatusLineState] = useState();
16
+ const [globalStatusLine, setGlobalStatusLineState] = useState();
17
+ const [isLoading, setIsLoading] = useState(true);
18
+ // Fetch data
19
+ const fetchData = useCallback(async () => {
20
+ setIsLoading(true);
21
+ try {
22
+ const [project, global] = await Promise.all([
23
+ getStatusLine(state.projectPath),
24
+ getGlobalStatusLine(),
25
+ ]);
26
+ setProjectStatusLineState(project);
27
+ setGlobalStatusLineState(global);
28
+ }
29
+ catch (error) {
30
+ // Silent error handling - show empty state
31
+ }
32
+ setIsLoading(false);
33
+ }, [state.projectPath]);
34
+ useEffect(() => {
35
+ fetchData();
36
+ }, [fetchData]);
37
+ // Get current status line based on scope
38
+ const getCurrentStatusLine = () => {
39
+ return statusLine.scope === "project"
40
+ ? projectStatusLine
41
+ : globalStatusLine;
42
+ };
43
+ // Build list items with categories
44
+ const buildListItems = () => {
45
+ const currentForScope = getCurrentStatusLine();
46
+ const items = [];
47
+ for (const category of statusLineCategories) {
48
+ items.push({
49
+ label: category.name,
50
+ isCategory: true,
51
+ });
52
+ for (const preset of category.presets) {
53
+ const isActive = currentForScope === preset.template;
54
+ const status = isActive ? "●" : "○";
55
+ items.push({
56
+ label: ` ${status} ${preset.name}`,
57
+ preset,
58
+ });
59
+ }
60
+ }
61
+ // Add custom option at the end
62
+ items.push({
63
+ label: "+ Custom Status Line",
64
+ isCustom: true,
65
+ });
66
+ return items;
67
+ };
68
+ const listItems = buildListItems();
69
+ // Keyboard handling
70
+ useKeyboard((event) => {
71
+ if (state.isSearching || state.modal)
72
+ return;
73
+ if (event.name === "up" || event.name === "k") {
74
+ const newIndex = Math.max(0, statusLine.selectedIndex - 1);
75
+ dispatch({ type: "STATUSLINE_SELECT", index: newIndex });
76
+ }
77
+ else if (event.name === "down" || event.name === "j") {
78
+ const newIndex = Math.min(listItems.length - 1, statusLine.selectedIndex + 1);
79
+ dispatch({ type: "STATUSLINE_SELECT", index: newIndex });
80
+ }
81
+ else if (event.name === "p") {
82
+ dispatch({ type: "STATUSLINE_SET_SCOPE", scope: "project" });
83
+ }
84
+ else if (event.name === "g") {
85
+ dispatch({ type: "STATUSLINE_SET_SCOPE", scope: "global" });
86
+ }
87
+ else if (event.name === "r") {
88
+ handleReset();
89
+ }
90
+ else if (event.name === "enter") {
91
+ handleSelect();
92
+ }
93
+ });
94
+ const saveStatusLine = async (template) => {
95
+ if (statusLine.scope === "global") {
96
+ await setGlobalStatusLine(template);
97
+ }
98
+ else {
99
+ await setStatusLine(template, state.projectPath);
100
+ }
101
+ };
102
+ const handleSelect = async () => {
103
+ const item = listItems[statusLine.selectedIndex];
104
+ if (!item || item.isCategory)
105
+ return;
106
+ const scopeLabel = statusLine.scope === "global" ? "Global" : "Project";
107
+ if (item.isCustom) {
108
+ const template = await modal.input(`Custom Status Line (${scopeLabel})`, "Enter template (use {model}, {cost}, etc.):", getCurrentStatusLine() || "");
109
+ if (template !== null) {
110
+ modal.loading(`Saving custom status line...`);
111
+ try {
112
+ await saveStatusLine(template);
113
+ modal.hideModal();
114
+ await modal.message("Saved", `Custom status line saved to ${scopeLabel}.\n\nRestart Claude Code to apply changes.`, "success");
115
+ fetchData();
116
+ }
117
+ catch (error) {
118
+ modal.hideModal();
119
+ await modal.message("Error", `Failed to save: ${error}`, "error");
120
+ }
121
+ }
122
+ }
123
+ else if (item.preset) {
124
+ modal.loading(`Applying ${item.preset.name}...`);
125
+ try {
126
+ await saveStatusLine(item.preset.template);
127
+ modal.hideModal();
128
+ await modal.message("Applied", `"${item.preset.name}" applied to ${scopeLabel}.\n\nRestart Claude Code to apply changes.`, "success");
129
+ fetchData();
130
+ }
131
+ catch (error) {
132
+ modal.hideModal();
133
+ await modal.message("Error", `Failed to apply: ${error}`, "error");
134
+ }
135
+ }
136
+ };
137
+ const handleReset = async () => {
138
+ const scopeLabel = statusLine.scope === "global" ? "Global" : "Project";
139
+ const confirmed = await modal.confirm("Reset Status Line", `Clear the ${scopeLabel} status line configuration?`);
140
+ if (confirmed) {
141
+ modal.loading("Resetting...");
142
+ try {
143
+ await saveStatusLine("");
144
+ modal.hideModal();
145
+ await modal.message("Reset", `${scopeLabel} status line has been cleared.`, "success");
146
+ fetchData();
147
+ }
148
+ catch (error) {
149
+ modal.hideModal();
150
+ await modal.message("Error", `Failed to reset: ${error}`, "error");
151
+ }
152
+ }
153
+ };
154
+ // Get selected item
155
+ const selectedItem = listItems[statusLine.selectedIndex];
156
+ // Build preview
157
+ const renderPreview = () => {
158
+ if (isLoading) {
159
+ return _jsx("text", { fg: "gray", children: "Loading status line settings..." });
160
+ }
161
+ if (!selectedItem || selectedItem.isCategory) {
162
+ return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: "gray", children: "Select a theme to see preview" }) }));
163
+ }
164
+ if (selectedItem.isCustom) {
165
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "\u2728 Custom Status Line" }) }), _jsx("text", { fg: "gray", children: "Create your own unique status line!" }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { fg: "yellow", children: _jsx("strong", { children: "Available variables:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { fg: "green", children: _jsx("strong", { children: "{model}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Model name"] }), _jsxs("text", { children: [_jsx("span", { fg: "green", children: _jsx("strong", { children: "{model_short}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Short name"] }), _jsxs("text", { children: [_jsx("span", { fg: "yellow", children: _jsx("strong", { children: "{cost}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Session cost"] }), _jsxs("text", { children: [_jsx("span", { fg: "blue", children: _jsx("strong", { children: "{cwd}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Working directory"] }), _jsxs("text", { children: [_jsx("span", { fg: "magenta", children: _jsx("strong", { children: "{git_branch}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Git branch"] }), _jsxs("text", { children: [_jsx("span", { fg: "cyan", children: _jsx("strong", { children: "{input_tokens}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Input tokens"] }), _jsxs("text", { children: [_jsx("span", { fg: "cyan", children: _jsx("strong", { children: "{output_tokens}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Output tokens"] }), _jsxs("text", { children: [_jsx("span", { fg: "red", children: _jsx("strong", { children: "{session_duration}" }) }), _jsx("span", { fg: "gray", children: "\u2192" }), " Duration"] })] })] })] }));
166
+ }
167
+ if (selectedItem.preset) {
168
+ const example = selectedItem.preset.template
169
+ .replace("{model}", "claude-sonnet-4")
170
+ .replace("{model_short}", "sonnet")
171
+ .replace("{cost}", "0.42")
172
+ .replace("{cwd}", "~/myapp")
173
+ .replace("{git_branch}", "main")
174
+ .replace("{input_tokens}", "1.2k")
175
+ .replace("{output_tokens}", "850")
176
+ .replace("{session_duration}", "12m");
177
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: ["\u25C6 ", selectedItem.preset.name] }) }), _jsx("text", { fg: "gray", children: selectedItem.preset.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { fg: "yellow", children: _jsx("strong", { children: "Preview:" }) }), _jsx("box", { marginTop: 1, paddingLeft: 1, paddingRight: 1, borderStyle: "rounded", borderColor: "green", children: _jsx("text", { fg: "white", children: example }) })] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { fg: "#666666", children: "Template:" }), _jsx("text", { fg: "gray", children: selectedItem.preset.template })] })] }));
178
+ }
179
+ return null;
180
+ };
181
+ const renderListItem = (item, _idx, isSelected) => {
182
+ if (item.isCategory) {
183
+ return (_jsx("text", { fg: "magenta", children: _jsxs("strong", { children: ["\u25B8 ", item.label] }) }));
184
+ }
185
+ if (item.isCustom) {
186
+ return isSelected ? (_jsx("text", { bg: "cyan", fg: "black", children: _jsxs("strong", { children: [" ", "\u2795 Custom Status Line", " "] }) })) : (_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: [" ", "\u2795 Custom Status Line"] }) }));
187
+ }
188
+ const currentForScope = getCurrentStatusLine();
189
+ const isActive = item.preset && currentForScope === item.preset.template;
190
+ return isSelected ? (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", isActive ? "●" : "○", " ", item.preset?.name || "", " "] })) : (_jsxs("text", { fg: isActive ? "green" : "white", children: [" ", isActive ? "●" : "○", " ", item.preset?.name || ""] }));
191
+ };
192
+ // Build status line content
193
+ const scopeLabel = statusLine.scope === "project" ? "Project" : "Global";
194
+ const currentValue = getCurrentStatusLine();
195
+ const statusContent = (_jsxs(_Fragment, { children: [_jsx("text", { fg: "gray", children: "Scope: " }), _jsx("text", { fg: "cyan", children: scopeLabel }), _jsx("text", { fg: "gray", children: " \u2502 Current: " }), _jsx("text", { fg: "green", children: currentValue
196
+ ? currentValue.slice(0, 35) + (currentValue.length > 35 ? "..." : "")
197
+ : "(not set)" })] }));
198
+ return (_jsx(ScreenLayout, { title: "claudeup Status Line", currentScreen: "statusline", statusLine: statusContent, footerHints: "\u2191\u2193:nav \u2502 Enter:apply \u2502 p:project \u2502 g:global \u2502 r:reset", listPanel: _jsx(ScrollableList, { items: listItems, selectedIndex: statusLine.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderPreview() }));
199
+ }
200
+ export default StatusLineScreen;
@@ -0,0 +1,411 @@
1
+ import React, { useEffect, useCallback, useState } from "react";
2
+ import { useApp, useModal } from "../state/AppContext.js";
3
+ import { useDimensions } from "../state/DimensionsContext.js";
4
+ import { useKeyboard } from "../hooks/useKeyboard.js";
5
+ import { ScreenLayout } from "../components/layout/index.js";
6
+ import { ScrollableList } from "../components/ScrollableList.js";
7
+ import { statusLineCategories } from "../../data/statuslines.js";
8
+ import type { StatusLineConfig } from "../../types/index.js";
9
+ import {
10
+ setStatusLine,
11
+ getStatusLine,
12
+ setGlobalStatusLine,
13
+ getGlobalStatusLine,
14
+ } from "../../services/claude-settings.js";
15
+
16
+ interface ListItem {
17
+ label: string;
18
+ preset?: StatusLineConfig;
19
+ isCustom?: boolean;
20
+ isCategory?: boolean;
21
+ }
22
+
23
+ export function StatusLineScreen() {
24
+ const { state, dispatch } = useApp();
25
+ const { statusline: statusLine } = state;
26
+ const modal = useModal();
27
+ const dimensions = useDimensions();
28
+
29
+ const [projectStatusLine, setProjectStatusLineState] = useState<
30
+ string | undefined
31
+ >();
32
+ const [globalStatusLine, setGlobalStatusLineState] = useState<
33
+ string | undefined
34
+ >();
35
+ const [isLoading, setIsLoading] = useState(true);
36
+
37
+ // Fetch data
38
+ const fetchData = useCallback(async () => {
39
+ setIsLoading(true);
40
+ try {
41
+ const [project, global] = await Promise.all([
42
+ getStatusLine(state.projectPath),
43
+ getGlobalStatusLine(),
44
+ ]);
45
+ setProjectStatusLineState(project);
46
+ setGlobalStatusLineState(global);
47
+ } catch (error) {
48
+ // Silent error handling - show empty state
49
+ }
50
+ setIsLoading(false);
51
+ }, [state.projectPath]);
52
+
53
+ useEffect(() => {
54
+ fetchData();
55
+ }, [fetchData]);
56
+
57
+ // Get current status line based on scope
58
+ const getCurrentStatusLine = (): string | undefined => {
59
+ return statusLine.scope === "project"
60
+ ? projectStatusLine
61
+ : globalStatusLine;
62
+ };
63
+
64
+ // Build list items with categories
65
+ const buildListItems = (): ListItem[] => {
66
+ const currentForScope = getCurrentStatusLine();
67
+ const items: ListItem[] = [];
68
+
69
+ for (const category of statusLineCategories) {
70
+ items.push({
71
+ label: category.name,
72
+ isCategory: true,
73
+ });
74
+
75
+ for (const preset of category.presets) {
76
+ const isActive = currentForScope === preset.template;
77
+ const status = isActive ? "●" : "○";
78
+ items.push({
79
+ label: ` ${status} ${preset.name}`,
80
+ preset,
81
+ });
82
+ }
83
+ }
84
+
85
+ // Add custom option at the end
86
+ items.push({
87
+ label: "+ Custom Status Line",
88
+ isCustom: true,
89
+ });
90
+
91
+ return items;
92
+ };
93
+
94
+ const listItems = buildListItems();
95
+
96
+ // Keyboard handling
97
+ useKeyboard((event) => {
98
+ if (state.isSearching || state.modal) return;
99
+
100
+ if (event.name === "up" || event.name === "k") {
101
+ const newIndex = Math.max(0, statusLine.selectedIndex - 1);
102
+ dispatch({ type: "STATUSLINE_SELECT", index: newIndex });
103
+ } else if (event.name === "down" || event.name === "j") {
104
+ const newIndex = Math.min(
105
+ listItems.length - 1,
106
+ statusLine.selectedIndex + 1,
107
+ );
108
+ dispatch({ type: "STATUSLINE_SELECT", index: newIndex });
109
+ } else if (event.name === "p") {
110
+ dispatch({ type: "STATUSLINE_SET_SCOPE", scope: "project" });
111
+ } else if (event.name === "g") {
112
+ dispatch({ type: "STATUSLINE_SET_SCOPE", scope: "global" });
113
+ } else if (event.name === "r") {
114
+ handleReset();
115
+ } else if (event.name === "enter") {
116
+ handleSelect();
117
+ }
118
+ });
119
+
120
+ const saveStatusLine = async (template: string): Promise<void> => {
121
+ if (statusLine.scope === "global") {
122
+ await setGlobalStatusLine(template);
123
+ } else {
124
+ await setStatusLine(template, state.projectPath);
125
+ }
126
+ };
127
+
128
+ const handleSelect = async () => {
129
+ const item = listItems[statusLine.selectedIndex];
130
+ if (!item || item.isCategory) return;
131
+
132
+ const scopeLabel = statusLine.scope === "global" ? "Global" : "Project";
133
+
134
+ if (item.isCustom) {
135
+ const template = await modal.input(
136
+ `Custom Status Line (${scopeLabel})`,
137
+ "Enter template (use {model}, {cost}, etc.):",
138
+ getCurrentStatusLine() || "",
139
+ );
140
+
141
+ if (template !== null) {
142
+ modal.loading(`Saving custom status line...`);
143
+ try {
144
+ await saveStatusLine(template);
145
+ modal.hideModal();
146
+ await modal.message(
147
+ "Saved",
148
+ `Custom status line saved to ${scopeLabel}.\n\nRestart Claude Code to apply changes.`,
149
+ "success",
150
+ );
151
+ fetchData();
152
+ } catch (error) {
153
+ modal.hideModal();
154
+ await modal.message("Error", `Failed to save: ${error}`, "error");
155
+ }
156
+ }
157
+ } else if (item.preset) {
158
+ modal.loading(`Applying ${item.preset.name}...`);
159
+ try {
160
+ await saveStatusLine(item.preset.template);
161
+ modal.hideModal();
162
+ await modal.message(
163
+ "Applied",
164
+ `"${item.preset.name}" applied to ${scopeLabel}.\n\nRestart Claude Code to apply changes.`,
165
+ "success",
166
+ );
167
+ fetchData();
168
+ } catch (error) {
169
+ modal.hideModal();
170
+ await modal.message("Error", `Failed to apply: ${error}`, "error");
171
+ }
172
+ }
173
+ };
174
+
175
+ const handleReset = async () => {
176
+ const scopeLabel = statusLine.scope === "global" ? "Global" : "Project";
177
+ const confirmed = await modal.confirm(
178
+ "Reset Status Line",
179
+ `Clear the ${scopeLabel} status line configuration?`,
180
+ );
181
+
182
+ if (confirmed) {
183
+ modal.loading("Resetting...");
184
+ try {
185
+ await saveStatusLine("");
186
+ modal.hideModal();
187
+ await modal.message(
188
+ "Reset",
189
+ `${scopeLabel} status line has been cleared.`,
190
+ "success",
191
+ );
192
+ fetchData();
193
+ } catch (error) {
194
+ modal.hideModal();
195
+ await modal.message("Error", `Failed to reset: ${error}`, "error");
196
+ }
197
+ }
198
+ };
199
+
200
+ // Get selected item
201
+ const selectedItem = listItems[statusLine.selectedIndex];
202
+
203
+ // Build preview
204
+ const renderPreview = () => {
205
+ if (isLoading) {
206
+ return <text fg="gray">Loading status line settings...</text>;
207
+ }
208
+
209
+ if (!selectedItem || selectedItem.isCategory) {
210
+ return (
211
+ <box
212
+ flexDirection="column"
213
+ alignItems="center"
214
+ justifyContent="center"
215
+ flexGrow={1}
216
+ >
217
+ <text fg="gray">Select a theme to see preview</text>
218
+ </box>
219
+ );
220
+ }
221
+
222
+ if (selectedItem.isCustom) {
223
+ return (
224
+ <box flexDirection="column">
225
+ <text fg="cyan">
226
+ <strong>✨ Custom Status Line</strong>
227
+ </text>
228
+ <text fg="gray">Create your own unique status line!</text>
229
+
230
+ <box marginTop={1} flexDirection="column">
231
+ <text fg="yellow">
232
+ <strong>Available variables:</strong>
233
+ </text>
234
+ <box marginTop={1} flexDirection="column">
235
+ <text>
236
+ <span fg="green">
237
+ <strong>{"{model}"}</strong>
238
+ </span>{" "}
239
+ <span fg="gray">→</span> Model name
240
+ </text>
241
+ <text>
242
+ <span fg="green">
243
+ <strong>{"{model_short}"}</strong>
244
+ </span>{" "}
245
+ <span fg="gray">→</span> Short name
246
+ </text>
247
+ <text>
248
+ <span fg="yellow">
249
+ <strong>{"{cost}"}</strong>
250
+ </span>{" "}
251
+ <span fg="gray">→</span> Session cost
252
+ </text>
253
+ <text>
254
+ <span fg="blue">
255
+ <strong>{"{cwd}"}</strong>
256
+ </span>{" "}
257
+ <span fg="gray">→</span> Working directory
258
+ </text>
259
+ <text>
260
+ <span fg="magenta">
261
+ <strong>{"{git_branch}"}</strong>
262
+ </span>{" "}
263
+ <span fg="gray">→</span> Git branch
264
+ </text>
265
+ <text>
266
+ <span fg="cyan">
267
+ <strong>{"{input_tokens}"}</strong>
268
+ </span>{" "}
269
+ <span fg="gray">→</span> Input tokens
270
+ </text>
271
+ <text>
272
+ <span fg="cyan">
273
+ <strong>{"{output_tokens}"}</strong>
274
+ </span>{" "}
275
+ <span fg="gray">→</span> Output tokens
276
+ </text>
277
+ <text>
278
+ <span fg="red">
279
+ <strong>{"{session_duration}"}</strong>
280
+ </span>
281
+ <span fg="gray">→</span> Duration
282
+ </text>
283
+ </box>
284
+ </box>
285
+ </box>
286
+ );
287
+ }
288
+
289
+ if (selectedItem.preset) {
290
+ const example = selectedItem.preset.template
291
+ .replace("{model}", "claude-sonnet-4")
292
+ .replace("{model_short}", "sonnet")
293
+ .replace("{cost}", "0.42")
294
+ .replace("{cwd}", "~/myapp")
295
+ .replace("{git_branch}", "main")
296
+ .replace("{input_tokens}", "1.2k")
297
+ .replace("{output_tokens}", "850")
298
+ .replace("{session_duration}", "12m");
299
+
300
+ return (
301
+ <box flexDirection="column">
302
+ <text fg="cyan">
303
+ <strong>◆ {selectedItem.preset.name}</strong>
304
+ </text>
305
+ <text fg="gray">{selectedItem.preset.description}</text>
306
+
307
+ <box marginTop={1} flexDirection="column">
308
+ <text fg="yellow">
309
+ <strong>Preview:</strong>
310
+ </text>
311
+ <box
312
+ marginTop={1}
313
+ paddingLeft={1} paddingRight={1}
314
+ borderStyle="rounded"
315
+ borderColor="green"
316
+ >
317
+ <text fg="white">{example}</text>
318
+ </box>
319
+ </box>
320
+
321
+ <box marginTop={1} flexDirection="column">
322
+ <text fg="#666666">Template:</text>
323
+ <text fg="gray">{selectedItem.preset.template}</text>
324
+ </box>
325
+ </box>
326
+ );
327
+ }
328
+
329
+ return null;
330
+ };
331
+
332
+ const renderListItem = (
333
+ item: ListItem,
334
+ _idx: number,
335
+ isSelected: boolean,
336
+ ) => {
337
+ if (item.isCategory) {
338
+ return (
339
+ <text fg="magenta" >
340
+ <strong>▸ {item.label}</strong>
341
+ </text>
342
+ );
343
+ }
344
+
345
+ if (item.isCustom) {
346
+ return isSelected ? (
347
+ <text bg="cyan" fg="black" >
348
+ <strong>
349
+ {" "}
350
+ ➕ Custom Status Line{" "}
351
+ </strong>
352
+ </text>
353
+ ) : (
354
+ <text fg="cyan" >
355
+ <strong>{" "}➕ Custom Status Line</strong>
356
+ </text>
357
+ );
358
+ }
359
+
360
+ const currentForScope = getCurrentStatusLine();
361
+ const isActive = item.preset && currentForScope === item.preset.template;
362
+
363
+ return isSelected ? (
364
+ <text bg="magenta" fg="white" >
365
+ {" "}
366
+ {isActive ? "●" : "○"} {item.preset?.name || ""}{" "}
367
+ </text>
368
+ ) : (
369
+ <text fg={isActive ? "green" : "white"} >
370
+ {" "}
371
+ {isActive ? "●" : "○"} {item.preset?.name || ""}
372
+ </text>
373
+ );
374
+ };
375
+
376
+ // Build status line content
377
+ const scopeLabel = statusLine.scope === "project" ? "Project" : "Global";
378
+ const currentValue = getCurrentStatusLine();
379
+ const statusContent = (
380
+ <>
381
+ <text fg="gray">Scope: </text>
382
+ <text fg="cyan">{scopeLabel}</text>
383
+ <text fg="gray"> │ Current: </text>
384
+ <text fg="green">
385
+ {currentValue
386
+ ? currentValue.slice(0, 35) + (currentValue.length > 35 ? "..." : "")
387
+ : "(not set)"}
388
+ </text>
389
+ </>
390
+ );
391
+
392
+ return (
393
+ <ScreenLayout
394
+ title="claudeup Status Line"
395
+ currentScreen="statusline"
396
+ statusLine={statusContent}
397
+ footerHints="↑↓:nav │ Enter:apply │ p:project │ g:global │ r:reset"
398
+ listPanel={
399
+ <ScrollableList
400
+ items={listItems}
401
+ selectedIndex={statusLine.selectedIndex}
402
+ renderItem={renderListItem}
403
+ maxHeight={dimensions.listPanelHeight}
404
+ />
405
+ }
406
+ detailPanel={renderPreview()}
407
+ />
408
+ );
409
+ }
410
+
411
+ export default StatusLineScreen;
@@ -0,0 +1,7 @@
1
+ export { PluginsScreen } from "./PluginsScreen.js";
2
+ export { McpScreen } from "./McpScreen.js";
3
+ export { McpRegistryScreen } from "./McpRegistryScreen.js";
4
+ export { StatusLineScreen } from "./StatusLineScreen.js";
5
+ export { EnvVarsScreen } from "./EnvVarsScreen.js";
6
+ export { CliToolsScreen } from "./CliToolsScreen.js";
7
+ export { ModelSelectorScreen } from "./ModelSelectorScreen.js";
@@ -0,0 +1,7 @@
1
+ export { PluginsScreen } from "./PluginsScreen.js";
2
+ export { McpScreen } from "./McpScreen.js";
3
+ export { McpRegistryScreen } from "./McpRegistryScreen.js";
4
+ export { StatusLineScreen } from "./StatusLineScreen.js";
5
+ export { EnvVarsScreen } from "./EnvVarsScreen.js";
6
+ export { CliToolsScreen } from "./CliToolsScreen.js";
7
+ export { ModelSelectorScreen } from "./ModelSelectorScreen.js";
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { createContext, useContext, useState, } from "react";
3
+ // ============================================================================
4
+ // Context
5
+ // ============================================================================
6
+ const AnimationContext = createContext(null);
7
+ export function AnimationProvider({ children }) {
8
+ const [timelines, setTimelines] = useState(new Map());
9
+ const registerTimeline = (id, timeline) => {
10
+ setTimelines((prev) => {
11
+ const next = new Map(prev);
12
+ next.set(id, timeline);
13
+ return next;
14
+ });
15
+ };
16
+ const unregisterTimeline = (id) => {
17
+ setTimelines((prev) => {
18
+ const next = new Map(prev);
19
+ next.delete(id);
20
+ return next;
21
+ });
22
+ };
23
+ return (_jsx(AnimationContext.Provider, { value: { timelines, registerTimeline, unregisterTimeline }, children: children }));
24
+ }
25
+ // ============================================================================
26
+ // Hook: useAnimation
27
+ // ============================================================================
28
+ export function useAnimation() {
29
+ const context = useContext(AnimationContext);
30
+ if (!context) {
31
+ throw new Error("useAnimation must be used within an AnimationProvider");
32
+ }
33
+ return context;
34
+ }