@navios/commander-tui 1.0.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 (348) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +275 -0
  3. package/coverage/__tests__/utils/factories.ts.html +1147 -0
  4. package/coverage/__tests__/utils/index.html +131 -0
  5. package/coverage/__tests__/utils/render-utils.tsx.html +202 -0
  6. package/coverage/base.css +224 -0
  7. package/coverage/block-navigation.js +87 -0
  8. package/coverage/clover.xml +959 -0
  9. package/coverage/components/filter/filter_bar.tsx.html +322 -0
  10. package/coverage/components/filter/index.html +116 -0
  11. package/coverage/components/log/index.html +131 -0
  12. package/coverage/components/log/index.ts.html +88 -0
  13. package/coverage/components/log/log_message.tsx.html +391 -0
  14. package/coverage/components/prompt/index.html +116 -0
  15. package/coverage/components/prompt/prompt_renderer.tsx.html +1123 -0
  16. package/coverage/components/screen/index.html +131 -0
  17. package/coverage/components/screen/loading_message.tsx.html +217 -0
  18. package/coverage/components/screen/progress_message.tsx.html +265 -0
  19. package/coverage/components/sidebar/index.html +146 -0
  20. package/coverage/components/sidebar/sidebar.tsx.html +391 -0
  21. package/coverage/components/sidebar/sidebar_item.tsx.html +235 -0
  22. package/coverage/components/sidebar/sidebar_separator.tsx.html +124 -0
  23. package/coverage/context/index.html +131 -0
  24. package/coverage/context/index.ts.html +88 -0
  25. package/coverage/context/logger_context.tsx.html +412 -0
  26. package/coverage/coverage-final.json +55 -0
  27. package/coverage/favicon.png +0 -0
  28. package/coverage/filter/filter_engine.ts.html +424 -0
  29. package/coverage/filter/index.html +116 -0
  30. package/coverage/hooks/index.html +131 -0
  31. package/coverage/hooks/index.ts.html +88 -0
  32. package/coverage/hooks/use_theme.ts.html +121 -0
  33. package/coverage/index.html +356 -0
  34. package/coverage/keyboard/index.html +116 -0
  35. package/coverage/keyboard/keyboard_manager.ts.html +784 -0
  36. package/coverage/prettify.css +1 -0
  37. package/coverage/prettify.js +2 -0
  38. package/coverage/schemas/index.html +161 -0
  39. package/coverage/schemas/index.ts.html +94 -0
  40. package/coverage/schemas/logger-options.ts.html +124 -0
  41. package/coverage/schemas/prompt-options.ts.html +112 -0
  42. package/coverage/schemas/screen-options.ts.html +127 -0
  43. package/coverage/services/index.html +146 -0
  44. package/coverage/services/logger.ts.html +1192 -0
  45. package/coverage/services/prompt.ts.html +568 -0
  46. package/coverage/services/screen.ts.html +1804 -0
  47. package/coverage/sort-arrow-sprite.png +0 -0
  48. package/coverage/sorter.js +210 -0
  49. package/coverage/themes/dark.ts.html +604 -0
  50. package/coverage/themes/high-contrast.ts.html +619 -0
  51. package/coverage/themes/index.html +176 -0
  52. package/coverage/themes/index.ts.html +97 -0
  53. package/coverage/themes/light.ts.html +601 -0
  54. package/coverage/themes/utils.ts.html +334 -0
  55. package/coverage/tokens/index.html +161 -0
  56. package/coverage/tokens/index.ts.html +94 -0
  57. package/coverage/tokens/logger.ts.html +115 -0
  58. package/coverage/tokens/prompt.ts.html +115 -0
  59. package/coverage/tokens/screen.ts.html +115 -0
  60. package/coverage/types/file.types.ts.html +265 -0
  61. package/coverage/types/filter.types.ts.html +238 -0
  62. package/coverage/types/index.html +236 -0
  63. package/coverage/types/index.ts.html +151 -0
  64. package/coverage/types/keyboard.types.ts.html +364 -0
  65. package/coverage/types/log.types.ts.html +268 -0
  66. package/coverage/types/message.types.ts.html +445 -0
  67. package/coverage/types/prompt.types.ts.html +403 -0
  68. package/coverage/types/screen.types.ts.html +451 -0
  69. package/coverage/types/theme.types.ts.html +841 -0
  70. package/coverage/utils/colors/file-colors.ts.html +112 -0
  71. package/coverage/utils/colors/helpers.ts.html +235 -0
  72. package/coverage/utils/colors/index.html +221 -0
  73. package/coverage/utils/colors/index.ts.html +145 -0
  74. package/coverage/utils/colors/log-colors.ts.html +253 -0
  75. package/coverage/utils/colors/progress-colors.ts.html +160 -0
  76. package/coverage/utils/colors/prompt-colors.ts.html +175 -0
  77. package/coverage/utils/colors/sidebar-colors.ts.html +241 -0
  78. package/coverage/utils/colors/table-colors.ts.html +118 -0
  79. package/coverage/utils/filetype.ts.html +277 -0
  80. package/coverage/utils/format.ts.html +241 -0
  81. package/coverage/utils/index.html +161 -0
  82. package/coverage/utils/index.ts.html +118 -0
  83. package/coverage/utils/stdout-printer.ts.html +523 -0
  84. package/dist/src/components/file/file_log.d.ts +35 -0
  85. package/dist/src/components/file/file_log.d.ts.map +1 -0
  86. package/dist/src/components/file/index.d.ts +2 -0
  87. package/dist/src/components/file/index.d.ts.map +1 -0
  88. package/dist/src/components/filter/filter_bar.d.ts +10 -0
  89. package/dist/src/components/filter/filter_bar.d.ts.map +1 -0
  90. package/dist/src/components/filter/index.d.ts +2 -0
  91. package/dist/src/components/filter/index.d.ts.map +1 -0
  92. package/dist/src/components/help/help_overlay.d.ts +10 -0
  93. package/dist/src/components/help/help_overlay.d.ts.map +1 -0
  94. package/dist/src/components/help/index.d.ts +2 -0
  95. package/dist/src/components/help/index.d.ts.map +1 -0
  96. package/dist/src/components/index.d.ts +27 -0
  97. package/dist/src/components/index.d.ts.map +1 -0
  98. package/dist/src/components/log/debug_log.d.ts +3 -0
  99. package/dist/src/components/log/debug_log.d.ts.map +1 -0
  100. package/dist/src/components/log/error_log.d.ts +3 -0
  101. package/dist/src/components/log/error_log.d.ts.map +1 -0
  102. package/dist/src/components/log/fatal_log.d.ts +3 -0
  103. package/dist/src/components/log/fatal_log.d.ts.map +1 -0
  104. package/dist/src/components/log/index.d.ts +9 -0
  105. package/dist/src/components/log/index.d.ts.map +1 -0
  106. package/dist/src/components/log/info_log.d.ts +3 -0
  107. package/dist/src/components/log/info_log.d.ts.map +1 -0
  108. package/dist/src/components/log/log_message.d.ts +33 -0
  109. package/dist/src/components/log/log_message.d.ts.map +1 -0
  110. package/dist/src/components/log/success_log.d.ts +3 -0
  111. package/dist/src/components/log/success_log.d.ts.map +1 -0
  112. package/dist/src/components/log/trace_log.d.ts +3 -0
  113. package/dist/src/components/log/trace_log.d.ts.map +1 -0
  114. package/dist/src/components/log/warning_log.d.ts +3 -0
  115. package/dist/src/components/log/warning_log.d.ts.map +1 -0
  116. package/dist/src/components/prompt/index.d.ts +3 -0
  117. package/dist/src/components/prompt/index.d.ts.map +1 -0
  118. package/dist/src/components/prompt/prompt_renderer.d.ts +6 -0
  119. package/dist/src/components/prompt/prompt_renderer.d.ts.map +1 -0
  120. package/dist/src/components/screen/group_renderer.d.ts +19 -0
  121. package/dist/src/components/screen/group_renderer.d.ts.map +1 -0
  122. package/dist/src/components/screen/index.d.ts +13 -0
  123. package/dist/src/components/screen/index.d.ts.map +1 -0
  124. package/dist/src/components/screen/loading_message.d.ts +6 -0
  125. package/dist/src/components/screen/loading_message.d.ts.map +1 -0
  126. package/dist/src/components/screen/message_renderer.d.ts +8 -0
  127. package/dist/src/components/screen/message_renderer.d.ts.map +1 -0
  128. package/dist/src/components/screen/progress_message.d.ts +8 -0
  129. package/dist/src/components/screen/progress_message.d.ts.map +1 -0
  130. package/dist/src/components/screen/screen_bridge.d.ts +20 -0
  131. package/dist/src/components/screen/screen_bridge.d.ts.map +1 -0
  132. package/dist/src/components/screen/table_message.d.ts +6 -0
  133. package/dist/src/components/screen/table_message.d.ts.map +1 -0
  134. package/dist/src/components/screen_manager_bridge.d.ts +11 -0
  135. package/dist/src/components/screen_manager_bridge.d.ts.map +1 -0
  136. package/dist/src/components/sidebar/index.d.ts +6 -0
  137. package/dist/src/components/sidebar/index.d.ts.map +1 -0
  138. package/dist/src/components/sidebar/sidebar.d.ts +18 -0
  139. package/dist/src/components/sidebar/sidebar.d.ts.map +1 -0
  140. package/dist/src/components/sidebar/sidebar_item.d.ts +14 -0
  141. package/dist/src/components/sidebar/sidebar_item.d.ts.map +1 -0
  142. package/dist/src/components/sidebar/sidebar_separator.d.ts +2 -0
  143. package/dist/src/components/sidebar/sidebar_separator.d.ts.map +1 -0
  144. package/dist/src/context/logger_context.d.ts +60 -0
  145. package/dist/src/context/logger_context.d.ts.map +1 -0
  146. package/dist/src/filter/filter_engine.d.ts +20 -0
  147. package/dist/src/filter/filter_engine.d.ts.map +1 -0
  148. package/dist/src/filter/index.d.ts +4 -0
  149. package/dist/src/filter/index.d.ts.map +1 -0
  150. package/dist/src/hooks/index.d.ts +2 -0
  151. package/dist/src/hooks/index.d.ts.map +1 -0
  152. package/dist/src/hooks/use_theme.d.ts +7 -0
  153. package/dist/src/hooks/use_theme.d.ts.map +1 -0
  154. package/dist/src/index.d.ts +88 -0
  155. package/dist/src/index.d.ts.map +1 -0
  156. package/dist/src/keyboard/create_bindings.d.ts +31 -0
  157. package/dist/src/keyboard/create_bindings.d.ts.map +1 -0
  158. package/dist/src/keyboard/index.d.ts +12 -0
  159. package/dist/src/keyboard/index.d.ts.map +1 -0
  160. package/dist/src/keyboard/keyboard_manager.d.ts +69 -0
  161. package/dist/src/keyboard/keyboard_manager.d.ts.map +1 -0
  162. package/dist/src/services/index.d.ts +5 -0
  163. package/dist/src/services/index.d.ts.map +1 -0
  164. package/dist/src/services/logger.d.ts +61 -0
  165. package/dist/src/services/logger.d.ts.map +1 -0
  166. package/dist/src/services/prompt.d.ts +37 -0
  167. package/dist/src/services/prompt.d.ts.map +1 -0
  168. package/dist/src/services/screen.d.ts +165 -0
  169. package/dist/src/services/screen.d.ts.map +1 -0
  170. package/dist/src/services/screen_manager.d.ts +124 -0
  171. package/dist/src/services/screen_manager.d.ts.map +1 -0
  172. package/dist/src/themes/dark.d.ts +7 -0
  173. package/dist/src/themes/dark.d.ts.map +1 -0
  174. package/dist/src/themes/high-contrast.d.ts +7 -0
  175. package/dist/src/themes/high-contrast.d.ts.map +1 -0
  176. package/dist/src/themes/index.d.ts +18 -0
  177. package/dist/src/themes/index.d.ts.map +1 -0
  178. package/dist/src/themes/light.d.ts +6 -0
  179. package/dist/src/themes/light.d.ts.map +1 -0
  180. package/dist/src/themes/utils.d.ts +22 -0
  181. package/dist/src/themes/utils.d.ts.map +1 -0
  182. package/dist/src/types/file.types.d.ts +40 -0
  183. package/dist/src/types/file.types.d.ts.map +1 -0
  184. package/dist/src/types/filter.types.d.ts +31 -0
  185. package/dist/src/types/filter.types.d.ts.map +1 -0
  186. package/dist/src/types/index.d.ts +81 -0
  187. package/dist/src/types/index.d.ts.map +1 -0
  188. package/dist/src/types/keyboard.types.d.ts +83 -0
  189. package/dist/src/types/keyboard.types.d.ts.map +1 -0
  190. package/dist/src/types/log.types.d.ts +32 -0
  191. package/dist/src/types/log.types.d.ts.map +1 -0
  192. package/dist/src/types/message.types.d.ts +91 -0
  193. package/dist/src/types/message.types.d.ts.map +1 -0
  194. package/dist/src/types/prompt.types.d.ts +89 -0
  195. package/dist/src/types/prompt.types.d.ts.map +1 -0
  196. package/dist/src/types/screen.types.d.ts +85 -0
  197. package/dist/src/types/screen.types.d.ts.map +1 -0
  198. package/dist/src/types/theme.types.d.ts +216 -0
  199. package/dist/src/types/theme.types.d.ts.map +1 -0
  200. package/dist/src/utils/colors/file-colors.d.ts +10 -0
  201. package/dist/src/utils/colors/file-colors.d.ts.map +1 -0
  202. package/dist/src/utils/colors/helpers.d.ts +29 -0
  203. package/dist/src/utils/colors/helpers.d.ts.map +1 -0
  204. package/dist/src/utils/colors/index.d.ts +13 -0
  205. package/dist/src/utils/colors/index.d.ts.map +1 -0
  206. package/dist/src/utils/colors/log-colors.d.ts +15 -0
  207. package/dist/src/utils/colors/log-colors.d.ts.map +1 -0
  208. package/dist/src/utils/colors/progress-colors.d.ts +25 -0
  209. package/dist/src/utils/colors/progress-colors.d.ts.map +1 -0
  210. package/dist/src/utils/colors/prompt-colors.d.ts +22 -0
  211. package/dist/src/utils/colors/prompt-colors.d.ts.map +1 -0
  212. package/dist/src/utils/colors/sidebar-colors.d.ts +50 -0
  213. package/dist/src/utils/colors/sidebar-colors.d.ts.map +1 -0
  214. package/dist/src/utils/colors/table-colors.d.ts +12 -0
  215. package/dist/src/utils/colors/table-colors.d.ts.map +1 -0
  216. package/dist/src/utils/filetype.d.ts +19 -0
  217. package/dist/src/utils/filetype.d.ts.map +1 -0
  218. package/dist/src/utils/format.d.ts +9 -0
  219. package/dist/src/utils/format.d.ts.map +1 -0
  220. package/dist/src/utils/index.d.ts +20 -0
  221. package/dist/src/utils/index.d.ts.map +1 -0
  222. package/dist/src/utils/stdout-printer.d.ts +10 -0
  223. package/dist/src/utils/stdout-printer.d.ts.map +1 -0
  224. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  225. package/dist/tsconfig.tsbuildinfo +1 -0
  226. package/dist/tsdown.config.d.mts +5 -0
  227. package/dist/tsdown.config.d.mts.map +1 -0
  228. package/dist/vitest.config.d.mts +3 -0
  229. package/dist/vitest.config.d.mts.map +1 -0
  230. package/lib/index.cjs +6349 -0
  231. package/lib/index.cjs.map +1 -0
  232. package/lib/index.d.cts +1720 -0
  233. package/lib/index.d.cts.map +1 -0
  234. package/lib/index.d.mts +1720 -0
  235. package/lib/index.d.mts.map +1 -0
  236. package/lib/index.mjs +6264 -0
  237. package/lib/index.mjs.map +1 -0
  238. package/lib/screen_manager_bridge-BpDgVu3e.cjs +3357 -0
  239. package/lib/screen_manager_bridge-BpDgVu3e.cjs.map +1 -0
  240. package/lib/screen_manager_bridge-CkV7637i.cjs +3 -0
  241. package/lib/screen_manager_bridge-DN2J6_k1.mjs +3 -0
  242. package/lib/screen_manager_bridge-Dfg4QUrl.mjs +3034 -0
  243. package/lib/screen_manager_bridge-Dfg4QUrl.mjs.map +1 -0
  244. package/package.json +43 -0
  245. package/project.json +60 -0
  246. package/src/__tests__/components/__snapshots__/filter_bar.spec.tsx.snap +2245 -0
  247. package/src/__tests__/components/__snapshots__/loading_message.spec.tsx.snap +1382 -0
  248. package/src/__tests__/components/__snapshots__/log_message.spec.tsx.snap +3169 -0
  249. package/src/__tests__/components/__snapshots__/progress_message.spec.tsx.snap +1743 -0
  250. package/src/__tests__/components/__snapshots__/prompt_renderer.spec.tsx.snap +3135 -0
  251. package/src/__tests__/components/__snapshots__/sidebar.spec.tsx.snap +2617 -0
  252. package/src/__tests__/components/filter_bar.spec.tsx +190 -0
  253. package/src/__tests__/components/loading_message.spec.tsx +110 -0
  254. package/src/__tests__/components/log_message.spec.tsx +166 -0
  255. package/src/__tests__/components/progress_message.spec.tsx +147 -0
  256. package/src/__tests__/components/prompt_renderer.spec.tsx +274 -0
  257. package/src/__tests__/components/sidebar.spec.tsx +305 -0
  258. package/src/__tests__/filter/filter_engine.spec.ts +325 -0
  259. package/src/__tests__/keyboard/keyboard_manager.spec.ts +557 -0
  260. package/src/__tests__/mocks/scm-mock.ts +5 -0
  261. package/src/__tests__/services/logger.spec.ts +630 -0
  262. package/src/__tests__/services/prompt.spec.ts +411 -0
  263. package/src/__tests__/services/screen.spec.ts +721 -0
  264. package/src/__tests__/setup.ts +43 -0
  265. package/src/__tests__/utils/factories.ts +354 -0
  266. package/src/__tests__/utils/filetype.spec.ts +195 -0
  267. package/src/__tests__/utils/format.spec.ts +178 -0
  268. package/src/__tests__/utils/render-utils.tsx +39 -0
  269. package/src/__tests__/utils/test-container.ts +48 -0
  270. package/src/components/file/file_log.tsx +241 -0
  271. package/src/components/file/index.ts +1 -0
  272. package/src/components/filter/filter_bar.tsx +79 -0
  273. package/src/components/filter/index.ts +1 -0
  274. package/src/components/help/help_overlay.tsx +100 -0
  275. package/src/components/help/index.ts +1 -0
  276. package/src/components/index.ts +15 -0
  277. package/src/components/log/index.ts +1 -0
  278. package/src/components/log/log_message.tsx +102 -0
  279. package/src/components/prompt/index.ts +2 -0
  280. package/src/components/prompt/prompt_renderer.tsx +346 -0
  281. package/src/components/screen/group_renderer.tsx +64 -0
  282. package/src/components/screen/index.ts +6 -0
  283. package/src/components/screen/loading_message.tsx +44 -0
  284. package/src/components/screen/message_renderer.tsx +108 -0
  285. package/src/components/screen/progress_message.tsx +60 -0
  286. package/src/components/screen/screen_bridge.tsx +149 -0
  287. package/src/components/screen/table_message.tsx +57 -0
  288. package/src/components/screen_manager_bridge.tsx +245 -0
  289. package/src/components/sidebar/index.ts +3 -0
  290. package/src/components/sidebar/sidebar.tsx +102 -0
  291. package/src/components/sidebar/sidebar_item.tsx +50 -0
  292. package/src/components/sidebar/sidebar_separator.tsx +13 -0
  293. package/src/context/index.ts +1 -0
  294. package/src/context/logger_context.tsx +109 -0
  295. package/src/factories/index.ts +1 -0
  296. package/src/factories/screen.factory.ts +22 -0
  297. package/src/filter/filter_engine.ts +113 -0
  298. package/src/filter/index.ts +1 -0
  299. package/src/hooks/index.ts +1 -0
  300. package/src/hooks/use_theme.ts +12 -0
  301. package/src/index.ts +64 -0
  302. package/src/keyboard/create_bindings.ts +457 -0
  303. package/src/keyboard/index.ts +2 -0
  304. package/src/keyboard/keyboard_manager.ts +233 -0
  305. package/src/overrides/console.logger.override.ts +61 -0
  306. package/src/overrides/index.ts +1 -0
  307. package/src/schemas/index.ts +3 -0
  308. package/src/schemas/logger-options.ts +13 -0
  309. package/src/schemas/prompt-options.ts +9 -0
  310. package/src/schemas/screen-options.ts +14 -0
  311. package/src/services/index.ts +4 -0
  312. package/src/services/logger.ts +369 -0
  313. package/src/services/prompt.ts +169 -0
  314. package/src/services/screen.ts +590 -0
  315. package/src/services/screen_manager.tsx +390 -0
  316. package/src/themes/dark.ts +173 -0
  317. package/src/themes/high-contrast.ts +178 -0
  318. package/src/themes/index.ts +4 -0
  319. package/src/themes/light.ts +172 -0
  320. package/src/themes/utils.ts +83 -0
  321. package/src/tokens/index.ts +3 -0
  322. package/src/tokens/logger.ts +10 -0
  323. package/src/tokens/prompt.ts +10 -0
  324. package/src/tokens/screen.ts +10 -0
  325. package/src/types/file.types.ts +60 -0
  326. package/src/types/filter.types.ts +51 -0
  327. package/src/types/index.ts +22 -0
  328. package/src/types/keyboard.types.ts +93 -0
  329. package/src/types/log.types.ts +61 -0
  330. package/src/types/message.types.ts +120 -0
  331. package/src/types/prompt.types.ts +106 -0
  332. package/src/types/screen.types.ts +124 -0
  333. package/src/types/theme.types.ts +252 -0
  334. package/src/utils/colors/file-colors.ts +9 -0
  335. package/src/utils/colors/helpers.ts +50 -0
  336. package/src/utils/colors/index.ts +20 -0
  337. package/src/utils/colors/log-colors.ts +56 -0
  338. package/src/utils/colors/progress-colors.ts +25 -0
  339. package/src/utils/colors/prompt-colors.ts +30 -0
  340. package/src/utils/colors/sidebar-colors.ts +52 -0
  341. package/src/utils/colors/table-colors.ts +11 -0
  342. package/src/utils/filetype.ts +64 -0
  343. package/src/utils/format.ts +52 -0
  344. package/src/utils/index.ts +11 -0
  345. package/src/utils/stdout-printer.ts +255 -0
  346. package/tsconfig.json +14 -0
  347. package/tsdown.config.mts +34 -0
  348. package/vitest.config.mts +10 -0
@@ -0,0 +1,390 @@
1
+ import { Injectable } from '@navios/core'
2
+ import { createCliRenderer, type CliRenderer } from '@opentui/core'
3
+ import { createRoot } from '@opentui/react'
4
+
5
+ import { getThemePreset } from '../themes/index.ts'
6
+ import { printMessagesToStdout } from '../utils/index.ts'
7
+
8
+ import type { ScreenOptions } from '../schemas/index.ts'
9
+ import type { BindOptions, FocusArea, Theme } from '../types/index.ts'
10
+
11
+ import { ScreenInstance } from './screen.ts'
12
+
13
+ @Injectable()
14
+ export class ScreenManager {
15
+ private screens: Map<string, ScreenInstance> = new Map()
16
+ private screenOrder: string[] = []
17
+ private activeScreenId: string | null = null
18
+ private renderer: CliRenderer | null = null
19
+ private root: ReturnType<typeof createRoot> | null = null
20
+ private isBound: boolean = false
21
+ private changeListeners: Set<() => void> = new Set()
22
+ private bindOptions: BindOptions = {}
23
+ private autoCloseTimer: ReturnType<typeof setTimeout> | null = null
24
+ private theme: Theme | undefined
25
+
26
+ // Keyboard navigation state (exposed for bridge component)
27
+ public focusArea: FocusArea = 'content'
28
+ public selectedIndex: number = 0
29
+
30
+ /**
31
+ * Create a new screen and return it
32
+ */
33
+ createScreen(options: ScreenOptions): ScreenInstance {
34
+ const id = `screen-${Date.now()}-${Math.random().toString(36).slice(2)}`
35
+ const screen = new ScreenInstance(id, options)
36
+
37
+ // Set up the screen with manager reference and print function
38
+ screen._setManager(this)
39
+ screen._setPrintFn(printMessagesToStdout)
40
+
41
+ // Subscribe to screen changes
42
+ screen.onChange(() => this.notifyChange())
43
+
44
+ this.screens.set(id, screen)
45
+ this.screenOrder.push(id)
46
+
47
+ // Set first visible screen as active by default
48
+ if (!this.activeScreenId && !screen.isHidden()) {
49
+ this.activeScreenId = id
50
+ }
51
+
52
+ this.notifyChange()
53
+ return screen
54
+ }
55
+
56
+ getScreenByName(name: string): ScreenInstance | undefined {
57
+ const id = Array.from(this.screens.keys()).find(
58
+ (id) => this.screens.get(id)?.getName() === name,
59
+ )
60
+ return id ? this.screens.get(id) : undefined
61
+ }
62
+
63
+ /**
64
+ * Remove a screen dynamically
65
+ */
66
+ removeScreen(screen: ScreenInstance): void {
67
+ const id = screen.getId()
68
+ if (!this.screens.has(id)) return
69
+
70
+ this.screens.delete(id)
71
+ this.screenOrder = this.screenOrder.filter((sid) => sid !== id)
72
+
73
+ // If removed screen was active, switch to first visible screen
74
+ if (this.activeScreenId === id) {
75
+ const visibleScreens = this.getScreens()
76
+ this.activeScreenId = visibleScreens[0]?.getId() ?? null
77
+ }
78
+
79
+ // Update selected index if out of bounds
80
+ const visibleScreens = this.getScreens()
81
+ if (this.selectedIndex >= visibleScreens.length) {
82
+ this.selectedIndex = Math.max(0, visibleScreens.length - 1)
83
+ }
84
+
85
+ this.notifyChange()
86
+ }
87
+
88
+ /**
89
+ * Non-blocking bind - starts TUI rendering in background
90
+ */
91
+ async bind(options?: BindOptions): Promise<void> {
92
+ if (this.isBound) return
93
+
94
+ this.bindOptions = options ?? {}
95
+
96
+ // Resolve theme from options
97
+ if (options?.theme) {
98
+ this.theme = typeof options.theme === 'string' ? getThemePreset(options.theme) : options.theme
99
+ }
100
+
101
+ this.renderer = await createCliRenderer({
102
+ exitOnCtrlC: options?.exitOnCtrlC ?? true,
103
+ useAlternateScreen: true,
104
+ useMouse: options?.useMouse ?? false,
105
+ })
106
+
107
+ this.root = createRoot(this.renderer)
108
+ this.isBound = true
109
+
110
+ // Initial render
111
+ this.render()
112
+ }
113
+
114
+ /**
115
+ * Get the configured theme
116
+ */
117
+ getTheme(): Theme | undefined {
118
+ return this.theme
119
+ }
120
+
121
+ /**
122
+ * Stop TUI rendering and cleanup
123
+ * Flushes all completed screens to stdout/stderr
124
+ */
125
+ unbind(): void {
126
+ if (!this.isBound) return
127
+
128
+ // Clear any pending auto-close timer
129
+ if (this.autoCloseTimer) {
130
+ clearTimeout(this.autoCloseTimer)
131
+ this.autoCloseTimer = null
132
+ }
133
+
134
+ this.root?.unmount()
135
+
136
+ // Explicitly disable mouse tracking before destroy to prevent escape sequence leakage
137
+ // Using type assertion to access private method as a safety measure
138
+ if (this.renderer && 'disableMouse' in this.renderer) {
139
+ ;(this.renderer as unknown as { disableMouse: () => void }).disableMouse()
140
+ }
141
+
142
+ this.renderer?.destroy()
143
+
144
+ this.renderer = null
145
+ this.root = null
146
+ this.isBound = false
147
+
148
+ // Flush all completed screens to console (in order)
149
+ this.flushCompletedScreens()
150
+ }
151
+
152
+ /**
153
+ * Print all completed screens that haven't been printed yet
154
+ */
155
+ private flushCompletedScreens(): void {
156
+ for (const id of this.screenOrder) {
157
+ const screen = this.screens.get(id)
158
+ if (screen && !screen.hasPrintedToConsole()) {
159
+ screen._flushToConsole(true)
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Check if TUI is currently bound
166
+ */
167
+ isTuiBound(): boolean {
168
+ return this.isBound
169
+ }
170
+
171
+ /**
172
+ * Called by Screen when a prompt becomes active
173
+ * Focuses the screen and switches to content area
174
+ */
175
+ onScreenPromptActivated(screen: ScreenInstance): void {
176
+ this.setActiveScreen(screen)
177
+ this.focusArea = 'content'
178
+ this.notifyChange()
179
+ }
180
+
181
+ /**
182
+ * Check if any screen has an active prompt
183
+ */
184
+ hasActivePrompt(): boolean {
185
+ for (const screen of this.screens.values()) {
186
+ if (screen.hasActivePrompt()) {
187
+ return true
188
+ }
189
+ }
190
+ return false
191
+ }
192
+
193
+ /**
194
+ * Get the screen with an active prompt (if any)
195
+ */
196
+ getScreenWithActivePrompt(): ScreenInstance | null {
197
+ for (const screen of this.screens.values()) {
198
+ if (screen.hasActivePrompt()) {
199
+ return screen
200
+ }
201
+ }
202
+ return null
203
+ }
204
+
205
+ /**
206
+ * Called by Screen when visibility changes
207
+ * Handles switching active screen if needed
208
+ */
209
+ onScreenVisibilityChanged(screen: ScreenInstance): void {
210
+ // If the hidden screen was active, switch to first visible screen
211
+ if (screen.isHidden() && this.activeScreenId === screen.getId()) {
212
+ const visibleScreens = this.getScreens()
213
+ this.activeScreenId = visibleScreens[0]?.getId() ?? null
214
+ this.selectedIndex = 0
215
+ }
216
+
217
+ // Update selected index if it's now out of bounds
218
+ const visibleScreens = this.getScreens()
219
+ if (this.selectedIndex >= visibleScreens.length) {
220
+ this.selectedIndex = Math.max(0, visibleScreens.length - 1)
221
+ }
222
+
223
+ this.notifyChange()
224
+ }
225
+
226
+ /**
227
+ * Called by Screen when status becomes success/fail
228
+ * Moves completed screens to end of sidebar list
229
+ */
230
+ onScreenCompleted(screen: ScreenInstance): void {
231
+ const id = screen.getId()
232
+ const index = this.screenOrder.indexOf(id)
233
+
234
+ if (index !== -1) {
235
+ // Remove from current position
236
+ this.screenOrder.splice(index, 1)
237
+ // Add to end
238
+ this.screenOrder.push(id)
239
+
240
+ // Update selected index if needed (based on visible screens)
241
+ const visibleScreens = this.getScreens()
242
+ if (this.selectedIndex >= visibleScreens.length) {
243
+ this.selectedIndex = Math.max(0, visibleScreens.length - 1)
244
+ }
245
+ }
246
+
247
+ // Check for auto-close
248
+ this.checkAutoClose()
249
+
250
+ this.notifyChange()
251
+ }
252
+
253
+ /**
254
+ * Check if all screens are successful and start auto-close timer if enabled
255
+ */
256
+ private checkAutoClose(): void {
257
+ const autoClose = this.bindOptions.autoClose
258
+ if (!autoClose || !this.isBound) return
259
+
260
+ // Clear any existing timer
261
+ if (this.autoCloseTimer) {
262
+ clearTimeout(this.autoCloseTimer)
263
+ this.autoCloseTimer = null
264
+ }
265
+
266
+ // Check if all screens are successful
267
+ const screens = this.getScreens()
268
+ if (screens.length === 0) return
269
+
270
+ const allSuccessful = screens.every((s) => s.getStatus() === 'success')
271
+ if (!allSuccessful) return
272
+
273
+ // Start auto-close timer
274
+ const delay = typeof autoClose === 'number' ? autoClose : 5000
275
+ this.autoCloseTimer = setTimeout(() => {
276
+ this.unbind()
277
+ }, delay)
278
+ }
279
+
280
+ /**
281
+ * Get visible screens in display order (excludes hidden screens)
282
+ */
283
+ getScreens(): ScreenInstance[] {
284
+ return this.screenOrder
285
+ .map((id) => this.screens.get(id))
286
+ .filter((s): s is ScreenInstance => s !== undefined && !s.isHidden())
287
+ }
288
+
289
+ /**
290
+ * Get all screens in display order (includes hidden screens)
291
+ */
292
+ getAllScreens(): ScreenInstance[] {
293
+ return this.screenOrder
294
+ .map((id) => this.screens.get(id))
295
+ .filter((s): s is ScreenInstance => s !== undefined)
296
+ }
297
+
298
+ /**
299
+ * Get the active screen
300
+ */
301
+ getActiveScreen(): ScreenInstance | null {
302
+ return this.activeScreenId ? (this.screens.get(this.activeScreenId) ?? null) : null
303
+ }
304
+
305
+ /**
306
+ * Set the active screen
307
+ */
308
+ setActiveScreen(screen: ScreenInstance): void {
309
+ this.activeScreenId = screen.getId()
310
+ this.notifyChange()
311
+ }
312
+
313
+ /**
314
+ * Get bind options
315
+ */
316
+ getBindOptions(): BindOptions {
317
+ return this.bindOptions
318
+ }
319
+
320
+ /**
321
+ * Set focus area (sidebar or content)
322
+ */
323
+ setFocusArea(area: FocusArea): void {
324
+ this.focusArea = area
325
+ this.notifyChange()
326
+ }
327
+
328
+ /**
329
+ * Set selected index in sidebar
330
+ */
331
+ setSelectedIndex(index: number): void {
332
+ const visibleScreens = this.getScreens()
333
+ this.selectedIndex = Math.max(0, Math.min(index, visibleScreens.length - 1))
334
+ this.notifyChange()
335
+ }
336
+
337
+ /**
338
+ * Navigate sidebar up
339
+ */
340
+ navigateUp(): void {
341
+ this.setSelectedIndex(this.selectedIndex - 1)
342
+ }
343
+
344
+ /**
345
+ * Navigate sidebar down
346
+ */
347
+ navigateDown(): void {
348
+ this.setSelectedIndex(this.selectedIndex + 1)
349
+ }
350
+
351
+ /**
352
+ * Select the currently highlighted screen
353
+ */
354
+ selectCurrent(): void {
355
+ const screens = this.getScreens()
356
+ const screen = screens[this.selectedIndex]
357
+ if (screen) {
358
+ this.setActiveScreen(screen)
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Toggle focus between sidebar and content
364
+ */
365
+ toggleFocus(): void {
366
+ this.focusArea = this.focusArea === 'sidebar' ? 'content' : 'sidebar'
367
+ this.notifyChange()
368
+ }
369
+
370
+ /**
371
+ * Register a change listener for re-renders
372
+ */
373
+ onChange(listener: () => void): () => void {
374
+ this.changeListeners.add(listener)
375
+ return () => this.changeListeners.delete(listener)
376
+ }
377
+
378
+ private notifyChange(): void {
379
+ // Notify listeners - React components will forceUpdate and re-render
380
+ this.changeListeners.forEach((listener) => listener())
381
+ }
382
+
383
+ private async render(): Promise<void> {
384
+ if (!this.root) return
385
+
386
+ // Import the bridge component (cached by the module system after first load)
387
+ const { ScreenManagerBridge } = await import('../components/screen_manager_bridge.tsx')
388
+ this.root.render(<ScreenManagerBridge manager={this} theme={this.theme} />)
389
+ }
390
+ }
@@ -0,0 +1,173 @@
1
+ import type { Theme } from '../types/index.ts'
2
+
3
+ /**
4
+ * Default dark theme.
5
+ * Migrated from the scattered color files in utils/colors/.
6
+ */
7
+ export const darkTheme: Theme = {
8
+ name: 'dark',
9
+
10
+ logLevels: {
11
+ verbose: {
12
+ border: '#6B7280', // Gray-500
13
+ background: '#6B728015', // Gray with 8% opacity
14
+ },
15
+ debug: {
16
+ border: '#8B5CF6', // Violet-500
17
+ background: '#8B5CF615', // Violet with 8% opacity
18
+ },
19
+ log: {
20
+ border: '#3B82F6', // Blue-500
21
+ background: '#3B82F615', // Blue with 8% opacity
22
+ },
23
+ warn: {
24
+ border: '#F59E0B', // Amber-500
25
+ background: '#F59E0B15', // Amber with 8% opacity
26
+ },
27
+ error: {
28
+ border: '#EF4444', // Red-500
29
+ background: '#EF444415', // Red with 8% opacity
30
+ },
31
+ fatal: {
32
+ border: '#DC2626', // Red-600
33
+ background: '#DC262625', // Red with 15% opacity
34
+ text: '#FCA5A5', // Light red text
35
+ },
36
+ },
37
+
38
+ sidebar: {
39
+ background: undefined,
40
+ selectedBackground: '#1F293780', // Gray-800 with 50% opacity
41
+ hoverBackground: '#374151', // Gray-700
42
+ text: '#E5E7EB', // Gray-200
43
+ textDim: '#6B7280', // Gray-500
44
+ border: '#374151', // Gray-700
45
+ badge: '#3B82F6', // Blue-500
46
+ focusBorder: '#3B82F6', // Blue-500
47
+ },
48
+
49
+ header: {
50
+ background: undefined,
51
+ text: '#F9FAFB', // Gray-50
52
+ border: '#374151', // Gray-700
53
+ },
54
+
55
+ statusIndicators: {
56
+ waiting: {
57
+ icon: '○',
58
+ color: '#6B7280', // Gray-500
59
+ },
60
+ pending: {
61
+ icon: '◐',
62
+ color: '#F59E0B', // Amber-500
63
+ },
64
+ success: {
65
+ icon: '✓',
66
+ color: '#22C55E', // Green-500
67
+ },
68
+ fail: {
69
+ icon: '✗',
70
+ color: '#EF4444', // Red-500
71
+ },
72
+ },
73
+
74
+ separator: {
75
+ line: '#374151', // Gray-700
76
+ text: '#6B7280', // Gray-500
77
+ },
78
+
79
+ progress: {
80
+ border: '#3B82F6', // Blue-500
81
+ background: '#3B82F615', // Blue with 8% opacity
82
+ barFilled: '#3B82F6', // Blue-500
83
+ barEmpty: '#374151', // Gray-700
84
+ text: '#E5E7EB', // Gray-200
85
+ textDim: '#9CA3AF', // Gray-400
86
+ complete: '#22C55E', // Green-500
87
+ completeBackground: '#22C55E15',
88
+ failed: '#EF4444', // Red-500
89
+ failedBackground: '#EF444415',
90
+ },
91
+
92
+ group: {
93
+ border: '#6B7280', // Gray-500
94
+ background: '#6B728010', // Gray with 6% opacity
95
+ headerText: '#E5E7EB', // Gray-200
96
+ icon: '#9CA3AF', // Gray-400
97
+ },
98
+
99
+ table: {
100
+ border: '#3B82F6', // Blue-500
101
+ background: '#3B82F615', // Blue with 8% opacity
102
+ headerText: '#F9FAFB', // Gray-50
103
+ cellText: '#E5E7EB', // Gray-200
104
+ title: '#F9FAFB', // Gray-50
105
+ separator: '#3B82F650', // Blue with 30% opacity
106
+ },
107
+
108
+ file: {
109
+ border: '#3B82F6', // Blue-500
110
+ background: '#3B82F615', // Blue with 8% opacity
111
+ headerText: '#F9FAFB', // Gray-50
112
+ headerBackground: '#3B82F625', // Blue with 15% opacity
113
+ },
114
+
115
+ prompt: {
116
+ question: '#F9FAFB', // Gray-50
117
+ optionText: '#E5E7EB', // Gray-200
118
+ optionTextDim: '#9CA3AF', // Gray-400
119
+ optionSelected: '#3B82F6', // Blue-500
120
+ optionSelectedBackground: '#1E3A5F', // Dark blue
121
+ confirmButton: '#22C55E', // Green-500
122
+ cancelButton: '#EF4444', // Red-500
123
+ buttonBackground: '#374151', // Gray-700
124
+ buttonSelectedBackground: '#1F2937', // Gray-800
125
+ inputBorder: '#3B82F6', // Blue-500
126
+ inputBackground: '#1F2937', // Gray-800
127
+ inputText: '#F9FAFB', // Gray-50
128
+ inputPlaceholder: '#6B7280', // Gray-500
129
+ inputCursor: '#3B82F6', // Blue-500
130
+ border: '#374151', // Gray-700
131
+ focusBorder: '#3B82F6', // Blue-500
132
+ },
133
+
134
+ errorHighlight: {
135
+ background: '#EF444425', // Red with 15% opacity
136
+ border: '#EF4444', // Red-500
137
+ gutterBackground: '#EF444440', // Red with 25% opacity
138
+ },
139
+
140
+ filter: {
141
+ background: '#1F293780', // Gray-800 with 50% opacity
142
+ border: '#3B82F6', // Blue-500
143
+ text: '#E5E7EB', // Gray-200
144
+ textDim: '#6B7280', // Gray-500
145
+ inputBackground: '#1F2937', // Gray-800
146
+ inputText: '#F9FAFB', // Gray-50
147
+ inputPlaceholder: '#6B7280', // Gray-500
148
+ cursor: '#3B82F6', // Blue-500
149
+ activeLevel: '#3B82F6', // Blue-500
150
+ inactiveLevel: '#4B5563', // Gray-600
151
+ },
152
+
153
+ help: {
154
+ background: '#1F2937', // Gray-800
155
+ border: '#3B82F6', // Blue-500
156
+ title: '#F9FAFB', // Gray-50
157
+ category: '#3B82F6', // Blue-500
158
+ key: '#F59E0B', // Amber-500
159
+ description: '#E5E7EB', // Gray-200
160
+ hint: '#6B7280', // Gray-500
161
+ },
162
+
163
+ colors: {
164
+ primary: '#3B82F6', // Blue-500
165
+ secondary: '#8B5CF6', // Violet-500
166
+ success: '#22C55E', // Green-500
167
+ warning: '#F59E0B', // Amber-500
168
+ error: '#EF4444', // Red-500
169
+ muted: '#6B7280', // Gray-500
170
+ background: '#111827', // Gray-900
171
+ foreground: '#F9FAFB', // Gray-50
172
+ },
173
+ }