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,441 @@
1
+ import React, { useMemo, useEffect } from "react";
2
+ import { useApp } from "../state/AppContext.js";
3
+ import { useDimensions } from "../state/DimensionsContext.js";
4
+ import { ScrollableList } from "../components/ScrollableList.js";
5
+ import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
6
+ import { useKeyboard } from "../hooks/useKeyboard.js";
7
+
8
+ interface ModelItem {
9
+ id: string;
10
+ type: "header" | "model";
11
+ label: string;
12
+ provider?: string;
13
+ configured?: boolean;
14
+ modelId?: string;
15
+ description?: string;
16
+ }
17
+
18
+ const RECENT_MODELS: ModelItem[] = [
19
+ {
20
+ id: "recent-1",
21
+ type: "model",
22
+ label: "MoonshotAI: Kimi K2 0905 (exacto)",
23
+ provider: "OpenRouter",
24
+ modelId: "moonshot/kimi-k2-0905",
25
+ configured: true,
26
+ },
27
+ ];
28
+
29
+ const ANTHROPIC_MODELS: ModelItem[] = [
30
+ {
31
+ id: "claude-opus-4",
32
+ type: "model",
33
+ label: "Claude Opus 4",
34
+ provider: "Anthropic",
35
+ modelId: "anthropic/claude-opus-4",
36
+ configured: true,
37
+ },
38
+ {
39
+ id: "claude-sonnet-4",
40
+ type: "model",
41
+ label: "Claude Sonnet 4",
42
+ provider: "Anthropic",
43
+ modelId: "anthropic/claude-sonnet-4",
44
+ configured: true,
45
+ },
46
+ {
47
+ id: "claude-opus-4.1",
48
+ type: "model",
49
+ label: "Claude Opus 4.1",
50
+ provider: "Anthropic",
51
+ modelId: "anthropic/claude-opus-4.1",
52
+ configured: true,
53
+ },
54
+ {
55
+ id: "claude-opus-4.5",
56
+ type: "model",
57
+ label: "Claude Opus 4.5",
58
+ provider: "Anthropic",
59
+ modelId: "anthropic/claude-opus-4.5",
60
+ configured: true,
61
+ },
62
+ {
63
+ id: "claude-3.5-haiku",
64
+ type: "model",
65
+ label: "Claude 3.5 Haiku",
66
+ provider: "Anthropic",
67
+ modelId: "anthropic/claude-3.5-haiku",
68
+ configured: true,
69
+ },
70
+ {
71
+ id: "claude-4.5-haiku",
72
+ type: "model",
73
+ label: "Claude 4.5 Haiku",
74
+ provider: "Anthropic",
75
+ modelId: "anthropic/claude-4.5-haiku",
76
+ configured: true,
77
+ },
78
+ {
79
+ id: "claude-3.7-sonnet",
80
+ type: "model",
81
+ label: "Claude 3.7 Sonnet",
82
+ provider: "Anthropic",
83
+ modelId: "anthropic/claude-3.7-sonnet",
84
+ configured: true,
85
+ },
86
+ {
87
+ id: "claude-sonnet-4.5",
88
+ type: "model",
89
+ label: "Claude Sonnet 4.5",
90
+ provider: "Anthropic",
91
+ modelId: "anthropic/claude-sonnet-4.5",
92
+ configured: true,
93
+ },
94
+ {
95
+ id: "claude-3.5-sonnet-new",
96
+ type: "model",
97
+ label: "Claude 3.5 Sonnet (New)",
98
+ provider: "Anthropic",
99
+ modelId: "anthropic/claude-3-5-sonnet-new",
100
+ configured: true,
101
+ },
102
+ {
103
+ id: "claude-3.5-sonnet-old",
104
+ type: "model",
105
+ label: "Claude 3.5 Sonnet (Old)",
106
+ provider: "Anthropic",
107
+ modelId: "anthropic/claude-3-5-sonnet-old",
108
+ configured: true,
109
+ },
110
+ ];
111
+
112
+ const OPENAI_MODELS: ModelItem[] = [
113
+ {
114
+ id: "gpt-5-codex",
115
+ type: "model",
116
+ label: "GPT-5 Codex",
117
+ provider: "OpenAI",
118
+ modelId: "openai/gpt-5-codex",
119
+ configured: true,
120
+ },
121
+ {
122
+ id: "gpt-5.1-codex",
123
+ type: "model",
124
+ label: "GPT-5.1 Codex",
125
+ provider: "OpenAI",
126
+ modelId: "openai/gpt-5.1-codex",
127
+ configured: true,
128
+ },
129
+ {
130
+ id: "gpt-5.1-codex-max",
131
+ type: "model",
132
+ label: "GPT-5.1 Codex Max",
133
+ provider: "OpenAI",
134
+ modelId: "openai/gpt-5.1-codex-max",
135
+ configured: true,
136
+ },
137
+ {
138
+ id: "gpt-5.1-codex-mini",
139
+ type: "model",
140
+ label: "GPT-5.1 Codex Mini",
141
+ provider: "OpenAI",
142
+ modelId: "openai/gpt-5.1-codex-mini",
143
+ configured: true,
144
+ },
145
+ ];
146
+
147
+ const XAI_MODELS: ModelItem[] = [
148
+ {
149
+ id: "grok-code-fast",
150
+ type: "model",
151
+ label: "Grok Code Fast",
152
+ provider: "xAI",
153
+ modelId: "xai/grok-code-fast",
154
+ configured: false,
155
+ }, // Configured check missing in reference image? No, line is very faint. Assuming not configured or just separator missing.
156
+ ];
157
+
158
+ export function ModelSelectorScreen() {
159
+ const { state, dispatch } = useApp();
160
+ const { modelSelector } = state;
161
+ const dimensions = useDimensions();
162
+
163
+ // Combine items into display list
164
+ const allItems = useMemo<ModelItem[]>(() => {
165
+ return [
166
+ {
167
+ id: "header-recent",
168
+ type: "header",
169
+ label: "Recently used",
170
+ provider: "OpenRouter",
171
+ }, // Provider column used for right text
172
+ ...RECENT_MODELS,
173
+ {
174
+ id: "header-anthropic",
175
+ type: "header",
176
+ label: "Anthropic",
177
+ configured: true,
178
+ },
179
+ ...ANTHROPIC_MODELS,
180
+ {
181
+ id: "header-openai",
182
+ type: "header",
183
+ label: "OpenAI",
184
+ configured: true,
185
+ },
186
+ ...OPENAI_MODELS,
187
+ { id: "header-xai", type: "header", label: "xAI" },
188
+ ...XAI_MODELS,
189
+ ];
190
+ }, []);
191
+
192
+ // Filter items
193
+ const filteredItems = useMemo(() => {
194
+ if (!modelSelector.searchQuery) return allItems;
195
+
196
+ // Filter only models
197
+ const models = allItems.filter((i) => i.type === "model");
198
+ const fuzzyResults = fuzzyFilter(
199
+ models,
200
+ modelSelector.searchQuery,
201
+ (item) => item.label,
202
+ );
203
+
204
+ // If searching, just show matched models without headers to keep it clean, or keep hierarchy?
205
+ // "Switch Model" TUI usually flattens on search.
206
+ return fuzzyResults.map(
207
+ (r) =>
208
+ ({ ...r.item, _matches: r.matches }) as ModelItem & {
209
+ _matches?: number[];
210
+ },
211
+ );
212
+ }, [allItems, modelSelector.searchQuery]);
213
+
214
+ // Ensure selection is valid (not a header) on mount or search change
215
+ useEffect(() => {
216
+ // If current selection is a header or out of bounds, find first model
217
+ const current = filteredItems[modelSelector.selectedIndex];
218
+ if (!current || current.type === "header") {
219
+ const firstModelIndex = filteredItems.findIndex(
220
+ (i) => i.type === "model",
221
+ );
222
+ if (
223
+ firstModelIndex !== -1 &&
224
+ firstModelIndex !== modelSelector.selectedIndex
225
+ ) {
226
+ dispatch({ type: "MODEL_SELECTOR_SELECT", index: firstModelIndex });
227
+ }
228
+ }
229
+ }, [filteredItems, modelSelector.selectedIndex, dispatch]);
230
+
231
+ // Handle keyboard
232
+ useKeyboard((event) => {
233
+ // Search input handling
234
+ if (event.name === "backspace" || event.name === "delete") {
235
+ dispatch({
236
+ type: "MODEL_SELECTOR_SET_SEARCH",
237
+ query: modelSelector.searchQuery.slice(0, -1),
238
+ });
239
+ return;
240
+ }
241
+
242
+ // Toggle Task Size
243
+ if (event.name === "tab") {
244
+ const newSize = modelSelector.taskSize === "large" ? "small" : "large";
245
+ dispatch({ type: "MODEL_SELECTOR_SET_TASK_SIZE", size: newSize });
246
+ return;
247
+ }
248
+
249
+ // Navigation
250
+ if (event.name === "up" || (event.ctrl && event.name === "p")) {
251
+ // Standard navigation
252
+ let newIndex = modelSelector.selectedIndex - 1;
253
+ // Skip headers while moving up
254
+ while (newIndex >= 0 && filteredItems[newIndex]?.type === "header") {
255
+ newIndex--;
256
+ }
257
+ if (newIndex >= 0) {
258
+ dispatch({ type: "MODEL_SELECTOR_SELECT", index: newIndex });
259
+ }
260
+ return;
261
+ }
262
+
263
+ if (event.name === "down" || (event.ctrl && event.name === "n")) {
264
+ let newIndex = modelSelector.selectedIndex + 1;
265
+ // Skip headers while moving down
266
+ while (
267
+ newIndex < filteredItems.length &&
268
+ filteredItems[newIndex]?.type === "header"
269
+ ) {
270
+ newIndex++;
271
+ }
272
+ if (newIndex < filteredItems.length) {
273
+ dispatch({ type: "MODEL_SELECTOR_SELECT", index: newIndex });
274
+ }
275
+ return;
276
+ }
277
+
278
+ if (event.name === "enter" || event.name === "return") {
279
+ // Just log selection for now or exit
280
+ // dispatch({ type: 'NAVIGATE', route: { screen: 'plugins' } });
281
+ return;
282
+ }
283
+
284
+ // Regular text input (single character keys)
285
+ if (
286
+ event.name.length === 1 &&
287
+ !event.ctrl &&
288
+ !event.shift
289
+ ) {
290
+ dispatch({
291
+ type: "MODEL_SELECTOR_SET_SEARCH",
292
+ query: modelSelector.searchQuery + event.name,
293
+ });
294
+ return;
295
+ }
296
+ });
297
+
298
+ // Render list item
299
+ const renderItem = (item: ModelItem, _index: number, isSelected: boolean) => {
300
+ if (item.type === "header") {
301
+ // Header style: "Name ----------------------------- Status"
302
+ const status = item.configured ? "✔ Configured" : "";
303
+
304
+ return (
305
+ <box margin={0}>
306
+ <text fg="gray">{item.label} </text>
307
+ <text fg="#888888">
308
+ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
309
+ </text>
310
+ {status && <text fg="green"> {status}</text>}
311
+ {item.provider && <text fg="blue"> {item.provider}</text>}
312
+ </box>
313
+ );
314
+ }
315
+
316
+ // Model item
317
+ // Matches highlighting
318
+ const matches = (item as ModelItem & { _matches?: number[] })._matches;
319
+ const labelSegment = matches ? highlightMatches(item.label, matches) : null;
320
+
321
+ const Label = () => (
322
+ <text fg={isSelected ? "white" : "white"}>
323
+ {labelSegment
324
+ ? labelSegment.map((seg, i) => (
325
+ <text
326
+ key={i}
327
+ fg={
328
+ seg.highlighted
329
+ ? isSelected
330
+ ? "white"
331
+ : "cyan"
332
+ : isSelected
333
+ ? "white"
334
+ : "white"
335
+ }
336
+ >
337
+ {seg.highlighted ? <u><strong>{seg.text}</strong></u> : seg.text}
338
+ </text>
339
+ ))
340
+ : item.label}
341
+ </text>
342
+ );
343
+
344
+ if (isSelected) {
345
+ return (
346
+ <box overflow="hidden">
347
+ <text bg="magenta" fg="white">
348
+ {" "}
349
+ </text>
350
+ <text bg="magenta" fg="white">
351
+ <Label />
352
+ </text>
353
+ <box flexGrow={1}>
354
+ <text bg="magenta"> </text>
355
+ </box>
356
+ {item.provider && (
357
+ <text bg="magenta" fg="white">
358
+ {item.provider}{" "}
359
+ </text>
360
+ )}
361
+ </box>
362
+ );
363
+ } else {
364
+ return (
365
+ <box overflow="hidden">
366
+ <Label />
367
+ <box flexGrow={1} />
368
+ {item.provider && <text fg="gray">{item.provider}</text>}
369
+ </box>
370
+ );
371
+ }
372
+ };
373
+
374
+ const footerHints = "↑↓ choose • tab toggle type • enter choose • esc exit";
375
+ // Available height calculation
376
+ // Header: 1 line (title) + 1 line (search) + 1 line (separator) = 3 lines?
377
+ // Title line: Switch Model /// (o) Large
378
+ // Cursor line: > c
379
+ // Separator: handled by list? or explicit?
380
+
381
+ const listHeight = Math.max(5, dimensions.contentHeight - 5);
382
+
383
+ return (
384
+ <box flexDirection="column" height={dimensions.contentHeight}>
385
+ {/* Top Bar */}
386
+ <box
387
+ flexDirection="row"
388
+ border
389
+ borderStyle="single"
390
+ borderColor="#7e57c2"
391
+ paddingLeft={1}
392
+ paddingRight={1}
393
+ >
394
+ <box flexDirection="column" flexGrow={1}>
395
+ <box flexDirection="row" justifyContent="space-between">
396
+ <box>
397
+ <text fg="#7e57c2">Switch Model </text>
398
+ <text
399
+ fg={modelSelector.taskSize === "large" ? "white" : "gray"}
400
+ >
401
+ {modelSelector.taskSize === "large" ? "◎" : "○"} Large Task
402
+ {" "}
403
+ </text>
404
+ <text
405
+ fg={modelSelector.taskSize === "small" ? "white" : "gray"}
406
+ >
407
+ {modelSelector.taskSize === "small" ? "◎" : "○"} Small Task
408
+ </text>
409
+ </box>
410
+ </box>
411
+ <box flexDirection="row" marginTop={1}>
412
+ <text fg="green">{"> "}</text>
413
+ <text>{modelSelector.searchQuery}</text>
414
+ <text bg="gray" fg="black">
415
+ {" "}
416
+ </text>
417
+ </box>
418
+ </box>
419
+ </box>
420
+
421
+ {/* Initial selection ensurement */}
422
+ {/* (Handled in reducer or manually here if needed to avoid index -1) */}
423
+
424
+ <box flexGrow={1} paddingLeft={1} paddingRight={1}>
425
+ <ScrollableList
426
+ items={filteredItems}
427
+ selectedIndex={modelSelector.selectedIndex}
428
+ renderItem={renderItem}
429
+ maxHeight={listHeight}
430
+ showScrollIndicators={false}
431
+ />
432
+ </box>
433
+
434
+ <box height={1}>
435
+ <text fg="#888888">{footerHints}</text>
436
+ </box>
437
+ </box>
438
+ );
439
+ }
440
+
441
+ export default ModelSelectorScreen;