@travisennis/acai 0.0.8 → 0.0.10

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 (386) hide show
  1. package/README.md +48 -729
  2. package/bin/acai +52 -0
  3. package/dist/agent/index.d.ts +12 -2
  4. package/dist/agent/index.d.ts.map +1 -1
  5. package/dist/agent/index.js +378 -168
  6. package/dist/agent/sub-agent.d.ts +23 -0
  7. package/dist/agent/sub-agent.d.ts.map +1 -0
  8. package/dist/agent/sub-agent.js +109 -0
  9. package/dist/cli/index.d.ts +26 -0
  10. package/dist/cli/index.d.ts.map +1 -0
  11. package/dist/{cli.js → cli/index.js} +84 -76
  12. package/dist/cli/stdin.d.ts +9 -0
  13. package/dist/cli/stdin.d.ts.map +1 -0
  14. package/dist/cli/stdin.js +37 -0
  15. package/dist/commands/copy/index.js +2 -2
  16. package/dist/commands/copy/utils.d.ts.map +1 -1
  17. package/dist/commands/copy/utils.js +15 -13
  18. package/dist/commands/generate-rules/index.d.ts +1 -1
  19. package/dist/commands/generate-rules/index.d.ts.map +1 -1
  20. package/dist/commands/generate-rules/index.js +16 -100
  21. package/dist/commands/generate-rules/service.d.ts +21 -0
  22. package/dist/commands/generate-rules/service.d.ts.map +1 -0
  23. package/dist/commands/generate-rules/service.js +103 -0
  24. package/dist/commands/handoff/index.js +2 -2
  25. package/dist/commands/health/index.js +1 -1
  26. package/dist/commands/health/utils.d.ts.map +1 -1
  27. package/dist/commands/health/utils.js +6 -0
  28. package/dist/commands/history/index.d.ts +1 -1
  29. package/dist/commands/history/index.d.ts.map +1 -1
  30. package/dist/commands/history/index.js +17 -18
  31. package/dist/commands/history/types.d.ts +38 -0
  32. package/dist/commands/history/types.d.ts.map +1 -1
  33. package/dist/commands/history/utils.d.ts.map +1 -1
  34. package/dist/commands/history/utils.js +63 -58
  35. package/dist/commands/init/index.d.ts.map +1 -1
  36. package/dist/commands/init/index.js +3 -8
  37. package/dist/commands/init-project/index.d.ts.map +1 -1
  38. package/dist/commands/init-project/index.js +3 -3
  39. package/dist/commands/init-project/utils.d.ts.map +1 -1
  40. package/dist/commands/init-project/utils.js +10 -2
  41. package/dist/commands/list-tools/index.d.ts.map +1 -1
  42. package/dist/commands/list-tools/index.js +7 -31
  43. package/dist/commands/manager.d.ts +2 -2
  44. package/dist/commands/manager.d.ts.map +1 -1
  45. package/dist/commands/manager.js +57 -33
  46. package/dist/commands/model/index.d.ts.map +1 -1
  47. package/dist/commands/model/index.js +20 -151
  48. package/dist/commands/model/model-panel.d.ts +4 -0
  49. package/dist/commands/model/model-panel.d.ts.map +1 -0
  50. package/dist/commands/model/model-panel.js +144 -0
  51. package/dist/commands/paste/index.d.ts.map +1 -1
  52. package/dist/commands/paste/index.js +59 -62
  53. package/dist/commands/paste/utils.d.ts.map +1 -1
  54. package/dist/commands/paste/utils.js +88 -58
  55. package/dist/commands/pickup/index.d.ts.map +1 -1
  56. package/dist/commands/pickup/index.js +6 -3
  57. package/dist/commands/pickup/utils.js +3 -3
  58. package/dist/commands/resources/index.d.ts.map +1 -1
  59. package/dist/commands/resources/index.js +33 -50
  60. package/dist/commands/review/index.d.ts.map +1 -1
  61. package/dist/commands/review/index.js +3 -117
  62. package/dist/commands/review/review-panel.d.ts +3 -0
  63. package/dist/commands/review/review-panel.d.ts.map +1 -0
  64. package/dist/commands/review/review-panel.js +186 -0
  65. package/dist/commands/review/utils.d.ts +9 -0
  66. package/dist/commands/review/utils.d.ts.map +1 -1
  67. package/dist/commands/review/utils.js +127 -68
  68. package/dist/commands/session/index.d.ts +1 -1
  69. package/dist/commands/session/index.d.ts.map +1 -1
  70. package/dist/commands/session/index.js +134 -112
  71. package/dist/commands/session/types.d.ts +7 -0
  72. package/dist/commands/session/types.d.ts.map +1 -1
  73. package/dist/commands/share/html-renderer.d.ts +25 -0
  74. package/dist/commands/share/html-renderer.d.ts.map +1 -0
  75. package/dist/commands/share/html-renderer.js +384 -0
  76. package/dist/commands/share/index.d.ts +3 -0
  77. package/dist/commands/share/index.d.ts.map +1 -0
  78. package/dist/commands/share/index.js +122 -0
  79. package/dist/commands/shell/index.d.ts.map +1 -1
  80. package/dist/commands/shell/index.js +16 -1
  81. package/dist/commands/types.d.ts +2 -2
  82. package/dist/commands/types.d.ts.map +1 -1
  83. package/dist/{config.d.ts → config/index.d.ts} +20 -9
  84. package/dist/config/index.d.ts.map +1 -0
  85. package/dist/{config.js → config/index.js} +43 -42
  86. package/dist/execution/index.d.ts.map +1 -1
  87. package/dist/execution/index.js +75 -55
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +170 -127
  91. package/dist/middleware/cache.d.ts.map +1 -1
  92. package/dist/middleware/cache.js +18 -36
  93. package/dist/models/ai-config.d.ts +1 -0
  94. package/dist/models/ai-config.d.ts.map +1 -1
  95. package/dist/models/ai-config.js +4 -3
  96. package/dist/models/anthropic-provider.d.ts +2 -5
  97. package/dist/models/anthropic-provider.d.ts.map +1 -1
  98. package/dist/models/anthropic-provider.js +3 -70
  99. package/dist/models/deepseek-provider.d.ts +1 -0
  100. package/dist/models/deepseek-provider.d.ts.map +1 -1
  101. package/dist/models/google-provider.d.ts +2 -3
  102. package/dist/models/google-provider.d.ts.map +1 -1
  103. package/dist/models/google-provider.js +0 -26
  104. package/dist/models/groq-provider.d.ts +1 -0
  105. package/dist/models/groq-provider.d.ts.map +1 -1
  106. package/dist/models/manager.d.ts +13 -2
  107. package/dist/models/manager.d.ts.map +1 -1
  108. package/dist/models/manager.js +20 -8
  109. package/dist/models/openai-provider.d.ts +5 -5
  110. package/dist/models/openai-provider.d.ts.map +1 -1
  111. package/dist/models/openai-provider.js +27 -40
  112. package/dist/models/opencode-zen-provider.d.ts +8 -3
  113. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  114. package/dist/models/opencode-zen-provider.js +68 -11
  115. package/dist/models/openrouter-provider.d.ts +24 -30
  116. package/dist/models/openrouter-provider.d.ts.map +1 -1
  117. package/dist/models/openrouter-provider.js +92 -177
  118. package/dist/models/providers.d.ts +1 -1
  119. package/dist/models/providers.d.ts.map +1 -1
  120. package/dist/models/xai-provider.d.ts +4 -3
  121. package/dist/models/xai-provider.d.ts.map +1 -1
  122. package/dist/models/xai-provider.js +18 -18
  123. package/dist/modes/manager.d.ts +23 -0
  124. package/dist/modes/manager.d.ts.map +1 -0
  125. package/dist/modes/manager.js +77 -0
  126. package/dist/modes/prompts.d.ts +2 -0
  127. package/dist/modes/prompts.d.ts.map +1 -0
  128. package/dist/modes/prompts.js +143 -0
  129. package/dist/prompts/mentions.d.ts +11 -0
  130. package/dist/prompts/mentions.d.ts.map +1 -0
  131. package/dist/{mentions.js → prompts/mentions.js} +21 -80
  132. package/dist/prompts/system-prompt.d.ts +26 -0
  133. package/dist/prompts/system-prompt.d.ts.map +1 -0
  134. package/dist/{prompts.js → prompts/system-prompt.js} +50 -22
  135. package/dist/repl/index.d.ts +174 -0
  136. package/dist/repl/index.d.ts.map +1 -0
  137. package/dist/{repl-new.js → repl/index.js} +399 -76
  138. package/dist/repl/project-status.d.ts +1 -0
  139. package/dist/repl/project-status.d.ts.map +1 -1
  140. package/dist/repl/project-status.js +4 -1
  141. package/dist/sessions/manager.d.ts +93 -1
  142. package/dist/sessions/manager.d.ts.map +1 -1
  143. package/dist/sessions/manager.js +264 -9
  144. package/dist/sessions/summary.d.ts +4 -0
  145. package/dist/sessions/summary.d.ts.map +1 -0
  146. package/dist/sessions/summary.js +30 -0
  147. package/dist/{skills.d.ts → skills/index.d.ts} +14 -2
  148. package/dist/skills/index.d.ts.map +1 -0
  149. package/dist/skills/index.js +294 -0
  150. package/dist/subagents/index.d.ts +15 -0
  151. package/dist/subagents/index.d.ts.map +1 -0
  152. package/dist/subagents/index.js +231 -0
  153. package/dist/terminal/control.d.ts +1 -1
  154. package/dist/terminal/control.d.ts.map +1 -1
  155. package/dist/terminal/control.js +30 -9
  156. package/dist/terminal/east-asian-width.d.ts.map +1 -1
  157. package/dist/terminal/east-asian-width.js +404 -351
  158. package/dist/terminal/keys.d.ts +17 -0
  159. package/dist/terminal/keys.d.ts.map +1 -1
  160. package/dist/terminal/keys.js +37 -0
  161. package/dist/terminal/select-prompt.d.ts.map +1 -1
  162. package/dist/terminal/select-prompt.js +24 -12
  163. package/dist/terminal/string-width.d.ts.map +1 -1
  164. package/dist/terminal/string-width.js +25 -27
  165. package/dist/terminal/style.d.ts.map +1 -1
  166. package/dist/terminal/style.js +4 -7
  167. package/dist/terminal/supports-color.d.ts.map +1 -1
  168. package/dist/terminal/supports-color.js +41 -27
  169. package/dist/terminal/table/cell.d.ts +12 -0
  170. package/dist/terminal/table/cell.d.ts.map +1 -1
  171. package/dist/terminal/table/cell.js +40 -25
  172. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  173. package/dist/terminal/table/layout-manager.js +100 -68
  174. package/dist/terminal/table/utils.d.ts.map +1 -1
  175. package/dist/terminal/table/utils.js +17 -10
  176. package/dist/terminal/wrap-ansi.d.ts.map +1 -1
  177. package/dist/terminal/wrap-ansi.js +172 -103
  178. package/dist/tokens/tracker.d.ts +1 -0
  179. package/dist/tokens/tracker.d.ts.map +1 -1
  180. package/dist/tokens/tracker.js +3 -0
  181. package/dist/tools/agent.d.ts +27 -0
  182. package/dist/tools/agent.d.ts.map +1 -0
  183. package/dist/tools/agent.js +81 -0
  184. package/dist/tools/bash.d.ts +4 -3
  185. package/dist/tools/bash.d.ts.map +1 -1
  186. package/dist/tools/bash.js +343 -121
  187. package/dist/tools/code-search.d.ts +41 -0
  188. package/dist/tools/code-search.d.ts.map +1 -0
  189. package/dist/tools/code-search.js +195 -0
  190. package/dist/tools/directory-tree.d.ts +3 -3
  191. package/dist/tools/directory-tree.d.ts.map +1 -1
  192. package/dist/tools/directory-tree.js +8 -5
  193. package/dist/tools/dynamic-tool-loader.d.ts +2 -5
  194. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  195. package/dist/tools/dynamic-tool-loader.js +20 -4
  196. package/dist/tools/edit-file.d.ts +7 -7
  197. package/dist/tools/edit-file.d.ts.map +1 -1
  198. package/dist/tools/edit-file.js +164 -66
  199. package/dist/tools/glob.d.ts +6 -6
  200. package/dist/tools/glob.d.ts.map +1 -1
  201. package/dist/tools/glob.js +95 -55
  202. package/dist/tools/grep.d.ts +15 -12
  203. package/dist/tools/grep.d.ts.map +1 -1
  204. package/dist/tools/grep.js +300 -192
  205. package/dist/tools/index.d.ts +143 -5
  206. package/dist/tools/index.d.ts.map +1 -1
  207. package/dist/tools/index.js +39 -24
  208. package/dist/tools/ls.d.ts +2 -2
  209. package/dist/tools/ls.d.ts.map +1 -1
  210. package/dist/tools/ls.js +7 -5
  211. package/dist/tools/read-file.d.ts +3 -3
  212. package/dist/tools/read-file.d.ts.map +1 -1
  213. package/dist/tools/read-file.js +74 -34
  214. package/dist/tools/save-file.d.ts +3 -3
  215. package/dist/tools/save-file.d.ts.map +1 -1
  216. package/dist/tools/save-file.js +11 -11
  217. package/dist/tools/skill.d.ts +23 -0
  218. package/dist/tools/skill.d.ts.map +1 -0
  219. package/dist/tools/skill.js +65 -0
  220. package/dist/tools/think.d.ts.map +1 -1
  221. package/dist/tools/think.js +2 -9
  222. package/dist/tools/utils.d.ts +2 -0
  223. package/dist/tools/utils.d.ts.map +1 -1
  224. package/dist/tools/utils.js +12 -0
  225. package/dist/tools/web-fetch.d.ts +62 -0
  226. package/dist/tools/web-fetch.d.ts.map +1 -0
  227. package/dist/tools/web-fetch.js +429 -0
  228. package/dist/tools/web-search.d.ts +62 -0
  229. package/dist/tools/web-search.d.ts.map +1 -0
  230. package/dist/tools/web-search.js +226 -0
  231. package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
  232. package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
  233. package/dist/tui/autocomplete/attachment-provider.js +25 -78
  234. package/dist/tui/autocomplete/base-provider.d.ts +1 -0
  235. package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
  236. package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
  237. package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
  238. package/dist/tui/autocomplete/combined-provider.js +3 -17
  239. package/dist/tui/autocomplete/command-provider.d.ts +1 -0
  240. package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
  241. package/dist/tui/autocomplete/command-provider.js +3 -0
  242. package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
  243. package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
  244. package/dist/tui/autocomplete/file-search-provider.js +36 -16
  245. package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
  246. package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
  247. package/dist/tui/autocomplete/skill-provider.js +49 -0
  248. package/dist/tui/autocomplete.d.ts +2 -2
  249. package/dist/tui/autocomplete.d.ts.map +1 -1
  250. package/dist/tui/autocomplete.js +3 -5
  251. package/dist/tui/components/assistant-message.d.ts.map +1 -1
  252. package/dist/tui/components/assistant-message.js +0 -4
  253. package/dist/tui/components/editor.d.ts +21 -2
  254. package/dist/tui/components/editor.d.ts.map +1 -1
  255. package/dist/tui/components/editor.js +228 -236
  256. package/dist/tui/components/footer.d.ts +6 -4
  257. package/dist/tui/components/footer.d.ts.map +1 -1
  258. package/dist/tui/components/footer.js +49 -25
  259. package/dist/tui/components/markdown.d.ts +8 -5
  260. package/dist/tui/components/markdown.d.ts.map +1 -1
  261. package/dist/tui/components/markdown.js +57 -39
  262. package/dist/tui/components/modal.d.ts.map +1 -1
  263. package/dist/tui/components/modal.js +35 -33
  264. package/dist/tui/components/notification.d.ts +13 -2
  265. package/dist/tui/components/notification.d.ts.map +1 -1
  266. package/dist/tui/components/notification.js +37 -2
  267. package/dist/tui/components/progress-bar.js +1 -1
  268. package/dist/tui/components/select-list.d.ts +1 -0
  269. package/dist/tui/components/select-list.d.ts.map +1 -1
  270. package/dist/tui/components/select-list.js +14 -11
  271. package/dist/tui/components/text.d.ts +16 -0
  272. package/dist/tui/components/text.d.ts.map +1 -1
  273. package/dist/tui/components/text.js +72 -57
  274. package/dist/tui/components/thinking-block.d.ts +9 -0
  275. package/dist/tui/components/thinking-block.d.ts.map +1 -1
  276. package/dist/tui/components/thinking-block.js +43 -11
  277. package/dist/tui/components/tool-execution.d.ts +5 -1
  278. package/dist/tui/components/tool-execution.d.ts.map +1 -1
  279. package/dist/tui/components/tool-execution.js +19 -10
  280. package/dist/tui/components/user-message.d.ts.map +1 -1
  281. package/dist/tui/components/user-message.js +0 -3
  282. package/dist/tui/components/welcome.js +2 -2
  283. package/dist/tui/editor-launcher.d.ts +13 -0
  284. package/dist/tui/editor-launcher.d.ts.map +1 -0
  285. package/dist/tui/editor-launcher.js +39 -0
  286. package/dist/tui/index.d.ts +3 -1
  287. package/dist/tui/index.d.ts.map +1 -1
  288. package/dist/tui/index.js +1 -0
  289. package/dist/tui/terminal.d.ts +27 -0
  290. package/dist/tui/terminal.d.ts.map +1 -1
  291. package/dist/tui/terminal.js +144 -15
  292. package/dist/tui/tui.d.ts +43 -0
  293. package/dist/tui/tui.d.ts.map +1 -1
  294. package/dist/tui/tui.js +172 -41
  295. package/dist/utils/bash/parse.d.ts +19 -0
  296. package/dist/utils/bash/parse.d.ts.map +1 -0
  297. package/dist/utils/bash/parse.js +223 -0
  298. package/dist/utils/bash/quote.d.ts +6 -0
  299. package/dist/utils/bash/quote.d.ts.map +1 -0
  300. package/dist/utils/bash/quote.js +23 -0
  301. package/dist/utils/bash.d.ts.map +1 -1
  302. package/dist/utils/bash.js +211 -126
  303. package/dist/utils/command-protection.d.ts +28 -0
  304. package/dist/utils/command-protection.d.ts.map +1 -0
  305. package/dist/utils/command-protection.js +324 -0
  306. package/dist/utils/dedent.d.ts.map +1 -0
  307. package/dist/utils/env-expand.d.ts +2 -0
  308. package/dist/utils/env-expand.d.ts.map +1 -0
  309. package/dist/utils/env-expand.js +8 -0
  310. package/dist/utils/filesystem/path-display.d.ts +11 -0
  311. package/dist/utils/filesystem/path-display.d.ts.map +1 -0
  312. package/dist/utils/filesystem/path-display.js +32 -0
  313. package/dist/utils/filesystem/security.d.ts +2 -2
  314. package/dist/utils/filesystem/security.d.ts.map +1 -1
  315. package/dist/utils/filesystem/security.js +32 -31
  316. package/dist/utils/formatting.d.ts.map +1 -0
  317. package/dist/{formatting.js → utils/formatting.js} +1 -1
  318. package/dist/utils/git.d.ts +4 -0
  319. package/dist/utils/git.d.ts.map +1 -1
  320. package/dist/utils/git.js +30 -0
  321. package/dist/utils/glob.d.ts +1 -1
  322. package/dist/utils/glob.d.ts.map +1 -1
  323. package/dist/utils/logger.d.ts.map +1 -0
  324. package/dist/{logger.js → utils/logger.js} +1 -1
  325. package/dist/utils/parsing.d.ts.map +1 -0
  326. package/dist/utils/process.d.ts.map +1 -1
  327. package/dist/utils/process.js +90 -37
  328. package/dist/utils/templates.d.ts +2 -0
  329. package/dist/utils/templates.d.ts.map +1 -0
  330. package/dist/utils/templates.js +24 -0
  331. package/dist/utils/version.d.ts.map +1 -0
  332. package/dist/{version.js → utils/version.js} +1 -1
  333. package/package.json +34 -25
  334. package/dist/cli.d.ts +0 -23
  335. package/dist/cli.d.ts.map +0 -1
  336. package/dist/commands/exit/index.d.ts +0 -10
  337. package/dist/commands/exit/index.d.ts.map +0 -1
  338. package/dist/commands/exit/index.js +0 -21
  339. package/dist/commands/exit/types.d.ts +0 -8
  340. package/dist/commands/exit/types.d.ts.map +0 -1
  341. package/dist/commands/exit/types.js +0 -1
  342. package/dist/commands/exit/utils.d.ts +0 -2
  343. package/dist/commands/exit/utils.d.ts.map +0 -1
  344. package/dist/commands/exit/utils.js +0 -13
  345. package/dist/commands/prompt/index.d.ts +0 -5
  346. package/dist/commands/prompt/index.d.ts.map +0 -1
  347. package/dist/commands/prompt/index.js +0 -126
  348. package/dist/commands/prompt/types.d.ts +0 -15
  349. package/dist/commands/prompt/types.d.ts.map +0 -1
  350. package/dist/commands/prompt/types.js +0 -1
  351. package/dist/commands/prompt/utils.d.ts +0 -12
  352. package/dist/commands/prompt/utils.d.ts.map +0 -1
  353. package/dist/commands/prompt/utils.js +0 -107
  354. package/dist/commands/reset/index.d.ts +0 -3
  355. package/dist/commands/reset/index.d.ts.map +0 -1
  356. package/dist/commands/reset/index.js +0 -25
  357. package/dist/commands/reset/types.d.ts +0 -1
  358. package/dist/commands/reset/types.d.ts.map +0 -1
  359. package/dist/commands/reset/types.js +0 -3
  360. package/dist/commands/save/index.d.ts +0 -3
  361. package/dist/commands/save/index.d.ts.map +0 -1
  362. package/dist/commands/save/index.js +0 -19
  363. package/dist/config.d.ts.map +0 -1
  364. package/dist/dedent.d.ts.map +0 -1
  365. package/dist/formatting.d.ts.map +0 -1
  366. package/dist/logger.d.ts.map +0 -1
  367. package/dist/mentions.d.ts +0 -14
  368. package/dist/mentions.d.ts.map +0 -1
  369. package/dist/parsing.d.ts.map +0 -1
  370. package/dist/prompts.d.ts +0 -10
  371. package/dist/prompts.d.ts.map +0 -1
  372. package/dist/repl-new.d.ts +0 -62
  373. package/dist/repl-new.d.ts.map +0 -1
  374. package/dist/skills.d.ts.map +0 -1
  375. package/dist/skills.js +0 -233
  376. package/dist/tui/autocomplete/path-provider.d.ts +0 -21
  377. package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
  378. package/dist/tui/autocomplete/path-provider.js +0 -164
  379. package/dist/version.d.ts.map +0 -1
  380. /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
  381. /package/dist/{dedent.js → utils/dedent.js} +0 -0
  382. /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
  383. /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
  384. /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
  385. /package/dist/{parsing.js → utils/parsing.js} +0 -0
  386. /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
@@ -1,5 +1,10 @@
1
+ import fs from "node:fs";
2
+ import tty from "node:tty";
1
3
  /**
2
4
  * Real terminal using process.stdin/stdout
5
+ *
6
+ * When useTty is true (for piped stdin scenarios), input is read from /dev/tty
7
+ * instead of process.stdin, allowing interactive input after piped content.
3
8
  */
4
9
  export class ProcessTerminal {
5
10
  wasRaw = false;
@@ -7,19 +12,47 @@ export class ProcessTerminal {
7
12
  boundInputListener;
8
13
  boundResizeListener;
9
14
  stopped = false;
15
+ inExternalMode = false;
16
+ resumeCallback;
17
+ inputStream;
18
+ ttyFd;
19
+ useTty;
20
+ constructor(options = {}) {
21
+ this.useTty = options.useTty ?? false;
22
+ this.inputStream = process.stdin;
23
+ }
24
+ handleSigCont = () => {
25
+ if (this.inExternalMode) {
26
+ this.exitExternalMode();
27
+ }
28
+ };
10
29
  start(onInput, onResize) {
11
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
30
+ if (this.useTty) {
31
+ try {
32
+ this.ttyFd = fs.openSync("/dev/tty", "r");
33
+ this.inputStream = new tty.ReadStream(this.ttyFd);
34
+ }
35
+ catch {
36
+ throw new Error("Cannot open /dev/tty for interactive input");
37
+ }
38
+ }
39
+ else {
40
+ this.inputStream = process.stdin;
41
+ }
42
+ if (!this.inputStream.isTTY || !process.stdout.isTTY) {
12
43
  throw new Error("Terminal requires TTY environment");
13
44
  }
14
- // Save previous state and enable raw mode
15
- this.wasRaw = process.stdin.isRaw ?? false;
16
- if (process.stdin.setRawMode) {
17
- process.stdin.setRawMode(true);
45
+ this.wasRaw = this.inputStream.isRaw ?? false;
46
+ if (this.inputStream.setRawMode) {
47
+ this.inputStream.setRawMode(true);
18
48
  }
19
- process.stdin.setEncoding("utf8");
20
- process.stdin.resume();
49
+ this.inputStream.setEncoding("utf8");
50
+ this.inputStream.resume();
21
51
  // Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
22
52
  process.stdout.write("\x1b[?2004h");
53
+ // Enable mouse tracking (SGR mode) so trackpad scroll sends mouse events
54
+ // instead of being translated into arrow key sequences
55
+ process.stdout.write("\x1b[?1000h\x1b[?1006h");
23
56
  // Create bound listeners so we can properly remove them later
24
57
  this.boundInputListener = (data) => {
25
58
  onInput(typeof data === "string" ? data : data.toString("utf8"));
@@ -27,13 +60,66 @@ export class ProcessTerminal {
27
60
  this.boundResizeListener = () => {
28
61
  onResize();
29
62
  };
30
- process.stdin.on("data", this.boundInputListener);
31
- process.stdout.on("resize", this.boundResizeListener);
63
+ this.attachListeners();
32
64
  this.sigintHandler = () => {
33
- // Let the custom editor.onCtrlC handler in NewRepl handle Ctrl+C
65
+ // Let the custom editor.onCtrlC handler in Repl handle Ctrl+C
34
66
  // This prevents a race condition where stop() gets called without the exit message
35
67
  };
36
68
  process.on("SIGINT", this.sigintHandler);
69
+ process.on("SIGCONT", this.handleSigCont);
70
+ process.on("SIGTERM", () => this.stop());
71
+ process.on("SIGHUP", () => this.stop());
72
+ this.setupCrashCleanup();
73
+ }
74
+ attachListeners() {
75
+ if (this.boundInputListener) {
76
+ this.inputStream.on("data", this.boundInputListener);
77
+ }
78
+ if (this.boundResizeListener) {
79
+ process.stdout.on("resize", this.boundResizeListener);
80
+ }
81
+ }
82
+ detachListeners() {
83
+ if (this.boundInputListener) {
84
+ this.inputStream.removeListener("data", this.boundInputListener);
85
+ }
86
+ if (this.boundResizeListener) {
87
+ process.stdout.removeListener("resize", this.boundResizeListener);
88
+ }
89
+ }
90
+ setupCrashCleanup() {
91
+ process.on("exit", () => {
92
+ process.stdout.write("\x1b[?25h");
93
+ process.stdout.write("\x1b[?2004l");
94
+ process.stdout.write("\x1b[?1000l\x1b[?1006l");
95
+ process.stdout.write("\x1b[0m");
96
+ if (this.inputStream.setRawMode) {
97
+ this.inputStream.setRawMode(this.wasRaw);
98
+ }
99
+ this.closeTtyFd();
100
+ });
101
+ process.on("uncaughtException", (error) => {
102
+ process.stdout.write("\x1b[?25h");
103
+ process.stdout.write("\x1b[?2004l");
104
+ process.stdout.write("\x1b[?1000l\x1b[?1006l");
105
+ process.stdout.write("\x1b[0m");
106
+ if (this.inputStream.setRawMode) {
107
+ this.inputStream.setRawMode(this.wasRaw);
108
+ }
109
+ this.closeTtyFd();
110
+ throw error;
111
+ });
112
+ }
113
+ closeTtyFd() {
114
+ if (this.ttyFd !== undefined) {
115
+ try {
116
+ fs.closeSync(this.ttyFd);
117
+ }
118
+ catch {
119
+ // Ignore close errors
120
+ }
121
+ this.ttyFd = undefined;
122
+ }
37
123
  }
38
124
  stop() {
39
125
  if (this.stopped)
@@ -43,22 +129,24 @@ export class ProcessTerminal {
43
129
  process.off("SIGINT", this.sigintHandler);
44
130
  this.sigintHandler = undefined;
45
131
  }
46
- // Disable bracketed paste mode
132
+ // Disable bracketed paste mode and mouse tracking
47
133
  process.stdout.write("\x1b[?2004l");
134
+ process.stdout.write("\x1b[?1000l\x1b[?1006l");
48
135
  // Remove event handlers using the exact references that were added
49
136
  if (this.boundInputListener) {
50
- process.stdin.removeListener("data", this.boundInputListener);
137
+ this.inputStream.removeListener("data", this.boundInputListener);
51
138
  this.boundInputListener = undefined;
52
139
  }
53
140
  if (this.boundResizeListener) {
54
141
  process.stdout.removeListener("resize", this.boundResizeListener);
55
142
  this.boundResizeListener = undefined;
56
143
  }
57
- process.stdin.pause();
144
+ this.inputStream.pause();
58
145
  // Restore raw mode state
59
- if (process.stdin.setRawMode) {
60
- process.stdin.setRawMode(this.wasRaw);
146
+ if (this.inputStream.setRawMode) {
147
+ this.inputStream.setRawMode(this.wasRaw);
61
148
  }
149
+ this.closeTtyFd();
62
150
  }
63
151
  write(data) {
64
152
  process.stdout.write(data);
@@ -95,4 +183,45 @@ export class ProcessTerminal {
95
183
  clearScreen() {
96
184
  process.stdout.write("\x1b[2J\x1b[H"); // Clear screen and move to home (1,1)
97
185
  }
186
+ enterExternalMode() {
187
+ if (this.inExternalMode || this.stopped)
188
+ return;
189
+ this.inExternalMode = true;
190
+ this.detachListeners();
191
+ this.inputStream.pause();
192
+ if (this.inputStream.setRawMode) {
193
+ this.inputStream.setRawMode(false);
194
+ }
195
+ process.stdout.write("\x1b[0m");
196
+ process.stdout.write("\x1b[?25h");
197
+ process.stdout.write("\x1b[?2004l");
198
+ process.stdout.write("\x1b[?1000l\x1b[?1006l");
199
+ }
200
+ exitExternalMode() {
201
+ if (!this.inExternalMode || this.stopped)
202
+ return;
203
+ this.inExternalMode = false;
204
+ if (this.inputStream.setRawMode) {
205
+ this.inputStream.setRawMode(true);
206
+ }
207
+ this.inputStream.setEncoding("utf8");
208
+ this.inputStream.resume();
209
+ process.stdout.write("\x1b[?2004h");
210
+ process.stdout.write("\x1b[?1000h\x1b[?1006h");
211
+ process.stdout.write("\x1b[?25l");
212
+ this.attachListeners();
213
+ if (this.resumeCallback) {
214
+ this.resumeCallback();
215
+ }
216
+ }
217
+ background() {
218
+ this.enterExternalMode();
219
+ process.kill(process.pid, "SIGSTOP");
220
+ }
221
+ isInExternalMode() {
222
+ return this.inExternalMode;
223
+ }
224
+ onResume(callback) {
225
+ this.resumeCallback = callback;
226
+ }
98
227
  }
package/dist/tui/tui.d.ts CHANGED
@@ -24,6 +24,11 @@ export interface Component {
24
24
  * and col is 0-indexed column position
25
25
  */
26
26
  getCursorPosition?(): [number, number] | null;
27
+ /**
28
+ * Optional method to indicate the component wants to handle Tab/Shift+Tab
29
+ * navigation keys itself, preventing the TUI from intercepting them.
30
+ */
31
+ wantsNavigationKeys?(): boolean;
27
32
  }
28
33
  export { visibleWidth };
29
34
  /**
@@ -38,6 +43,14 @@ export declare class Container implements Component {
38
43
  }
39
44
  /**
40
45
  * TUI - Main class for managing terminal UI with differential rendering
46
+ *
47
+ * The TUI splits its children into two regions:
48
+ * - Scrollable content: children added before setFixedFooterStart()
49
+ * - Fixed footer: children added after setFixedFooterStart()
50
+ *
51
+ * The scrollable content region supports virtual scrolling via trackpad/mouse
52
+ * wheel. The fixed footer (editor, footer bar, notifications) is always
53
+ * pinned to the bottom of the terminal.
41
54
  */
42
55
  export declare class TUI extends Container {
43
56
  private terminal;
@@ -46,13 +59,43 @@ export declare class TUI extends Container {
46
59
  private isRendering;
47
60
  private renderAgain;
48
61
  private activeModal;
62
+ private scrollOffset;
63
+ private lastScrollableHeight;
64
+ private fixedFooterIndex;
65
+ private isUserScrolledUp;
49
66
  onCtrlC?: () => void;
67
+ onCtrlD?: () => void;
68
+ onReconstructSession?: () => void;
69
+ onCtrlN?: () => void;
70
+ onCtrlO?: () => void;
71
+ /** Callback invoked when Ctrl+M is pressed - opens model selector. */
72
+ onCtrlM?: () => void;
73
+ onCtrlR?: () => void;
74
+ onShiftTab?: () => void;
50
75
  constructor(terminal: Terminal);
76
+ /**
77
+ * Mark the boundary between scrollable content and fixed footer.
78
+ * All children added after this call will be rendered as fixed
79
+ * footer content pinned to the bottom of the terminal.
80
+ */
81
+ setFixedFooterStart(): void;
51
82
  setFocus(component: Component | null): void;
83
+ /**
84
+ * Get the underlying terminal instance for external mode operations.
85
+ * Used primarily for spawning external editors.
86
+ */
87
+ getTerminal(): Terminal;
52
88
  start(): void;
53
89
  stop(): void;
54
90
  requestRender(): void;
91
+ private inBracketedPaste;
92
+ private focusedComponentWantsNavigation;
55
93
  private handleInput;
94
+ private handleMouseEvent;
95
+ scrollToBottom(): void;
96
+ private getFixedFooterHeight;
97
+ private renderScrollableContent;
98
+ private renderFixedFooter;
56
99
  private doRender;
57
100
  /**
58
101
  * Show a modal dialog
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../source/tui/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEjC;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CAC/C;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IACzC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAE3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAIpC,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAOvC,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAGhC;AAED;;GAEG;AAGH,qBAAa,GAAI,SAAQ,SAAS;IAChC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAsB;IAElC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;gBAEhB,QAAQ,EAAE,QAAQ;IAK9B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI;IAI3C,KAAK,IAAI,IAAI;IASb,IAAI,IAAI,IAAI;IAKZ,aAAa,IAAI,IAAI;IAarB,OAAO,CAAC,WAAW;IA4BnB,OAAO,CAAC,QAAQ;IA0FhB;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAK7B;;OAEG;IACH,SAAS,IAAI,IAAI;IAKjB;;OAEG;IACH,aAAa,IAAI,OAAO;CAGzB"}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../source/tui/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEjC;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAE9C;;;OAGG;IACH,mBAAmB,CAAC,IAAI,OAAO,CAAC;CACjC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IACzC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAE3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAIpC,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAOvC,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAGhC;AAED;;;;;;;;;;GAUG;AAGH,qBAAa,GAAI,SAAQ,SAAS;IAChC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IAE1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,sEAAsE;IAC/D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;gBAEnB,QAAQ,EAAE,QAAQ;IAK9B;;;;OAIG;IACH,mBAAmB,IAAI,IAAI;IAI3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI;IAI3C;;;OAGG;IACH,WAAW,IAAI,QAAQ;IAIvB,KAAK,IAAI,IAAI;IAUb,IAAI,IAAI,IAAI;IAKZ,aAAa,IAAI,IAAI;IAarB,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,+BAA+B;IAIvC,OAAO,CAAC,WAAW;IAyFnB,OAAO,CAAC,gBAAgB;IAiCxB,cAAc,IAAI,IAAI;IAWtB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,QAAQ;IAoFhB;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAK7B;;OAEG;IACH,SAAS,IAAI,IAAI;IAKjB;;OAEG;IACH,aAAa,IAAI,OAAO;CAGzB"}
package/dist/tui/tui.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Minimal TUI implementation with differential rendering
3
3
  */
4
- import { getTerminalSize, isCtrlC, isEscape } from "../terminal/control.js";
4
+ import { getTerminalSize, isCtrlC, isCtrlD, isCtrlM, isCtrlN, isCtrlO, isCtrlR, isCtrlZ, isEscape, } from "../terminal/control.js";
5
+ import { isShiftTab } from "../terminal/keys.js";
5
6
  import style from "../terminal/style.js";
6
7
  import { visibleWidth } from "./utils.js";
7
8
  export { visibleWidth };
@@ -28,6 +29,14 @@ export class Container {
28
29
  }
29
30
  /**
30
31
  * TUI - Main class for managing terminal UI with differential rendering
32
+ *
33
+ * The TUI splits its children into two regions:
34
+ * - Scrollable content: children added before setFixedFooterStart()
35
+ * - Fixed footer: children added after setFixedFooterStart()
36
+ *
37
+ * The scrollable content region supports virtual scrolling via trackpad/mouse
38
+ * wheel. The fixed footer (editor, footer bar, notifications) is always
39
+ * pinned to the bottom of the terminal.
31
40
  */
32
41
  // biome-ignore lint/style/useNamingConvention: override
33
42
  export class TUI extends Container {
@@ -37,16 +46,44 @@ export class TUI extends Container {
37
46
  isRendering = false;
38
47
  renderAgain = false;
39
48
  activeModal = null;
49
+ scrollOffset = 0;
50
+ lastScrollableHeight = 0;
51
+ fixedFooterIndex = -1;
52
+ isUserScrolledUp = false;
40
53
  onCtrlC;
54
+ onCtrlD;
55
+ onReconstructSession;
56
+ onCtrlN;
57
+ onCtrlO;
58
+ /** Callback invoked when Ctrl+M is pressed - opens model selector. */
59
+ onCtrlM;
60
+ onCtrlR;
61
+ onShiftTab;
41
62
  constructor(terminal) {
42
63
  super();
43
64
  this.terminal = terminal;
44
65
  }
66
+ /**
67
+ * Mark the boundary between scrollable content and fixed footer.
68
+ * All children added after this call will be rendered as fixed
69
+ * footer content pinned to the bottom of the terminal.
70
+ */
71
+ setFixedFooterStart() {
72
+ this.fixedFooterIndex = this.children.length;
73
+ }
45
74
  setFocus(component) {
46
75
  this.focusedComponent = component;
47
76
  }
77
+ /**
78
+ * Get the underlying terminal instance for external mode operations.
79
+ * Used primarily for spawning external editors.
80
+ */
81
+ getTerminal() {
82
+ return this.terminal;
83
+ }
48
84
  start() {
49
85
  this.terminal.start((data) => this.handleInput(data), () => this.requestRender());
86
+ this.terminal.onResume(() => this.requestRender());
50
87
  this.terminal.hideCursor();
51
88
  this.requestRender();
52
89
  }
@@ -67,7 +104,29 @@ export class TUI extends Container {
67
104
  this.doRender();
68
105
  });
69
106
  }
107
+ inBracketedPaste = false;
108
+ focusedComponentWantsNavigation() {
109
+ return this.focusedComponent?.wantsNavigationKeys?.() ?? false;
110
+ }
70
111
  handleInput(data) {
112
+ // Handle mouse tracking events (SGR format: \x1b[<button;x;yM)
113
+ if (data.startsWith("\x1b[<")) {
114
+ this.handleMouseEvent(data);
115
+ return;
116
+ }
117
+ if (data.includes("\x1b[200~")) {
118
+ this.inBracketedPaste = true;
119
+ }
120
+ if (data.includes("\x1b[201~")) {
121
+ this.inBracketedPaste = false;
122
+ }
123
+ // Handle Ctrl+Z - background the process (POSIX only)
124
+ if (isCtrlZ(data) &&
125
+ !this.inBracketedPaste &&
126
+ process.platform !== "win32") {
127
+ this.terminal.background();
128
+ return;
129
+ }
71
130
  // Handle Ctrl+C globally - exit the application
72
131
  if (isCtrlC(data)) {
73
132
  console.info("\nCtrl+C pressed - exiting...");
@@ -79,6 +138,36 @@ export class TUI extends Container {
79
138
  process.exit(0);
80
139
  }
81
140
  }
141
+ // Handle Ctrl+D - exit only if editor is empty (handled by Repl)
142
+ if (isCtrlD(data)) {
143
+ if (this.onCtrlD) {
144
+ this.onCtrlD();
145
+ }
146
+ return;
147
+ }
148
+ const keyBindings = [
149
+ { check: isCtrlO, handler: this.onCtrlO },
150
+ { check: isCtrlR, handler: this.onCtrlR },
151
+ { check: isCtrlN, handler: this.onCtrlN },
152
+ { check: isCtrlM, handler: this.onCtrlM },
153
+ ];
154
+ for (const binding of keyBindings) {
155
+ if (binding.check(data)) {
156
+ binding.handler?.();
157
+ return;
158
+ }
159
+ }
160
+ // Handle Shift+Tab - cycle mode only when no modal is active,
161
+ // the focused component won't handle navigation (e.g., model selector),
162
+ // and the editor isn't showing autocomplete
163
+ if (isShiftTab(data) && !this.inBracketedPaste) {
164
+ if (!this.activeModal && !this.focusedComponentWantsNavigation()) {
165
+ if (this.onShiftTab) {
166
+ this.onShiftTab();
167
+ }
168
+ return;
169
+ }
170
+ }
82
171
  // Handle Escape key to close modal if one is active
83
172
  if (isEscape(data) && this.activeModal) {
84
173
  this.hideModal();
@@ -91,9 +180,69 @@ export class TUI extends Container {
91
180
  }
92
181
  else if (this.focusedComponent?.handleInput) {
93
182
  this.focusedComponent.handleInput(data);
183
+ this.scrollToBottom();
184
+ this.requestRender();
185
+ }
186
+ }
187
+ handleMouseEvent(data) {
188
+ // SGR mouse format: \x1b[<button;x;yM (press) or \x1b[<button;x;ym (release)
189
+ // Extract button code between "<" and first ";"
190
+ const start = data.indexOf("<");
191
+ const semi = data.indexOf(";", start);
192
+ if (start === -1 || semi === -1)
193
+ return;
194
+ const button = Number.parseInt(data.slice(start + 1, semi), 10);
195
+ const scrollLines = 3;
196
+ const { rows } = getTerminalSize();
197
+ const fixedHeight = this.getFixedFooterHeight(this.terminal.columns);
198
+ const scrollableViewport = rows - fixedHeight;
199
+ // Button 64 = scroll up, button 65 = scroll down
200
+ if (button === 64) {
201
+ this.scrollOffset = Math.max(0, this.scrollOffset - scrollLines);
202
+ const maxOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
203
+ this.isUserScrolledUp = this.scrollOffset < maxOffset;
204
+ this.requestRender();
205
+ }
206
+ else if (button === 65) {
207
+ const maxOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
208
+ this.scrollOffset = Math.min(maxOffset, this.scrollOffset + scrollLines);
209
+ this.isUserScrolledUp = this.scrollOffset < maxOffset;
94
210
  this.requestRender();
95
211
  }
96
212
  }
213
+ scrollToBottom() {
214
+ const { rows } = getTerminalSize();
215
+ const fixedHeight = this.getFixedFooterHeight(this.terminal.columns);
216
+ const scrollableViewport = rows - fixedHeight;
217
+ this.scrollOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
218
+ this.isUserScrolledUp = false;
219
+ }
220
+ getFixedFooterHeight(width) {
221
+ if (this.fixedFooterIndex < 0)
222
+ return 0;
223
+ let height = 0;
224
+ for (let i = this.fixedFooterIndex; i < this.children.length; i++) {
225
+ height += this.children[i].render(width).length;
226
+ }
227
+ return height;
228
+ }
229
+ renderScrollableContent(width) {
230
+ const end = this.fixedFooterIndex >= 0 ? this.fixedFooterIndex : this.children.length;
231
+ const lines = [];
232
+ for (let i = 0; i < end; i++) {
233
+ lines.push(...this.children[i].render(width));
234
+ }
235
+ return lines;
236
+ }
237
+ renderFixedFooter(width) {
238
+ if (this.fixedFooterIndex < 0)
239
+ return [];
240
+ const lines = [];
241
+ for (let i = this.fixedFooterIndex; i < this.children.length; i++) {
242
+ lines.push(...this.children[i].render(width));
243
+ }
244
+ return lines;
245
+ }
97
246
  doRender() {
98
247
  if (this.isRendering) {
99
248
  this.renderAgain = true;
@@ -102,13 +251,32 @@ export class TUI extends Container {
102
251
  this.isRendering = true;
103
252
  try {
104
253
  const width = this.terminal.columns;
105
- // Render all components to get new lines
106
- const newLines = this.render(width);
254
+ const { rows } = getTerminalSize();
255
+ // Render fixed footer and scrollable content separately
256
+ const fixedLines = this.renderFixedFooter(width);
257
+ const scrollableLines = this.renderScrollableContent(width);
258
+ this.lastScrollableHeight = scrollableLines.length;
259
+ const fixedHeight = fixedLines.length;
260
+ const scrollableViewport = rows - fixedHeight;
261
+ // Auto-scroll to bottom when new content arrives,
262
+ // unless the user has explicitly scrolled up
263
+ if (!this.isUserScrolledUp) {
264
+ this.scrollOffset = Math.max(0, scrollableLines.length - scrollableViewport);
265
+ }
266
+ // Clamp scroll offset
267
+ const maxOffset = Math.max(0, scrollableLines.length - scrollableViewport);
268
+ if (this.scrollOffset > maxOffset) {
269
+ this.scrollOffset = maxOffset;
270
+ }
271
+ // Apply scroll offset to get visible scrollable lines
272
+ const visibleScrollable = scrollableLines.slice(this.scrollOffset, this.scrollOffset + scrollableViewport);
273
+ // Combine visible scrollable content with fixed footer
274
+ const visibleLines = [...visibleScrollable, ...fixedLines];
107
275
  // Build output buffer using array join (more efficient than string concat)
108
276
  const bufferParts = [
109
277
  "\x1b[?2026h", // Begin synchronized output
110
278
  "\x1b[3J\x1b[2J\x1b[H", // Clear scrollback, screen, and home
111
- newLines.join("\r\n"),
279
+ visibleLines.join("\r\n"),
112
280
  ];
113
281
  // Render modal on top if active
114
282
  if (this.activeModal) {
@@ -139,43 +307,6 @@ export class TUI extends Container {
139
307
  }
140
308
  }
141
309
  }
142
- // private positionCursor(
143
- // componentCursorPos: [number, number] | null,
144
- // ): void {
145
- // if (!componentCursorPos) {
146
- // // No cursor position from component, hide cursor
147
- // this.terminal.hideCursor();
148
- // return;
149
- // }
150
- // const [cursorRow, cursorCol] = componentCursorPos;
151
- //
152
- // // Calculate absolute cursor position in the terminal
153
- // // We need to find which line in newLines corresponds to the component's cursor row
154
- // // and then position the cursor at that line and column
155
- //
156
- // // Find the line offset for the focused component
157
- // let componentStartLine = 0;
158
- // if (this.focusedComponent) {
159
- // // Find the line where this component starts by summing heights of previous components
160
- // for (const child of this.children) {
161
- // if (child === this.focusedComponent) {
162
- // break;
163
- // }
164
- // // Use the already-rendered lines to calculate height, not re-render
165
- // const childLines = child.render(this.terminal.columns);
166
- // componentStartLine += childLines.length;
167
- // }
168
- // }
169
- //
170
- // const absoluteRow = componentStartLine + cursorRow;
171
- // const absoluteCol = cursorCol;
172
- //
173
- // // Position cursor using absolute positioning
174
- // // Move to home position first, then move down to row, then right to column
175
- // // Note: terminal rows/columns are 1-indexed, so we add 1
176
- // this.terminal.write(`\x1b[H\x1b[${absoluteRow + 1}B\x1b[${absoluteCol + 1}G`);
177
- // this.terminal.showCursor();
178
- // }
179
310
  /**
180
311
  * Show a modal dialog
181
312
  */
@@ -0,0 +1,19 @@
1
+ interface ControlOperator {
2
+ op: "||" | "&&" | ";;" | "|&" | "<(" | "<<<" | ">>" | ">&" | "&" | ";" | "(" | ")" | "|" | "<" | ">";
3
+ }
4
+ interface GlobOperator {
5
+ op: "glob";
6
+ pattern: string;
7
+ }
8
+ interface CommentOperator {
9
+ comment: string;
10
+ }
11
+ type ParseEntry = string | ControlOperator | GlobOperator | CommentOperator;
12
+ interface ParseOptions {
13
+ escape?: string;
14
+ }
15
+ type EnvFunction = (key: string) => unknown;
16
+ export declare function parse(s: string, env?: Record<string, string | undefined> | EnvFunction, opts?: ParseOptions): ParseEntry[];
17
+ export declare function parse<T extends object | string>(s: string, env: (key: string) => T | undefined, opts?: ParseOptions): (ParseEntry | T)[];
18
+ export {};
19
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../source/utils/bash/parse.ts"],"names":[],"mappings":"AA+BA,UAAU,eAAe;IACvB,EAAE,EACE,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,IAAI,GACJ,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,CAAC;CACT;AAED,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,UAAU,GAAG,MAAM,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe,CAAC;AAE5E,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AA8L5C,wBAAgB,KAAK,CACnB,CAAC,EAAE,MAAM,EACT,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,WAAW,EACtD,IAAI,CAAC,EAAE,YAAY,GAClB,UAAU,EAAE,CAAC;AAChB,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC7C,CAAC,EAAE,MAAM,EACT,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,EACnC,IAAI,CAAC,EAAE,YAAY,GAClB,CAAC,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC"}