@ebowwa/coder 0.2.1 → 0.7.64

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 (401) hide show
  1. package/README.md +31 -32
  2. package/dist/core/__tests__/permissions.test.d.ts +12 -0
  3. package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
  4. package/dist/core/__tests__/permissions.test.js +851 -0
  5. package/dist/core/agent-loop/__tests__/compaction.test.d.ts +5 -0
  6. package/dist/core/agent-loop/__tests__/compaction.test.d.ts.map +1 -0
  7. package/dist/core/agent-loop/__tests__/compaction.test.js +209 -0
  8. package/dist/core/agent-loop/__tests__/formatters.test.d.ts +5 -0
  9. package/dist/core/agent-loop/__tests__/formatters.test.d.ts.map +1 -0
  10. package/dist/core/agent-loop/__tests__/formatters.test.js +195 -0
  11. package/dist/core/agent-loop/__tests__/index.test.d.ts +5 -0
  12. package/dist/core/agent-loop/__tests__/index.test.d.ts.map +1 -0
  13. package/dist/core/agent-loop/__tests__/index.test.js +121 -0
  14. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts +5 -0
  15. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts.map +1 -0
  16. package/dist/core/agent-loop/__tests__/loop-state.test.js +340 -0
  17. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts +5 -0
  18. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts.map +1 -0
  19. package/dist/core/agent-loop/__tests__/message-builder.test.js +178 -0
  20. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts +5 -0
  21. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts.map +1 -0
  22. package/dist/core/agent-loop/__tests__/tool-executor.test.js +331 -0
  23. package/dist/core/agent-loop/compaction.d.ts +39 -0
  24. package/dist/core/agent-loop/compaction.d.ts.map +1 -0
  25. package/dist/core/agent-loop/compaction.js +51 -0
  26. package/dist/core/agent-loop/formatters.d.ts +21 -0
  27. package/dist/core/agent-loop/formatters.d.ts.map +1 -0
  28. package/dist/core/agent-loop/formatters.js +42 -0
  29. package/dist/core/agent-loop/index.d.ts +25 -0
  30. package/dist/core/agent-loop/index.d.ts.map +1 -0
  31. package/dist/core/agent-loop/index.js +83 -0
  32. package/dist/core/agent-loop/loop-state.d.ts +74 -0
  33. package/dist/core/agent-loop/loop-state.d.ts.map +1 -0
  34. package/dist/core/agent-loop/loop-state.js +147 -0
  35. package/dist/core/agent-loop/message-builder.d.ts +13 -0
  36. package/dist/core/agent-loop/message-builder.d.ts.map +1 -0
  37. package/dist/core/agent-loop/message-builder.js +49 -0
  38. package/dist/core/agent-loop/tool-executor.d.ts +23 -0
  39. package/dist/core/agent-loop/tool-executor.d.ts.map +1 -0
  40. package/dist/core/agent-loop/tool-executor.js +152 -0
  41. package/dist/core/agent-loop/turn-executor.d.ts +57 -0
  42. package/dist/core/agent-loop/turn-executor.d.ts.map +1 -0
  43. package/dist/core/agent-loop/turn-executor.js +124 -0
  44. package/dist/core/agent-loop/types.d.ts +141 -0
  45. package/dist/core/agent-loop/types.d.ts.map +1 -0
  46. package/dist/core/agent-loop/types.js +4 -0
  47. package/dist/core/agent-loop.d.ts +17 -0
  48. package/dist/core/agent-loop.d.ts.map +1 -0
  49. package/dist/core/agent-loop.js +16 -0
  50. package/dist/core/api-client-impl.d.ts +62 -0
  51. package/dist/core/api-client-impl.d.ts.map +1 -0
  52. package/dist/core/api-client-impl.js +479 -0
  53. package/dist/core/api-client.d.ts +6 -0
  54. package/dist/core/api-client.d.ts.map +1 -0
  55. package/dist/core/api-client.js +5 -0
  56. package/dist/core/checkpoints.d.ts +128 -0
  57. package/dist/core/checkpoints.d.ts.map +1 -0
  58. package/dist/core/checkpoints.js +438 -0
  59. package/dist/core/claude-md.d.ts +71 -0
  60. package/dist/core/claude-md.d.ts.map +1 -0
  61. package/dist/core/claude-md.js +198 -0
  62. package/dist/core/cognitive-security/hooks.d.ts +138 -0
  63. package/dist/core/cognitive-security/hooks.d.ts.map +1 -0
  64. package/dist/core/cognitive-security/hooks.js +389 -0
  65. package/dist/core/cognitive-security/index.d.ts +751 -0
  66. package/dist/core/cognitive-security/index.d.ts.map +1 -0
  67. package/dist/core/cognitive-security/index.js +1123 -0
  68. package/dist/core/cognitive-security/middleware.d.ts +136 -0
  69. package/dist/core/cognitive-security/middleware.d.ts.map +1 -0
  70. package/dist/core/cognitive-security/middleware.js +376 -0
  71. package/dist/core/config-loader.d.ts +127 -0
  72. package/dist/core/config-loader.d.ts.map +1 -0
  73. package/dist/core/config-loader.js +219 -0
  74. package/dist/core/context-compaction.d.ts +87 -0
  75. package/dist/core/context-compaction.d.ts.map +1 -0
  76. package/dist/core/context-compaction.js +428 -0
  77. package/dist/core/git-status.d.ts +25 -0
  78. package/dist/core/git-status.d.ts.map +1 -0
  79. package/dist/core/git-status.js +204 -0
  80. package/dist/core/image.d.ts +69 -0
  81. package/dist/core/image.d.ts.map +1 -0
  82. package/dist/core/image.js +290 -0
  83. package/dist/core/image.test.d.ts +2 -0
  84. package/dist/core/image.test.d.ts.map +1 -0
  85. package/dist/core/image.test.js +149 -0
  86. package/dist/core/models.d.ts +123 -0
  87. package/dist/core/models.d.ts.map +1 -0
  88. package/dist/core/models.js +325 -0
  89. package/dist/core/permissions.d.ts +81 -0
  90. package/dist/core/permissions.d.ts.map +1 -0
  91. package/dist/core/permissions.js +327 -0
  92. package/dist/core/retry.d.ts +25 -0
  93. package/dist/core/retry.d.ts.map +1 -0
  94. package/dist/core/retry.js +121 -0
  95. package/dist/core/session-store.d.ts +9 -0
  96. package/dist/core/session-store.d.ts.map +1 -0
  97. package/dist/core/session-store.js +10 -0
  98. package/dist/core/sessions/export.d.ts +47 -0
  99. package/dist/core/sessions/export.d.ts.map +1 -0
  100. package/dist/core/sessions/export.js +256 -0
  101. package/dist/core/sessions/index.d.ts +132 -0
  102. package/dist/core/sessions/index.d.ts.map +1 -0
  103. package/dist/core/sessions/index.js +442 -0
  104. package/dist/core/sessions/metadata.d.ts +77 -0
  105. package/dist/core/sessions/metadata.d.ts.map +1 -0
  106. package/dist/core/sessions/metadata.js +233 -0
  107. package/dist/core/sessions/persistence.d.ts +72 -0
  108. package/dist/core/sessions/persistence.d.ts.map +1 -0
  109. package/dist/core/sessions/persistence.js +201 -0
  110. package/dist/core/sessions/types.d.ts +110 -0
  111. package/dist/core/sessions/types.d.ts.map +1 -0
  112. package/dist/core/sessions/types.js +4 -0
  113. package/dist/core/stream-highlighter.d.ts +18 -0
  114. package/dist/core/stream-highlighter.d.ts.map +1 -0
  115. package/dist/core/stream-highlighter.js +916 -0
  116. package/dist/core/system-reminders.d.ts +89 -0
  117. package/dist/core/system-reminders.d.ts.map +1 -0
  118. package/dist/core/system-reminders.js +285 -0
  119. package/dist/ecosystem/hooks/__tests__/index.test.d.ts +5 -0
  120. package/dist/ecosystem/hooks/__tests__/index.test.d.ts.map +1 -0
  121. package/dist/ecosystem/hooks/__tests__/index.test.js +458 -0
  122. package/dist/ecosystem/hooks/index.d.ts +59 -0
  123. package/dist/ecosystem/hooks/index.d.ts.map +1 -0
  124. package/dist/ecosystem/hooks/index.js +294 -0
  125. package/dist/ecosystem/hooks/prompt-evaluator.d.ts +32 -0
  126. package/dist/ecosystem/hooks/prompt-evaluator.d.ts.map +1 -0
  127. package/dist/ecosystem/hooks/prompt-evaluator.js +229 -0
  128. package/dist/ecosystem/skills/index.d.ts +55 -0
  129. package/dist/ecosystem/skills/index.d.ts.map +1 -0
  130. package/dist/ecosystem/skills/index.js +258 -0
  131. package/dist/ecosystem/tools/__tests__/index.test.d.ts +7 -0
  132. package/dist/ecosystem/tools/__tests__/index.test.d.ts.map +1 -0
  133. package/dist/ecosystem/tools/__tests__/index.test.js +856 -0
  134. package/dist/ecosystem/tools/index.d.ts +24 -0
  135. package/dist/ecosystem/tools/index.d.ts.map +1 -0
  136. package/dist/ecosystem/tools/index.js +1709 -0
  137. package/dist/index.d.ts +24 -0
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +32 -2
  140. package/dist/interfaces/mcp/client.d.ts +40 -0
  141. package/dist/interfaces/mcp/client.d.ts.map +1 -0
  142. package/dist/interfaces/mcp/client.js +309 -0
  143. package/dist/interfaces/ui/index.d.ts +36 -0
  144. package/dist/interfaces/ui/index.d.ts.map +1 -0
  145. package/dist/interfaces/ui/index.js +61 -0
  146. package/dist/interfaces/ui/spinner.d.ts +140 -0
  147. package/dist/interfaces/ui/spinner.d.ts.map +1 -0
  148. package/dist/interfaces/ui/spinner.js +342 -0
  149. package/dist/interfaces/ui/terminal/cli/index.d.ts +12 -0
  150. package/dist/interfaces/ui/terminal/cli/index.d.ts.map +1 -0
  151. package/dist/interfaces/ui/terminal/cli/index.js +167 -0
  152. package/dist/interfaces/ui/terminal/shared/args.d.ts +39 -0
  153. package/dist/interfaces/ui/terminal/shared/args.d.ts.map +1 -0
  154. package/dist/interfaces/ui/terminal/shared/args.js +176 -0
  155. package/dist/interfaces/ui/terminal/shared/index.d.ts +11 -0
  156. package/dist/interfaces/ui/terminal/shared/index.d.ts.map +1 -0
  157. package/dist/interfaces/ui/terminal/shared/index.js +16 -0
  158. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts +124 -0
  159. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts.map +1 -0
  160. package/dist/interfaces/ui/terminal/shared/loading-state.js +246 -0
  161. package/dist/interfaces/ui/terminal/shared/query.d.ts +22 -0
  162. package/dist/interfaces/ui/terminal/shared/query.d.ts.map +1 -0
  163. package/dist/interfaces/ui/terminal/shared/query.js +100 -0
  164. package/dist/interfaces/ui/terminal/shared/setup.d.ts +33 -0
  165. package/dist/interfaces/ui/terminal/shared/setup.d.ts.map +1 -0
  166. package/dist/interfaces/ui/terminal/shared/setup.js +226 -0
  167. package/dist/interfaces/ui/terminal/shared/status-line.d.ts +117 -0
  168. package/dist/interfaces/ui/terminal/shared/status-line.d.ts.map +1 -0
  169. package/dist/interfaces/ui/terminal/shared/status-line.js +267 -0
  170. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts +38 -0
  171. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts.map +1 -0
  172. package/dist/interfaces/ui/terminal/shared/system-prompt.js +102 -0
  173. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts +39 -0
  174. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts.map +1 -0
  175. package/dist/interfaces/ui/terminal/tui/HelpPanel.js +215 -0
  176. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts +91 -0
  177. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts.map +1 -0
  178. package/dist/interfaces/ui/terminal/tui/InputContext.js +154 -0
  179. package/dist/interfaces/ui/terminal/tui/InputField.d.ts +18 -0
  180. package/dist/interfaces/ui/terminal/tui/InputField.d.ts.map +1 -0
  181. package/dist/interfaces/ui/terminal/tui/InputField.js +41 -0
  182. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts +16 -0
  183. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts.map +1 -0
  184. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.js +451 -0
  185. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts +10 -0
  186. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts.map +1 -0
  187. package/dist/interfaces/ui/terminal/tui/MessageArea.js +91 -0
  188. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts +48 -0
  189. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts.map +1 -0
  190. package/dist/interfaces/ui/terminal/tui/MessageStore.js +151 -0
  191. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts +9 -0
  192. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts.map +1 -0
  193. package/dist/interfaces/ui/terminal/tui/StatusBar.js +36 -0
  194. package/dist/interfaces/ui/terminal/tui/commands.d.ts +21 -0
  195. package/dist/interfaces/ui/terminal/tui/commands.d.ts.map +1 -0
  196. package/dist/interfaces/ui/terminal/tui/commands.js +359 -0
  197. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts +115 -0
  198. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts.map +1 -0
  199. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.js +306 -0
  200. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts +92 -0
  201. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts.map +1 -0
  202. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.js +399 -0
  203. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts +59 -0
  204. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts.map +1 -0
  205. package/dist/interfaces/ui/terminal/tui/components/PaneManager.js +139 -0
  206. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts +68 -0
  207. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts.map +1 -0
  208. package/dist/interfaces/ui/terminal/tui/components/Sidebar.js +340 -0
  209. package/dist/interfaces/ui/terminal/tui/components/index.d.ts +23 -0
  210. package/dist/interfaces/ui/terminal/tui/components/index.d.ts.map +1 -0
  211. package/dist/interfaces/ui/terminal/tui/components/index.js +51 -0
  212. package/dist/interfaces/ui/terminal/tui/console.d.ts +20 -0
  213. package/dist/interfaces/ui/terminal/tui/console.d.ts.map +1 -0
  214. package/dist/interfaces/ui/terminal/tui/console.js +46 -0
  215. package/dist/interfaces/ui/terminal/tui/index.d.ts +20 -0
  216. package/dist/interfaces/ui/terminal/tui/index.d.ts.map +1 -0
  217. package/dist/interfaces/ui/terminal/tui/index.js +28 -0
  218. package/dist/interfaces/ui/terminal/tui/run.d.ts +13 -0
  219. package/dist/interfaces/ui/terminal/tui/run.d.ts.map +1 -0
  220. package/dist/interfaces/ui/terminal/tui/run.js +31 -0
  221. package/dist/interfaces/ui/terminal/tui/spinner.d.ts +44 -0
  222. package/dist/interfaces/ui/terminal/tui/spinner.d.ts.map +1 -0
  223. package/dist/interfaces/ui/terminal/tui/spinner.js +59 -0
  224. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts +39 -0
  225. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts.map +1 -0
  226. package/dist/interfaces/ui/terminal/tui/tui-app.js +198 -0
  227. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts +167 -0
  228. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts.map +1 -0
  229. package/dist/interfaces/ui/terminal/tui/tui-footer.js +330 -0
  230. package/dist/interfaces/ui/terminal/tui/types.d.ts +165 -0
  231. package/dist/interfaces/ui/terminal/tui/types.d.ts.map +1 -0
  232. package/dist/interfaces/ui/terminal/tui/types.js +5 -0
  233. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts +23 -0
  234. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts.map +1 -0
  235. package/dist/interfaces/ui/terminal/tui/useInputHandler.js +72 -0
  236. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts +90 -0
  237. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts.map +1 -0
  238. package/dist/interfaces/ui/terminal/tui/useNativeInput.js +188 -0
  239. package/dist/native/index.d.ts +480 -0
  240. package/dist/native/index.d.ts.map +1 -0
  241. package/dist/native/index.js +1625 -0
  242. package/dist/teammates/index.d.ts +161 -0
  243. package/dist/teammates/index.d.ts.map +1 -0
  244. package/dist/teammates/index.js +827 -0
  245. package/dist/types/index.d.ts +482 -0
  246. package/dist/types/index.d.ts.map +1 -0
  247. package/dist/types/index.js +52 -0
  248. package/native/README.md +5 -5
  249. package/native/index.darwin-arm64.node +0 -0
  250. package/native/index.node +0 -0
  251. package/native/package.json +4 -4
  252. package/package.json +33 -16
  253. package/packages/src/core/__tests__/permissions.test.ts +1091 -0
  254. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +280 -0
  255. package/packages/src/core/agent-loop/__tests__/formatters.test.ts +234 -0
  256. package/packages/src/core/agent-loop/__tests__/index.test.ts +162 -0
  257. package/packages/src/core/agent-loop/__tests__/loop-state.test.ts +413 -0
  258. package/packages/src/core/agent-loop/__tests__/message-builder.test.ts +229 -0
  259. package/packages/src/core/agent-loop/__tests__/tool-executor.test.ts +457 -0
  260. package/packages/src/core/agent-loop/compaction.ts +88 -0
  261. package/packages/src/core/agent-loop/formatters.ts +50 -0
  262. package/packages/src/core/agent-loop/index.ts +135 -0
  263. package/packages/src/core/agent-loop/loop-state.ts +187 -0
  264. package/packages/src/core/agent-loop/message-builder.ts +62 -0
  265. package/packages/src/core/agent-loop/tool-executor.ts +211 -0
  266. package/packages/src/core/agent-loop/turn-executor.ts +222 -0
  267. package/packages/src/core/agent-loop/types.ts +148 -0
  268. package/packages/src/core/agent-loop.ts +18 -0
  269. package/packages/src/core/api-client-impl.ts +619 -0
  270. package/packages/src/core/api-client.ts +6 -0
  271. package/packages/src/core/checkpoints.ts +606 -0
  272. package/packages/src/core/claude-md.ts +272 -0
  273. package/packages/src/core/cognitive-security/hooks.ts +590 -0
  274. package/packages/src/core/cognitive-security/index.ts +2041 -0
  275. package/packages/src/core/cognitive-security/middleware.ts +536 -0
  276. package/packages/src/core/config-loader.ts +324 -0
  277. package/packages/src/core/context-compaction.ts +578 -0
  278. package/packages/src/core/git-status.ts +262 -0
  279. package/packages/src/core/image.test.ts +180 -0
  280. package/packages/src/core/image.ts +350 -0
  281. package/packages/src/core/lmdb.db +0 -0
  282. package/packages/src/core/lmdb.db-lock +0 -0
  283. package/packages/src/core/models.ts +430 -0
  284. package/packages/src/core/normalizers/todo +4 -0
  285. package/packages/src/core/permissions.ts +431 -0
  286. package/packages/src/core/retry.ts +170 -0
  287. package/packages/src/core/session-store.ts +36 -0
  288. package/packages/src/core/sessions/export.ts +329 -0
  289. package/packages/src/core/sessions/index.ts +587 -0
  290. package/packages/src/core/sessions/metadata.ts +309 -0
  291. package/packages/src/core/sessions/persistence.ts +244 -0
  292. package/packages/src/core/sessions/types.ts +169 -0
  293. package/packages/src/core/stream-highlighter.ts +1123 -0
  294. package/packages/src/core/system-reminders.ts +402 -0
  295. package/packages/src/core/todo +8 -0
  296. package/packages/src/ecosystem/hooks/__tests__/index.test.ts +561 -0
  297. package/packages/src/ecosystem/hooks/index.ts +341 -0
  298. package/packages/src/ecosystem/hooks/prompt-evaluator.ts +300 -0
  299. package/packages/src/ecosystem/skills/index.ts +295 -0
  300. package/packages/src/ecosystem/tools/__tests__/index.test.ts +1335 -0
  301. package/packages/src/ecosystem/tools/index.ts +1877 -0
  302. package/packages/src/index.ts +120 -0
  303. package/packages/src/interfaces/mcp/client.ts +389 -0
  304. package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
  305. package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
  306. package/packages/src/interfaces/ui/index.ts +161 -0
  307. package/packages/src/interfaces/ui/lmdb.db +0 -0
  308. package/packages/src/interfaces/ui/lmdb.db-lock +0 -0
  309. package/packages/src/interfaces/ui/spinner.ts +451 -0
  310. package/packages/src/interfaces/ui/terminal/cli/index.ts +228 -0
  311. package/packages/src/interfaces/ui/terminal/lmdb.db +0 -0
  312. package/packages/src/interfaces/ui/terminal/lmdb.db-lock +0 -0
  313. package/packages/src/interfaces/ui/terminal/shared/args.ts +222 -0
  314. package/packages/src/interfaces/ui/terminal/shared/index.ts +71 -0
  315. package/packages/src/interfaces/ui/terminal/shared/loading-state.ts +322 -0
  316. package/packages/src/interfaces/ui/terminal/shared/query.ts +146 -0
  317. package/packages/src/interfaces/ui/terminal/shared/setup.ts +295 -0
  318. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +358 -0
  319. package/packages/src/interfaces/ui/terminal/shared/system-prompt.ts +146 -0
  320. package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +262 -0
  321. package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +232 -0
  322. package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +62 -0
  323. package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +537 -0
  324. package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +107 -0
  325. package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +240 -0
  326. package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +54 -0
  327. package/packages/src/interfaces/ui/terminal/tui/commands.ts +438 -0
  328. package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +584 -0
  329. package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +614 -0
  330. package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +333 -0
  331. package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +604 -0
  332. package/packages/src/interfaces/ui/terminal/tui/components/index.ts +118 -0
  333. package/packages/src/interfaces/ui/terminal/tui/console.ts +49 -0
  334. package/packages/src/interfaces/ui/terminal/tui/index.ts +90 -0
  335. package/packages/src/interfaces/ui/terminal/tui/run.tsx +42 -0
  336. package/packages/src/interfaces/ui/terminal/tui/spinner.ts +69 -0
  337. package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +390 -0
  338. package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +422 -0
  339. package/packages/src/interfaces/ui/terminal/tui/types.ts +186 -0
  340. package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +104 -0
  341. package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +239 -0
  342. package/packages/src/lmdb.db +0 -0
  343. package/packages/src/lmdb.db-lock +0 -0
  344. package/packages/src/native/index.ts +2345 -0
  345. package/packages/src/teammates/index.ts +982 -0
  346. package/packages/src/types/index.ts +722 -0
  347. package/dist/cli.js +0 -148
  348. package/dist/index-0pkak453.js +0 -136
  349. package/dist/index-0qd0x8b4.js +0 -110
  350. package/dist/index-0x3kprq6.js +0 -240
  351. package/dist/index-1eawy937.js +0 -308
  352. package/dist/index-24m2aygy.js +0 -240
  353. package/dist/index-29xcjnne.js +0 -280
  354. package/dist/index-2avyytn5.js +0 -349
  355. package/dist/index-4ms367ey.js +0 -136
  356. package/dist/index-4w2t3b0m.js +0 -240
  357. package/dist/index-4xfgd8nz.js +0 -261
  358. package/dist/index-5acjp9gc.js +0 -157
  359. package/dist/index-5s15hr56.js +0 -136
  360. package/dist/index-6e4wf341.js +0 -349
  361. package/dist/index-6fvnkedw.js +0 -240
  362. package/dist/index-6rqpmd4g.js +0 -128
  363. package/dist/index-77ckwnbm.js +0 -280
  364. package/dist/index-9knxy49k.js +0 -128
  365. package/dist/index-9zrnw4zx.js +0 -128
  366. package/dist/index-bk21w99v.js +0 -280
  367. package/dist/index-c41n76fv.js +0 -240
  368. package/dist/index-cb4ppjdt.js +0 -255
  369. package/dist/index-cfb2edt6.js +0 -240
  370. package/dist/index-cmfa38hh.js +0 -308
  371. package/dist/index-datjz8q1.js +0 -257
  372. package/dist/index-eadf4wvn.js +0 -240
  373. package/dist/index-em5k0m3z.js +0 -345
  374. package/dist/index-gh8r333a.js +0 -110
  375. package/dist/index-gkx6k2tr.js +0 -261
  376. package/dist/index-h5cabfks.js +0 -155
  377. package/dist/index-hcrpwyy3.js +0 -261
  378. package/dist/index-hk7fwwa8.js +0 -257
  379. package/dist/index-jb8cw7f8.js +0 -136
  380. package/dist/index-kbyw4th1.js +0 -347
  381. package/dist/index-kgj5gqnm.js +0 -345
  382. package/dist/index-mdf6xp1z.js +0 -255
  383. package/dist/index-mrhv8kvc.js +0 -280
  384. package/dist/index-mt4743dd.js +0 -161
  385. package/dist/index-qnwsg97q.js +0 -240
  386. package/dist/index-qwdy6x44.js +0 -261
  387. package/dist/index-rmj77261.js +0 -157
  388. package/dist/index-sbbw1a61.js +0 -349
  389. package/dist/index-svy5bcpn.js +0 -345
  390. package/dist/index-tvmy7tm9.js +0 -261
  391. package/dist/index-tzz4vzkj.js +0 -312
  392. package/dist/index-vz80zmhe.js +0 -110
  393. package/dist/index-wed2fk67.js +0 -240
  394. package/dist/index-wksgzz8e.js +0 -280
  395. package/dist/index-wn2m4wma.js +0 -240
  396. package/dist/index-xha05vjc.js +0 -257
  397. package/dist/index-yc6eh8p8.js +0 -136
  398. package/dist/index-ycjxx9ft.js +0 -240
  399. package/dist/index-z0gzd0fc.js +0 -110
  400. package/dist/index-z8cwtf8j.js +0 -240
  401. package/dist/index-zy5mtt00.js +0 -128
@@ -0,0 +1,1123 @@
1
+ /**
2
+ * Streaming text highlighter for code blocks and diffs
3
+ * Buffers text and highlights as content completes during streaming output
4
+ */
5
+
6
+ import { highlight_code, highlight_diff } from "../native/index.js";
7
+
8
+ // ANSI color codes - Base16 Ocean color scheme
9
+ const COLORS = {
10
+ reset: "\x1b[0m",
11
+ bold: "\x1b[1m",
12
+ dim: "\x1b[2m",
13
+ italic: "\x1b[3m",
14
+ underline: "\x1b[4m",
15
+ strikethrough: "\x1b[9m",
16
+ // Syntax colors
17
+ purple: "\x1b[38;2;180;142;173m", // keywords
18
+ blue: "\x1b[38;2;143;161;179m", // functions
19
+ green: "\x1b[38;2;163;190;140m", // strings, additions
20
+ orange: "\x1b[38;2;208;135;112m", // numbers
21
+ red: "\x1b[38;2;191;97;106m", // types, deletions
22
+ cyan: "\x1b[38;2;150;181;180m", // builtins
23
+ gray: "\x1b[38;2;192;197;206m", // default
24
+ yellow: "\x1b[38;2;235;203;139m", // warnings, highlights
25
+ comment: "\x1b[38;2;101;115;126m", // comments
26
+ // Markdown specific
27
+ header: "\x1b[38;2;143;161;179m\x1b[1m", // bold blue
28
+ link: "\x1b[38;2;150;181;180m\x1b[4m", // cyan underline
29
+ list: "\x1b[38;2;180;142;173m", // purple bullet
30
+ blockquote: "\x1b[38;2;101;115;126m\x1b[3m", // dim italic gray
31
+ hr: "\x1b[38;2;101;115;126m", // dim gray
32
+ tableBorder: "\x1b[38;2;143;161;179m", // blue for table borders
33
+ tableHeader: "\x1b[38;2;180;142;173m\x1b[1m", // bold purple for table headers
34
+ // Financial/Trading colors
35
+ money: "\x1b[38;2;163;190;140m", // green for money amounts
36
+ moneyLoss: "\x1b[38;2;191;97;106m", // red for losses
37
+ moneyGain: "\x1b[38;2;140;190;140m\x1b[1m", // bold green for gains
38
+ percentUp: "\x1b[38;2;163;190;140m", // green for positive %
39
+ percentDown: "\x1b[38;2;191;97;106m", // red for negative %
40
+ ticker: "\x1b[38;2;235;203;139m\x1b[1m", // bold yellow for stock tickers
41
+ crypto: "\x1b[38;2;247;140;108m", // orange for crypto
42
+ timestamp: "\x1b[38;2;101;115;126m", // dim gray for timestamps
43
+ math: "\x1b[38;2;180;142;173m", // purple for math operators
44
+ bullish: "\x1b[38;2;163;190;140m\x1b[1m", // bold green for bullish terms
45
+ bearish: "\x1b[38;2;191;97;106m\x1b[1m", // bold red for bearish terms
46
+ neutral: "\x1b[38;2;235;203;139m", // yellow for neutral terms
47
+ priceTarget: "\x1b[38;2;143;161;179m\x1b[4m", // blue underline for price targets
48
+ };
49
+
50
+ // Currency symbols and their names
51
+ const CURRENCY_SYMBOLS: Record<string, string> = {
52
+ '$': 'USD', '€': 'EUR', '£': 'GBP', '¥': 'JPY', '₹': 'INR',
53
+ '₽': 'RUB', '₩': 'KRW', '₿': 'BTC', 'Ξ': 'ETH', '₮': 'USDT',
54
+ '₳': 'ADA', '₴': 'UAH', '₸': 'KZT', '₺': 'TRY', '₼': 'AZN',
55
+ '₪': 'ILS', '₫': 'VND', '₭': 'LAK', '₱': 'PHP', '₲': 'PYG',
56
+ '₵': 'GHS', '₡': 'CRC', '₦': 'NGN', '₣': 'CHF', '₤': 'TRY',
57
+ };
58
+
59
+ // Crypto symbols
60
+ const CRYPTO_SYMBOLS = new Set([
61
+ 'BTC', 'ETH', 'USDT', 'BNB', 'XRP', 'SOL', 'USDC', 'ADA', 'DOGE', 'DOT',
62
+ 'MATIC', 'LTC', 'SHIB', 'TRX', 'AVAX', 'LINK', 'ATOM', 'UNI', 'XMR', 'ETC',
63
+ 'XLM', 'BCH', 'APT', 'NEAR', 'FIL', 'LDO', 'ARB', 'OP', 'HBAR', 'VET',
64
+ 'ICP', 'QNT', 'AAVE', 'GRT', 'ALGO', 'SAND', 'MANA', 'AXS', 'FTM', 'EGLD',
65
+ 'SUSHI', 'THETA', 'XTZ', 'EOS', 'FLOW', 'CHZ', 'SNX', 'LUNC', 'RUNE', 'KAVA',
66
+ 'CRV', 'COMP', 'YFI', 'MKR', 'ZEC', 'DASH', 'NEO', 'WAVES', 'ENJ', 'BAT',
67
+ '1INCH', 'ANKR', 'CELO', 'CVC', 'DIA', 'GNO', 'KNC', 'OCEAN', 'OMG', 'REN',
68
+ 'RLC', 'STORJ', 'SYN', 'UMA', 'ZRX', 'BLUR', 'DYDX', 'GMX', 'PEPE', 'BONK',
69
+ 'WIF', 'FLOKI', 'INJ', 'SUI', 'SEI', 'TIA', 'IMX', 'ORDI', 'JUP', 'W',
70
+ ]);
71
+
72
+ // Stock ticker pattern (1-5 uppercase letters)
73
+ const TICKER_PATTERN = /\$?([A-Z]{1,5})(?=\s|\.|,|:|;|!|\?|$|\(|\)|\[|\]|-)/g;
74
+
75
+ // Trading terms
76
+ const BULLISH_TERMS = new Set([
77
+ 'bull', 'bullish', 'long', 'buy', 'call', 'pump', 'moon', 'rocket', 'lambo',
78
+ 'hold', 'hodl', 'diamond', 'hands', 'gains', 'profit', 'rally', 'surge',
79
+ 'breakout', 'uptrend', 'accumulation', 'support', 'bounce', 'recovery',
80
+ 'all-time high', 'ath', 'bagholder', 'whale buy', 'accumulation', 'undervalued',
81
+ ]);
82
+
83
+ const BEARISH_TERMS = new Set([
84
+ 'bear', 'bearish', 'short', 'sell', 'put', 'dump', 'crash', 'dump', 'rekt',
85
+ 'liquidated', 'loss', 'bleed', 'downtrend', 'breakdown', 'resistance',
86
+ 'correction', 'dip', ' capitulation', 'panic sell', 'overvalued', 'bubble',
87
+ ]);
88
+
89
+ const NEUTRAL_TERMS = new Set([
90
+ 'position', 'entry', 'exit', 'stop', 'limit', 'market', 'order', 'trade',
91
+ 'volume', 'liquidity', 'spread', 'slippage', 'leverage', 'margin', 'futures',
92
+ 'options', 'perpetual', 'funding', 'basis', 'arbitrage', 'hedge', 'delta',
93
+ 'gamma', 'theta', 'vega', 'iv', 'hv', 'volatility', 'oi', 'open interest',
94
+ ]);
95
+
96
+ // Math operators and symbols
97
+ const MATH_PATTERNS = [
98
+ { pattern: /[+\-×÷*\/%^=<>≤≥≠≈±√∫∑∏∂∆∇]/g, color: 'math' },
99
+ { pattern: /\b(pi|π|e|phi|inf|infinity|NaN|null)\b/gi, color: 'orange' },
100
+ ];
101
+
102
+ /**
103
+ * Detects if content looks like a diff (unified diff format)
104
+ */
105
+ function isDiffContent(content: string): boolean {
106
+ const lines = content.split('\n').filter(l => l.length > 0);
107
+ if (lines.length < 3) return false;
108
+
109
+ // Check for diff markers
110
+ let diffMarkers = 0;
111
+ for (const line of lines.slice(0, 10)) {
112
+ if (line.startsWith('+++') || line.startsWith('---') ||
113
+ line.startsWith('@@') || line.startsWith('diff ') ||
114
+ line.startsWith('index ')) {
115
+ diffMarkers++;
116
+ }
117
+ }
118
+
119
+ // Also check for +/- lines
120
+ let plusLines = 0;
121
+ let minusLines = 0;
122
+ for (const line of lines) {
123
+ if (line.startsWith('+') && !line.startsWith('+++')) plusLines++;
124
+ if (line.startsWith('-') && !line.startsWith('---')) minusLines++;
125
+ }
126
+
127
+ return diffMarkers >= 2 || (plusLines > 0 && minusLines > 0);
128
+ }
129
+
130
+ /**
131
+ * Highlight diff content with ANSI colors
132
+ * Supports unified diff, git diff, and context diff formats
133
+ */
134
+ function highlightDiffContent(content: string): string {
135
+ const lines = content.split('\n');
136
+ const result: string[] = [];
137
+
138
+ for (const line of lines) {
139
+ if (line.startsWith('@@')) {
140
+ // Hunk header - cyan with function context
141
+ // Format: @@ -start,count +start,count @@ optional_function_name
142
+ const match = line.match(/^(@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@)(.*)$/);
143
+ if (match) {
144
+ result.push(`${COLORS.cyan}${match[1]}${COLORS.reset}${COLORS.dim}${match[2]}${COLORS.reset}`);
145
+ } else {
146
+ result.push(`${COLORS.cyan}${line}${COLORS.reset}`);
147
+ }
148
+ } else if (line.startsWith('+++') || line.startsWith('---')) {
149
+ // File headers - bold
150
+ const prefix = line.startsWith('+++') ? '+' : '-';
151
+ const rest = line.slice(3);
152
+ result.push(`${COLORS.bold}${prefix}${prefix}${prefix}${COLORS.reset}${COLORS.cyan}${rest}${COLORS.reset}`);
153
+ } else if (line.startsWith('diff --git')) {
154
+ // Git diff header
155
+ result.push(`${COLORS.dim}${line}${COLORS.reset}`);
156
+ } else if (line.startsWith('index ')) {
157
+ // Index line
158
+ result.push(`${COLORS.dim}${line}${COLORS.reset}`);
159
+ } else if (line.startsWith('new file') || line.startsWith('deleted file')) {
160
+ // New/deleted file markers
161
+ result.push(`${COLORS.yellow}${line}${COLORS.reset}`);
162
+ } else if (line.startsWith('Binary files')) {
163
+ // Binary file markers
164
+ result.push(`${COLORS.yellow}${line}${COLORS.reset}`);
165
+ } else if (line.startsWith('rename from') || line.startsWith('rename to')) {
166
+ // Rename markers
167
+ result.push(`${COLORS.purple}${line}${COLORS.reset}`);
168
+ } else if (line.startsWith('similarity index')) {
169
+ // Similarity index
170
+ result.push(`${COLORS.dim}${line}${COLORS.reset}`);
171
+ } else if (line.startsWith('+')) {
172
+ // Additions - green with brighter first char
173
+ if (line === '+') {
174
+ result.push(`${COLORS.green}+${COLORS.reset}`);
175
+ } else {
176
+ result.push(`${COLORS.green}+${line.slice(1)}${COLORS.reset}`);
177
+ }
178
+ } else if (line.startsWith('-')) {
179
+ // Deletions - red
180
+ if (line === '-') {
181
+ result.push(`${COLORS.red}-${COLORS.reset}`);
182
+ } else {
183
+ result.push(`${COLORS.red}-${line.slice(1)}${COLORS.reset}`);
184
+ }
185
+ } else if (line.startsWith(' ') || line === '') {
186
+ // Context lines - dim
187
+ result.push(`${COLORS.dim}${line}${COLORS.reset}`);
188
+ } else if (line.match(/^(={67}|>{67}|<{67})/)) {
189
+ // Context diff separators
190
+ result.push(`${COLORS.dim}${line}${COLORS.reset}`);
191
+ } else if (line.startsWith('***************')) {
192
+ // Context diff hunk marker
193
+ result.push(`${COLORS.cyan}${line}${COLORS.reset}`);
194
+ } else if (line.startsWith('*** ') || line.startsWith('--- ')) {
195
+ // Context diff file markers (at line start, not diff markers)
196
+ result.push(`${COLORS.cyan}${line}${COLORS.reset}`);
197
+ } else {
198
+ result.push(line);
199
+ }
200
+ }
201
+
202
+ return result.join('\n');
203
+ }
204
+
205
+ /**
206
+ * Highlight inline code with a subtle color
207
+ */
208
+ function highlightInlineCode(text: string): string {
209
+ // Skip if we're inside a code block
210
+ if (text.includes('```')) {
211
+ return text;
212
+ }
213
+
214
+ // Match inline code with single backticks
215
+ // Also handle escaped backticks and nested code
216
+ return text
217
+ // Single backtick inline code
218
+ .replace(/`([^`\n]+)`/g, (_, code) => {
219
+ return `${COLORS.dim}\`${COLORS.orange}${code}${COLORS.dim}\`${COLORS.reset}`;
220
+ })
221
+ // Double backtick inline code (allows single backticks inside)
222
+ .replace(/``([^`\n]+)``/g, (_, code) => {
223
+ return `${COLORS.dim}\`\`${COLORS.orange}${code}${COLORS.dim}\`\`${COLORS.reset}`;
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Highlight URLs that aren't already part of markdown links
229
+ */
230
+ function highlightUrls(text: string): string {
231
+ // Skip if already in a markdown link
232
+ if (/\[[^\]]+\]\([^)]+\)/.test(text)) {
233
+ return text;
234
+ }
235
+
236
+ // Match bare URLs (http:// or https://)
237
+ return text.replace(
238
+ /(https?:\/\/[^\s<>\[\]()]+)/g,
239
+ `${COLORS.link}$1${COLORS.reset}`
240
+ );
241
+ }
242
+
243
+ /**
244
+ * Highlight emoji shortcodes :emoji:
245
+ */
246
+ function highlightEmoji(text: string): string {
247
+ return text.replace(/:([a-z0-9_+-]+):/g, (_, name) => {
248
+ return `${COLORS.yellow}:${name}:${COLORS.reset}`;
249
+ });
250
+ }
251
+
252
+ // ===== Financial/Trading Highlighting =====
253
+
254
+ /**
255
+ * Format large numbers with K, M, B, T suffixes
256
+ */
257
+ function formatLargeNumber(num: number): string {
258
+ if (num >= 1e12) return (num / 1e12).toFixed(1) + 'T';
259
+ if (num >= 1e9) return (num / 1e9).toFixed(1) + 'B';
260
+ if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M';
261
+ if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K';
262
+ return num.toString();
263
+ }
264
+
265
+ /**
266
+ * Highlight currency amounts ($100, €50, ¥1000, etc.)
267
+ */
268
+ function highlightCurrency(text: string): string {
269
+ // Skip if inside code block or has ANSI codes
270
+ if (text.includes('```') || text.includes('\x1b[')) return text;
271
+
272
+ // Currency symbols pattern - match currency symbol followed by number
273
+ const currencyPattern = new RegExp(
274
+ `([${Object.keys(CURRENCY_SYMBOLS).join('')}])\\s?([\\d,]+(?:\\.\\d{1,8})?)`,
275
+ 'g'
276
+ );
277
+
278
+ let result = text.replace(currencyPattern, (_, symbol, amount) => {
279
+ const currency = CURRENCY_SYMBOLS[symbol] || '';
280
+ return `${COLORS.money}${symbol}${amount}${COLORS.reset}`;
281
+ });
282
+
283
+ // Named currency formats (USD 100, EUR 50)
284
+ result = result.replace(
285
+ /\b(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|MXN|BRL|KRW|SGD|HKD|NOK|SEK|DKK|NZD|ZAR|RUB|TRY|PLN|THB|IDR|MYR|PHP|VND)\s+([\d,]+(?:\.\d{1,8})?)/gi,
286
+ (_, currency, amount) => `${COLORS.money}${currency} ${amount}${COLORS.reset}`
287
+ );
288
+
289
+ return result;
290
+ }
291
+
292
+ /**
293
+ * Highlight cryptocurrency amounts and symbols
294
+ */
295
+ function highlightCrypto(text: string): string {
296
+ // Skip if inside code block
297
+ if (text.includes('```')) return text;
298
+
299
+ let result = text;
300
+
301
+ // Crypto amounts with symbol (0.5 BTC, 10 ETH, etc.)
302
+ const cryptoPattern = new RegExp(
303
+ `([\\d,]+(?:\\.\\d{1,8})?)\\s*(${Array.from(CRYPTO_SYMBOLS).join('|')})(?=\\s|\\.|\,|:|;|!|\\?|$)`,
304
+ 'gi'
305
+ );
306
+
307
+ result = result.replace(cryptoPattern, (_, amount, symbol) => {
308
+ const upperSymbol = symbol.toUpperCase();
309
+ return `${COLORS.crypto}${amount} ${upperSymbol}${COLORS.reset}`;
310
+ });
311
+
312
+ // Satoshi amounts
313
+ result = result.replace(
314
+ /([\d,]+(?:\.\d{1,8})?)\s*(sats?|satoshis?)(?=\s|\.|,|:|;|!|\?|$)/gi,
315
+ (_, amount, unit) => {
316
+ return `${COLORS.crypto}${amount} ${unit.toLowerCase()}${COLORS.reset}`;
317
+ }
318
+ );
319
+
320
+ // Gwei amounts (gas prices)
321
+ result = result.replace(
322
+ /([\d,]+(?:\.\d{1,2})?)\s*gwei(?=\s|\.|,|:|;|!|\?|$)/gi,
323
+ (_, amount) => {
324
+ return `${COLORS.crypto}${amount} gwei${COLORS.reset}`;
325
+ }
326
+ );
327
+
328
+ // Wei amounts
329
+ result = result.replace(
330
+ /([\d,]+)\s*wei(?=\s|\.|,|:|;|!|\?|$)/gi,
331
+ (_, amount) => {
332
+ return `${COLORS.dim}${amount} wei${COLORS.reset}`;
333
+ }
334
+ );
335
+
336
+ return result;
337
+ }
338
+
339
+ /**
340
+ * Highlight percentages with color based on positive/negative
341
+ */
342
+ function highlightPercentages(text: string): string {
343
+ // Skip if inside code block or already has ANSI codes
344
+ if (text.includes('```') || text.includes('\x1b[')) return text;
345
+
346
+ let result = text;
347
+
348
+ // Positive percentages (+5.2%, +10%) - must have space or start before
349
+ result = result.replace(
350
+ /(^|\s)\+([\d,]+(?:\.\d{1,4})?)\s*%/gm,
351
+ (_, prefix, num) => `${prefix}${COLORS.percentUp}+${num}%${COLORS.reset}`
352
+ );
353
+
354
+ // Negative percentages (-5.2%, -10%) - must have space or start before
355
+ result = result.replace(
356
+ /(^|\s)-([\d,]+(?:\.\d{1,4})?)\s*%/gm,
357
+ (_, prefix, num) => `${prefix}${COLORS.percentDown}-${num}%${COLORS.reset}`
358
+ );
359
+
360
+ // Neutral percentages (5.2%, 10%) - must not be part of +/-
361
+ result = result.replace(
362
+ /(^|\s)([\d,]+(?:\.\d{1,4})?)\s*%/gm,
363
+ (_, prefix, num) => `${prefix}${COLORS.orange}${num}%${COLORS.reset}`
364
+ );
365
+
366
+ return result;
367
+ }
368
+
369
+ /**
370
+ * Highlight stock tickers ($AAPL, $TSLA, etc.)
371
+ */
372
+ function highlightTickers(text: string): string {
373
+ // Skip if inside code block or already has ANSI codes
374
+ if (text.includes('```') || text.includes('\x1b[')) return text;
375
+
376
+ // Match $TICKER with word boundaries - must have $ prefix and be 1-5 uppercase letters
377
+ return text.replace(
378
+ /(?<![a-zA-Z0-9])\$([A-Z]{1,5})(?![a-zA-Z0-9])/g,
379
+ (_, ticker) => `${COLORS.ticker}$${ticker}${COLORS.reset}`
380
+ );
381
+ }
382
+
383
+ /**
384
+ * Highlight trading terms (bullish/bearish/neutral)
385
+ */
386
+ function highlightTradingTerms(text: string): string {
387
+ // Skip if inside code block or already has ANSI codes
388
+ if (text.includes('```') || text.includes('\x1b[')) return text;
389
+
390
+ let result = text;
391
+
392
+ // Bullish terms - whole word matches only
393
+ const bullishRegex = new RegExp(
394
+ `\\b(${Array.from(BULLISH_TERMS).join('|')})\\b`,
395
+ 'gi'
396
+ );
397
+ result = result.replace(bullishRegex, (match) => {
398
+ return `${COLORS.bullish}${match}${COLORS.reset}`;
399
+ });
400
+
401
+ // Bearish terms - whole word matches only
402
+ const bearishRegex = new RegExp(
403
+ `\\b(${Array.from(BEARISH_TERMS).join('|')})\\b`,
404
+ 'gi'
405
+ );
406
+ result = result.replace(bearishRegex, (match) => {
407
+ return `${COLORS.bearish}${match}${COLORS.reset}`;
408
+ });
409
+
410
+ return result;
411
+ }
412
+
413
+ /**
414
+ * Highlight price targets and ranges
415
+ */
416
+ function highlightPriceTargets(text: string): string {
417
+ // Skip if inside code block
418
+ if (text.includes('```')) return text;
419
+
420
+ let result = text;
421
+
422
+ // Price targets ($100 target, PT: $150, etc.)
423
+ result = result.replace(
424
+ /\b(?:PT|price target|target|TP|take profit)\s*:?\s*([\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)/gi,
425
+ (_, price) => `${COLORS.priceTarget}PT: ${price}${COLORS.reset}`
426
+ );
427
+
428
+ // Stop loss levels
429
+ result = result.replace(
430
+ /\b(?:SL|stop loss|stop)\s*:?\s*([\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)/gi,
431
+ (_, price) => `${COLORS.red}SL: ${price}${COLORS.reset}`
432
+ );
433
+
434
+ // Entry levels
435
+ result = result.replace(
436
+ /\b(?:entry|enter|buy in)\s*:?\s*([\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)/gi,
437
+ (_, price) => `${COLORS.green}Entry: ${price}${COLORS.reset}`
438
+ );
439
+
440
+ // Price ranges ($100-$150, 50-75)
441
+ result = result.replace(
442
+ /([\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)\s*[-–]\s*([\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)/g,
443
+ (_, low, high) => `${COLORS.money}${low}-${high}${COLORS.reset}`
444
+ );
445
+
446
+ return result;
447
+ }
448
+
449
+ /**
450
+ * Highlight timestamps and dates
451
+ */
452
+ function highlightTimestamps(text: string): string {
453
+ // Skip if inside code block or already has ANSI codes
454
+ if (text.includes('```') || text.includes('\x1b[')) return text;
455
+
456
+ let result = text;
457
+
458
+ // ISO dates (2024-01-15) - specific pattern
459
+ result = result.replace(
460
+ /\b(\d{4}-\d{2}-\d{2})\b/g,
461
+ (_, date) => `${COLORS.timestamp}${date}${COLORS.reset}`
462
+ );
463
+
464
+ // ISO datetime with T (2024-01-15T14:30:00Z)
465
+ result = result.replace(
466
+ /\b(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(Z)?\b/g,
467
+ (_, datetime, z) => `${COLORS.timestamp}${datetime}${z || ''}${COLORS.reset}`
468
+ );
469
+
470
+ // Time only (14:30:00) - must have word boundary
471
+ result = result.replace(
472
+ /\b(\d{2}:\d{2}:\d{2})\b/g,
473
+ (_, time) => `${COLORS.timestamp}${time}${COLORS.reset}`
474
+ );
475
+
476
+ // Relative times (1h ago, 2d ago, etc.)
477
+ result = result.replace(
478
+ /\b(\d+)\s*(second|minute|hour|day|week|month|year)s?\s+ago\b/gi,
479
+ (_, num, unit) => `${COLORS.timestamp}${num} ${unit}s ago${COLORS.reset}`
480
+ );
481
+
482
+ return result;
483
+ }
484
+
485
+ /**
486
+ * Highlight mathematical expressions
487
+ */
488
+ function highlightMath(text: string): string {
489
+ // Skip if inside code block or has ANSI codes
490
+ if (text.includes('```') || text.includes('\x1b[')) return text;
491
+
492
+ let result = text;
493
+
494
+ // Mathematical constants - be specific with word boundaries
495
+ result = result.replace(
496
+ /\b(pi|π|euler|infinity|inf)\b/gi,
497
+ (_, constant) => `${COLORS.orange}${constant}${COLORS.reset}`
498
+ );
499
+
500
+ // Exponents (x^2, 10^6) - only in math contexts
501
+ result = result.replace(
502
+ /(\d)\^(\d+)/g,
503
+ (_, base, exp) => `${COLORS.math}${base}^${exp}${COLORS.reset}`
504
+ );
505
+
506
+ // Scientific notation (1.5e10, 2E-5)
507
+ result = result.replace(
508
+ /(\d+\.\d+)[eE]([+-]?\d+)/g,
509
+ (_, mantissa, exp) => `${COLORS.orange}${mantissa}e${exp}${COLORS.reset}`
510
+ );
511
+
512
+ return result;
513
+ }
514
+
515
+ /**
516
+ * Highlight P/L (profit/loss) statements
517
+ */
518
+ function highlightPL(text: string): string {
519
+ // Skip if inside code block
520
+ if (text.includes('```')) return text;
521
+
522
+ let result = text;
523
+
524
+ // P/L with amounts
525
+ result = result.replace(
526
+ /\b(?:P\/L|PnL|profit\/loss|PL)\s*:?\s*([+\-]?[\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)/gi,
527
+ (_, amount) => {
528
+ const isPositive = !amount.startsWith('-');
529
+ const color = isPositive ? COLORS.moneyGain : COLORS.moneyLoss;
530
+ return `${color}P/L: ${amount}${COLORS.reset}`;
531
+ }
532
+ );
533
+
534
+ // ROI percentages
535
+ result = result.replace(
536
+ /\b(?:ROI|return)\s*:?\s*([+\-]?[\d,]+(?:\.\d{1,2})?\s*%)/gi,
537
+ (_, percent) => {
538
+ const isPositive = !percent.startsWith('-');
539
+ const color = isPositive ? COLORS.percentUp : COLORS.percentDown;
540
+ return `${color}ROI: ${percent}${COLORS.reset}`;
541
+ }
542
+ );
543
+
544
+ // Unrealized/Realized P/L
545
+ result = result.replace(
546
+ /\b(unrealized|realized)\s+(p\/l|pnl|profit|loss)\s*:?\s*([+\-]?[\$€£¥]?\s*[\d,]+(?:\.\d{1,4})?)/gi,
547
+ (_, status, _type, amount) => {
548
+ const isPositive = !amount.startsWith('-');
549
+ const color = isPositive ? COLORS.moneyGain : COLORS.moneyLoss;
550
+ return `${color}${status} P/L: ${amount}${COLORS.reset}`;
551
+ }
552
+ );
553
+
554
+ return result;
555
+ }
556
+
557
+ /**
558
+ * Highlight OHLC (Open, High, Low, Close) values
559
+ */
560
+ function highlightOHLC(text: string): string {
561
+ // Skip if inside code block
562
+ if (text.includes('```')) return text;
563
+
564
+ let result = text;
565
+
566
+ // OHLC pattern
567
+ result = result.replace(
568
+ /\b(?:O|Open)\s*:?\s*([\d,]+(?:\.\d{1,8})?)/gi,
569
+ (_, val) => `${COLORS.neutral}O: ${val}${COLORS.reset}`
570
+ );
571
+ result = result.replace(
572
+ /\b(?:H|High)\s*:?\s*([\d,]+(?:\.\d{1,8})?)/gi,
573
+ (_, val) => `${COLORS.moneyGain}H: ${val}${COLORS.reset}`
574
+ );
575
+ result = result.replace(
576
+ /\b(?:L|Low)\s*:?\s*([\d,]+(?:\.\d{1,8})?)/gi,
577
+ (_, val) => `${COLORS.moneyLoss}L: ${val}${COLORS.reset}`
578
+ );
579
+ result = result.replace(
580
+ /\b(?:C|Close)\s*:?\s*([\d,]+(?:\.\d{1,8})?)/gi,
581
+ (_, val) => `${COLORS.neutral}C: ${val}${COLORS.reset}`
582
+ );
583
+
584
+ // Volume
585
+ result = result.replace(
586
+ /\b(?:V|Vol|Volume)\s*:?\s*([\d,]+(?:\.\d{1,2})?[KkMmBb]?)/gi,
587
+ (_, val) => `${COLORS.cyan}Vol: ${val}${COLORS.reset}`
588
+ );
589
+
590
+ // Market Cap
591
+ result = result.replace(
592
+ /\b(?:MCap|Market Cap|Market Capitalization)\s*:?\s*([\$€£¥]?\s*[\d,]+(?:\.\d{1,2})?[KkMmBbTt]?)/gi,
593
+ (_, val) => `${COLORS.money}MCap: ${val}${COLORS.reset}`
594
+ );
595
+
596
+ return result;
597
+ }
598
+
599
+ /**
600
+ * Apply all financial highlighting - runs FIRST before any other highlighting
601
+ */
602
+ function highlightFinancial(text: string): string {
603
+ // Skip if already has ANSI codes or inside code block
604
+ if (text.includes('\x1b[') || text.includes('```')) return text;
605
+
606
+ let result = text;
607
+
608
+ // Order matters - most specific patterns first
609
+ // 1. Crypto (specific symbols)
610
+ result = highlightCrypto(result);
611
+ // 2. Currency (specific symbols)
612
+ result = highlightCurrency(result);
613
+ // 3. Percentages (with +/-)
614
+ result = highlightPercentages(result);
615
+ // 4. P/L statements
616
+ result = highlightPL(result);
617
+ // 5. Price targets (PT:, SL:)
618
+ result = highlightPriceTargets(result);
619
+ // 6. OHLC values
620
+ result = highlightOHLC(result);
621
+ // 7. Stock tickers ($AAPL)
622
+ result = highlightTickers(result);
623
+ // 8. Trading terms (bullish/bearish)
624
+ result = highlightTradingTerms(result);
625
+ // 9. Timestamps
626
+ result = highlightTimestamps(result);
627
+ // 10. Math expressions
628
+ result = highlightMath(result);
629
+
630
+ return result;
631
+ }
632
+
633
+ /**
634
+ * Highlight markdown formatting in plain text
635
+ */
636
+ function highlightMarkdown(text: string): string {
637
+ let result = text;
638
+
639
+ // Skip processing if we're inside a code block (basic check)
640
+ if (text.includes('```')) {
641
+ return result;
642
+ }
643
+
644
+ // Horizontal rules (---, ***, ___) - must be on own line
645
+ result = result.replace(/^( {0,3})([-*_])( {0,2}\2){2,}$/gm,
646
+ `${COLORS.hr}$1$2$2$2${COLORS.reset}`);
647
+
648
+ // Headers (# ## ### etc) - with setext style support
649
+ result = result.replace(/^(#{1,6})\s+(.+)$/gm, (_, hashes, content) => {
650
+ // Color the header level differently based on depth
651
+ const level = hashes.length;
652
+ if (level === 1) {
653
+ return `${COLORS.bold}${COLORS.blue}${hashes} ${content}${COLORS.reset}`;
654
+ } else if (level === 2) {
655
+ return `${COLORS.bold}${COLORS.cyan}${hashes} ${content}${COLORS.reset}`;
656
+ }
657
+ return `${COLORS.header}${hashes} ${content}${COLORS.reset}`;
658
+ });
659
+
660
+ // Setext-style headers (underlined with === or ---)
661
+ result = result.replace(/^(.+)\n([=]+)$/gm, (_, content, line) => {
662
+ return `${COLORS.bold}${COLORS.blue}${content}${COLORS.reset}\n${COLORS.hr}${line}${COLORS.reset}`;
663
+ });
664
+ result = result.replace(/^(.+)\n([-]+)$/gm, (_, content, line) => {
665
+ return `${COLORS.bold}${COLORS.cyan}${content}${COLORS.reset}\n${COLORS.hr}${line}${COLORS.reset}`;
666
+ });
667
+
668
+ // Blockquotes (> at start of line)
669
+ result = result.replace(/^(>{1,})\s?(.*)$/gm, (_, markers, content) => {
670
+ const depth = markers.length;
671
+ // Different color for nested quotes
672
+ const color = depth > 1 ? COLORS.dim : COLORS.blockquote;
673
+ return `${COLORS.green}${markers}${COLORS.reset} ${color}${content}${COLORS.reset}`;
674
+ });
675
+
676
+ // Tables - simple detection and formatting
677
+ result = result.replace(/^\|(.+)\|\s*$/gm, (match, content) => {
678
+ // Table row
679
+ const cells = content.split('|');
680
+ const formattedCells = cells.map((cell: string) => {
681
+ const trimmed = cell.trim();
682
+ if (trimmed) {
683
+ return ` ${COLORS.gray}${trimmed}${COLORS.reset} `;
684
+ }
685
+ return cell;
686
+ }).join(`${COLORS.tableBorder}|${COLORS.reset}`);
687
+ return `${COLORS.tableBorder}|${COLORS.reset}${formattedCells}${COLORS.tableBorder}|${COLORS.reset}`;
688
+ });
689
+
690
+ // Table separator line (|---|---|)
691
+ result = result.replace(/^\|([-:\s|]+)\|\s*$/gm, (_, content) => {
692
+ return `${COLORS.tableBorder}|${content}|${COLORS.reset}`;
693
+ });
694
+
695
+ // Bold (**text** or __text__)
696
+ result = result.replace(/\*\*([^*]+)\*\*/g, `${COLORS.bold}$1${COLORS.reset}`);
697
+ result = result.replace(/__([^_]+)__/g, `${COLORS.bold}$1${COLORS.reset}`);
698
+
699
+ // Strikethrough (~~text~~)
700
+ result = result.replace(/~~([^~]+)~~/g, `${COLORS.strikethrough}${COLORS.dim}$1${COLORS.reset}`);
701
+
702
+ // Italic (*text* or _text_) - be careful not to match inside words
703
+ result = result.replace(/(?<![a-zA-Z\*])\*([^*]+)\*(?![a-zA-Z\*])/g, `${COLORS.italic}$1${COLORS.reset}`);
704
+ result = result.replace(/(?<![a-zA-Z_])_([^_]+)_(?![a-zA-Z_])/g, `${COLORS.italic}$1${COLORS.reset}`);
705
+
706
+ // Bold+Italic (***text*** or ___text___)
707
+ result = result.replace(/\*\*\*([^*]+)\*\*\*/g, `${COLORS.bold}${COLORS.italic}$1${COLORS.reset}`);
708
+ result = result.replace(/___([^_]+)___/g, `${COLORS.bold}${COLORS.italic}$1${COLORS.reset}`);
709
+
710
+ // Task lists (- [ ] or - [x])
711
+ result = result.replace(/^(\s*)[-*]\s\[([ xX])\]/gm, (_, indent, checked) => {
712
+ const mark = checked.toLowerCase() === 'x' ? '✓' : ' ';
713
+ const color = checked.toLowerCase() === 'x' ? COLORS.green : COLORS.dim;
714
+ return `${indent}${COLORS.list}-${COLORS.reset} ${color}[${mark}]${COLORS.reset}`;
715
+ });
716
+
717
+ // Regular list items (- or * at start of line)
718
+ result = result.replace(/^(\s*)([-*])\s(?!\[)/gm, `$1${COLORS.list}$2${COLORS.reset} `);
719
+
720
+ // Numbered lists
721
+ result = result.replace(/^(\s*)(\d+\.)(?=\s)/gm, `$1${COLORS.list}$2${COLORS.reset}`);
722
+
723
+ // Definition lists (term : definition)
724
+ result = result.replace(/^([^:\n]+):\s{2,}(.+)$/gm,
725
+ `${COLORS.bold}$1${COLORS.reset}:${COLORS.dim} $2${COLORS.reset}`);
726
+
727
+ // Links [text](url)
728
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
729
+ `${COLORS.link}[$1]${COLORS.reset}${COLORS.dim}($2)${COLORS.reset}`);
730
+
731
+ // Reference-style links [text][ref]
732
+ result = result.replace(/\[([^\]]+)\]\[([^\]]*)\]/g,
733
+ `${COLORS.link}[$1]${COLORS.reset}${COLORS.dim}[$2]${COLORS.reset}`);
734
+
735
+ // Images ![alt](url)
736
+ result = result.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,
737
+ `${COLORS.yellow}![${COLORS.reset}${COLORS.dim}$1${COLORS.reset}${COLORS.yellow}]${COLORS.reset}${COLORS.dim}($2)${COLORS.reset}`);
738
+
739
+ // Footnotes [^1] and [^1]: definition
740
+ result = result.replace(/\[\^([^\]]+)\]/g, `${COLORS.yellow}[^$1]${COLORS.reset}`);
741
+ result = result.replace(/^\[\^([^\]]+)\]:\s*(.+)$/gm,
742
+ `${COLORS.yellow}[^$1]${COLORS.reset}:${COLORS.dim} $2${COLORS.reset}`);
743
+
744
+ // HTML tags (basic detection)
745
+ result = result.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)(\s[^>]*)?>/g,
746
+ `${COLORS.dim}<${COLORS.purple}$1${COLORS.reset}$2${COLORS.dim}>${COLORS.reset}`);
747
+
748
+ // Highlight/mark (==text==)
749
+ result = result.replace(/==([^=]+)==/g, `${COLORS.yellow}$1${COLORS.reset}`);
750
+
751
+ // Keyboard shortcuts (<kbd>text</kbd> - if not already handled by HTML)
752
+ result = result.replace(/<kbd>([^<]+)<\/kbd>/gi,
753
+ `${COLORS.dim}[${COLORS.orange}$1${COLORS.dim}]${COLORS.reset}`);
754
+
755
+ return result;
756
+ }
757
+
758
+ type BlockState = 'text' | 'code' | 'diff';
759
+
760
+ /**
761
+ * Extract language and optional file path from fence info
762
+ * Examples: "typescript", "typescript path/to/file.ts", "ts {linenos=true}"
763
+ */
764
+ function parseFenceInfo(fenceInfo: string): { language: string; filePath?: string; attrs: string } {
765
+ const parts = fenceInfo.split(/\s+/);
766
+ const language = parts[0] || "";
767
+ let filePath: string | undefined;
768
+ let attrs = "";
769
+
770
+ for (let i = 1; i < parts.length; i++) {
771
+ const part = parts[i];
772
+ if (!part) continue;
773
+ if (part.includes('=')) {
774
+ attrs += (attrs ? ' ' : '') + part;
775
+ } else if (part.includes('.') || part.includes('/')) {
776
+ // Looks like a file path
777
+ filePath = part;
778
+ }
779
+ }
780
+
781
+ return { language, filePath, attrs };
782
+ }
783
+
784
+ /**
785
+ * Map file extensions to languages
786
+ */
787
+ const EXT_TO_LANG: Record<string, string> = {
788
+ '.ts': 'typescript',
789
+ '.tsx': 'typescript',
790
+ '.js': 'javascript',
791
+ '.jsx': 'javascript',
792
+ '.mjs': 'javascript',
793
+ '.cjs': 'javascript',
794
+ '.py': 'python',
795
+ '.rb': 'ruby',
796
+ '.rs': 'rust',
797
+ '.go': 'go',
798
+ '.java': 'java',
799
+ '.kt': 'kotlin',
800
+ '.swift': 'swift',
801
+ '.c': 'c',
802
+ '.cpp': 'cpp',
803
+ '.cc': 'cpp',
804
+ '.cxx': 'cpp',
805
+ '.h': 'c',
806
+ '.hpp': 'cpp',
807
+ '.cs': 'csharp',
808
+ '.php': 'php',
809
+ '.sh': 'bash',
810
+ '.bash': 'bash',
811
+ '.zsh': 'bash',
812
+ '.ps1': 'powershell',
813
+ '.json': 'json',
814
+ '.yaml': 'yaml',
815
+ '.yml': 'yaml',
816
+ '.toml': 'toml',
817
+ '.md': 'markdown',
818
+ '.html': 'html',
819
+ '.htm': 'html',
820
+ '.css': 'css',
821
+ '.scss': 'css',
822
+ '.less': 'css',
823
+ '.sql': 'sql',
824
+ '.xml': 'xml',
825
+ '.svg': 'xml',
826
+ '.vue': 'vue',
827
+ '.svelte': 'svelte',
828
+ '.dockerfile': 'dockerfile',
829
+ '.makefile': 'makefile',
830
+ '.cmake': 'cmake',
831
+ '.lua': 'lua',
832
+ '.r': 'r',
833
+ '.ex': 'elixir',
834
+ '.exs': 'elixir',
835
+ '.erl': 'erlang',
836
+ '.hs': 'haskell',
837
+ '.ml': 'ocaml',
838
+ '.clj': 'clojure',
839
+ '.scala': 'scala',
840
+ '.groovy': 'groovy',
841
+ '.pl': 'perl',
842
+ '.pm': 'perl',
843
+ };
844
+
845
+ /**
846
+ * Detect language from file path
847
+ */
848
+ function detectLanguageFromPath(filePath: string): string | undefined {
849
+ const ext = filePath.toLowerCase().slice(filePath.lastIndexOf('.'));
850
+ return EXT_TO_LANG[ext];
851
+ }
852
+
853
+ /**
854
+ * Apply all text highlighting (markdown, inline code, urls, emoji)
855
+ */
856
+ function highlightAllText(text: string): string {
857
+ // Skip if text already has ANSI codes (already processed)
858
+ if (text.includes('\x1b[')) {
859
+ return text;
860
+ }
861
+
862
+ let result = text;
863
+
864
+ // Apply in order - more specific patterns should come first
865
+ // Financial highlighting first since it has specific patterns
866
+ result = highlightFinancial(result);
867
+ // Then inline code (but skip if in code blocks)
868
+ result = highlightInlineCode(result);
869
+ // Markdown formatting
870
+ result = highlightMarkdown(result);
871
+ // URLs and emoji last
872
+ result = highlightUrls(result);
873
+ result = highlightEmoji(result);
874
+
875
+ return result;
876
+ }
877
+
878
+ /**
879
+ * Creates a streaming highlighter that detects and highlights code blocks
880
+ * and diffs as they complete during streaming output.
881
+ */
882
+ export function createStreamHighlighter(): {
883
+ process: (text: string) => string;
884
+ flush: () => string;
885
+ } {
886
+ let buffer = "";
887
+ let state: BlockState = 'text';
888
+ let codeBlockLang = "";
889
+ let blockContent = "";
890
+ let blockFilePath = "";
891
+
892
+ function process(text: string): string {
893
+ buffer += text;
894
+ let output = "";
895
+
896
+ while (true) {
897
+ if (state === 'code' || state === 'diff') {
898
+ // Look for closing fence (``` at start of line)
899
+ const closeMatch = buffer.match(/\n```/);
900
+ if (!closeMatch) {
901
+ // No closing fence yet, keep buffering
902
+ // But output most of the buffer to avoid memory issues
903
+ if (buffer.length > 2000) {
904
+ const safeChunk = buffer.slice(0, -200); // Keep last 200 chars in case fence is partial
905
+ blockContent += safeChunk;
906
+ // For diffs, we can output as we go with line-by-line highlighting
907
+ if (state === 'diff') {
908
+ output += highlightDiffContent(safeChunk);
909
+ } else {
910
+ output += safeChunk; // Code gets highlighted at end for proper syntax
911
+ }
912
+ buffer = buffer.slice(-200);
913
+ }
914
+ break;
915
+ }
916
+
917
+ const closeIndex = closeMatch.index!;
918
+
919
+ // Found closing fence - extract remaining content
920
+ blockContent += buffer.slice(0, closeIndex);
921
+
922
+ // Highlight based on block type
923
+ let highlighted: string;
924
+ const effectiveLang = codeBlockLang || (blockFilePath ? detectLanguageFromPath(blockFilePath) : '');
925
+
926
+ if (state === 'diff' || (codeBlockLang === 'diff' || isDiffContent(blockContent))) {
927
+ highlighted = highlightDiffContent(blockContent);
928
+ } else if (effectiveLang) {
929
+ try {
930
+ highlighted = highlight_code(blockContent, effectiveLang).html;
931
+ } catch {
932
+ highlighted = blockContent;
933
+ }
934
+ } else {
935
+ highlighted = blockContent;
936
+ }
937
+
938
+ // Remove trailing reset and whitespace from highlighted code
939
+ const trimmed = highlighted
940
+ .replace(/\x1b\[0m$/, '')
941
+ .trimEnd();
942
+
943
+ output += trimmed;
944
+ output += `\n${COLORS.reset}\`\`\``; // Newline, reset, and closing fence
945
+
946
+ // Skip past the closing fence in buffer
947
+ buffer = buffer.slice(closeIndex + 4); // +4 for "\n```"
948
+
949
+ state = 'text';
950
+ codeBlockLang = "";
951
+ blockContent = "";
952
+ blockFilePath = "";
953
+ } else {
954
+ // Look for opening fence: ``` followed by optional lang and newline
955
+ // Support: ```typescript, ```typescript file.ts, ```{ .typescript }
956
+ const openMatch = buffer.match(/```([^\n]*)\n/);
957
+ if (!openMatch) {
958
+ // No complete opening fence yet
959
+ // Check if buffer might contain partial fence at end
960
+ const partialFence = buffer.match(/```([^\n]*)$/);
961
+ if (partialFence) {
962
+ // Partial fence - output everything before it and keep fence in buffer
963
+ const beforeFence = buffer.slice(0, buffer.length - partialFence[0].length);
964
+ // Apply all text highlighting
965
+ output += highlightAllText(beforeFence);
966
+ buffer = partialFence[0];
967
+ break;
968
+ }
969
+
970
+ // No fence at all - output everything with markdown highlighting
971
+ // Keep last 4 chars in case of partial ``` (but try to keep complete lines)
972
+ if (buffer.length > 4) {
973
+ // Find the last newline to avoid splitting mid-line
974
+ const lastNewline = buffer.lastIndexOf('\n', buffer.length - 4);
975
+ let toOutput: string;
976
+ if (lastNewline > 0 && buffer.length - lastNewline <= 10) {
977
+ // If there's a recent newline, output up to it
978
+ toOutput = buffer.slice(0, lastNewline + 1);
979
+ buffer = buffer.slice(lastNewline + 1);
980
+ } else if (buffer.length > 100) {
981
+ // Long buffer with no newline - output all but last 4 chars
982
+ toOutput = buffer.slice(0, -4);
983
+ buffer = buffer.slice(-4);
984
+ } else {
985
+ // Short buffer - keep it all for now
986
+ break;
987
+ }
988
+ output += highlightAllText(toOutput);
989
+ }
990
+ break;
991
+ }
992
+
993
+ // Found complete opening fence
994
+ const fenceStart = openMatch.index!;
995
+ const fenceInfo = openMatch[1] || "";
996
+ const { language, filePath } = parseFenceInfo(fenceInfo);
997
+
998
+ // Output text before the fence with markdown highlighting
999
+ const beforeFence = buffer.slice(0, fenceStart);
1000
+ output += highlightAllText(beforeFence);
1001
+
1002
+ // Output the opening fence line
1003
+ // Show with file path if available
1004
+ const detectedLang = language || (filePath ? detectLanguageFromPath(filePath) : '');
1005
+ if (filePath) {
1006
+ const langDisplay = language || detectedLang || '';
1007
+ output += `${COLORS.dim}\`\`\`${COLORS.cyan}${langDisplay}${COLORS.reset} ${COLORS.comment}${filePath}${COLORS.reset}\n`;
1008
+ } else if (language) {
1009
+ output += `${COLORS.dim}\`\`\`${COLORS.cyan}${language}${COLORS.reset}\n`;
1010
+ } else {
1011
+ output += `${COLORS.dim}\`\`\`${COLORS.reset}\n`;
1012
+ }
1013
+
1014
+ // Skip past the opening fence in buffer
1015
+ buffer = buffer.slice(fenceStart + openMatch[0].length);
1016
+
1017
+ // Determine block type
1018
+ if (language === 'diff' || language === 'patch') {
1019
+ state = 'diff';
1020
+ } else {
1021
+ state = 'code';
1022
+ }
1023
+ codeBlockLang = language;
1024
+ blockContent = "";
1025
+ blockFilePath = filePath || "";
1026
+ }
1027
+ }
1028
+
1029
+ return output;
1030
+ }
1031
+
1032
+ function flush(): string {
1033
+ let output = "";
1034
+
1035
+ if (state === 'code' || state === 'diff') {
1036
+ // Unclosed code block - highlight what we have
1037
+ const effectiveLang = codeBlockLang || (blockFilePath ? detectLanguageFromPath(blockFilePath) : '');
1038
+
1039
+ if (state === 'diff' || isDiffContent(blockContent)) {
1040
+ output += highlightDiffContent(blockContent);
1041
+ } else if (effectiveLang) {
1042
+ try {
1043
+ output += highlight_code(blockContent, effectiveLang).html;
1044
+ } catch {
1045
+ output += blockContent;
1046
+ }
1047
+ } else {
1048
+ output += blockContent;
1049
+ }
1050
+ output += COLORS.reset;
1051
+ }
1052
+
1053
+ if (buffer) {
1054
+ output += highlightAllText(buffer);
1055
+ buffer = "";
1056
+ }
1057
+
1058
+ return output;
1059
+ }
1060
+
1061
+ return { process, flush };
1062
+ }
1063
+
1064
+ /**
1065
+ * Highlight complete text with code blocks
1066
+ * Used for non-streaming contexts or final output
1067
+ */
1068
+ export function highlightTextWithCodeBlocks(text: string): string {
1069
+ // Split by code blocks and highlight each
1070
+ // Support language + optional file path: ```typescript path/to/file.ts
1071
+ const codeBlockRegex = /```([^\n]*)\n([\s\S]*?)```/g;
1072
+ let result = "";
1073
+ let lastIndex = 0;
1074
+
1075
+ let match;
1076
+ while ((match = codeBlockRegex.exec(text)) !== null) {
1077
+ // Add text before code block with all highlighting
1078
+ const beforeText = text.slice(lastIndex, match.index);
1079
+ result += highlightAllText(beforeText);
1080
+
1081
+ const fenceInfo = match[1] || "";
1082
+ const { language, filePath } = parseFenceInfo(fenceInfo);
1083
+ const code = match[2] || "";
1084
+
1085
+ // Determine effective language
1086
+ const effectiveLang = language || (filePath ? detectLanguageFromPath(filePath) : '');
1087
+
1088
+ // Show fence with file path if available
1089
+ if (filePath) {
1090
+ const langDisplay = language || effectiveLang || '';
1091
+ result += `${COLORS.dim}\`\`\`${COLORS.cyan}${langDisplay}${COLORS.reset} ${COLORS.comment}${filePath}${COLORS.reset}\n`;
1092
+ } else if (language) {
1093
+ result += `${COLORS.dim}\`\`\`${COLORS.cyan}${language}${COLORS.reset}\n`;
1094
+ } else {
1095
+ result += `${COLORS.dim}\`\`\`${COLORS.reset}\n`;
1096
+ }
1097
+
1098
+ if (language === 'diff' || language === 'patch' || isDiffContent(code)) {
1099
+ // Highlight as diff
1100
+ result += highlightDiffContent(code);
1101
+ result += `\n${COLORS.reset}\`\`\``;
1102
+ } else if (effectiveLang) {
1103
+ // Highlight with language
1104
+ try {
1105
+ result += highlight_code(code, effectiveLang).html;
1106
+ } catch {
1107
+ result += code;
1108
+ }
1109
+ result += `\n${COLORS.reset}\`\`\``;
1110
+ } else {
1111
+ // No language, output as-is
1112
+ result += code;
1113
+ result += `\n${COLORS.reset}\`\`\``;
1114
+ }
1115
+
1116
+ lastIndex = match.index + match[0].length;
1117
+ }
1118
+
1119
+ // Add remaining text with all highlighting
1120
+ result += highlightAllText(text.slice(lastIndex));
1121
+
1122
+ return result;
1123
+ }