@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,60 @@
1
+ import { useTheme } from '../../hooks/index.ts'
2
+
3
+ import type { ProgressMessageData } from '../../types/index.ts'
4
+
5
+ export interface ProgressMessageProps {
6
+ message: ProgressMessageData
7
+ }
8
+
9
+ export function ProgressMessage({ message }: ProgressMessageProps) {
10
+ const theme = useTheme()
11
+ const percent = Math.round((message.current / message.total) * 100)
12
+ const barWidth = 20
13
+ const filled = Math.round((percent / 100) * barWidth)
14
+ const empty = barWidth - filled
15
+
16
+ const barFilled = '█'.repeat(filled)
17
+ const barEmpty = '░'.repeat(empty)
18
+
19
+ // Determine colors based on status
20
+ const borderColor =
21
+ message.status === 'complete'
22
+ ? theme.progress.complete
23
+ : message.status === 'failed'
24
+ ? theme.progress.failed
25
+ : theme.progress.border
26
+
27
+ const backgroundColor =
28
+ message.status === 'complete'
29
+ ? theme.progress.completeBackground
30
+ : message.status === 'failed'
31
+ ? theme.progress.failedBackground
32
+ : theme.progress.background
33
+
34
+ const barColor =
35
+ message.status === 'complete'
36
+ ? theme.progress.complete
37
+ : message.status === 'failed'
38
+ ? theme.progress.failed
39
+ : theme.progress.barFilled
40
+
41
+ const displayLabel = message.resolvedContent ?? message.label
42
+
43
+ return (
44
+ <box
45
+ flexDirection="column"
46
+ border={['left']}
47
+ borderColor={borderColor}
48
+ backgroundColor={backgroundColor}
49
+ paddingLeft={1}
50
+ paddingRight={1}
51
+ >
52
+ <box flexDirection="row" gap={1}>
53
+ <text fg={barColor}>[{barFilled}</text>
54
+ <text fg={theme.progress.barEmpty}>{barEmpty}]</text>
55
+ <text fg={theme.progress.textDim}>{percent}%</text>
56
+ <text fg={theme.progress.text}>{displayLabel}</text>
57
+ </box>
58
+ </box>
59
+ )
60
+ }
@@ -0,0 +1,149 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+ import { useState, useEffect } from 'react'
3
+
4
+ import { useTheme } from '../../hooks/index.ts'
5
+ import { PromptRenderer } from '../prompt/index.ts'
6
+
7
+ import type { ScreenInstance } from '../../services/index.ts'
8
+ import type { MessageData } from '../../types/index.ts'
9
+
10
+ import { GroupRenderer } from './group_renderer.tsx'
11
+ import { MessageRenderer } from './message_renderer.tsx'
12
+
13
+ export interface ScreenBridgeProps {
14
+ screen: ScreenInstance
15
+ focused: boolean
16
+ /** Pre-filtered messages (if filtering is active) */
17
+ filteredMessages?: MessageData[]
18
+ /** Whether any filter is currently active */
19
+ isFiltering?: boolean
20
+ /** Total message count (before filtering) */
21
+ totalMessages?: number
22
+ }
23
+
24
+ // Helper to process messages and organize them into groups
25
+ interface ProcessedMessage {
26
+ type: 'single' | 'group'
27
+ message?: MessageData
28
+ label?: string
29
+ messages?: MessageData[]
30
+ }
31
+
32
+ function processMessagesIntoGroups(messages: MessageData[]): ProcessedMessage[] {
33
+ const result: ProcessedMessage[] = []
34
+ let i = 0
35
+
36
+ while (i < messages.length) {
37
+ const msg = messages[i]!
38
+
39
+ if (msg.type === 'group' && !msg.isEnd) {
40
+ // Start of a group - collect all messages until groupEnd
41
+ const groupLabel = msg.label
42
+ const groupMessages: MessageData[] = []
43
+ i++ // Move past the group start
44
+
45
+ while (i < messages.length) {
46
+ const innerMsg = messages[i]!
47
+ if (innerMsg.type === 'group' && innerMsg.isEnd) {
48
+ i++ // Move past the group end
49
+ break
50
+ }
51
+ groupMessages.push(innerMsg)
52
+ i++
53
+ }
54
+
55
+ result.push({
56
+ type: 'group',
57
+ label: groupLabel,
58
+ messages: groupMessages,
59
+ })
60
+ } else if (msg.type === 'group' && msg.isEnd) {
61
+ // Stray group end - skip it
62
+ i++
63
+ } else {
64
+ // Regular message
65
+ result.push({ type: 'single', message: msg })
66
+ i++
67
+ }
68
+ }
69
+
70
+ return result
71
+ }
72
+
73
+ export function ScreenBridge({
74
+ screen,
75
+ focused,
76
+ filteredMessages,
77
+ isFiltering,
78
+ totalMessages,
79
+ }: ScreenBridgeProps) {
80
+ const theme = useTheme()
81
+ const [, forceUpdate] = useState({})
82
+
83
+ // Subscribe to screen changes
84
+ useEffect(() => {
85
+ return screen.onChange(() => forceUpdate({}))
86
+ }, [screen])
87
+
88
+ // Use filtered messages if provided, otherwise get from screen
89
+ const messages = filteredMessages ?? screen.getMessages()
90
+ const activePrompt = screen.getActivePrompt()
91
+ const processedMessages = processMessagesIntoGroups(messages)
92
+
93
+ // Calculate filter stats
94
+ const filteredCount = messages.length
95
+ const total = totalMessages ?? messages.length
96
+ const showFilterStatus = isFiltering && filteredCount !== total
97
+
98
+ return (
99
+ <box flexDirection="column" flexGrow={1}>
100
+ {/* Screen header */}
101
+ <box
102
+ backgroundColor={theme.header.background}
103
+ borderColor={focused ? theme.sidebar.focusBorder : theme.header.border}
104
+ border={['bottom']}
105
+ paddingLeft={1}
106
+ paddingRight={1}
107
+ flexDirection="row"
108
+ justifyContent="space-between"
109
+ >
110
+ <text fg={theme.header.text} attributes={TextAttributes.BOLD}>
111
+ {screen.getName()}
112
+ </text>
113
+ {showFilterStatus && (
114
+ <text fg={theme.sidebar.textDim}>
115
+ {filteredCount}/{total} messages
116
+ </text>
117
+ )}
118
+ </box>
119
+
120
+ {/* Scrollable content area */}
121
+ <scrollbox
122
+ flexGrow={1}
123
+ scrollY
124
+ stickyScroll
125
+ stickyStart="bottom"
126
+ contentOptions={{
127
+ paddingLeft: 1,
128
+ paddingRight: 1,
129
+ paddingTop: 1,
130
+ paddingBottom: 1,
131
+ gap: 1,
132
+ }}
133
+ >
134
+ {processedMessages.map((item, index) => {
135
+ if (item.type === 'group') {
136
+ return (
137
+ <GroupRenderer key={`group-${index}`} label={item.label!} messages={item.messages!} />
138
+ )
139
+ } else {
140
+ return <MessageRenderer key={item.message!.id} message={item.message!} />
141
+ }
142
+ })}
143
+
144
+ {/* Render active prompt at the end of messages */}
145
+ {activePrompt && <PromptRenderer prompt={activePrompt} />}
146
+ </scrollbox>
147
+ </box>
148
+ )
149
+ }
@@ -0,0 +1,57 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+
3
+ import { TABLE_COLORS } from '../../utils/index.ts'
4
+
5
+ import type { TableMessageData } from '../../types/index.ts'
6
+
7
+ export interface TableMessageProps {
8
+ message: TableMessageData
9
+ }
10
+
11
+ export function TableMessage({ message }: TableMessageProps) {
12
+ // Calculate column widths
13
+ const colWidths = message.headers.map((h, i) => {
14
+ const headerLen = h.length
15
+ const maxRowLen =
16
+ message.rows.length > 0
17
+ ? Math.max(...message.rows.map((r) => (r[i] ?? '').length))
18
+ : 0
19
+ return Math.max(headerLen, maxRowLen)
20
+ })
21
+
22
+ const pad = (str: string, width: number) => str.padEnd(width)
23
+
24
+ const headerRow = message.headers.map((h, i) => pad(h, colWidths[i] ?? 0)).join(' │ ')
25
+
26
+ const separator = colWidths.map((w) => '─'.repeat(w)).join('─┼─')
27
+
28
+ const dataRows = message.rows.map((row) =>
29
+ row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(' │ '),
30
+ )
31
+
32
+ return (
33
+ <box
34
+ flexDirection="column"
35
+ border={['left']}
36
+ borderColor={TABLE_COLORS.border}
37
+ backgroundColor={TABLE_COLORS.background}
38
+ paddingLeft={1}
39
+ paddingRight={1}
40
+ >
41
+ {message.title && (
42
+ <text fg={TABLE_COLORS.title} attributes={TextAttributes.BOLD}>
43
+ {message.title}
44
+ </text>
45
+ )}
46
+ <text fg={TABLE_COLORS.headerText} attributes={TextAttributes.BOLD}>
47
+ {headerRow}
48
+ </text>
49
+ <text fg={TABLE_COLORS.separator}>{separator}</text>
50
+ {dataRows.map((row, i) => (
51
+ <text key={i} fg={TABLE_COLORS.cellText}>
52
+ {row}
53
+ </text>
54
+ ))}
55
+ </box>
56
+ )
57
+ }
@@ -0,0 +1,245 @@
1
+ import { useKeyboard } from '@opentui/react'
2
+ import { useState, useEffect, useCallback, useMemo, useSyncExternalStore } from 'react'
3
+
4
+ import { LoggerProvider } from '../context/index.ts'
5
+ import { FilterEngine } from '../filter/index.ts'
6
+ import { KeyboardManager, createDefaultBindings, handlePrintableInput } from '../keyboard/index.ts'
7
+ import {
8
+ ALL_LOG_LEVELS,
9
+ createDefaultFilterState,
10
+ hasActiveFilter,
11
+ type FilterState,
12
+ type KeyboardContext,
13
+ type Theme,
14
+ } from '../types/index.ts'
15
+
16
+ import type { ScreenManager } from '../services/index.ts'
17
+
18
+ import { FilterBar } from './filter/filter_bar.tsx'
19
+ import { HelpOverlay } from './help/help_overlay.tsx'
20
+ import { ScreenBridge } from './screen/screen_bridge.tsx'
21
+ import { Sidebar } from './sidebar/sidebar.tsx'
22
+
23
+ export interface ScreenManagerBridgeProps {
24
+ manager: ScreenManager
25
+ theme?: Theme
26
+ }
27
+
28
+ export function ScreenManagerBridge({ manager, theme }: ScreenManagerBridgeProps) {
29
+ const [, forceUpdate] = useState({})
30
+ const [showHelp, setShowHelp] = useState(false)
31
+ const [filter, setFilter] = useState<FilterState>(createDefaultFilterState)
32
+
33
+ // Subscribe to manager changes
34
+ useEffect(() => {
35
+ return manager.onChange(() => forceUpdate({}))
36
+ }, [manager])
37
+
38
+ // Helper functions for keyboard bindings
39
+ const getActiveScreen = useCallback(() => manager.getActiveScreen(), [manager])
40
+
41
+ const toggleHelp = useCallback(() => {
42
+ setShowHelp((prev) => !prev)
43
+ }, [])
44
+
45
+ const toggleFilter = useCallback(() => {
46
+ setFilter((prev) => ({
47
+ ...prev,
48
+ isVisible: !prev.isVisible,
49
+ focusedField: 'search',
50
+ }))
51
+ }, [])
52
+
53
+ const closeFilter = useCallback(() => {
54
+ setFilter((prev) => ({ ...prev, isVisible: false }))
55
+ }, [])
56
+
57
+ const filterAppendChar = useCallback((char: string) => {
58
+ setFilter((prev) => ({
59
+ ...prev,
60
+ searchQuery: prev.searchQuery + char,
61
+ }))
62
+ }, [])
63
+
64
+ const filterDeleteChar = useCallback(() => {
65
+ setFilter((prev) => ({
66
+ ...prev,
67
+ searchQuery: prev.searchQuery.slice(0, -1),
68
+ }))
69
+ }, [])
70
+
71
+ const filterToggleLevel = useCallback((index: number) => {
72
+ setFilter((prev) => {
73
+ const level = ALL_LOG_LEVELS[index]
74
+ if (!level) return prev
75
+
76
+ const newLevels = new Set(prev.enabledLevels)
77
+ if (newLevels.has(level)) {
78
+ newLevels.delete(level)
79
+ } else {
80
+ newLevels.add(level)
81
+ }
82
+
83
+ return { ...prev, enabledLevels: newLevels }
84
+ })
85
+ }, [])
86
+
87
+ const filterCycleField = useCallback(() => {
88
+ setFilter((prev) => ({
89
+ ...prev,
90
+ focusedField: prev.focusedField === 'search' ? 'levels' : 'search',
91
+ }))
92
+ }, [])
93
+
94
+ // Create keyboard manager with bindings
95
+ const keyboardManager = useMemo(() => {
96
+ const km = new KeyboardManager()
97
+ const bindings = createDefaultBindings({
98
+ manager,
99
+ getActiveScreen,
100
+ toggleHelp,
101
+ toggleFilter,
102
+ closeFilter,
103
+ filterAppendChar,
104
+ filterDeleteChar,
105
+ filterToggleLevel,
106
+ filterCycleField,
107
+ })
108
+ km.addBindings(bindings)
109
+ return km
110
+ }, [
111
+ manager,
112
+ getActiveScreen,
113
+ toggleHelp,
114
+ toggleFilter,
115
+ closeFilter,
116
+ filterAppendChar,
117
+ filterDeleteChar,
118
+ filterToggleLevel,
119
+ filterCycleField,
120
+ ])
121
+
122
+ // Keyboard handler
123
+ const handleKeyboard = useCallback(
124
+ (key: { name: string; ctrl?: boolean; meta?: boolean; sequence?: string }) => {
125
+ const screens = manager.getScreens()
126
+ const activeScreen = manager.getActiveScreen()
127
+
128
+ const context: KeyboardContext = {
129
+ hasSidebar: screens.length > 1,
130
+ focusArea: manager.focusArea,
131
+ hasPrompt: activeScreen?.hasActivePrompt() ?? false,
132
+ inInputMode: activeScreen?.isPromptInInputMode() ?? false,
133
+ isFilterActive: filter.isVisible,
134
+ isHelpVisible: showHelp,
135
+ }
136
+
137
+ // Try keyboard manager first
138
+ if (keyboardManager.handleKey(key, context)) {
139
+ return
140
+ }
141
+
142
+ // Handle printable characters (for input mode and filter)
143
+ handlePrintableInput(key, context, {
144
+ manager,
145
+ getActiveScreen,
146
+ toggleHelp,
147
+ toggleFilter,
148
+ closeFilter,
149
+ filterAppendChar,
150
+ filterDeleteChar,
151
+ filterToggleLevel,
152
+ filterCycleField,
153
+ })
154
+ },
155
+ [
156
+ manager,
157
+ keyboardManager,
158
+ filter.isVisible,
159
+ showHelp,
160
+ getActiveScreen,
161
+ toggleHelp,
162
+ toggleFilter,
163
+ closeFilter,
164
+ filterAppendChar,
165
+ filterDeleteChar,
166
+ filterToggleLevel,
167
+ filterCycleField,
168
+ ],
169
+ )
170
+
171
+ // Handle keyboard input
172
+ useKeyboard(handleKeyboard)
173
+
174
+ const screens = manager.getScreens()
175
+ const activeScreen = manager.getActiveScreen()
176
+ const activeScreenId = activeScreen?.getId() ?? screens[0]?.getId() ?? ''
177
+ const bindOptions = manager.getBindOptions()
178
+ const hasSidebar = screens.length > 1
179
+
180
+ const activeScreenVersion = useSyncExternalStore(
181
+ (updater) => activeScreen?.onChange(updater) ?? (() => {}),
182
+ () => activeScreen?.getVersion() ?? -1,
183
+ )
184
+ // Get level counts for filter bar
185
+ const levelCounts = useMemo(() => {
186
+ if (!activeScreen) {
187
+ return {
188
+ verbose: 0,
189
+ debug: 0,
190
+ log: 0,
191
+ warn: 0,
192
+ error: 0,
193
+ fatal: 0,
194
+ }
195
+ }
196
+ return FilterEngine.countByLevel(activeScreen.getMessages())
197
+ }, [activeScreenVersion, activeScreen])
198
+
199
+ // Filter messages for the active screen
200
+ const filteredMessages = useMemo(() => {
201
+ if (!activeScreen) return []
202
+ return FilterEngine.filterMessages(activeScreen.getMessages(), filter)
203
+ }, [activeScreenVersion, activeScreen, filter])
204
+
205
+ const isFiltering = useMemo(() => hasActiveFilter(filter), [filter])
206
+
207
+ return (
208
+ <LoggerProvider theme={theme}>
209
+ <box flexDirection="row" flexGrow={1}>
210
+ {/* Sidebar */}
211
+ {hasSidebar && (
212
+ <Sidebar
213
+ screens={screens}
214
+ selectedIndex={manager.selectedIndex}
215
+ activeScreenId={activeScreenId}
216
+ focused={manager.focusArea === 'sidebar'}
217
+ width={bindOptions.sidebarWidth ?? 25}
218
+ title={bindOptions.sidebarTitle ?? 'Screens'}
219
+ />
220
+ )}
221
+
222
+ {/* Main content area */}
223
+ <box flexDirection="column" flexGrow={1}>
224
+ {/* Filter bar */}
225
+ {filter.isVisible && <FilterBar filter={filter} levelCounts={levelCounts} />}
226
+
227
+ {/* Screen content */}
228
+ {activeScreen && (
229
+ <ScreenBridge
230
+ key={activeScreen.getId()}
231
+ screen={activeScreen}
232
+ focused={manager.focusArea === 'content'}
233
+ filteredMessages={filteredMessages}
234
+ isFiltering={isFiltering}
235
+ totalMessages={activeScreen.getMessages().length}
236
+ />
237
+ )}
238
+ </box>
239
+
240
+ {/* Help overlay */}
241
+ {showHelp && <HelpOverlay bindings={keyboardManager.getBindingsForHelp()} />}
242
+ </box>
243
+ </LoggerProvider>
244
+ )
245
+ }
@@ -0,0 +1,3 @@
1
+ export * from './sidebar.tsx'
2
+ export * from './sidebar_item.tsx'
3
+ export * from './sidebar_separator.tsx'
@@ -0,0 +1,102 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+
3
+ import { useTheme } from '../../hooks/index.ts'
4
+
5
+ import type { ScreenInstance } from '../../services/index.ts'
6
+
7
+ import { SidebarItem } from './sidebar_item.tsx'
8
+ import { SidebarSeparator } from './sidebar_separator.tsx'
9
+
10
+ export interface SidebarProps {
11
+ screens: ScreenInstance[]
12
+ selectedIndex: number
13
+ activeScreenId: string
14
+ focused: boolean
15
+ width: number
16
+ title: string
17
+ }
18
+
19
+ export function Sidebar({
20
+ screens,
21
+ selectedIndex,
22
+ activeScreenId,
23
+ focused,
24
+ width,
25
+ title,
26
+ }: SidebarProps) {
27
+ const theme = useTheme()
28
+
29
+ // Separate screens: only "pending" status on top, everything else below
30
+ const pendingScreens: { screen: ScreenInstance; originalIndex: number }[] = []
31
+ const otherScreens: { screen: ScreenInstance; originalIndex: number }[] = []
32
+
33
+ screens.forEach((screen, index) => {
34
+ const status = screen.getStatus()
35
+ if (status === 'pending') {
36
+ pendingScreens.push({ screen, originalIndex: index })
37
+ } else {
38
+ otherScreens.push({ screen, originalIndex: index })
39
+ }
40
+ })
41
+
42
+ const hasPending = pendingScreens.length > 0
43
+ const hasOther = otherScreens.length > 0
44
+ const showSeparator = hasPending && hasOther
45
+
46
+ return (
47
+ <box
48
+ flexDirection="column"
49
+ width={width}
50
+ borderColor={focused ? theme.sidebar.focusBorder : theme.sidebar.border}
51
+ border={['right']}
52
+ >
53
+ {/* Sidebar header */}
54
+ <box
55
+ backgroundColor={theme.header.background}
56
+ paddingLeft={1}
57
+ paddingRight={1}
58
+ borderColor={theme.header.border}
59
+ border={['bottom']}
60
+ >
61
+ <text fg={theme.header.text} attributes={TextAttributes.BOLD}>
62
+ {title}
63
+ </text>
64
+ </box>
65
+
66
+ {/* Screen list */}
67
+ <scrollbox scrollY stickyScroll={false} flexGrow={1} contentOptions={{ flexGrow: 1 }}>
68
+ <box flexDirection="column">
69
+ {/* Pending screens (active work) */}
70
+ {pendingScreens.map(({ screen, originalIndex }) => (
71
+ <SidebarItem
72
+ key={screen.getId()}
73
+ screen={screen}
74
+ isSelected={originalIndex === selectedIndex}
75
+ isActive={screen.getId() === activeScreenId}
76
+ focused={focused}
77
+ />
78
+ ))}
79
+
80
+ {/* Separator between pending and other screens */}
81
+ {showSeparator && <SidebarSeparator />}
82
+
83
+ {/* Other screens (waiting, success, fail) */}
84
+ {otherScreens.map(({ screen, originalIndex }) => (
85
+ <SidebarItem
86
+ key={screen.getId()}
87
+ screen={screen}
88
+ isSelected={originalIndex === selectedIndex}
89
+ isActive={screen.getId() === activeScreenId}
90
+ focused={focused}
91
+ />
92
+ ))}
93
+ </box>
94
+ </scrollbox>
95
+
96
+ {/* Footer with keybindings hint */}
97
+ <box paddingLeft={1} paddingRight={1} borderColor={theme.sidebar.border} border={['top']}>
98
+ <text fg={theme.sidebar.text}>q: exit | Tab: focus | ?: help</text>
99
+ </box>
100
+ </box>
101
+ )
102
+ }
@@ -0,0 +1,50 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+
3
+ import { useTheme } from '../../hooks/index.ts'
4
+
5
+ import type { ScreenInstance } from '../../services/index.ts'
6
+
7
+ export interface SidebarItemProps {
8
+ screen: ScreenInstance
9
+ isSelected: boolean
10
+ isActive: boolean
11
+ focused: boolean
12
+ }
13
+
14
+ export function SidebarItem({ screen, isSelected, isActive, focused }: SidebarItemProps) {
15
+ const theme = useTheme()
16
+ const status = screen.getStatus()
17
+ const statusIndicator = theme.statusIndicators[status]
18
+
19
+ // Determine background color based on state
20
+ let backgroundColor: string | undefined = undefined
21
+ if (isSelected && focused) {
22
+ backgroundColor = theme.sidebar.selectedBackground
23
+ } else if (isActive) {
24
+ backgroundColor = theme.sidebar.selectedBackground + '80'
25
+ }
26
+
27
+ return (
28
+ <box flexDirection="row" paddingLeft={1} paddingRight={1} backgroundColor={backgroundColor}>
29
+ {/* Selection indicator */}
30
+ <text fg={isSelected && focused ? theme.sidebar.focusBorder : 'transparent'}>{'>'} </text>
31
+
32
+ {/* Status indicator */}
33
+ <text fg={statusIndicator.color}>{statusIndicator.icon} </text>
34
+
35
+ {/* Screen name */}
36
+ <text fg={isActive ? theme.sidebar.text : theme.sidebar.textDim} flexGrow={1}>
37
+ {screen.getName()}
38
+ </text>
39
+
40
+ {/* Badge if present */}
41
+ {screen.getBadgeCount() > 0 && (
42
+ <box backgroundColor={theme.sidebar.badge} paddingLeft={1} paddingRight={1}>
43
+ <text fg={theme.colors.foreground} attributes={TextAttributes.BOLD}>
44
+ {screen.getBadgeCount() > 99 ? '99+' : screen.getBadgeCount()}
45
+ </text>
46
+ </box>
47
+ )}
48
+ </box>
49
+ )
50
+ }
@@ -0,0 +1,13 @@
1
+ import { useTheme } from '../../hooks/index.ts'
2
+
3
+ export function SidebarSeparator() {
4
+ const theme = useTheme()
5
+
6
+ return (
7
+ <box flexDirection="row" paddingTop={1}>
8
+ <box flexGrow={1} />
9
+ <text fg={theme.separator.line}>· · ·</text>
10
+ <box flexGrow={1} />
11
+ </box>
12
+ )
13
+ }
@@ -0,0 +1 @@
1
+ export * from './logger_context.tsx'